/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
private: // The this value to use for invocation of the callback.
RefPtr<Element> mThisObject;
RefPtr<CallbackFunction> mCallback; // The type of callback (eCreated, eAttached, etc.)
ElementCallbackType mType; // Arguments to be passed to the callback,
LifecycleCallbackArgs mArgs;
};
class CustomElementCallbackReaction final : public CustomElementReaction { public: explicit CustomElementCallbackReaction(
UniquePtr<CustomElementCallback> aCustomElementCallback)
: mCustomElementCallback(std::move(aCustomElementCallback)) {}
size_t LifecycleCallbackArgs::SizeOfExcludingThis(
MallocSizeOf aMallocSizeOf) const {
size_t n = mOldValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
n += mNewValue.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
n += mNamespaceURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf); return n;
}
/* static */
UniquePtr<CustomElementCallback> CustomElementCallback::Create(
ElementCallbackType aType, Element* aCustomElement, const LifecycleCallbackArgs& aArgs, CustomElementDefinition* aDefinition) {
MOZ_ASSERT(aDefinition, "CustomElementDefinition should not be null");
MOZ_ASSERT(aCustomElement->GetCustomElementData(), "CustomElementData should exist");
// Let CALLBACK be the callback associated with the key NAME in CALLBACKS.
CallbackFunction* func = nullptr; switch (aType) { case ElementCallbackType::eConnected: if (aDefinition->mCallbacks->mConnectedCallback.WasPassed()) {
func = aDefinition->mCallbacks->mConnectedCallback.Value();
} break;
case ElementCallbackType::eDisconnected: if (aDefinition->mCallbacks->mDisconnectedCallback.WasPassed()) {
func = aDefinition->mCallbacks->mDisconnectedCallback.Value();
} break;
case ElementCallbackType::eAdopted: if (aDefinition->mCallbacks->mAdoptedCallback.WasPassed()) {
func = aDefinition->mCallbacks->mAdoptedCallback.Value();
} break;
case ElementCallbackType::eAttributeChanged: if (aDefinition->mCallbacks->mAttributeChangedCallback.WasPassed()) {
func = aDefinition->mCallbacks->mAttributeChangedCallback.Value();
} break;
case ElementCallbackType::eFormAssociated: if (aDefinition->mFormAssociatedCallbacks->mFormAssociatedCallback
.WasPassed()) {
func = aDefinition->mFormAssociatedCallbacks->mFormAssociatedCallback
.Value();
} break;
case ElementCallbackType::eFormReset: if (aDefinition->mFormAssociatedCallbacks->mFormResetCallback
.WasPassed()) {
func =
aDefinition->mFormAssociatedCallbacks->mFormResetCallback.Value();
} break;
case ElementCallbackType::eFormDisabled: if (aDefinition->mFormAssociatedCallbacks->mFormDisabledCallback
.WasPassed()) {
func = aDefinition->mFormAssociatedCallbacks->mFormDisabledCallback
.Value();
} break;
case ElementCallbackType::eFormStateRestore: if (aDefinition->mFormAssociatedCallbacks->mFormStateRestoreCallback
.WasPassed()) {
func = aDefinition->mFormAssociatedCallbacks->mFormStateRestoreCallback
.Value();
} break;
case ElementCallbackType::eGetCustomInterface:
MOZ_ASSERT_UNREACHABLE("Don't call GetCustomInterface through callback"); break;
}
// If there is no such callback, stop. if (!func) { return nullptr;
}
void CustomElementCallback::Call() { switch (mType) { case ElementCallbackType::eConnected: static_cast<LifecycleConnectedCallback*>(mCallback.get())
->Call(mThisObject); break; case ElementCallbackType::eDisconnected: static_cast<LifecycleDisconnectedCallback*>(mCallback.get())
->Call(mThisObject); break; case ElementCallbackType::eAdopted: static_cast<LifecycleAdoptedCallback*>(mCallback.get())
->Call(mThisObject, mArgs.mOldDocument, mArgs.mNewDocument); break; case ElementCallbackType::eAttributeChanged: static_cast<LifecycleAttributeChangedCallback*>(mCallback.get())
->Call(mThisObject, nsDependentAtomString(mArgs.mName),
mArgs.mOldValue, mArgs.mNewValue, mArgs.mNamespaceURI); break; case ElementCallbackType::eFormAssociated: static_cast<LifecycleFormAssociatedCallback*>(mCallback.get())
->Call(mThisObject, mArgs.mForm); break; case ElementCallbackType::eFormReset: static_cast<LifecycleFormResetCallback*>(mCallback.get())
->Call(mThisObject); break; case ElementCallbackType::eFormDisabled: static_cast<LifecycleFormDisabledCallback*>(mCallback.get())
->Call(mThisObject, mArgs.mDisabled); break; case ElementCallbackType::eFormStateRestore: { if (mArgs.mState.IsNull()) {
MOZ_ASSERT_UNREACHABLE( "A null state should never be restored to a form-associated " "custom element"); return;
}
size_t CustomElementCallback::SizeOfIncludingThis(
MallocSizeOf aMallocSizeOf) const {
size_t n = aMallocSizeOf(this);
// We don't uniquely own mThisObject.
// We own mCallback but it doesn't have any special memory reporting we can do // for it other than report its own size.
n += aMallocSizeOf(mCallback);
CustomElementData::CustomElementData(nsAtom* aType, State aState)
: mState(aState), mType(aType) {}
void CustomElementData::SetCustomElementDefinition(
CustomElementDefinition* aDefinition) { // Only allow reset definition to nullptr if the custom element state is // "failed".
MOZ_ASSERT(aDefinition ? !mCustomElementDefinition
: mState == State::eFailed);
MOZ_ASSERT_IF(aDefinition, aDefinition->mType == mType);
CustomElementDefinition* CustomElementData::GetCustomElementDefinition() const { // Per spec, if there is a definition, the custom element state should be // either "failed" (during upgrade) or "customized".
MOZ_ASSERT_IF(mCustomElementDefinition, mState != State::eUndefined);
size_t CustomElementData::SizeOfIncludingThis(
MallocSizeOf aMallocSizeOf) const {
size_t n = aMallocSizeOf(this);
n += mReactionQueue.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (auto& reaction : mReactionQueue) { // "reaction" can be null if we're being called indirectly from // InvokeReactions (e.g. due to a reaction causing a memory report to be // captured somehow). if (reaction) {
n += reaction->SizeOfIncludingThis(aMallocSizeOf);
}
}
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.");
RefPtr<CustomElementDefinition> definition =
mRegistry->mCustomDefinitions.Get(mAtom); if (!definition) { // Callback should set the definition of the type.
MOZ_DIAGNOSTIC_ASSERT(false, "Callback should set the definition of the type."); return NS_ERROR_FAILURE;
}
MOZ_ASSERT(!mRegistry->mElementCreationCallbacks.GetWeak(mAtom), "Callback should be removed.");
mozilla::UniquePtr<nsTHashSet<RefPtr<nsIWeakReference>>> elements;
mRegistry->mElementCreationCallbacksUpgradeCandidatesMap.Remove(mAtom,
&elements);
MOZ_ASSERT(elements, "There should be a list");
for (constauto& key : *elements) {
nsCOMPtr<Element> elem = do_QueryReferent(key); if (!elem) { continue;
}
CustomElementRegistry::Upgrade(elem, definition, er);
MOZ_ASSERT(NS_SUCCEEDED(er.StealNSResult()), "chrome JavaScript error in custom element construction.");
}
CustomElementDefinition* CustomElementRegistry::LookupCustomElementDefinition(
JSContext* aCx, JSObject* aConstructor) const { // We're looking up things that tested true for JS::IsConstructor, // so doing a CheckedUnwrapStatic is fine here.
JS::Rooted<JSObject*> constructor(aCx, js::CheckedUnwrapStatic(aConstructor));
constauto& ptr = mConstructors.lookup(constructor); if (!ptr) { return nullptr;
}
CustomElementDefinition* definition =
mCustomDefinitions.GetWeak(ptr->value());
MOZ_ASSERT(definition, "Definition must be found in mCustomDefinitions");
return definition;
}
void CustomElementRegistry::RegisterUnresolvedElement(Element* aElement,
nsAtom* aTypeName) { // We don't have a use-case for a Custom Element inside NAC, and continuing // here causes performance issues for NAC + XBL anonymous content. if (aElement->IsInNativeAnonymousSubtree()) { return;
}
mozilla::dom::NodeInfo* info = aElement->NodeInfo();
// Candidate may be a custom element through extension, // in which case the custom element type name will not // match the element tag name. e.g. <button is="x-button">.
RefPtr<nsAtom> typeName = aTypeName; if (!typeName) { typeName = info->NameAtom();
}
if (mCustomDefinitions.GetWeak(typeName)) { return;
}
nsTArray<nsCOMPtr<Element>> CandidateFinder::OrderedCandidates() { if (mCandidates.Count() == 1) { // Fast path for one candidate. auto iter = mCandidates.Iter();
nsTArray<nsCOMPtr<Element>> rval({std::move(iter.Data())});
iter.Remove(); return rval;
}
nsTArray<nsCOMPtr<Element>> orderedElements(mCandidates.Count()); for (nsINode* node : ShadowIncludingTreeIterator(*mDoc)) {
Element* element = Element::FromNode(node); if (!element) { continue;
}
nsCOMPtr<Element> elem; if (mCandidates.Remove(element, getter_AddRefs(elem))) {
orderedElements.AppendElement(std::move(elem)); if (mCandidates.Count() == 0) { break;
}
}
}
if (!iterable.isUndefined()) { if (!iterable.isObject()) {
aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(NS_ConvertUTF16toUTF8(aName), "sequence"); returnfalse;
}
JS::ForOfIterator iter(aCx); if (!iter.init(iterable, JS::ForOfIterator::AllowNonIterable)) {
aRv.NoteJSContextException(aCx); returnfalse;
}
if (!iter.valueIsIterable()) {
aRv.ThrowTypeError<MSG_CONVERSION_ERROR>(NS_ConvertUTF16toUTF8(aName), "sequence"); returnfalse;
}
JS::Rooted<JS::Value> attribute(aCx); while (true) { bool done; if (!iter.next(&attribute, &done)) {
aRv.NoteJSContextException(aCx); returnfalse;
} if (done) { break;
}
// We need to do a dynamic unwrap in order to throw the right exception. We // could probably avoid that if we just threw MSG_NOT_CONSTRUCTOR if unwrap // fails. // // In any case, aCx represents the global we want to be using for the unwrap // here.
JS::Rooted<JSObject*> constructorUnwrapped(
aCx, js::CheckedUnwrapDynamic(constructor, aCx)); if (!constructorUnwrapped) { // If the caller's compartment does not have permission to access the // unwrapped constructor then throw.
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); return;
}
/** * 1. If IsConstructor(constructor) is false, then throw a TypeError and abort * these steps.
*/ if (!JS::IsConstructor(constructorUnwrapped)) {
aRv.ThrowTypeError<MSG_NOT_CONSTRUCTOR>("Argument 2"); return;
}
/** * 2. If name is not a valid custom element name, then throw a "SyntaxError" * DOMException and abort these steps.
*/
Document* doc = mWindow->GetExtantDoc();
RefPtr<nsAtom> nameAtom(NS_Atomize(aName)); if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
aRv.ThrowSyntaxError(
nsPrintfCString("'%s' is not a valid custom element name",
NS_ConvertUTF16toUTF8(aName).get())); return;
}
/** * 3. If this CustomElementRegistry contains an entry with name name, then * throw a "NotSupportedError" DOMException and abort these steps.
*/ if (mCustomDefinitions.GetWeak(nameAtom)) {
aRv.ThrowNotSupportedError(
nsPrintfCString("'%s' has already been defined as a custom element",
NS_ConvertUTF16toUTF8(aName).get())); return;
}
/** * 4. If this CustomElementRegistry contains an entry with constructor * constructor, then throw a "NotSupportedError" DOMException and abort these * steps.
*/ constauto& ptr = mConstructors.lookup(constructorUnwrapped); if (ptr) {
MOZ_ASSERT(mCustomDefinitions.GetWeak(ptr->value()), "Definition must be found in mCustomDefinitions");
nsAutoCString name;
ptr->value()->ToUTF8String(name);
aRv.ThrowNotSupportedError(
nsPrintfCString("'%s' and '%s' have the same constructor",
NS_ConvertUTF16toUTF8(aName).get(), name.get())); return;
}
/** * 5. Let localName be name. * 6. Let extends be the value of the extends member of options, or null if * no such member exists. * 7. If extends is not null, then: * 1. If extends is a valid custom element name, then throw a * "NotSupportedError" DOMException. * 2. If the element interface for extends and the HTML namespace is * HTMLUnknownElement (e.g., if extends does not indicate an element * definition in this specification), then throw a "NotSupportedError" * DOMException. * 3. Set localName to extends. * * Special note for XUL elements: * * For step 7.1, we'll subject XUL to the same rules as HTML, so that a * custom built-in element will not be extending from a dashed name. * Step 7.2 is disregarded. But, we do check if the name is a dashed name * (i.e. step 2) given that there is no reason for a custom built-in element * type to take on a non-dashed name. * This also ensures the name of the built-in custom element type can never * be the same as the built-in element name, so we don't break the assumption * elsewhere.
*/
nsAutoString localName(aName); if (aOptions.mExtends.WasPassed()) {
doc->SetUseCounter(eUseCounter_custom_CustomizedBuiltin);
RefPtr<nsAtom> extendsAtom(NS_Atomize(aOptions.mExtends.Value())); if (nsContentUtils::IsCustomElementName(extendsAtom, kNameSpaceID_XHTML)) {
aRv.ThrowNotSupportedError(
nsPrintfCString("'%s' cannot extend a custom element",
NS_ConvertUTF16toUTF8(aName).get())); return;
}
if (nameSpaceID == kNameSpaceID_XHTML) { // bgsound and multicol are unknown html element.
int32_t tag = nsHTMLTags::CaseSensitiveAtomTagToId(extendsAtom); if (tag == eHTMLTag_userdefined || tag == eHTMLTag_bgsound ||
tag == eHTMLTag_multicol) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return;
}
} else { // kNameSpaceID_XUL // As stated above, ensure the name of the customized built-in element // (the one that goes to the |is| attribute) is a dashed name. if (!nsContentUtils::IsNameWithDash(nameAtom)) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); return;
}
}
localName.Assign(aOptions.mExtends.Value());
}
/** * 8. If this CustomElementRegistry's element definition is running flag is * set, then throw a "NotSupportedError" DOMException and abort these steps.
*/ if (mIsCustomDefinitionRunning) {
aRv.ThrowNotSupportedError( "Cannot define a custom element while defining another custom element"); return;
}
auto callbacksHolder = MakeUnique<LifecycleCallbacks>(); auto formAssociatedCallbacksHolder =
MakeUnique<FormAssociatedLifecycleCallbacks>();
nsTArray<RefPtr<nsAtom>> observedAttributes;
AutoTArray<RefPtr<nsAtom>, 2> disabledFeatures; bool formAssociated = false; bool disableInternals = false; bool disableShadow = false;
{ // Set mIsCustomDefinitionRunning. /** * 9. Set this CustomElementRegistry's element definition is running flag.
*/
AutoRestore<bool> restoreRunning(mIsCustomDefinitionRunning);
mIsCustomDefinitionRunning = true;
/** * 14.1. Let prototype be Get(constructor, "prototype"). Rethrow any * exceptions.
*/ // The .prototype on the constructor passed could be an "expando" of a // wrapper. So we should get it from wrapper instead of the underlying // object.
JS::Rooted<JS::Value> prototype(aCx); if (!JS_GetProperty(aCx, constructor, "prototype", &prototype)) {
aRv.NoteJSContextException(aCx); return;
}
/** * 14.2. If Type(prototype) is not Object, then throw a TypeError exception.
*/ if (!prototype.isObject()) {
aRv.ThrowTypeError<MSG_NOT_OBJECT>("constructor.prototype"); return;
}
/** * 14.3. Let lifecycleCallbacks be a map with the four keys * "connectedCallback", "disconnectedCallback", "adoptedCallback", and * "attributeChangedCallback", each of which belongs to an entry whose * value is null. The 'getCustomInterface' callback is also included * for chrome usage. * 14.4. For each of the four keys callbackName in lifecycleCallbacks: * 1. Let callbackValue be Get(prototype, callbackName). Rethrow any * exceptions. * 2. If callbackValue is not undefined, then set the value of the * entry in lifecycleCallbacks with key callbackName to the result * of converting callbackValue to the Web IDL Function callback * type. Rethrow any exceptions from the conversion.
*/ if (!callbacksHolder->Init(aCx, prototype)) {
aRv.NoteJSContextException(aCx); return;
}
/** * 14.5. If the value of the entry in lifecycleCallbacks with key * "attributeChangedCallback" is not null, then: * 1. Let observedAttributesIterable be Get(constructor, * "observedAttributes"). Rethrow any exceptions. * 2. If observedAttributesIterable is not undefined, then set * observedAttributes to the result of converting * observedAttributesIterable to a sequence<DOMString>. Rethrow * any exceptions from the conversion.
*/ if (callbacksHolder->mAttributeChangedCallback.WasPassed()) { if (!JSObjectToAtomArray(aCx, constructor, u"observedAttributes"_ns,
observedAttributes, aRv)) { return;
}
}
/** * 14.6. Let disabledFeatures be an empty sequence<DOMString>. * 14.7. Let disabledFeaturesIterable be Get(constructor, * "disabledFeatures"). Rethrow any exceptions. * 14.8. If disabledFeaturesIterable is not undefined, then set * disabledFeatures to the result of converting * disabledFeaturesIterable to a sequence<DOMString>. * Rethrow any exceptions from the conversion.
*/ if (!JSObjectToAtomArray(aCx, constructor, u"disabledFeatures"_ns,
disabledFeatures, aRv)) { return;
}
// 14.9. Set disableInternals to true if disabledFeaturesSequence contains // "internals".
disableInternals = disabledFeatures.Contains( static_cast<nsStaticAtom*>(nsGkAtoms::internals));
// 14.10. Set disableShadow to true if disabledFeaturesSequence contains // "shadow".
disableShadow = disabledFeatures.Contains( static_cast<nsStaticAtom*>(nsGkAtoms::shadow));
// 14.11. Let formAssociatedValue be Get(constructor, "formAssociated"). // Rethrow any exceptions.
JS::Rooted<JS::Value> formAssociatedValue(aCx); if (!JS_GetProperty(aCx, constructor, "formAssociated",
&formAssociatedValue)) {
aRv.NoteJSContextException(aCx); return;
}
// 14.12. Set formAssociated to the result of converting // formAssociatedValue to a boolean. Rethrow any exceptions from // the conversion. if (!ValueToPrimitive<bool, eDefault>(aCx, formAssociatedValue, "formAssociated", &formAssociated)) {
aRv.NoteJSContextException(aCx); return;
}
/** * 14.13. If formAssociated is true, for each of "formAssociatedCallback", * "formResetCallback", "formDisabledCallback", and * "formStateRestoreCallback" callbackName: * 1. Let callbackValue be ? Get(prototype, callbackName). * 2. If callbackValue is not undefined, then set the value of the * entry in lifecycleCallbacks with key callbackName to the result * of converting callbackValue to the Web IDL Function callback * type. Rethrow any exceptions from the conversion.
*/ if (formAssociated &&
!formAssociatedCallbacksHolder->Init(aCx, prototype)) {
aRv.NoteJSContextException(aCx); return;
}
} // Unset mIsCustomDefinitionRunning
/** * 15. Let definition be a new custom element definition with name name, * local name localName, constructor constructor, prototype prototype, * observed attributes observedAttributes, and lifecycle callbacks * lifecycleCallbacks.
*/ // Associate the definition with the custom element.
RefPtr<nsAtom> localNameAtom(NS_Atomize(localName));
/** * 16. Add definition to this CustomElementRegistry.
*/ if (!mConstructors.put(constructorUnwrapped, nameAtom)) {
aRv.Throw(NS_ERROR_FAILURE); return;
}
/** * 20. If this CustomElementRegistry's when-defined promise map contains an * entry with key name: * 1. Let promise be the value of that entry. * 2. Resolve promise with undefined. * 3. Delete the entry with key name from this CustomElementRegistry's * when-defined promise map.
*/
RefPtr<Promise> promise;
mWhenDefinedPromiseMap.Remove(nameAtom, getter_AddRefs(promise)); if (promise) {
promise->MaybeResolve(def->mConstructor);
}
// Dispatch a "customelementdefined" event for DevTools.
{
JSString* nameJsStr =
JS_NewUCStringCopyN(aCx, aName.BeginReading(), aName.Length());
if (aDefinition) {
aDefinition->mType->ToString(aResult);
} else {
aResult.SetIsVoid(true);
}
}
already_AddRefed<Promise> CustomElementRegistry::WhenDefined( const nsAString& aName, ErrorResult& aRv) { // Define a function that lazily creates a Promise and perform some action on // it when creation succeeded. It's needed in multiple cases below, but not in // all of them. auto createPromise = [&](auto&& action) -> already_AddRefed<Promise> {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
RefPtr<Promise> promise = Promise::Create(global, aRv);
if (aRv.Failed()) { return nullptr;
}
action(promise);
return promise.forget();
};
RefPtr<nsAtom> nameAtom(NS_Atomize(aName));
Document* doc = mWindow->GetExtantDoc();
uint32_t nameSpaceID =
doc ? doc->GetDefaultNamespaceID() : kNameSpaceID_XHTML; if (!nsContentUtils::IsCustomElementName(nameAtom, nameSpaceID)) {
aRv.ThrowSyntaxError(
nsPrintfCString("'%s' is not a valid custom element name",
NS_ConvertUTF16toUTF8(aName).get())); return nullptr;
}
MOZ_CAN_RUN_SCRIPT staticvoid DoUpgrade(Element* aElement, CustomElementDefinition* aDefinition,
CustomElementConstructor* aConstructor,
ErrorResult& aRv) { if (aDefinition->mDisableShadow && aElement->GetShadowRoot()) {
aRv.ThrowNotSupportedError(nsPrintfCString( "Custom element upgrade to '%s' is disabled because a shadow root " "already exists",
NS_ConvertUTF16toUTF8(aDefinition->mType->GetUTF16String()).get())); return;
}
CustomElementData* data = aElement->GetCustomElementData();
MOZ_ASSERT(data, "CustomElementData should exist");
data->mState = CustomElementData::State::ePrecustomized;
JS::Rooted<JS::Value> constructResult(RootingCx()); // Rethrow the exception since it might actually throw the exception from the // upgrade steps back out to the caller of document.createElement.
aConstructor->Construct(&constructResult, aRv, "Custom Element Upgrade",
CallbackFunction::eRethrowExceptions); if (aRv.Failed()) { return;
}
Element* element; // constructResult is an ObjectValue because construction with a callback // always forms the return value from a JSObject. if (NS_FAILED(UNWRAP_OBJECT(Element, &constructResult, element)) ||
element != aElement) {
aRv.ThrowTypeError("Custom element constructor returned a wrong element"); return;
}
}
// Step 4. if (!aDefinition->mObservedAttributes.IsEmpty()) {
uint32_t count = aElement->GetAttrCount(); for (uint32_t i = 0; i < count; i++) {
mozilla::dom::BorrowedAttrInfo info = aElement->GetAttrInfoAt(i);
const nsAttrName* name = info.mName;
nsAtom* attrName = name->LocalName();
// Step 7 and step 8.
DoUpgrade(aElement, aDefinition, MOZ_KnownLive(aDefinition->mConstructor),
aRv); if (aRv.Failed()) {
MOZ_ASSERT(data->mState == CustomElementData::State::eFailed ||
data->mState == CustomElementData::State::ePrecustomized); // Spec doesn't set custom element state to failed here, but without this we // would have inconsistent state on a custom elemet that is failed to // upgrade, see https://github.com/whatwg/html/issues/6929, and // https://github.com/web-platform-tests/wpt/pull/29911 for the test.
data->mState = CustomElementData::State::eFailed;
aElement->SetCustomElementDefinition(nullptr); // Empty element's custom element reaction queue.
data->mReactionQueue.Clear(); return;
}
if (!nsContentUtils::IsChromeDoc(aElement->OwnerDoc())) { return nullptr;
}
// Try to get our GetCustomInterfaceCallback callback.
CustomElementDefinition* definition = aElement->GetCustomElementDefinition(); if (!definition || !definition->mCallbacks ||
!definition->mCallbacks->mGetCustomInterfaceCallback.WasPassed() ||
(definition->mLocalName != aElement->NodeInfo()->NameAtom())) { return nullptr;
}
LifecycleGetCustomInterfaceCallback* func =
definition->mCallbacks->mGetCustomInterfaceCallback.Value();
// Initialize a AutoJSAPI to enter the compartment of the callback.
AutoJSAPI jsapi;
JS::Rooted<JSObject*> funcGlobal(RootingCx(), func->CallbackGlobalOrNull()); if (!funcGlobal || !jsapi.Init(funcGlobal)) { return nullptr;
}
// Push a new element queue onto the custom element reactions stack.
mReactionsStack.AppendElement(MakeUnique<ElementQueue>());
mIsElementQueuePushedForCurrentRecursionDepth = true;
}
// Pop the element queue from the custom element reactions stack, // and invoke custom element reactions in that queue. const uint32_t lastIndex = mReactionsStack.Length() - 1;
ElementQueue* elementQueue = mReactionsStack.ElementAt(lastIndex).get(); // Check element queue size in order to reduce function call overhead. if (!elementQueue->IsEmpty()) { // 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. // This may be null if it's called from parser, see the case of // attributeChangedCallback in // https://html.spec.whatwg.org/multipage/parsing.html#create-an-element-for-the-token // In that case, the exception of callback reactions will be automatically // reported in CallSetup.
nsIGlobalObject* global = GetEntryGlobal();
InvokeReactions(elementQueue, MOZ_KnownLive(global));
}
// InvokeReactions() might create other custom element reactions, but those // new reactions should be already consumed and removed at this point.
MOZ_ASSERT(
lastIndex == mReactionsStack.Length() - 1, "reactions created by InvokeReactions() should be consumed and removed");
if (mRecursionDepth) { // If the element queue is not created for current recursion depth, create // and push an element queue to reactions stack first. if (!mIsElementQueuePushedForCurrentRecursionDepth) {
CreateAndPushElementQueue();
}
MOZ_ASSERT(!mReactionsStack.IsEmpty()); // Add element to the current element queue.
mReactionsStack.LastElement()->AppendElement(aElement);
elementData->mReactionQueue.AppendElement(aReaction); return;
}
// If the custom element reactions stack is empty, then: // Add element to the backup element queue.
MOZ_ASSERT(mReactionsStack.IsEmpty(), "custom element reactions stack should be empty");
mBackupQueue.AppendElement(aElement);
elementData->mReactionQueue.AppendElement(aReaction);
void CustomElementReactionsStack::InvokeBackupQueue() { // Check backup queue size in order to reduce function call overhead. if (!mBackupQueue.IsEmpty()) { // 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);
}
MOZ_ASSERT(
mBackupQueue.IsEmpty(), "There are still some reactions in BackupQueue not being consumed!?!");
}
void 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) {
Element* element = aElementQueue->ElementAt(i); // ElementQueue hold a element's strong reference, it should not be a // nullptr.
MOZ_ASSERT(element);
CustomElementData* elementData = element->GetCustomElementData(); if (!elementData || !element->GetOwnerGlobal()) { // This happens when the document is destroyed and the element is already // unlinked, no need to fire the callbacks in this case. continue;
}
auto& reactions = elementData->mReactionQueue; for (uint32_t j = 0; j < reactions.Length(); ++j) { // Transfer the ownership of the entry due to reentrant invocation of // this function. auto reaction(std::move(reactions.ElementAt(j))); if (reaction) { if (!aGlobal && reaction->IsUpgradeReaction()) {
nsIGlobalObject* global = element->GetOwnerGlobal();
MOZ_ASSERT(!aes);
aes.emplace(global, "custom elements reaction invocation");
}
ErrorResult rv;
reaction->Invoke(MOZ_KnownLive(element), rv); if (aes) {
JSContext* cx = aes->cx(); if (rv.MaybeSetPendingException(cx)) {
aes->ReportException();
}
MOZ_ASSERT(!JS_IsExceptionPending(cx)); if (!aGlobal && reaction->IsUpgradeReaction()) {
aes.reset();
}
}
MOZ_ASSERT(!rv.Failed());
}
}
reactions.Clear();
}
aElementQueue->Clear();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.