commit 430017d12e9af3be497a3b1a1727577ccf07caf5
parent 34e1fad34c31fd7c04ae609b1f905a6050fffb6a
Author: Denis Palmeiro <dpalmeiro@mozilla.com>
Date: Thu, 13 Nov 2025 04:16:37 +0000
Bug 1995614: Atomize ATTR_TYPE strings and any single digit strings during tokenization. r=hsivonen
Add static atoms for "2" -> "9", and do a fast path check for any single digit strings during tokenization and return the static atom directly. Also atomize type attributes during tokenization. This patch also cleans up some code in Element::SetAttr and nsHtml5TreeOperation so that it's easier to pass through an atom.
Differential Revision: https://phabricator.services.mozilla.com/D269859
Diffstat:
9 files changed, 165 insertions(+), 39 deletions(-)
diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp
@@ -2834,7 +2834,40 @@ nsresult Element::SetAttr(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix,
nsIPrincipal* aSubjectPrincipal, bool aNotify) {
// 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);
+ }
+ });
+}
+nsresult Element::SetAttr(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix,
+ nsAtom* aValue, nsIPrincipal* aSubjectPrincipal,
+ bool aNotify) {
+ // Keep this in sync with SetParsedAttr below and SetSingleClassFromParser
+ // 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);
+ }
+ });
+}
+
+template <typename ParseFunc>
+nsresult Element::SetAttrInternal(int32_t aNamespaceID, nsAtom* aName,
+ nsAtom* aPrefix,
+ const nsAttrValueOrString& aValue,
+ nsIPrincipal* aSubjectPrincipal, bool aNotify,
+ ParseFunc&& aParseFn) {
NS_ENSURE_ARG_POINTER(aName);
NS_ASSERTION(aNamespaceID != kNameSpaceID_Unknown,
"Don't call SetAttr with unknown namespace");
@@ -2843,13 +2876,10 @@ nsresult Element::SetAttr(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix,
nsAttrValue oldValue;
bool oldValueSet;
- {
- const nsAttrValueOrString value(aValue);
- if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, value, aNotify,
- oldValue, &modType, &oldValueSet)) {
- OnAttrSetButNotChanged(aNamespaceID, aName, value, aNotify);
- return NS_OK;
- }
+ if (OnlyNotifySameValueSet(aNamespaceID, aName, aPrefix, aValue, aNotify,
+ oldValue, &modType, &oldValueSet)) {
+ OnAttrSetButNotChanged(aNamespaceID, aName, aValue, aNotify);
+ return NS_OK;
}
// Hold a script blocker while calling ParseAttribute since that can call
@@ -2863,10 +2893,7 @@ nsresult Element::SetAttr(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix,
}
nsAttrValue attrValue;
- if (!ParseAttribute(aNamespaceID, aName, aValue, aSubjectPrincipal,
- attrValue)) {
- attrValue.SetTo(aValue);
- }
+ aParseFn(attrValue);
BeforeSetAttr(aNamespaceID, aName, &attrValue, aNotify);
diff --git a/dom/base/Element.h b/dom/base/Element.h
@@ -1044,6 +1044,10 @@ class Element : public FragmentOrElement {
const nsAString& aValue,
nsIPrincipal* aMaybeScriptedPrincipal, bool aNotify);
+ nsresult SetAttr(int32_t aNameSpaceID, nsAtom* aName, nsAtom* aPrefix,
+ nsAtom* aValue, nsIPrincipal* aMaybeScriptedPrincipal,
+ bool aNotify);
+
/**
* Remove an attribute so that it is no longer explicitly specified.
*
@@ -2033,6 +2037,16 @@ class Element : public FragmentOrElement {
static const DOMTokenListSupportedToken sSupportedBlockingValues[];
/**
+ * Common implementation for SetAttr overloads. Takes a callback to perform
+ * the type-specific parsing and value setting.
+ */
+ template <typename ParseFunc>
+ nsresult SetAttrInternal(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix,
+ const nsAttrValueOrString& aValueForComparison,
+ nsIPrincipal* aSubjectPrincipal, bool aNotify,
+ ParseFunc&& aParseFn);
+
+ /**
* Set attribute and (if needed) notify documentobservers. This will send the
* AttributeChanged notification. Callers of this method are responsible for
* calling AttributeWillChange, since that needs to happen before the new attr
diff --git a/parser/html/javasrc/Tokenizer.java b/parser/html/javasrc/Tokenizer.java
@@ -998,8 +998,21 @@ public class Tokenizer implements Locator, Locator2 {
* @return the buffer as a string
*/
@Inline protected String strBufToString() {
+ // CPPONLY: String digitAtom = TryAtomizeForSingleDigit();
+ // CPPONLY: if (digitAtom) {
+ // CPPONLY: return digitAtom;
+ // CPPONLY: }
+ // CPPONLY:
+ // CPPONLY: boolean maybeAtomize = false;
+ // CPPONLY: if (!newAttributesEachTime) {
+ // CPPONLY: if (attributeName == AttributeName.CLASS ||
+ // CPPONLY: attributeName == AttributeName.TYPE) {
+ // CPPONLY: maybeAtomize = true;
+ // CPPONLY: }
+ // CPPONLY: }
+ // CPPONLY:
String str = Portability.newStringFromBuffer(strBuf, 0, strBufLen
- // CPPONLY: , tokenHandler, !newAttributesEachTime && attributeName == AttributeName.CLASS
+ // CPPONLY: , tokenHandler, maybeAtomize
);
clearStrBufAfterUse();
return str;
diff --git a/parser/html/nsHtml5Tokenizer.h b/parser/html/nsHtml5Tokenizer.h
@@ -368,10 +368,19 @@ class nsHtml5Tokenizer {
protected:
inline nsHtml5String strBufToString() {
+ nsHtml5String digitAtom = TryAtomizeForSingleDigit();
+ if (digitAtom) {
+ return digitAtom;
+ }
+ bool maybeAtomize = false;
+ if (!newAttributesEachTime) {
+ if (attributeName == nsHtml5AttributeName::ATTR_CLASS ||
+ attributeName == nsHtml5AttributeName::ATTR_TYPE) {
+ maybeAtomize = true;
+ }
+ }
nsHtml5String str = nsHtml5Portability::newStringFromBuffer(
- strBuf, 0, strBufLen, tokenHandler,
- !newAttributesEachTime &&
- attributeName == nsHtml5AttributeName::ATTR_CLASS);
+ strBuf, 0, strBufLen, tokenHandler, maybeAtomize);
clearStrBufAfterUse();
return str;
}
diff --git a/parser/html/nsHtml5TokenizerCppSupplement.h b/parser/html/nsHtml5TokenizerCppSupplement.h
@@ -120,6 +120,21 @@ void nsHtml5Tokenizer::SetViewSourceOpSink(nsAHtml5TreeOpSink* aOpSink) {
void nsHtml5Tokenizer::RewindViewSource() { mViewSource->Rewind(); }
+nsHtml5String nsHtml5Tokenizer::TryAtomizeForSingleDigit() {
+ if (!newAttributesEachTime && strBufLen == 1 && strBuf[0] >= '0' &&
+ strBuf[0] <= '9') {
+ static nsStaticAtom* const digitAtoms[10] = {
+ nsGkAtoms::_0, nsGkAtoms::_1, nsGkAtoms::_2, nsGkAtoms::_3,
+ nsGkAtoms::_4, nsGkAtoms::_5, nsGkAtoms::_6, nsGkAtoms::_7,
+ nsGkAtoms::_8, nsGkAtoms::_9};
+ nsAtom* atom = digitAtoms[strBuf[0] - '0'];
+ nsHtml5String result = nsHtml5String::FromAtom(do_AddRef(atom));
+ clearStrBufAfterUse();
+ return result;
+ }
+ return nullptr;
+}
+
void nsHtml5Tokenizer::errWarnLtSlashInRcdata() {}
// The null checks below annotated MOZ_LIKELY are not actually necessary.
diff --git a/parser/html/nsHtml5TokenizerHSupplement.h b/parser/html/nsHtml5TokenizerHSupplement.h
@@ -70,6 +70,8 @@ bool EnsureBufferSpace(int32_t aLength);
MOZ_COLD MOZ_NEVER_INLINE void EnsureBufferSpaceShouldNeverHappen(
int32_t aLength);
+nsHtml5String TryAtomizeForSingleDigit();
+
bool TemplatePushedOrHeadPopped();
void RememberGt(int32_t aPos);
diff --git a/parser/html/nsHtml5TreeBuilder.h b/parser/html/nsHtml5TreeBuilder.h
@@ -33,6 +33,7 @@
#include "jArray.h"
#include "mozilla/ImportScanner.h"
+#include "mozilla/Likely.h"
#include "nsAHtml5TreeBuilderState.h"
#include "nsAtom.h"
#include "nsContentUtils.h"
diff --git a/parser/html/nsHtml5TreeOperation.cpp b/parser/html/nsHtml5TreeOperation.cpp
@@ -426,10 +426,19 @@ nsresult nsHtml5TreeOperation::AddAttributes(nsIContent* aNode,
int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
if (!node->HasAttr(nsuri, localName) &&
!(nsuri == kNameSpaceID_None && localName == nsGkAtoms::nonce)) {
- nsString value; // Not Auto, because using it to hold nsStringBuffer*
- aAttributes->getValueNoBoundsCheck(i).ToString(value);
- node->SetAttr(nsuri, localName, aAttributes->getPrefixNoBoundsCheck(i),
- value, true);
+ nsHtml5String val = aAttributes->getValueNoBoundsCheck(i);
+ nsAtom* prefix = aAttributes->getPrefixNoBoundsCheck(i);
+
+ // If value is already an atom, use it directly to avoid string
+ // allocation.
+ nsAtom* valAtom = val.MaybeAsAtom();
+ if (valAtom) {
+ node->SetAttr(nsuri, localName, prefix, valAtom, nullptr, true);
+ } else {
+ nsString value; // Not Auto, because using it to hold nsStringBuffer*
+ val.ToString(value);
+ node->SetAttr(nsuri, localName, prefix, value, true);
+ }
// XXX what to do with nsresult?
}
}
@@ -445,13 +454,23 @@ void nsHtml5TreeOperation::SetHTMLElementAttributes(
}
for (int32_t i = 0; i < len; i++) {
nsHtml5String val = aAttributes->getValueNoBoundsCheck(i);
- nsAtom* klass = val.MaybeAsAtom();
- if (klass) {
- aElement->SetClassAttrFromParser(klass);
+ nsAtom* localName = aAttributes->getLocalNameNoBoundsCheck(i);
+ if (localName == nsGkAtoms::_class) {
+ nsAtom* klass = val.MaybeAsAtom();
+ if (klass) {
+ aElement->SetClassAttrFromParser(klass);
+ continue;
+ }
+ }
+
+ nsAtom* prefix = aAttributes->getPrefixNoBoundsCheck(i);
+ int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+
+ // If value is already an atom, use it directly to avoid string allocation.
+ nsAtom* valAtom = val.MaybeAsAtom();
+ if (valAtom) {
+ aElement->SetAttr(nsuri, localName, prefix, valAtom, nullptr, false);
} else {
- nsAtom* localName = aAttributes->getLocalNameNoBoundsCheck(i);
- nsAtom* prefix = aAttributes->getPrefixNoBoundsCheck(i);
- int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
nsString value; // Not Auto, because using it to hold nsStringBuffer*
val.ToString(value);
aElement->SetAttr(nsuri, localName, prefix, value, false);
@@ -577,14 +596,23 @@ nsIContent* nsHtml5TreeOperation::CreateSVGElement(
int32_t len = aAttributes->getLength();
for (int32_t i = 0; i < len; i++) {
nsHtml5String val = aAttributes->getValueNoBoundsCheck(i);
- nsAtom* klass = val.MaybeAsAtom();
- if (klass) {
- newContent->SetClassAttrFromParser(klass);
- } else {
- nsAtom* localName = aAttributes->getLocalNameNoBoundsCheck(i);
- nsAtom* prefix = aAttributes->getPrefixNoBoundsCheck(i);
- int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+ nsAtom* localName = aAttributes->getLocalNameNoBoundsCheck(i);
+ if (localName == nsGkAtoms::_class) {
+ nsAtom* klass = val.MaybeAsAtom();
+ if (klass) {
+ newContent->SetClassAttrFromParser(klass);
+ continue;
+ }
+ }
+ nsAtom* prefix = aAttributes->getPrefixNoBoundsCheck(i);
+ int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+
+ // If value is already an atom, use it directly to avoid string allocation.
+ nsAtom* valAtom = val.MaybeAsAtom();
+ if (valAtom) {
+ newContent->SetAttr(nsuri, localName, prefix, valAtom, nullptr, false);
+ } else {
nsString value; // Not Auto, because using it to hold nsStringBuffer*
val.ToString(value);
newContent->SetAttr(nsuri, localName, prefix, value, false);
@@ -629,14 +657,23 @@ nsIContent* nsHtml5TreeOperation::CreateMathMLElement(
int32_t len = aAttributes->getLength();
for (int32_t i = 0; i < len; i++) {
nsHtml5String val = aAttributes->getValueNoBoundsCheck(i);
- nsAtom* klass = val.MaybeAsAtom();
- if (klass) {
- newContent->SetClassAttrFromParser(klass);
- } else {
- nsAtom* localName = aAttributes->getLocalNameNoBoundsCheck(i);
- nsAtom* prefix = aAttributes->getPrefixNoBoundsCheck(i);
- int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+ nsAtom* localName = aAttributes->getLocalNameNoBoundsCheck(i);
+ if (localName == nsGkAtoms::_class) {
+ nsAtom* klass = val.MaybeAsAtom();
+ if (klass) {
+ newContent->SetClassAttrFromParser(klass);
+ continue;
+ }
+ }
+ nsAtom* prefix = aAttributes->getPrefixNoBoundsCheck(i);
+ int32_t nsuri = aAttributes->getURINoBoundsCheck(i);
+
+ // If value is already an atom, use it directly to avoid string allocation.
+ nsAtom* valAtom = val.MaybeAsAtom();
+ if (valAtom) {
+ newContent->SetAttr(nsuri, localName, prefix, valAtom, nullptr, false);
+ } else {
nsString value; // Not Auto, because using it to hold nsStringBuffer*
val.ToString(value);
newContent->SetAttr(nsuri, localName, prefix, value, false);
diff --git a/xpcom/ds/StaticAtoms.py b/xpcom/ds/StaticAtoms.py
@@ -37,6 +37,14 @@ STATIC_ATOMS = [
Atom("_empty", ""),
Atom("_0", "0"),
Atom("_1", "1"),
+ Atom("_2", "2"),
+ Atom("_3", "3"),
+ Atom("_4", "4"),
+ Atom("_5", "5"),
+ Atom("_6", "6"),
+ Atom("_7", "7"),
+ Atom("_8", "8"),
+ Atom("_9", "9"),
Atom("mozframetype", "mozframetype"),
Atom("_moz_abspos", "_moz_abspos"),
Atom("_moz_activated", "_moz_activated"),