From 01ee16866bb816f6f5330b42e9eb91d4a92cd38d Mon Sep 17 00:00:00 2001 From: Jan-Niklas Jaeschke Date: Tue, 16 Jun 2026 21:31:56 +0000 Subject: [PATCH] Bug 2047481 - Skip redundant scans and atomize the lookup name in setAttribute. r=smaug,emilio When Element::SetAttribute determines that the attribute is new, propagate that as an IsKnownNewAttr hint through SetAttr, SetAttrInternal, SetAttrAndNotify and SetAndSwapAttr so the secondary scans in MaybeCheckSameAttrVal and AttrArray::SetAndSwapAttr can be skipped. AttrArray::GetExistingAttrNameFromQName atomizes the lookup name once up front and compares attribute names by atom pointer, falling back to QualifiedNameEquals only for NodeInfo-typed entries. Differential Revision: https://phabricator.services.mozilla.com/D306935 --- dom/base/Attr.cpp | 3 +- dom/base/AttrArray.cpp | 60 ++++++++++---- dom/base/AttrArray.h | 19 ++++- dom/base/Element.cpp | 147 +++++++++++++++++++++------------- dom/base/Element.h | 31 ++++--- dom/base/nsStyledElement.cpp | 5 +- dom/html/ElementInternals.cpp | 6 +- dom/svg/SVGElement.cpp | 11 +-- dom/xul/nsXULElement.cpp | 3 +- 9 files changed, 188 insertions(+), 97 deletions(-) diff --git a/dom/base/Attr.cpp b/dom/base/Attr.cpp index 3839df519ee3..10af927a9fad 100644 --- a/dom/base/Attr.cpp +++ b/dom/base/Attr.cpp @@ -177,7 +177,8 @@ void Attr::SetValueInternal(const nsAString& aValue, ErrorResult& aRv) { RefPtr nameAtom = mNodeInfo->NameAtom(); aRv = element->SetAttr(mNodeInfo->NamespaceID(), nameAtom, - mNodeInfo->GetPrefixAtom(), aValue, nullptr, true); + mNodeInfo->GetPrefixAtom(), aValue, nullptr, true, + IsKnownNewAttr::No); } bool Attr::Specified() const { return true; } diff --git a/dom/base/AttrArray.cpp b/dom/base/AttrArray.cpp index 950387088533..e714ba68548f 100644 --- a/dom/base/AttrArray.cpp +++ b/dom/base/AttrArray.cpp @@ -132,36 +132,48 @@ const nsAttrValue* AttrArray::AddNewAttributeAssumeAvailableSlot( } nsresult AttrArray::SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue, - bool* aHadValue) { + bool* aHadValue, + mozilla::dom::IsKnownNewAttr aIsKnownNew) { *aHadValue = false; - for (InternalAttr& attr : Attrs()) { - if (attr.mName.Equals(aLocalName)) { - attr.mValue.SwapValueWith(aValue); - *aHadValue = true; - return NS_OK; + if (aIsKnownNew == mozilla::dom::IsKnownNewAttr::No) { + for (InternalAttr& attr : Attrs()) { + if (attr.mName.Equals(aLocalName)) { + attr.mValue.SwapValueWith(aValue); + *aHadValue = true; + return NS_OK; + } } + } else { + MOZ_ASSERT(IndexOfAttr(aLocalName) == -1, + "Caller asserted attribute is new but it already exists"); } return AddNewAttribute(aLocalName, aValue); } nsresult AttrArray::SetAndSwapAttr(mozilla::dom::NodeInfo* aName, - nsAttrValue& aValue, bool* aHadValue) { + nsAttrValue& aValue, bool* aHadValue, + mozilla::dom::IsKnownNewAttr aIsKnownNew) { int32_t namespaceID = aName->NamespaceID(); nsAtom* localName = aName->NameAtom(); if (namespaceID == kNameSpaceID_None) { - return SetAndSwapAttr(localName, aValue, aHadValue); + return SetAndSwapAttr(localName, aValue, aHadValue, aIsKnownNew); } *aHadValue = false; - for (InternalAttr& attr : Attrs()) { - if (attr.mName.Equals(localName, namespaceID)) { - attr.mName.SetTo(aName); - attr.mValue.SwapValueWith(aValue); - *aHadValue = true; - return NS_OK; + if (aIsKnownNew == mozilla::dom::IsKnownNewAttr::No) { + for (InternalAttr& attr : Attrs()) { + if (attr.mName.Equals(localName, namespaceID)) { + attr.mName.SetTo(aName); + attr.mValue.SwapValueWith(aValue); + *aHadValue = true; + return NS_OK; + } } + } else { + MOZ_ASSERT(IndexOfAttr(localName, namespaceID) == -1, + "Caller asserted attribute is new but it already exists"); } return AddNewAttribute(aName, aValue); @@ -213,11 +225,27 @@ const nsAttrName* AttrArray::GetSafeAttrNameAt(uint32_t aPos) const { } const nsAttrName* AttrArray::GetExistingAttrNameFromQName( - const nsAString& aName) const { + const nsAString& aName, RefPtr* aOutAtom) const { + if (aOutAtom) { + *aOutAtom = nullptr; + } + if (!HasAttrs()) { + return nullptr; + } + RefPtr nameAtom = NS_AtomizeMainThread(aName); for (const InternalAttr& attr : Attrs()) { - if (attr.mName.QualifiedNameEquals(aName)) { + // Equals(nsAtom*) compares mBits; for NodeInfo-typed entries mBits has the + // tag bit set and can never equal a clean atom pointer, so this is safe + // for both kinds without branching on IsAtom(). + if (attr.mName.Equals(nameAtom.get())) { return &attr.mName; } + if (!attr.mName.IsAtom() && attr.mName.QualifiedNameEquals(aName)) { + return &attr.mName; + } + } + if (aOutAtom) { + *aOutAtom = nameAtom.forget(); } return nullptr; } diff --git a/dom/base/AttrArray.h b/dom/base/AttrArray.h index 96e13f3b1cf3..362707916176 100644 --- a/dom/base/AttrArray.h +++ b/dom/base/AttrArray.h @@ -25,6 +25,10 @@ struct StyleLockedDeclarationBlock; namespace dom { class Element; class ElementInternals; + +// Caller-supplied assertion that an attribute does not already exist, used +// to skip redundant scans on the addition path. +enum class IsKnownNewAttr : bool { No, Yes }; } // namespace dom } // namespace mozilla @@ -98,7 +102,14 @@ class AttrArray { // Otherwise, crash. const nsAttrName* GetSafeAttrNameAt(uint32_t aPos) const; - const nsAttrName* GetExistingAttrNameFromQName(const nsAString& aName) const; + // Find an existing attribute by qualified name. + // + // When aOutAtom is non-null and no matching attribute is found, *aOutAtom + // is set to the atomized lookup name so callers can reuse it without + // re-atomizing. In all other cases (match found, or the element has no + // attributes), *aOutAtom is set to nullptr. + const nsAttrName* GetExistingAttrNameFromQName( + const nsAString& aName, RefPtr* aOutAtom = nullptr) const; int32_t IndexOfAttr(const nsAtom* aLocalName) const; int32_t IndexOfAttr(const nsAtom* aLocalName, int32_t aNamespaceID) const; @@ -369,9 +380,11 @@ class AttrArray { // aValue and aHadValue will be set to false. Otherwise, aHadValue will be set // to true. nsresult SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue, - bool* aHadValue); + bool* aHadValue, + mozilla::dom::IsKnownNewAttr aIsKnownNew); nsresult SetAndSwapAttr(mozilla::dom::NodeInfo* aName, nsAttrValue& aValue, - bool* aHadValue); + bool* aHadValue, + mozilla::dom::IsKnownNewAttr aIsKnownNew); // mImpl serves dual purposes using pointer tagging: // diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index a0fb5092f3f8..beff3694f2ae 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -1794,20 +1794,20 @@ void Element::SetAttribute(const nsAString& aName, const nsAString& aValue, } nsAutoString nameToUse; - const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse); + RefPtr nameAtom; + const nsAttrName* name = + InternalGetAttrNameFromQName(aName, &nameToUse, &nameAtom); if (!name) { - RefPtr nameAtom = NS_AtomizeMainThread(nameToUse); if (!nameAtom) { - aError.Throw(NS_ERROR_OUT_OF_MEMORY); - return; + nameAtom = NS_AtomizeMainThread(nameToUse); } - aError = SetAttr(kNameSpaceID_None, nameAtom, aValue, aTriggeringPrincipal, - true); + aError = SetAttr(kNameSpaceID_None, nameAtom, nullptr, aValue, + aTriggeringPrincipal, true, IsKnownNewAttr::Yes); return; } aError = SetAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(), - aValue, aTriggeringPrincipal, true); + aValue, aTriggeringPrincipal, true, IsKnownNewAttr::No); } void Element::RemoveAttribute(const nsAString& aName, ErrorResult& aError) { @@ -1884,7 +1884,7 @@ void Element::SetAttributeNS(const nsAString& aNamespaceURI, } aError = SetAttr(ni->NamespaceID(), ni->NameAtom(), ni->GetPrefixAtom(), - aValue, aTriggeringPrincipal, true); + aValue, aTriggeringPrincipal, true, IsKnownNewAttr::No); } already_AddRefed Element::CreateDevtoolsPrincipal() { @@ -1920,10 +1920,15 @@ void Element::SetAttribute( } nsAutoString nameToUse; - const nsAttrName* name = InternalGetAttrNameFromQName(aName, &nameToUse); + RefPtr nameAtom; + const nsAttrName* name = + InternalGetAttrNameFromQName(aName, &nameToUse, &nameAtom); if (!name) { - RefPtr nameAtom = NS_AtomizeMainThread(nameToUse); + if (!nameAtom) { + nameAtom = NS_AtomizeMainThread(nameToUse); + } Maybe compliantStringHolder; + nsMutationGuard guard; const nsAString* compliantString = TrustedTypeUtils::GetTrustedTypesCompliantAttributeValue( *this, nameAtom, kNameSpaceID_None, aValue, aTriggeringPrincipal, @@ -1931,8 +1936,12 @@ void Element::SetAttribute( if (aError.Failed()) { return; } - aError = SetAttr(kNameSpaceID_None, nameAtom, *compliantString, - aTriggeringPrincipal, true); + // The fast path is only safe if nothing script-runnable above mutated + // mAttrs out from under us. + const IsKnownNewAttr isKnownNew = + guard.Mutated(0) ? IsKnownNewAttr::No : IsKnownNewAttr::Yes; + aError = SetAttr(kNameSpaceID_None, nameAtom, nullptr, *compliantString, + aTriggeringPrincipal, true, isKnownNew); return; } @@ -1948,7 +1957,8 @@ void Element::SetAttribute( } if (!guard.Mutated(0)) { aError = SetAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(), - *compliantString, aTriggeringPrincipal, true); + *compliantString, aTriggeringPrincipal, true, + IsKnownNewAttr::No); return; } @@ -1980,8 +1990,9 @@ void Element::SetAttributeNS( if (aError.Failed()) { return; } - aError = SetAttr(ni->NamespaceID(), ni->NameAtom(), ni->GetPrefixAtom(), - *compliantString, aTriggeringPrincipal, true); + aError = + SetAttr(ni->NamespaceID(), ni->NameAtom(), ni->GetPrefixAtom(), + *compliantString, aTriggeringPrincipal, true, IsKnownNewAttr::No); } void Element::SetAttributeDevtools(const nsAString& aName, @@ -3489,19 +3500,20 @@ void Element::SetEventHandler(nsAtom* aEventName, const nsAString& aValue, //---------------------------------------------------------------------- const nsAttrName* Element::InternalGetAttrNameFromQName( - const nsAString& aStr, nsAutoString* aNameToUse) const { + const nsAString& aStr, nsAutoString* aNameToUse, + RefPtr* aOutAtom) const { MOZ_ASSERT(!aNameToUse || aNameToUse->IsEmpty()); const nsAttrName* val = nullptr; if (IsHTMLElement() && IsInHTMLDocument()) { nsAutoString lower; nsAutoString& outStr = aNameToUse ? *aNameToUse : lower; nsContentUtils::ASCIIToLower(aStr, outStr); - val = mAttrs.GetExistingAttrNameFromQName(outStr); + val = mAttrs.GetExistingAttrNameFromQName(outStr, aOutAtom); if (val) { outStr.Truncate(); } } else { - val = mAttrs.GetExistingAttrNameFromQName(aStr); + val = mAttrs.GetExistingAttrNameFromQName(aStr, aOutAtom); if (!val && aNameToUse) { *aNameToUse = aStr; } @@ -3586,28 +3598,32 @@ nsresult Element::SetClassAttrFromParser(nsAtom* aValue) { nullptr, // old value value, nullptr, AttrModType::Addition, false, // notify - kCallAfterSetAttr, document, updateBatch); + kCallAfterSetAttr, document, updateBatch, + IsKnownNewAttr::Yes); } nsresult Element::SetAttr(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix, const nsAString& aValue, - nsIPrincipal* aSubjectPrincipal, bool aNotify) { + nsIPrincipal* aSubjectPrincipal, bool aNotify, + IsKnownNewAttr aIsKnownNew) { // Keep this in sync with SetParsedAttr below and SetSingleClassFromParser // above. const nsAttrValueOrString valueForComparison(aValue); - return SetAttrInternal(aNamespaceID, aName, aPrefix, valueForComparison, - aSubjectPrincipal, aNotify, - [&](nsAttrValue& attrValue) { - if (!ParseAttribute(aNamespaceID, aName, aValue, - aSubjectPrincipal, attrValue)) { - attrValue.SetTo(aValue); - } - }); + return SetAttrInternal( + aNamespaceID, aName, aPrefix, valueForComparison, aSubjectPrincipal, + aNotify, + [&](nsAttrValue& attrValue) { + if (!ParseAttribute(aNamespaceID, aName, aValue, aSubjectPrincipal, + attrValue)) { + attrValue.SetTo(aValue); + } + }, + aIsKnownNew); } nsresult Element::SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue, - bool* aHadValue) { - MOZ_TRY(mAttrs.SetAndSwapAttr(aLocalName, aValue, aHadValue)); + bool* aHadValue, IsKnownNewAttr aIsKnownNew) { + MOZ_TRY(mAttrs.SetAndSwapAttr(aLocalName, aValue, aHadValue, aIsKnownNew)); if (aLocalName == nsGkAtoms::_class) { UpdateSubtreeBloomFilterForClass(GetClasses()); @@ -3619,8 +3635,9 @@ nsresult Element::SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue, } nsresult Element::SetAndSwapAttr(mozilla::dom::NodeInfo* aName, - nsAttrValue& aValue, bool* aHadValue) { - MOZ_TRY(mAttrs.SetAndSwapAttr(aName, aValue, aHadValue)); + nsAttrValue& aValue, bool* aHadValue, + IsKnownNewAttr aIsKnownNew) { + MOZ_TRY(mAttrs.SetAndSwapAttr(aName, aValue, aHadValue, aIsKnownNew)); // Only update bloom filter for null-namespace attributes, since the // querySelector bloom filter optimization only applies to those. @@ -3643,14 +3660,16 @@ nsresult Element::SetAttr(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix, // above. const nsDependentAtomString valueString(aValue); const nsAttrValueOrString valueForComparison(valueString); - return SetAttrInternal(aNamespaceID, aName, aPrefix, valueForComparison, - aSubjectPrincipal, aNotify, - [&](nsAttrValue& attrValue) { - if (!ParseAttribute(aNamespaceID, aName, valueString, - aSubjectPrincipal, attrValue)) { - attrValue.SetTo(aValue); - } - }); + return SetAttrInternal( + aNamespaceID, aName, aPrefix, valueForComparison, aSubjectPrincipal, + aNotify, + [&](nsAttrValue& attrValue) { + if (!ParseAttribute(aNamespaceID, aName, valueString, aSubjectPrincipal, + attrValue)) { + attrValue.SetTo(aValue); + } + }, + IsKnownNewAttr::No); } template @@ -3658,17 +3677,23 @@ nsresult Element::SetAttrInternal(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix, const nsAttrValueOrString& aValue, nsIPrincipal* aSubjectPrincipal, bool aNotify, - ParseFunc&& aParseFn) { + ParseFunc&& aParseFn, + IsKnownNewAttr aIsKnownNew) { NS_ENSURE_ARG_POINTER(aName); NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown, "Don't call SetAttr with unknown namespace"); AttrModType modType{0}; // NOTE: Initialized with invalid value. nsAttrValue oldValue; - bool oldValueSet; + bool oldValueSet = false; - if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, aValue, aNotify, - oldValue, &modType, &oldValueSet)) { + if (aIsKnownNew == IsKnownNewAttr::Yes) { + MOZ_ASSERT(mAttrs.IndexOfAttr(aName, aNamespaceID) == -1, + "Caller asserted attribute is new but it already exists"); + modType = AttrModType::Addition; + } else if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, aValue, + aNotify, oldValue, &modType, + &oldValueSet)) { return NS_OK; } @@ -3689,15 +3714,15 @@ nsresult Element::SetAttrInternal(int32_t aNamespaceID, nsAtom* aName, PreIdMaybeChange(aNamespaceID, aName, &attrValue); - return SetAttrAndNotify(aNamespaceID, aName, aPrefix, - oldValueSet ? &oldValue : nullptr, attrValue, - aSubjectPrincipal, modType, aNotify, - kCallAfterSetAttr, document, updateBatch); + return SetAttrAndNotify( + aNamespaceID, aName, aPrefix, oldValueSet ? &oldValue : nullptr, + attrValue, aSubjectPrincipal, modType, aNotify, kCallAfterSetAttr, + document, updateBatch, aIsKnownNew); } nsresult Element::SetParsedAttr(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix, nsAttrValue& aParsedValue, - bool aNotify) { + bool aNotify, IsKnownNewAttr aIsKnownNew) { // Keep this in sync with SetAttr and SetSingleClassFromParser above NS_ENSURE_ARG_POINTER(aName); @@ -3706,9 +3731,13 @@ nsresult Element::SetParsedAttr(int32_t aNamespaceID, nsAtom* aName, AttrModType modType{0}; // NOTE: Initialized with invalid value. nsAttrValue oldValue; - bool oldValueSet; + bool oldValueSet = false; - { + if (aIsKnownNew == IsKnownNewAttr::Yes) { + MOZ_ASSERT(mAttrs.IndexOfAttr(aName, aNamespaceID) == -1, + "Caller asserted attribute is new but it already exists"); + modType = AttrModType::Addition; + } else { const nsAttrValueOrString value(aParsedValue); if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify, oldValue, &modType, &oldValueSet)) { @@ -3731,7 +3760,7 @@ nsresult Element::SetParsedAttr(int32_t aNamespaceID, nsAtom* aName, return SetAttrAndNotify(aNamespaceID, aName, aPrefix, oldValueSet ? &oldValue : nullptr, aParsedValue, nullptr, modType, aNotify, kCallAfterSetAttr, - document, updateBatch); + document, updateBatch, aIsKnownNew); } static MOZ_ALWAYS_INLINE void SetLifecycleCallbackNamespaceURI( @@ -3930,7 +3959,7 @@ nsresult Element::SetAttrAndNotify( const nsAttrValue* aOldValue, nsAttrValue& aParsedValue, nsIPrincipal* aSubjectPrincipal, AttrModType aModType, bool aNotify, bool aCallAfterSetAttr, Document* aComposedDocument, - const mozAutoDocUpdate& aGuard) { + const mozAutoDocUpdate& aGuard, IsKnownNewAttr aIsKnownNew) { // NOTE: Please keep changes to this method in sync with // `SetNoNameSpaceAttrOnNewlyCreatedElement`! nsMutationGuard::DidMutate(); @@ -3952,7 +3981,7 @@ nsresult Element::SetAttrAndNotify( hadDirAuto = HasDirAuto(); // already takes bdi into account } - MOZ_TRY(SetAndSwapAttr(aName, aParsedValue, &oldValueSet)); + MOZ_TRY(SetAndSwapAttr(aName, aParsedValue, &oldValueSet, aIsKnownNew)); if (IsAttributeMapped(aName) && !IsPendingMappedAttributeEvaluation()) { mAttrs.InfallibleMarkAsPendingPresAttributeEvaluation(); if (Document* doc = GetComposedDoc()) { @@ -3962,7 +3991,7 @@ nsresult Element::SetAttrAndNotify( } else { RefPtr ni = NodeInfoManager()->GetNodeInfo( aName, aPrefix, aNamespaceID, ATTRIBUTE_NODE); - MOZ_TRY(SetAndSwapAttr(ni, aParsedValue, &oldValueSet)); + MOZ_TRY(SetAndSwapAttr(ni, aParsedValue, &oldValueSet, aIsKnownNew)); } PostIdMaybeChange(aNamespaceID, aName, &valueForAfterSetAttr); @@ -4770,14 +4799,18 @@ nsresult Element::CopyInnerTo(Element* aDst) { } else if (isSVG) { nsAutoString valStr; value->ToString(valStr); + // aDst started empty via EnsureCapacityToClone, so every attribute here + // is a fresh addition and the fast path is safe. MOZ_TRY(aDst->SetAttr(name->NamespaceID(), name->LocalName(), - name->GetPrefix(), valStr, false)); + name->GetPrefix(), valStr, nullptr, false, + IsKnownNewAttr::Yes)); continue; } MOZ_ASSERT(value->StoresOwnData()); nsAttrValue valueCopy(*info.mValue); MOZ_TRY(aDst->SetParsedAttr(name->NamespaceID(), name->LocalName(), - name->GetPrefix(), valueCopy, false)); + name->GetPrefix(), valueCopy, false, + IsKnownNewAttr::Yes)); } // https://dom.spec.whatwg.org/#clone-a-single-node diff --git a/dom/base/Element.h b/dom/base/Element.h index d855b1769871..caa2e3164f78 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -920,7 +920,8 @@ class Element : public FragmentOrElement { // aParsedValue receives the old value of the attribute. That's useful if // either the input or output value of aParsedValue is StoresOwnData. nsresult SetParsedAttr(int32_t aNameSpaceID, nsAtom* aName, nsAtom* aPrefix, - nsAttrValue& aParsedValue, bool aNotify); + nsAttrValue& aParsedValue, bool aNotify, + IsKnownNewAttr aIsKnownNew); /** * This is meant to be called only by the HTML parser and, at this time, @@ -1064,16 +1065,18 @@ class Element : public FragmentOrElement { */ nsresult SetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAString& aValue, bool aNotify) { - return SetAttr(aNameSpaceID, aName, nullptr, aValue, aNotify); + return SetAttr(aNameSpaceID, aName, nullptr, aValue, nullptr, aNotify, + IsKnownNewAttr::No); } nsresult SetAttr(int32_t aNameSpaceID, nsAtom* aName, nsAtom* aPrefix, const nsAString& aValue, bool aNotify) { - return SetAttr(aNameSpaceID, aName, aPrefix, aValue, nullptr, aNotify); + return SetAttr(aNameSpaceID, aName, aPrefix, aValue, nullptr, aNotify, + IsKnownNewAttr::No); } nsresult SetAttr(int32_t aNameSpaceID, nsAtom* aName, const nsAString& aValue, nsIPrincipal* aTriggeringPrincipal, bool aNotify) { return SetAttr(aNameSpaceID, aName, nullptr, aValue, aTriggeringPrincipal, - aNotify); + aNotify, IsKnownNewAttr::No); } /** @@ -1098,7 +1101,8 @@ class Element : public FragmentOrElement { */ nsresult SetAttr(int32_t aNameSpaceID, nsAtom* aName, nsAtom* aPrefix, const nsAString& aValue, - nsIPrincipal* aMaybeScriptedPrincipal, bool aNotify); + nsIPrincipal* aMaybeScriptedPrincipal, bool aNotify, + IsKnownNewAttr aIsKnownNew); nsresult SetAttr(int32_t aNameSpaceID, nsAtom* aName, nsAtom* aPrefix, nsAtom* aValue, nsIPrincipal* aMaybeScriptedPrincipal, @@ -1125,7 +1129,7 @@ class Element : public FragmentOrElement { * @param aHadValue set to true if attribute existed, false otherwise */ nsresult SetAndSwapAttr(nsAtom* aLocalName, nsAttrValue& aValue, - bool* aHadValue); + bool* aHadValue, IsKnownNewAttr aIsKnownNew); /** * Swap an attribute value. This is a public wrapper that ensures bloom @@ -1138,7 +1142,7 @@ class Element : public FragmentOrElement { * @param aHadValue set to true if attribute existed, false otherwise */ nsresult SetAndSwapAttr(mozilla::dom::NodeInfo* aName, nsAttrValue& aValue, - bool* aHadValue); + bool* aHadValue, IsKnownNewAttr aIsKnownNew); /** * Get the namespace / name / prefix of a given attribute. @@ -2282,7 +2286,7 @@ class Element : public FragmentOrElement { nsresult SetAttrInternal(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix, const nsAttrValueOrString& aValueForComparison, nsIPrincipal* aSubjectPrincipal, bool aNotify, - ParseFunc&& aParseFn); + ParseFunc&& aParseFn, IsKnownNewAttr aIsKnownNew); /** * Set attribute and (if needed) notify documentobservers. This will send the @@ -2330,7 +2334,8 @@ class Element : public FragmentOrElement { nsIPrincipal* aSubjectPrincipal, AttrModType aModType, bool aNotify, bool aCallAfterSetAttr, Document* aComposedDocument, - const mozAutoDocUpdate& aGuard); + const mozAutoDocUpdate& aGuard, + IsKnownNewAttr aIsKnownNew); /** * Convert an attribute string value to attribute type based on the type of @@ -2467,9 +2472,15 @@ class Element : public FragmentOrElement { * Internal hook for converting an attribute name-string to nsAttrName in * case there is such existing attribute. aNameToUse can be passed to get * name which was used for looking for the attribute (lowercase in HTML). + * + * When aOutAtom is non-null and no matching attribute is found, *aOutAtom + * is set to the atomized lookup name so callers can reuse it without + * re-atomizing. In all other cases (match found, or the element has no + * attributes), *aOutAtom is set to nullptr. */ const nsAttrName* InternalGetAttrNameFromQName( - const nsAString& aStr, nsAutoString* aNameToUse = nullptr) const; + const nsAString& aStr, nsAutoString* aNameToUse = nullptr, + RefPtr* aOutAtom = nullptr) const; virtual Element* GetNameSpaceElement() override { return this; } diff --git a/dom/base/nsStyledElement.cpp b/dom/base/nsStyledElement.cpp index 5f03b0a35e78..63812aa985cc 100644 --- a/dom/base/nsStyledElement.cpp +++ b/dom/base/nsStyledElement.cpp @@ -106,7 +106,7 @@ nsresult nsStyledElement::SetInlineStyleDeclaration( return SetAttrAndNotify(kNameSpaceID_None, nsGkAtoms::style, nullptr, aData.mOldValue.ptrOr(nullptr), attrValue, nullptr, aData.mModType, true, kDontCallAfterSetAttr, document, - updateBatch); + updateBatch, mozilla::dom::IsKnownNewAttr::No); } // --------------------------------------------------------------- @@ -152,7 +152,8 @@ nsresult nsStyledElement::ReparseStyleAttribute(bool aForceInDataDoc) { // Don't bother going through SetInlineStyleDeclaration; we don't // want to fire off mutation events or document notifications anyway bool oldValueSet; - nsresult rv = SetAndSwapAttr(nsGkAtoms::style, attrValue, &oldValueSet); + nsresult rv = SetAndSwapAttr(nsGkAtoms::style, attrValue, &oldValueSet, + mozilla::dom::IsKnownNewAttr::No); NS_ENSURE_SUCCESS(rv, rv); } diff --git a/dom/html/ElementInternals.cpp b/dom/html/ElementInternals.cpp index d1fc00b3244a..4e57e26e358b 100644 --- a/dom/html/ElementInternals.cpp +++ b/dom/html/ElementInternals.cpp @@ -473,7 +473,8 @@ nsresult ElementInternals::SetAttr(nsAtom* aName, const nsAString& aValue) { } } else { bool attrHadValue = false; - rs = mAttrs.SetAndSwapAttr(aName, attrValue, &attrHadValue); + rs = mAttrs.SetAndSwapAttr(aName, attrValue, &attrHadValue, + mozilla::dom::IsKnownNewAttr::No); } nsMutationGuard::DidMutate(); @@ -489,7 +490,8 @@ nsresult ElementInternals::SetAttrInternal(nsAtom* aName, const nsAString& aValue) { bool attrHadValue; nsAttrValue attrValue(aValue); - return mAttrs.SetAndSwapAttr(aName, attrValue, &attrHadValue); + return mAttrs.SetAndSwapAttr(aName, attrValue, &attrHadValue, + mozilla::dom::IsKnownNewAttr::No); } nsresult ElementInternals::UnsetAttrInternal(nsAtom* aName) { diff --git a/dom/svg/SVGElement.cpp b/dom/svg/SVGElement.cpp index e21c3ba948fa..858e03e77db2 100644 --- a/dom/svg/SVGElement.cpp +++ b/dom/svg/SVGElement.cpp @@ -1469,7 +1469,8 @@ void SVGElement::DidChangeValue(nsAtom* aName, nsAttrValue& aNewValue, const nsAttrValue emptyValue; SetAttrAndNotify(kNameSpaceID_None, aName, nullptr, &emptyValue, aNewValue, nullptr, modType, kNotifyDocumentObservers, - kCallAfterSetAttr, GetComposedDoc(), aProofOfUpdate); + kCallAfterSetAttr, GetComposedDoc(), aProofOfUpdate, + mozilla::dom::IsKnownNewAttr::No); } nsAtom* SVGElement::GetEventNameForAttr(nsAtom* aAttr) { @@ -1763,7 +1764,7 @@ void SVGElement::DidChangeNumber(uint8_t aAttrEnum) { attrValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, - attrValue, true); + attrValue, true, mozilla::dom::IsKnownNewAttr::No); } void SVGElement::GetAnimatedNumberValues(float* aFirst, ...) { @@ -1822,7 +1823,7 @@ void SVGElement::DidChangeInteger(uint8_t aAttrEnum) { attrValue.SetTo(info.mValues[aAttrEnum].GetBaseValue(), nullptr); SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, - attrValue, true); + attrValue, true, mozilla::dom::IsKnownNewAttr::No); } void SVGElement::GetAnimatedIntegerValues(int32_t* aFirst, ...) { @@ -1880,7 +1881,7 @@ void SVGElement::DidChangeBoolean(uint8_t aAttrEnum) { nsAttrValue attrValue(info.mValues[aAttrEnum].GetBaseValueAtom()); SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, - attrValue, true); + attrValue, true, mozilla::dom::IsKnownNewAttr::No); } SVGElement::EnumAttributesInfo SVGElement::GetEnumInfo() { @@ -1896,7 +1897,7 @@ void SVGElement::DidChangeEnum(uint8_t aAttrEnum) { nsAttrValue attrValue(info.mValues[aAttrEnum].GetBaseValueAtom(this)); SetParsedAttr(kNameSpaceID_None, info.mInfos[aAttrEnum].mName, nullptr, - attrValue, true); + attrValue, true, mozilla::dom::IsKnownNewAttr::No); } SVGAnimatedOrient* SVGElement::GetAnimatedOrient() { return nullptr; } diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp index 9f375a3d0c41..ff12e1ac96f0 100644 --- a/dom/xul/nsXULElement.cpp +++ b/dom/xul/nsXULElement.cpp @@ -997,7 +997,8 @@ nsresult nsXULElement::MakeHeavyweight(nsXULPrototypeElement* aPrototype) { nsAttrValue value(protoattr.mValue); MOZ_TRY(SetParsedAttr( protoattr.mName.NamespaceID(), protoattr.mName.LocalName(), - protoattr.mName.GetPrefix(), value, /* aNotify = */ false)); + protoattr.mName.GetPrefix(), value, + /* aNotify = */ false, mozilla::dom::IsKnownNewAttr::No)); } return NS_OK; }