From c249eee4d157c5253a9523ae3457d85cd66f784e Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Thu, 2 Apr 2026 16:37:26 -0400 Subject: [PATCH 1/5] Issue #3003 prerequisite - Split non-sRGB CSS color helpers out of nsCSSParser --- layout/style/nsCSSNonSRGBColorSpace.h | 81 +++++++++++++++++++++++++++ layout/style/nsCSSParser.cpp | 65 ++------------------- 2 files changed, 87 insertions(+), 59 deletions(-) create mode 100644 layout/style/nsCSSNonSRGBColorSpace.h diff --git a/layout/style/nsCSSNonSRGBColorSpace.h b/layout/style/nsCSSNonSRGBColorSpace.h new file mode 100644 index 0000000000..c1704cae4b --- /dev/null +++ b/layout/style/nsCSSNonSRGBColorSpace.h @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsCSSNonSRGBColorSpace_h___ +#define nsCSSNonSRGBColorSpace_h___ + +#include + +#include "mozilla/MathAlgorithms.h" +#include "nsColor.h" +#include "nsStyleUtil.h" + +namespace mozilla { +namespace css { + +static constexpr float kOklabPercentScaleAB = 0.4f; +static constexpr double kRadiansPerDegree = 0.01745329251994329576923690768489; + +inline float +LinearSRGBToEncoded(float aValue) +{ + if (aValue <= 0.0031308f) { + return 12.92f * aValue; + } + return 1.055f * std::pow(aValue, 1.0f / 2.4f) - 0.055f; +} + +inline nscolor +OklabToSRGBColor(float aL, float aA, float aB, float aAlpha) +{ + // Per CSS Color, the lightness component for Oklab/Oklch is clamped. + float lightness = mozilla::clamped(aL, 0.0f, 1.0f); + uint8_t alpha = + nsStyleUtil::FloatToColorComponent(mozilla::clamped(aAlpha, 0.0f, 1.0f)); + + // Treat values extremely close to zero as zero to avoid tiny floating-point + // representation differences for percentage inputs. + static constexpr float kLightnessEndpointEpsilon = 0.000002f; + + if (lightness <= kLightnessEndpointEpsilon) { + return NS_RGBA(0, 0, 0, alpha); + } + + float lRoot = lightness + 0.3963377774f * aA + 0.2158037573f * aB; + float mRoot = lightness - 0.1055613458f * aA - 0.0638541728f * aB; + float sRoot = lightness - 0.0894841775f * aA - 1.2914855480f * aB; + + float l = lRoot * lRoot * lRoot; + float m = mRoot * mRoot * mRoot; + float s = sRoot * sRoot * sRoot; + + float linearR = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s; + float linearG = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s; + float linearB = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s; + + float r = mozilla::clamped(LinearSRGBToEncoded(linearR), 0.0f, 1.0f); + float g = mozilla::clamped(LinearSRGBToEncoded(linearG), 0.0f, 1.0f); + float b = mozilla::clamped(LinearSRGBToEncoded(linearB), 0.0f, 1.0f); + + return NS_RGBA( + NSToIntRound(r * 255.0f), + NSToIntRound(g * 255.0f), + NSToIntRound(b * 255.0f), + alpha); +} + +inline nscolor +OklchToSRGBColor(float aL, float aChroma, float aHue, float aAlpha) +{ + double hueRadians = aHue * kRadiansPerDegree; + float a = aChroma * std::cos(hueRadians); + float b = aChroma * std::sin(hueRadians); + return OklabToSRGBColor(aL, a, b, aAlpha); +} + +} // namespace css +} // namespace mozilla + +#endif /* nsCSSNonSRGBColorSpace_h___ */ diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 9d37f04035..6cdc1d4eb9 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -20,6 +20,7 @@ #include "nsCSSParser.h" #include "CSSNestingFlattener.h" #include "nsAlgorithm.h" +#include "nsCSSNonSRGBColorSpace.h" #include "nsCSSProps.h" #include "nsCSSKeywords.h" #include "nsCSSScanner.h" @@ -169,57 +170,6 @@ struct ReducePercentageCalcOps : ReduceNumberCalcOps } }; -static constexpr float kOKLabPercentScaleAB = 0.4f; -static constexpr double kRadiansPerDegree = 0.01745329251994329576923690768489; - -static inline float -LinearSRGBToEncoded(float aValue) -{ - if (aValue <= 0.0031308f) { - return 12.92f * aValue; - } - return 1.055f * std::pow(aValue, 1.0f / 2.4f) - 0.055f; -} - -static nscolor -OKLabToSRGBColor(float aL, float aA, float aB, float aAlpha) -{ - // Per CSS Color, the lightness component for Oklab/Oklch is clamped. - float lightness = mozilla::clamped(aL, 0.0f, 1.0f); - uint8_t alpha = - nsStyleUtil::FloatToColorComponent(mozilla::clamped(aAlpha, 0.0f, 1.0f)); - - // Treat values extremely close to zero as zero to avoid tiny floating-point - // representation differences for percentage inputs. - static constexpr float kLightnessEndpointEpsilon = 0.000002f; - - if (lightness <= kLightnessEndpointEpsilon) { - return NS_RGBA(0, 0, 0, alpha); - } - - float lRoot = lightness + 0.3963377774f * aA + 0.2158037573f * aB; - float mRoot = lightness - 0.1055613458f * aA - 0.0638541728f * aB; - float sRoot = lightness - 0.0894841775f * aA - 1.2914855480f * aB; - - float l = lRoot * lRoot * lRoot; - float m = mRoot * mRoot * mRoot; - float s = sRoot * sRoot * sRoot; - - float linearR = 4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s; - float linearG = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s; - float linearB = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s; - - float r = mozilla::clamped(LinearSRGBToEncoded(linearR), 0.0f, 1.0f); - float g = mozilla::clamped(LinearSRGBToEncoded(linearG), 0.0f, 1.0f); - float b = mozilla::clamped(LinearSRGBToEncoded(linearB), 0.0f, 1.0f); - - return NS_RGBA( - NSToIntRound(r * 255.0f), - NSToIntRound(g * 255.0f), - NSToIntRound(b * 255.0f), - alpha); -} - static_assert(css::eAuthorSheetFeatures == 0 && css::eUserSheetFeatures == 1 && css::eAgentSheetFeatures == 2, @@ -7932,14 +7882,14 @@ CSSParserImpl::ParseOKLabColor(nscolor& aColor) bool hasComma = ExpectSymbol(commaSeparator, true); const char separatorBeforeAlpha = hasComma ? commaSeparator : '/'; - if (!ParseOKLabComponent(a, kOKLabPercentScaleAB, + if (!ParseOKLabComponent(a, kOklabPercentScaleAB, hasComma ? Some(commaSeparator) : Nothing()) || - !ParseOKLabComponent(b, kOKLabPercentScaleAB, Nothing()) || + !ParseOKLabComponent(b, kOklabPercentScaleAB, Nothing()) || !ParseColorOpacityAndCloseParen(alpha, separatorBeforeAlpha)) { return false; } - aColor = OKLabToSRGBColor(l, a, b, alpha); + aColor = OklabToSRGBColor(l, a, b, alpha); return true; } @@ -7955,17 +7905,14 @@ CSSParserImpl::ParseOKLCHColor(nscolor& aColor) bool hasComma = ExpectSymbol(commaSeparator, true); const char separatorBeforeAlpha = hasComma ? commaSeparator : '/'; - if (!ParseOKLabComponent(chroma, kOKLabPercentScaleAB, + if (!ParseOKLabComponent(chroma, kOklabPercentScaleAB, hasComma ? Some(commaSeparator) : Nothing()) || !ParseHue(hue) || !ParseColorOpacityAndCloseParen(alpha, separatorBeforeAlpha)) { return false; } - double hueRadians = hue * kRadiansPerDegree; - float a = chroma * std::cos(hueRadians); - float b = chroma * std::sin(hueRadians); - aColor = OKLabToSRGBColor(l, a, b, alpha); + aColor = OklchToSRGBColor(l, chroma, hue, alpha); return true; } From 576265905a4ac930a94f183d9a9a62f11a3bacf2 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Thu, 2 Apr 2026 16:43:54 -0400 Subject: [PATCH 2/5] Issue #3003 - Add CSS lch() color parsing support --- layout/style/nsCSSNonSRGBColorSpace.h | 91 +++++++++++++++++++--- layout/style/nsCSSParser.cpp | 29 ++++++- layout/style/test/property_database.js | 7 +- layout/style/test/test_computed_style.html | 5 ++ 4 files changed, 121 insertions(+), 11 deletions(-) diff --git a/layout/style/nsCSSNonSRGBColorSpace.h b/layout/style/nsCSSNonSRGBColorSpace.h index c1704cae4b..4ce2e56a1b 100644 --- a/layout/style/nsCSSNonSRGBColorSpace.h +++ b/layout/style/nsCSSNonSRGBColorSpace.h @@ -15,6 +15,8 @@ namespace mozilla { namespace css { +static constexpr float kLabLightnessMax = 100.0f; +static constexpr float kLchPercentScaleC = 150.0f; static constexpr float kOklabPercentScaleAB = 0.4f; static constexpr double kRadiansPerDegree = 0.01745329251994329576923690768489; @@ -27,6 +29,21 @@ LinearSRGBToEncoded(float aValue) return 1.055f * std::pow(aValue, 1.0f / 2.4f) - 0.055f; } +inline nscolor +LinearSRGBToColor(float aLinearR, float aLinearG, float aLinearB, + uint8_t aAlpha) +{ + float r = mozilla::clamped(LinearSRGBToEncoded(aLinearR), 0.0f, 1.0f); + float g = mozilla::clamped(LinearSRGBToEncoded(aLinearG), 0.0f, 1.0f); + float b = mozilla::clamped(LinearSRGBToEncoded(aLinearB), 0.0f, 1.0f); + + return NS_RGBA( + NSToIntRound(r * 255.0f), + NSToIntRound(g * 255.0f), + NSToIntRound(b * 255.0f), + aAlpha); +} + inline nscolor OklabToSRGBColor(float aL, float aA, float aB, float aAlpha) { @@ -55,15 +72,7 @@ OklabToSRGBColor(float aL, float aA, float aB, float aAlpha) float linearG = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s; float linearB = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s; - float r = mozilla::clamped(LinearSRGBToEncoded(linearR), 0.0f, 1.0f); - float g = mozilla::clamped(LinearSRGBToEncoded(linearG), 0.0f, 1.0f); - float b = mozilla::clamped(LinearSRGBToEncoded(linearB), 0.0f, 1.0f); - - return NS_RGBA( - NSToIntRound(r * 255.0f), - NSToIntRound(g * 255.0f), - NSToIntRound(b * 255.0f), - alpha); + return LinearSRGBToColor(linearR, linearG, linearB, alpha); } inline nscolor @@ -75,6 +84,70 @@ OklchToSRGBColor(float aL, float aChroma, float aHue, float aAlpha) return OklabToSRGBColor(aL, a, b, aAlpha); } +inline nscolor +LabToSRGBColor(float aL, float aA, float aB, float aAlpha) +{ + float lightness = mozilla::clamped(aL, 0.0f, kLabLightnessMax); + uint8_t alpha = + nsStyleUtil::FloatToColorComponent(mozilla::clamped(aAlpha, 0.0f, 1.0f)); + + if (lightness <= 0.0f) { + return NS_RGBA(0, 0, 0, alpha); + } + if (lightness >= kLabLightnessMax) { + return NS_RGBA(255, 255, 255, alpha); + } + + static constexpr float kLabEpsilon = 216.0f / 24389.0f; + static constexpr float kLabKappa = 24389.0f / 27.0f; + static constexpr float kD50WhitePointX = 0.9642956764295677f; + static constexpr float kD50WhitePointZ = 0.8251046025104602f; + + float fy = (lightness + 16.0f) / 116.0f; + float fx = aA / 500.0f + fy; + float fz = fy - aB / 200.0f; + + float fx3 = fx * fx * fx; + float fy3 = fy * fy * fy; + float fz3 = fz * fz * fz; + + float x = fx3 > kLabEpsilon ? fx3 : (116.0f * fx - 16.0f) / kLabKappa; + float y = lightness > kLabKappa * kLabEpsilon ? fy3 : lightness / kLabKappa; + float z = fz3 > kLabEpsilon ? fz3 : (116.0f * fz - 16.0f) / kLabKappa; + + x *= kD50WhitePointX; + z *= kD50WhitePointZ; + + float adaptedX = 0.955473421488075f * x - 0.02309845494876471f * y + + 0.06325924320057072f * z; + float adaptedY = -0.0283697093338637f * x + 1.0099953980813041f * y + + 0.021041441191917323f * z; + float adaptedZ = 0.012314014864481998f * x - 0.020507649298898964f * y + + 1.330365926242124f * z; + + float linearR = (12831.0f / 3959.0f) * adaptedX + + (-329.0f / 214.0f) * adaptedY + + (-1974.0f / 3959.0f) * adaptedZ; + float linearG = (-851781.0f / 878810.0f) * adaptedX + + (1648619.0f / 878810.0f) * adaptedY + + (36519.0f / 878810.0f) * adaptedZ; + float linearB = (705.0f / 12673.0f) * adaptedX + + (-2585.0f / 12673.0f) * adaptedY + + (705.0f / 667.0f) * adaptedZ; + + return LinearSRGBToColor(linearR, linearG, linearB, alpha); +} + +inline nscolor +LchToSRGBColor(float aL, float aChroma, float aHue, float aAlpha) +{ + float chroma = aChroma < 0.0f ? 0.0f : aChroma; + double hueRadians = aHue * kRadiansPerDegree; + float a = chroma * std::cos(hueRadians); + float b = chroma * std::sin(hueRadians); + return LabToSRGBColor(aL, a, b, aAlpha); +} + } // namespace css } // namespace mozilla diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 6cdc1d4eb9..528076b8f1 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -1239,6 +1239,7 @@ protected: ComponentType& aA); bool ParseHSLColor(float& aHue, float& aSaturation, float& aLightness, float& aOpacity); + bool ParseLCHColor(nscolor& aColor); bool ParseOKLabColor(nscolor& aColor); bool ParseOKLCHColor(nscolor& aColor); bool ParseOKLabComponent(float& aComponent, float aPercentScale, @@ -7625,6 +7626,14 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) SkipUntil(')'); return CSSParseResult::Error; } + else if (mToken.mIdent.LowerCaseEqualsLiteral("lch")) { + if (ParseLCHColor(rgba)) { + aValue.SetColorValue(rgba); + return CSSParseResult::Ok; + } + SkipUntil(')'); + return CSSParseResult::Error; + } else if (mToken.mIdent.LowerCaseEqualsLiteral("oklab")) { if (ParseOKLabColor(rgba)) { aValue.SetColorValue(rgba); @@ -7870,6 +7879,22 @@ CSSParserImpl::ParseOKLabComponent(float& aComponent, float aPercentScale, return true; } +bool +CSSParserImpl::ParseLCHColor(nscolor& aColor) +{ + float l, chroma, hue, alpha; + + if (!ParseOKLabComponent(l, kLabLightnessMax, Nothing()) || + !ParseOKLabComponent(chroma, kLchPercentScaleC, Nothing()) || + !ParseHue(hue) || + !ParseColorOpacityAndCloseParen(alpha, '/')) { + return false; + } + + aColor = LchToSRGBColor(l, chroma, hue, alpha); + return true; +} + bool CSSParserImpl::ParseOKLabColor(nscolor& aColor) { @@ -8864,6 +8889,7 @@ CSSParserImpl::ParseVariant(nsCSSValue& aValue, tk->mIdent.LowerCaseEqualsLiteral("hsl") || tk->mIdent.LowerCaseEqualsLiteral("rgba") || tk->mIdent.LowerCaseEqualsLiteral("hsla") || + tk->mIdent.LowerCaseEqualsLiteral("lch") || tk->mIdent.LowerCaseEqualsLiteral("oklab") || tk->mIdent.LowerCaseEqualsLiteral("oklch") || tk->mIdent.LowerCaseEqualsLiteral("color-mix")))) @@ -11119,7 +11145,8 @@ CSSParserImpl::ParseGradientInterpolationMethod() mToken.mIdent.LowerCaseEqualsLiteral("oklab")) { isPolarColorSpace = false; } else if (mToken.mIdent.LowerCaseEqualsLiteral("hsl") || - mToken.mIdent.LowerCaseEqualsLiteral("oklch")) { + mToken.mIdent.LowerCaseEqualsLiteral("oklch") || + mToken.mIdent.LowerCaseEqualsLiteral("lch")) { isPolarColorSpace = true; } else { return CSSParseResult::Error; diff --git a/layout/style/test/property_database.js b/layout/style/test/property_database.js index f3b78f6c87..3e7ee12a3c 100644 --- a/layout/style/test/property_database.js +++ b/layout/style/test/property_database.js @@ -124,6 +124,7 @@ var validGradientAndElementValues = [ "linear-gradient(.414rad, red, blue)", "linear-gradient(90deg in srgb, yellow, purple)", "linear-gradient(90deg in hsl, yellow, purple)", + "linear-gradient(90deg in lch, yellow, purple)", "linear-gradient(90deg in oklch, yellow, purple)", "linear-gradient(in oklab, yellow, purple)", @@ -4294,7 +4295,8 @@ var gCSSProperties = { "hsl(0rad, 0%, 0%)", "hsl(0turn, 0%, 0%)", "hsl(1turn, 0%, 0%)", - /* oklab() and oklch(). */ + /* lch(), oklab(), and oklch(). */ + "lch(0 0 0)", "oklab(0 0 0)", "oklch(0 0 0)", ], @@ -4319,6 +4321,7 @@ var gCSSProperties = { "hsl(0.5grad 400% 500% / 9.0)", "hsl(33rad 100% 90% / 4)", "hsl(0.33turn, 40%, 40%, 10%)", + "lch(70% 50 180deg / 40%)", "oklab(100% 0 0)", "oklab(60% 0.1 -0.1 / 75%)", "oklch(70% 0.2 180deg / 40%)", @@ -4353,6 +4356,8 @@ var gCSSProperties = { "rgb(0, 0, 0 /)", "hsl(0 0% 0% /)", "hsl(0, 0%, 0% /)", + "lch(0, 0 0)", + "lch(0 0 / 1)", "oklab(0, 0 0)", "oklab(0 0 / 1)", "oklch(0, 0 0)", diff --git a/layout/style/test/test_computed_style.html b/layout/style/test/test_computed_style.html index 4e7d629a06..55ba9edb30 100644 --- a/layout/style/test/test_computed_style.html +++ b/layout/style/test/test_computed_style.html @@ -399,6 +399,11 @@ var noframe_container = document.getElementById("content"); ["hsla(0deg 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"], ["hsl(0 0% 0% / 1)", "rgb(0, 0, 0)"], ["hsl(0 0% 0% / 0.5)", "rgba(0, 0, 0, 0.5)"], + ["lch(0 0 0 / 1)", "rgb(0, 0, 0)"], + ["lch(100% 0 0 / 0.5)", "rgba(255, 255, 255, 0.5)"], + ["lch(52.2345% 72.2 56.2 / 1)", "rgb(198, 93, 6)"], + ["lch(29.69% 45.553% 327.1 / 1)", "rgb(129, 0, 129)"], + ["lch(150% 50 60 / 1)", "rgb(255, 255, 255)"], ["oklab(0 0 0 / 1)", "rgb(0, 0, 0)"], ["oklab(0 0 0 / 0.5)", "rgba(0, 0, 0, 0.5)"], ["oklab(150% 0.5 0.2 / 1)", "rgb(255, 0, 0)"], From 3c7ab8a384f12e1f4e5b0a54ed16bd603bd9fb57 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Fri, 10 Apr 2026 14:31:54 -0400 Subject: [PATCH 3/5] Issue #3043 - Fix stack OOB write in nsLocaleService::GetLocaleFromAcceptLanguage bounds checks --- intl/locale/nsLocaleService.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intl/locale/nsLocaleService.cpp b/intl/locale/nsLocaleService.cpp index d81fb50c2e..562216eb57 100644 --- a/intl/locale/nsLocaleService.cpp +++ b/intl/locale/nsLocaleService.cpp @@ -289,7 +289,7 @@ nsLocaleService::GetLocaleFromAcceptLanguage(const char *acceptLanguage, nsILoca PR_sscanf(cPtr1,";q=%f",&qvalue[countLang]); *cPtr1 = '\0'; } - if (strlen(cPtr)=NSILOCALE_MAX_ACCEPT_LANGUAGE) break; /* quit if too many */ @@ -321,8 +321,8 @@ nsLocaleService::GetLocaleFromAcceptLanguage(const char *acceptLanguage, nsILoca cPtr = nsCRT::strtok(input.get(),",",&cPtr2); while (cPtr) { if (strlen(cPtr)=NSILOCALE_MAX_ACCEPT_LANGUAGE) break; /* quit if too many */ PL_strncpyz(acceptLanguageList[countLang++],cPtr,NSILOCALE_MAX_ACCEPT_LENGTH); - if (countLang>=NSILOCALE_MAX_ACCEPT_LENGTH) break; /* quit if too many */ } cPtr = nsCRT::strtok(cPtr2,",",&cPtr2); } From b44216a883a64958d9a13130f115fae42401d039 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Tue, 14 Apr 2026 00:05:30 -0400 Subject: [PATCH 4/5] Issue #3045 - backport of bug 1350760 - atomization fast-path improvements --- js/public/HashTable.h | 43 +++++++++++++++++++++++++++++++----------- js/src/gc/Zone.cpp | 4 +++- js/src/gc/Zone.h | 6 +++++- js/src/jsatom.cpp | 38 +++++++++++++++++++++++++++---------- js/src/jsatom.h | 8 ++++---- js/src/jsatominlines.h | 2 +- js/src/jsgc.cpp | 3 +++ js/src/vm/String-inl.h | 9 --------- js/src/vm/String.h | 9 +++++++-- 9 files changed, 83 insertions(+), 39 deletions(-) diff --git a/js/public/HashTable.h b/js/public/HashTable.h index 72c14ec69d..8c70ed5e0f 100644 --- a/js/public/HashTable.h +++ b/js/public/HashTable.h @@ -104,11 +104,13 @@ class HashMap // // Also see the definition of Ptr in HashTable above (with T = Entry). typedef typename Impl::Ptr Ptr; - Ptr lookup(const Lookup& l) const { return impl.lookup(l); } + MOZ_ALWAYS_INLINE Ptr lookup(const Lookup& l) const { return impl.lookup(l); } // Like lookup, but does not assert if two threads call lookup at the same // time. Only use this method when none of the threads will modify the map. - Ptr readonlyThreadsafeLookup(const Lookup& l) const { return impl.readonlyThreadsafeLookup(l); } + MOZ_ALWAYS_INLINE Ptr readonlyThreadsafeLookup(const Lookup& l) const { + return impl.readonlyThreadsafeLookup(l); + } // Assuming |p.found()|, remove |*p|. void remove(Ptr p) { impl.remove(p); } @@ -147,7 +149,7 @@ class HashMap // assert(p->key == 3); // char val = p->value; typedef typename Impl::AddPtr AddPtr; - AddPtr lookupForAdd(const Lookup& l) const { + MOZ_ALWAYS_INLINE AddPtr lookupForAdd(const Lookup& l) const { return impl.lookupForAdd(l); } @@ -198,6 +200,10 @@ class HashMap // using the finish() method. void clear() { impl.clear(); } + // Remove all entries. Unlike clear() this method tries to shrink the table. + // Unlike finish() it does not require the map to be initialized again. + void clearAndShrink() { impl.clearAndShrink(); } + // Remove all the entries and release all internal buffers. The map must // be initialized again before any use. void finish() { impl.finish(); } @@ -355,11 +361,13 @@ class HashSet // // Also see the definition of Ptr in HashTable above. typedef typename Impl::Ptr Ptr; - Ptr lookup(const Lookup& l) const { return impl.lookup(l); } + MOZ_ALWAYS_INLINE Ptr lookup(const Lookup& l) const { return impl.lookup(l); } // Like lookup, but does not assert if two threads call lookup at the same // time. Only use this method when none of the threads will modify the map. - Ptr readonlyThreadsafeLookup(const Lookup& l) const { return impl.readonlyThreadsafeLookup(l); } + MOZ_ALWAYS_INLINE Ptr readonlyThreadsafeLookup(const Lookup& l) const { + return impl.readonlyThreadsafeLookup(l); + } // Assuming |p.found()|, remove |*p|. void remove(Ptr p) { impl.remove(p); } @@ -397,7 +405,9 @@ class HashSet // Note that relookupOrAdd(p,l,t) performs Lookup using |l| and adds the // entry |t|, where the caller ensures match(l,t). typedef typename Impl::AddPtr AddPtr; - AddPtr lookupForAdd(const Lookup& l) const { return impl.lookupForAdd(l); } + MOZ_ALWAYS_INLINE AddPtr lookupForAdd(const Lookup& l) const { + return impl.lookupForAdd(l); + } template MOZ_MUST_USE bool add(AddPtr& p, U&& u) { @@ -437,6 +447,10 @@ class HashSet // using the finish() method. void clear() { impl.clear(); } + // Remove all entries. Unlike clear() this method tries to shrink the table. + // Unlike finish() it does not require the set to be initialized again. + void clearAndShrink() { impl.clearAndShrink(); } + // Remove all the entries and release all internal buffers. The set must // be initialized again before any use. void finish() { impl.finish(); } @@ -1383,7 +1397,7 @@ class HashTable : private AllocPolicy return wouldBeUnderloaded(capacity(), entryCount); } - static bool match(Entry& e, const Lookup& l) + static MOZ_ALWAYS_INLINE bool match(Entry& e, const Lookup& l) { return HashPolicy::match(HashPolicy::getKey(e.get()), l); } @@ -1393,7 +1407,8 @@ class HashTable : private AllocPolicy // (The use of the METER() macro to increment stats violates this // restriction but we will live with that for now because it's enabled so // rarely.) - Entry& lookup(const Lookup& l, HashNumber keyHash, unsigned collisionBit) const + MOZ_ALWAYS_INLINE Entry& + lookup(const Lookup& l, HashNumber keyHash, unsigned collisionBit) const { MOZ_ASSERT(isLiveHash(keyHash)); MOZ_ASSERT(!(keyHash & sCollisionBit)); @@ -1689,6 +1704,12 @@ class HashTable : private AllocPolicy #endif } + void clearAndShrink() + { + clear(); + compactIfUnderloaded(); + } + void finish() { #ifdef JS_DEBUG @@ -1747,7 +1768,7 @@ class HashTable : private AllocPolicy return mallocSizeOf(this) + sizeOfExcludingThis(mallocSizeOf); } - Ptr lookup(const Lookup& l) const + MOZ_ALWAYS_INLINE Ptr lookup(const Lookup& l) const { mozilla::ReentrancyGuard g(*this); if (!HasHash(l)) @@ -1756,7 +1777,7 @@ class HashTable : private AllocPolicy return Ptr(lookup(l, keyHash, 0), *this); } - Ptr readonlyThreadsafeLookup(const Lookup& l) const + MOZ_ALWAYS_INLINE Ptr readonlyThreadsafeLookup(const Lookup& l) const { if (!HasHash(l)) return Ptr(); @@ -1764,7 +1785,7 @@ class HashTable : private AllocPolicy return Ptr(lookup(l, keyHash, 0), *this); } - AddPtr lookupForAdd(const Lookup& l) const + MOZ_ALWAYS_INLINE AddPtr lookupForAdd(const Lookup& l) const { mozilla::ReentrancyGuard g(*this); if (!EnsureHash(l)) diff --git a/js/src/gc/Zone.cpp b/js/src/gc/Zone.cpp index 7f2cd83bd7..2b6b38aae9 100644 --- a/js/src/gc/Zone.cpp +++ b/js/src/gc/Zone.cpp @@ -32,6 +32,7 @@ JS::Zone::Zone(JSRuntime* rt) gcGrayRoots(), gcWeakKeys(SystemAllocPolicy(), rt->randomHashCodeScrambler()), typeDescrObjects(this, SystemAllocPolicy()), + atomCache_(), gcMallocBytes(0), gcMallocGCTriggered(false), usage(&rt->gc.usage), @@ -82,7 +83,8 @@ bool Zone::init(bool isSystemArg) return uniqueIds_.init() && gcZoneGroupEdges.init() && gcWeakKeys.init() && - typeDescrObjects.init(); + typeDescrObjects.init() && + atomCache().init(); } void diff --git a/js/src/gc/Zone.h b/js/src/gc/Zone.h index 323aa27758..a11c9603ab 100644 --- a/js/src/gc/Zone.h +++ b/js/src/gc/Zone.h @@ -355,9 +355,13 @@ struct Zone : public JS::shadow::Zone, js::MovableCellHasher, js::SystemAllocPolicy>; JS::WeakCache typeDescrObjects; - + bool addTypeDescrObject(JSContext* cx, HandleObject obj); + // Set of atoms recently used by this Zone. Purged on GC. + js::AtomSet atomCache_; + + js::AtomSet& atomCache() { return atomCache_; } // Malloc counter to measure memory pressure for GC scheduling. It runs from // gcMaxMallocBytes down to zero. This counter should be used only when it's diff --git a/js/src/jsatom.cpp b/js/src/jsatom.cpp index 2a72ac38a3..169600f00e 100644 --- a/js/src/jsatom.cpp +++ b/js/src/jsatom.cpp @@ -10,7 +10,9 @@ #include "jsatominlines.h" #include "mozilla/ArrayUtils.h" +#include "mozilla/Maybe.h" #include "mozilla/RangedPtr.h" +#include "mozilla/Unused.h" #include @@ -63,7 +65,9 @@ const char js_setter_str[] = "setter"; // which create a small number of atoms. static const uint32_t JS_STRING_HASH_COUNT = 64; -AtomSet::Ptr js::FrozenAtomSet::readonlyThreadsafeLookup(const AtomSet::Lookup& l) const { +MOZ_ALWAYS_INLINE AtomSet::Ptr +js::FrozenAtomSet::readonlyThreadsafeLookup(const AtomSet::Lookup& l) const +{ return mSet->readonlyThreadsafeLookup(l); } @@ -283,10 +287,18 @@ static JSAtom* AtomizeAndCopyChars(ExclusiveContext* cx, const CharT* tbchars, size_t length, PinningBehavior pin) { if (JSAtom* s = cx->staticStrings().lookup(tbchars, length)) - return s; + return s; AtomHasher::Lookup lookup(tbchars, length); + JS::Zone* zone = cx->zone(); + mozilla::Maybe zonePtr; + if (zone && pin == DoNotPinAtom) { + zonePtr.emplace(zone->atomCache().lookupForAdd(lookup)); + if (zonePtr.ref()) + return zonePtr.ref()->asPtrUnbarriered(); + } + // Note: when this function is called while the permanent atoms table is // being initialized (in initializeAtoms()), |permanentAtoms| is not yet // initialized so this lookup is always skipped. Only once @@ -294,8 +306,12 @@ AtomizeAndCopyChars(ExclusiveContext* cx, const CharT* tbchars, size_t length, P // initialized and then this lookup will go ahead. if (cx->isPermanentAtomsInitialized()) { AtomSet::Ptr pp = cx->permanentAtoms().readonlyThreadsafeLookup(lookup); - if (pp) - return pp->asPtr(cx); + if (pp) { + JSAtom* atom = pp->asPtr(cx); + if (zonePtr) + mozilla::Unused << zone->atomCache().add(zonePtr.ref(), AtomStateEntry(atom, false)); + return atom; + } } AutoLockForExclusiveAccess lock(cx); @@ -305,9 +321,14 @@ AtomizeAndCopyChars(ExclusiveContext* cx, const CharT* tbchars, size_t length, P if (p) { JSAtom* atom = p->asPtr(cx); p->setPinned(bool(pin)); + if (zonePtr) + mozilla::Unused << zone->atomCache().add(zonePtr.ref(), AtomStateEntry(atom, false)); return atom; } + if (!JSString::validateLength(cx, length)) + return nullptr; + AutoCompartment ac(cx, cx->atomsCompartment(lock), &lock); JSFlatString* flat = NewStringCopyN(cx, tbchars, length); @@ -330,6 +351,9 @@ AtomizeAndCopyChars(ExclusiveContext* cx, const CharT* tbchars, size_t length, P return nullptr; } + if (zonePtr) + mozilla::Unused << zone->atomCache().add(zonePtr.ref(), AtomStateEntry(atom, false)); + return atom; } @@ -382,9 +406,6 @@ js::Atomize(ExclusiveContext* cx, const char* bytes, size_t length, PinningBehav { CHECK_REQUEST(cx); - if (!JSString::validateLength(cx, length)) - return nullptr; - const Latin1Char* chars = reinterpret_cast(bytes); return AtomizeAndCopyChars(cx, chars, length, pin); } @@ -395,9 +416,6 @@ js::AtomizeChars(ExclusiveContext* cx, const CharT* chars, size_t length, Pinnin { CHECK_REQUEST(cx); - if (!JSString::validateLength(cx, length)) - return nullptr; - return AtomizeAndCopyChars(cx, chars, length, pin); } diff --git a/js/src/jsatom.h b/js/src/jsatom.h index eb43442e63..c4223da238 100644 --- a/js/src/jsatom.h +++ b/js/src/jsatom.h @@ -80,12 +80,12 @@ struct AtomHasher HashNumber hash; - Lookup(const char16_t* chars, size_t length) + MOZ_ALWAYS_INLINE Lookup(const char16_t* chars, size_t length) : twoByteChars(chars), isLatin1(false), length(length), atom(nullptr) { hash = mozilla::HashString(chars, length); } - Lookup(const JS::Latin1Char* chars, size_t length) + MOZ_ALWAYS_INLINE Lookup(const JS::Latin1Char* chars, size_t length) : latin1Chars(chars), isLatin1(true), length(length), atom(nullptr) { hash = mozilla::HashString(chars, length); @@ -94,7 +94,7 @@ struct AtomHasher }; static HashNumber hash(const Lookup& l) { return l.hash; } - static inline bool match(const AtomStateEntry& entry, const Lookup& lookup); + static MOZ_ALWAYS_INLINE bool match(const AtomStateEntry& entry, const Lookup& lookup); static void rekey(AtomStateEntry& k, const AtomStateEntry& newKey) { k = newKey; } }; @@ -113,7 +113,7 @@ public: ~FrozenAtomSet() { js_delete(mSet); } - AtomSet::Ptr readonlyThreadsafeLookup(const AtomSet::Lookup& l) const; + MOZ_ALWAYS_INLINE AtomSet::Ptr readonlyThreadsafeLookup(const AtomSet::Lookup& l) const; size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { return mSet->sizeOfIncludingThis(mallocSizeOf); diff --git a/js/src/jsatominlines.h b/js/src/jsatominlines.h index ab91f974d1..9864bd7054 100644 --- a/js/src/jsatominlines.h +++ b/js/src/jsatominlines.h @@ -165,7 +165,7 @@ AtomHasher::Lookup::Lookup(const JSAtom* atom) } } -inline bool +MOZ_ALWAYS_INLINE bool AtomHasher::match(const AtomStateEntry& entry, const Lookup& lookup) { JSAtom* key = entry.asPtrUnbarriered(); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index fd7e6212c6..a41c635161 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -3419,6 +3419,9 @@ GCRuntime::purgeRuntime(AutoLockForExclusiveAccess& lock) for (GCCompartmentsIter comp(rt); !comp.done(); comp.next()) comp->purge(); + for (GCZonesIter zone(rt); !zone.done(); zone.next()) + zone->atomCache().clearAndShrink(); + freeUnusedLifoBlocksAfterSweeping(&rt->tempLifoAlloc); rt->interpreterStack().purge(rt); diff --git a/js/src/vm/String-inl.h b/js/src/vm/String-inl.h index 52261d0f75..b85be3748b 100644 --- a/js/src/vm/String-inl.h +++ b/js/src/vm/String-inl.h @@ -350,15 +350,6 @@ js::StaticStrings::getUnitStringForElement(JSContext* cx, JSString* str, size_t return NewDependentString(cx, str, index, 1); } -inline JSAtom* -js::StaticStrings::getLength2(char16_t c1, char16_t c2) -{ - MOZ_ASSERT(fitsInSmallChar(c1)); - MOZ_ASSERT(fitsInSmallChar(c2)); - size_t index = (((size_t)toSmallChar[c1]) << 6) + toSmallChar[c2]; - return length2StaticTable[index]; -} - MOZ_ALWAYS_INLINE void JSString::finalize(js::FreeOp* fop) { diff --git a/js/src/vm/String.h b/js/src/vm/String.h index 5eaf9e0c2e..0b8aeb3a31 100644 --- a/js/src/vm/String.h +++ b/js/src/vm/String.h @@ -1146,7 +1146,7 @@ class StaticStrings } template - JSAtom* lookup(const CharT* chars, size_t length) { + MOZ_ALWAYS_INLINE JSAtom* lookup(const CharT* chars, size_t length) { switch (length) { case 1: { char16_t c = chars[0]; @@ -1194,7 +1194,12 @@ class StaticStrings static const SmallChar toSmallChar[]; - JSAtom* getLength2(char16_t c1, char16_t c2); + MOZ_ALWAYS_INLINE JSAtom* getLength2(char16_t c1, char16_t c2) { + MOZ_ASSERT(fitsInSmallChar(c1)); + MOZ_ASSERT(fitsInSmallChar(c2)); + size_t index = (size_t(toSmallChar[c1]) << 6) + toSmallChar[c2]; + return length2StaticTable[index]; + } JSAtom* getLength2(uint32_t u) { MOZ_ASSERT(u < 100); return getLength2('0' + u / 10, '0' + u % 10); From 3836a57d4732978a6a7628ed16061acaf2c6b916 Mon Sep 17 00:00:00 2001 From: Basilisk-Dev Date: Mon, 13 Apr 2026 22:29:57 -0400 Subject: [PATCH 5/5] Issue #3047 - Added a null-argument guard in uriloader/exthandler/nsExternalHelperAppService setProtocolHandlerDefaults(null, ...) now returns NS_ERROR_INVALID_ARG instead of dereferencing aHandlerInfo and crashing --- uriloader/exthandler/nsExternalHelperAppService.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/uriloader/exthandler/nsExternalHelperAppService.cpp b/uriloader/exthandler/nsExternalHelperAppService.cpp index c01e82b0b2..2573d59f0f 100644 --- a/uriloader/exthandler/nsExternalHelperAppService.cpp +++ b/uriloader/exthandler/nsExternalHelperAppService.cpp @@ -1111,6 +1111,8 @@ NS_IMETHODIMP nsExternalHelperAppService::SetProtocolHandlerDefaults(nsIHandlerInfo *aHandlerInfo, bool aOSHandlerExists) { + NS_ENSURE_ARG_POINTER(aHandlerInfo); + // this type isn't in our database, so we've only got an OS default handler, // if one exists