From be3d8491c7570a8e042eea1a9795a0e927f99bdc Mon Sep 17 00:00:00 2001 From: Francis Dominic Fajardo Date: Sat, 26 Jul 2025 14:55:03 +0800 Subject: [PATCH 01/30] Issue #2835 - Part 1: Expose nsCSSRuleProcessor::CascadeSheet --- layout/style/nsCSSRuleProcessor.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/layout/style/nsCSSRuleProcessor.h b/layout/style/nsCSSRuleProcessor.h index 9d695fedd3..3a018a8090 100644 --- a/layout/style/nsCSSRuleProcessor.h +++ b/layout/style/nsCSSRuleProcessor.h @@ -154,13 +154,13 @@ public: bool IsInRuleProcessorCache() const { return mInRuleProcessorCache; } bool IsUsedByMultipleStyleSets() const { return mStyleSetRefCnt > 1; } + static bool CascadeSheet(mozilla::CSSStyleSheet* aSheet, + CascadeLayer* aLayer); + protected: virtual ~nsCSSRuleProcessor(); private: - static bool CascadeSheet(mozilla::CSSStyleSheet* aSheet, - CascadeLayer* aLayer); - RuleProcessorGroup* GetGroup(nsPresContext* aPresContext); void RefreshGroup(nsPresContext* aPresContext); From 37230e7b21893cb518f7884a3cf9d18d5d4ed3cb Mon Sep 17 00:00:00 2001 From: Francis Dominic Fajardo Date: Wed, 23 Jul 2025 20:39:56 +0800 Subject: [PATCH 02/30] Issue #2835 - Part 2: Implement processing of import rules based on order of appearance --- layout/style/nsCSSRuleProcessor.cpp | 17 +++++++++++++---- layout/style/nsCSSRuleUtils.cpp | 10 ++++++++++ layout/style/nsCSSRuleUtils.h | 2 ++ modules/libpref/init/all.js | 6 ++++++ 4 files changed, 31 insertions(+), 4 deletions(-) diff --git a/layout/style/nsCSSRuleProcessor.cpp b/layout/style/nsCSSRuleProcessor.cpp index ebae46cd70..974284d501 100644 --- a/layout/style/nsCSSRuleProcessor.cpp +++ b/layout/style/nsCSSRuleProcessor.cpp @@ -16,6 +16,7 @@ #include "PLDHashTable.h" #include "nsICSSPseudoComparator.h" #include "mozilla/MemoryReporting.h" +#include "mozilla/css/ImportRule.h" #include "mozilla/css/StyleRule.h" #include "mozilla/css/GroupRule.h" #include "nsIDocument.h" @@ -649,6 +650,12 @@ CascadeRuleEnumFunc(css::Rule* aRule, void* aData) if (!layer->mData->mCounterStyleRules.AppendElement(counterStyleRule)) { return false; } + } else if (css::Rule::IMPORT_RULE == type && + nsCSSRuleUtils::LoadImportedSheetsInOrderEnabled()) { + css::ImportRule* importRule = static_cast(aRule); + nsCSSRuleProcessor::CascadeSheet( + importRule->GetStyleSheet()->AsConcrete(), + layer); } return true; } @@ -660,10 +667,12 @@ nsCSSRuleProcessor::CascadeSheet(CSSStyleSheet* aSheet, CascadeLayer* aLayer) aSheet->UseForPresentation(aLayer->mPresContext, aLayer->mCacheKey) && aSheet->mInner) { - CSSStyleSheet* child = aSheet->mInner->mFirstChild; - while (child) { - CascadeSheet(child, aLayer); - child = child->mNext; + if (!nsCSSRuleUtils::LoadImportedSheetsInOrderEnabled()) { + CSSStyleSheet* child = aSheet->mInner->mFirstChild; + while (child) { + CascadeSheet(child, aLayer); + child = child->mNext; + } } if (!aSheet->mInner->mOrderedRules.EnumerateForwards(CascadeRuleEnumFunc, diff --git a/layout/style/nsCSSRuleUtils.cpp b/layout/style/nsCSSRuleUtils.cpp index 996e1f5223..9717e42cb3 100644 --- a/layout/style/nsCSSRuleUtils.cpp +++ b/layout/style/nsCSSRuleUtils.cpp @@ -19,6 +19,7 @@ using namespace mozilla::dom; #define VISITED_PSEUDO_PREF "layout.css.visited_links_enabled" static bool gSupportVisitedPseudo = true; +static bool gLoadImportedSheetsInOrder = true; static nsTArray>* sSystemMetrics = 0; @@ -31,6 +32,9 @@ nsCSSRuleUtils::Startup() { Preferences::AddBoolVarCache( &gSupportVisitedPseudo, VISITED_PSEUDO_PREF, true); + Preferences::AddBoolVarCache(&gLoadImportedSheetsInOrder, + "layout.css.load-imported-sheets-in-order", + true); } static bool @@ -206,6 +210,12 @@ nsCSSRuleUtils::HasSystemMetric(nsIAtom* aMetric) return sSystemMetrics->IndexOf(aMetric) != sSystemMetrics->NoIndex; } +/* static */ bool +nsCSSRuleUtils::LoadImportedSheetsInOrderEnabled() +{ + return gLoadImportedSheetsInOrder; +} + #ifdef XP_WIN /* static */ uint8_t nsCSSRuleUtils::GetWindowsThemeIdentifier() diff --git a/layout/style/nsCSSRuleUtils.h b/layout/style/nsCSSRuleUtils.h index f1dff880bd..016159a8d1 100644 --- a/layout/style/nsCSSRuleUtils.h +++ b/layout/style/nsCSSRuleUtils.h @@ -27,6 +27,8 @@ struct nsCSSRuleUtils static void FreeSystemMetrics(); static bool HasSystemMetric(nsIAtom* aMetric); + static bool LoadImportedSheetsInOrderEnabled(); + #ifdef XP_WIN // Cached theme identifier for the moz-windows-theme media query. static uint8_t GetWindowsThemeIdentifier(); diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index aa9ec326a4..cbffe84c23 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2704,6 +2704,12 @@ pref("layout.css.resizeobserver.enabled", true); // Is support for cascade layers enabled? pref("layout.css.cascade-layers.enabled", true); +// Should rules in imported style sheets be added based on the order +// of appearance of their respective @import rules in the parent +// style sheet? Otherwise, they are added before rules preceding +// @import are processed, which is problematic for cascade layers. +pref("layout.css.load-imported-sheets-in-order", true); + // pref for which side vertical scrollbars should be on // 0 = end-side in UI direction // 1 = end-side in document/content direction From 2c39bb7901f74a7e99f8eb87af2e975d6e58e027 Mon Sep 17 00:00:00 2001 From: Moonchild Date: Sun, 27 Jul 2025 13:11:28 +0200 Subject: [PATCH 03/30] No issue - Regenerate devtools CSS database To pick up new public use of clip, addition of overflow-inline and addition of overflow-block. --- .../shared/css/generated/properties-db.js | 42 ++++++++++++++++++- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js index d9ffee46cb..9dd28d1b34 100644 --- a/devtools/shared/css/generated/properties-db.js +++ b/devtools/shared/css/generated/properties-db.js @@ -3381,7 +3381,9 @@ exports.CSS_PROPERTIES = { "word-spacing", "overflow-wrap", "writing-mode", - "z-index" + "z-index", + "overflow-block", + "overflow-inline" ], "supports": [ 1, @@ -3411,7 +3413,6 @@ exports.CSS_PROPERTIES = { "-moz-grid-line", "-moz-groupbox", "-moz-gtk-info-bar", - "clip", "-moz-image-rect", "-moz-inline-box", "-moz-inline-grid", @@ -3502,6 +3503,7 @@ exports.CSS_PROPERTIES = { "checkbox-container", "checkbox-label", "checkmenuitem", + "clip", "clone", "collapse", "color", @@ -8568,6 +8570,42 @@ exports.CSS_PROPERTIES = { "visible" ] }, + "overflow-block": { + "isInherited": false, + "subproperties": [ + "overflow-block" + ], + "supports": [], + "values": [ + "auto", + "clip", + "hidden", + "inherit", + "initial", + "revert", + "scroll", + "unset", + "visible" + ] + }, + "overflow-inline": { + "isInherited": false, + "subproperties": [ + "overflow-inline" + ], + "supports": [], + "values": [ + "auto", + "clip", + "hidden", + "inherit", + "initial", + "revert", + "scroll", + "unset", + "visible" + ] + }, "overflow-wrap": { "isInherited": true, "subproperties": [ From 055a685c2ef19702923d79ebbe1a30b16bac3e5c Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 19 Jul 2025 07:20:29 +0800 Subject: [PATCH 04/30] Issue #2489: Add CSS color-mix keyword support --- layout/style/nsCSSKeywordList.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index 5c0057aa76..1affe1024d 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -197,6 +197,7 @@ CSS_KEY(collapse, collapse) CSS_KEY(color, color) CSS_KEY(color-burn, color_burn) CSS_KEY(color-dodge, color_dodge) +CSS_KEY(color-mix, color_mix) CSS_KEY(common-ligatures, common_ligatures) CSS_KEY(column, column) CSS_KEY(column-reverse, column_reverse) @@ -312,6 +313,7 @@ CSS_KEY(horizontal, horizontal) CSS_KEY(horizontal-tb, horizontal_tb) CSS_KEY(hue, hue) CSS_KEY(hue-rotate, hue_rotate) +CSS_KEY(hsl, hsl) CSS_KEY(hz, hz) CSS_KEY(icon, icon) CSS_KEY(ignore, ignore) From 7800eb012696a9ad428935823442c56b30958f34 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 19 Jul 2025 07:45:24 +0800 Subject: [PATCH 05/30] Issue #2489: ColorMixValue support and structure --- layout/style/nsCSSValue.cpp | 40 +++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 24e804d800..9503e09f57 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -166,6 +166,10 @@ nsCSSValue::nsCSSValue(const nsCSSValue& aCopy) mValue.mComplexColor = aCopy.mValue.mComplexColor; mValue.mComplexColor->AddRef(); } + else if (eCSSUnit_ColorMix == mUnit) { + mValue.mColorMix = aCopy.mValue.mColorMix; + mValue.mColorMix->AddRef(); + } else if (eCSSUnit_Revert == mUnit) { mValue.mCascadeOrigin = aCopy.mValue.mCascadeOrigin; } @@ -282,6 +286,14 @@ bool nsCSSValue::operator==(const nsCSSValue& aOther) const else if (eCSSUnit_ComplexColor == mUnit) { return *mValue.mComplexColor == *aOther.mValue.mComplexColor; } + else if (eCSSUnit_ColorMix == mUnit) { + return mValue.mColorMix == aOther.mValue.mColorMix || + (mValue.mColorMix->mColorSpace == aOther.mValue.mColorMix->mColorSpace && + mValue.mColorMix->mColor1 == aOther.mValue.mColorMix->mColor1 && + mValue.mColorMix->mColor2 == aOther.mValue.mColorMix->mColor2 && + mValue.mColorMix->mWeight1 == aOther.mValue.mColorMix->mWeight1 && + mValue.mColorMix->mWeight2 == aOther.mValue.mColorMix->mWeight2); + } else if (eCSSUnit_Revert == mUnit) { return mValue.mCascadeOrigin == aOther.mValue.mCascadeOrigin; } @@ -421,6 +433,8 @@ void nsCSSValue::DoReset() mValue.mFloatColor->Release(); } else if (eCSSUnit_ComplexColor == mUnit) { mValue.mComplexColor->Release(); + } else if (eCSSUnit_ColorMix == mUnit) { + mValue.mColorMix->Release(); } else if (UnitHasArrayValue()) { mValue.mArray->Release(); } else if (eCSSUnit_URL == mUnit) { @@ -545,6 +559,14 @@ nsCSSValue::SetComplexColorValue(already_AddRefed aValue) mValue.mComplexColor = aValue.take(); } +void +nsCSSValue::SetColorMixValue(already_AddRefed aValue) +{ + Reset(); + mUnit = eCSSUnit_ColorMix; + mValue.mColorMix = aValue.take(); +} + void nsCSSValue::SetCascadeOriginValue(mozilla::SheetType aValue, nsCSSUnit aUnit) { @@ -2007,6 +2029,7 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult, case eCSSUnit_HSLColor: break; case eCSSUnit_HSLAColor: break; case eCSSUnit_ComplexColor: break; + case eCSSUnit_ColorMix: break; case eCSSUnit_Percent: aResult.Append(char16_t('%')); break; case eCSSUnit_Number: break; case eCSSUnit_Gradient: break; @@ -2221,6 +2244,11 @@ nsCSSValue::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const n += mValue.mComplexColor->SizeOfIncludingThis(aMallocSizeOf); break; + // Color Mix + case eCSSUnit_ColorMix: + n += mValue.mColorMix->SizeOfIncludingThis(aMallocSizeOf); + break; + // Cascade Origin: nothing extra to measure. case eCSSUnit_Revert: break; @@ -3075,6 +3103,18 @@ css::ComplexColorValue::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const return n; } +size_t +css::ColorMixValue::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const +{ + // Only measure it if it's unshared, to avoid double-counting. + size_t n = 0; + if (mRefCnt <= 1) { + n += aMallocSizeOf(this); + n += mColor1.SizeOfExcludingThis(aMallocSizeOf); + n += mColor2.SizeOfExcludingThis(aMallocSizeOf); + } + return n; +} nsCSSValueGradientStop::nsCSSValueGradientStop() : mLocation(eCSSUnit_None), mColor(eCSSUnit_Null), From b42cca5fa5884e680ec9099efb6bf81a71719e0b Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 19 Jul 2025 07:49:42 +0800 Subject: [PATCH 06/30] Issue #2489: Add color-mix serialization support --- layout/style/nsCSSValue.cpp | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 9503e09f57..1dc7e71792 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -1731,6 +1731,28 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult, } serializable.AppendToString(aProperty, aResult, aSerialization); } + else if (eCSSUnit_ColorMix == unit) { + const ColorMixValue* colorMix = GetColorMixValue(); + aResult.AppendLiteral("color-mix(in "); + + // append color space + switch (colorMix->mColorSpace) { + case eColorMixColorSpace_sRGB: + aResult.AppendLiteral("srgb"); + break; + case eColorMixColorSpace_HSL: + aResult.AppendLiteral("hsl"); + break; + } + + // append color1 and color2 + aResult.AppendLiteral(", "); + colorMix->mColor1.AppendToString(aProperty, aResult, aSerialization); + aResult.AppendLiteral(", "); + colorMix->mColor2.AppendToString(aProperty, aResult, aSerialization); + + aResult.Append(')'); + } else if (eCSSUnit_URL == unit || eCSSUnit_Image == unit) { aResult.AppendLiteral("url("); nsStyleUtil::AppendEscapedCSSString( From 58d7206259591009e48f0058dc9f560566849a1a Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 19 Jul 2025 07:50:34 +0800 Subject: [PATCH 07/30] Issue #2489: Add color-mix computation support --- layout/style/nsRuleNode.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index a0a5ec1fd6..53dfe3ef09 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -1137,6 +1137,21 @@ static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, aResult = aParentColor; result = true; aConditions.SetUncacheable(); + } else if (eCSSUnit_ColorMix == unit) { + const mozilla::css::ColorMixValue* colorMix = aValue.GetColorMixValue(); + if (colorMix) { + nscolor color1, color2; + if (SetColor(colorMix->mColor1, aParentColor, aPresContext, aContext, color1, aConditions) && + SetColor(colorMix->mColor2, aParentColor, aPresContext, aContext, color2, aConditions)) { + // simple linear interpolation (50/50 sRGB mix) + uint8_t r = (NS_GET_R(color1) + NS_GET_R(color2)) / 2; + uint8_t g = (NS_GET_G(color1) + NS_GET_G(color2)) / 2; + uint8_t b = (NS_GET_B(color1) + NS_GET_B(color2)) / 2; + uint8_t a = (NS_GET_A(color1) + NS_GET_A(color2)) / 2; + aResult = NS_RGBA(r, g, b, a); + result = true; + } + } } else if (eCSSUnit_Enumerated == unit && aValue.GetIntValue() == NS_STYLE_COLOR_INHERIT_FROM_BODY) { NS_ASSERTION(aPresContext->CompatibilityMode() == eCompatibility_NavQuirks, From 6b938c48a5cd19e648d41315c5fcd2cfd47efd6e Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 19 Jul 2025 12:13:58 +0800 Subject: [PATCH 08/30] Issue #2489: Include color-mix to avoid filtering --- layout/style/nsCSSParser.cpp | 3 ++- layout/style/nsCSSValue.h | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index fdb9df8411..56fb2c39d3 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -8650,7 +8650,8 @@ CSSParserImpl::ParseVariant(nsCSSValue& aValue, (tk->mIdent.LowerCaseEqualsLiteral("rgb") || tk->mIdent.LowerCaseEqualsLiteral("hsl") || tk->mIdent.LowerCaseEqualsLiteral("rgba") || - tk->mIdent.LowerCaseEqualsLiteral("hsla")))) + tk->mIdent.LowerCaseEqualsLiteral("hsla") || + tk->mIdent.LowerCaseEqualsLiteral("color-mix")))) { // Put token back so that parse color can get it UngetToken(); diff --git a/layout/style/nsCSSValue.h b/layout/style/nsCSSValue.h index 680e26732a..4282d55375 100644 --- a/layout/style/nsCSSValue.h +++ b/layout/style/nsCSSValue.h @@ -804,7 +804,7 @@ public: MOZ_ASSERT(mUnit == eCSSUnit_ComplexColor); return mValue.mComplexColor->ToComplexColor(); } - + Array* GetArrayValue() const { MOZ_ASSERT(UnitHasArrayValue(), "not an array value"); From 99d7fb8e95baf050cc76733c6b40976d921f4333 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 19 Jul 2025 12:18:01 +0800 Subject: [PATCH 09/30] Issue #2489: Update enum class syntax --- layout/style/nsCSSValue.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 1dc7e71792..1e43377460 100644 --- a/layout/style/nsCSSValue.cpp +++ b/layout/style/nsCSSValue.cpp @@ -1737,10 +1737,10 @@ nsCSSValue::AppendToString(nsCSSPropertyID aProperty, nsAString& aResult, // append color space switch (colorMix->mColorSpace) { - case eColorMixColorSpace_sRGB: + case mozilla::css::ColorMixColorSpace::sRGB: aResult.AppendLiteral("srgb"); break; - case eColorMixColorSpace_HSL: + case mozilla::css::ColorMixColorSpace::HSL: aResult.AppendLiteral("hsl"); break; } From cb60df2169a2c1acd50cfe9754bbdc2e3e0354fd Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 19 Jul 2025 12:22:06 +0800 Subject: [PATCH 10/30] Issue #2489: Create ColorMixValue structure --- layout/style/nsCSSValue.h | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/layout/style/nsCSSValue.h b/layout/style/nsCSSValue.h index 4282d55375..ae50355147 100644 --- a/layout/style/nsCSSValue.h +++ b/layout/style/nsCSSValue.h @@ -521,6 +521,7 @@ enum nsCSSUnit { eCSSUnit_HSLColor = 89, // (nsCSSValueFloatColor*) eCSSUnit_HSLAColor = 90, // (nsCSSValueFloatColor*) eCSSUnit_ComplexColor = 91, // (ComplexColorValue*) + eCSSUnit_ColorMix = 92, // (ColorMixValue*) eCSSUnit_Percent = 100, // (float) 1.0 == 100%) value is percentage of something eCSSUnit_Number = 101, // (float) value is numeric (usually multiplier, different behavior than percent) @@ -606,6 +607,13 @@ struct nsCSSValueTriplet; struct nsCSSValueTriplet_heap; class nsCSSValueFloatColor; +namespace mozilla { +namespace css { +enum class ColorMixColorSpace; +struct ColorMixValue; +} // namespace css +} // namespace mozilla + class nsCSSValue { public: struct Array; @@ -805,6 +813,12 @@ public: return mValue.mComplexColor->ToComplexColor(); } + mozilla::css::ColorMixValue* GetColorMixValue() const + { + MOZ_ASSERT(mUnit == eCSSUnit_ColorMix); + return mValue.mColorMix; + } + Array* GetArrayValue() const { MOZ_ASSERT(UnitHasArrayValue(), "not an array value"); @@ -950,6 +964,8 @@ public: void SetRGBAColorValue(const mozilla::css::RGBAColorData& aValue); void SetComplexColorValue( already_AddRefed aValue); + void SetColorMixValue( + already_AddRefed aValue); void SetCascadeOriginValue(mozilla::SheetType aValue, nsCSSUnit aUnit); void SetArrayValue(nsCSSValue::Array* aArray, nsCSSUnit aUnit); void SetURLValue(mozilla::css::URLValue* aURI); @@ -1063,6 +1079,7 @@ protected: nsCSSValueFloatColor* MOZ_OWNING_REF mFloatColor; mozilla::css::FontFamilyListRefCnt* MOZ_OWNING_REF mFontFamilyList; mozilla::css::ComplexColorValue* MOZ_OWNING_REF mComplexColor; + mozilla::css::ColorMixValue* MOZ_OWNING_REF mColorMix; mozilla::SheetType mCascadeOrigin; } mValue; }; @@ -1972,5 +1989,41 @@ protected: static const corner_type corners[4]; }; +namespace mozilla { +namespace css { + +enum class ColorMixColorSpace { + sRGB, + HSL +}; + +struct ColorMixValue final +{ + ColorMixColorSpace mColorSpace; + nsCSSValue mColor1; + nsCSSValue mColor2; + float mWeight1; + float mWeight2; + + ColorMixValue(ColorMixColorSpace aColorSpace, const nsCSSValue& aColor1, const nsCSSValue& aColor2) + : mColorSpace(aColorSpace), mColor1(aColor1), mColor2(aColor2), mWeight1(0.5f), mWeight2(0.5f) {} + + ColorMixValue(ColorMixColorSpace aColorSpace, const nsCSSValue& aColor1, const nsCSSValue& aColor2, + float aWeight1, float aWeight2) + : mColorSpace(aColorSpace), mColor1(aColor1), mColor2(aColor2), mWeight1(aWeight1), mWeight2(aWeight2) {} + + ColorMixValue(const ColorMixValue&) = delete; + + NS_INLINE_DECL_REFCOUNTING(ColorMixValue) + + size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const; + +private: + ~ColorMixValue() {} +}; + +} // namespace css +} // namespace mozilla + #endif /* nsCSSValue_h___ */ From 7899850a19d288ff74844066050bfbb672ac1ca0 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 19 Jul 2025 12:32:43 +0800 Subject: [PATCH 11/30] Issue #2489: color-mix function parsing --- layout/style/nsCSSParser.cpp | 56 ++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 56fb2c39d3..d27763d03c 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -7444,6 +7444,62 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) } break; case eCSSToken_Function: { + // check for color-mix function + if (mToken.mIdent.LowerCaseEqualsLiteral("color-mix")) { + // parse color-mix function + RefPtr colorMix = new mozilla::css::ColorMixValue( + mozilla::css::ColorMixColorSpace::sRGB, nsCSSValue(), nsCSSValue()); + + if (!GetToken(true)) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + if (mToken.mType != eCSSToken_Ident || !mToken.mIdent.LowerCaseEqualsLiteral("in")) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + if (!GetToken(true) || mToken.mType != eCSSToken_Ident || !mToken.mIdent.LowerCaseEqualsLiteral("srgb")) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + colorMix->mColorSpace = mozilla::css::ColorMixColorSpace::sRGB; + + if (!ExpectSymbol(',', true)) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + nsCSSValue color1; + if (ParseColor(color1) != CSSParseResult::Ok) { + SkipUntil(')'); + return CSSParseResult::Error; + } + colorMix->mColor1 = color1; + + if (!ExpectSymbol(',', true)) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + nsCSSValue color2; + if (ParseColor(color2) != CSSParseResult::Ok) { + SkipUntil(')'); + return CSSParseResult::Error; + } + colorMix->mColor2 = color2; + + if (!ExpectSymbol(')', true)) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + aValue.SetColorMixValue(colorMix.forget()); + return CSSParseResult::Ok; + } + bool isRGB; bool isHSL; if ((isRGB = mToken.mIdent.LowerCaseEqualsLiteral("rgb")) || From a3cb79912612463d989c1bb2bff2aae3d49146a7 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Fri, 25 Jul 2025 04:47:13 +0800 Subject: [PATCH 12/30] Issue #2489: Fix CSS color-mix() percentage parsing --- layout/style/nsCSSParser.cpp | 42 ++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index d27763d03c..0aad8add3f 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -7477,7 +7477,21 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) SkipUntil(')'); return CSSParseResult::Error; } - colorMix->mColor1 = color1; + + // parse optional weight for first color + bool w1_specified = false; + float w1 = 0.5f; // Default to 50% + if (GetToken(true)) { + if (mToken.mType == eCSSToken_Percentage) { + w1 = mToken.mNumber; // percentage tokens are already normalized (0.0-1.0) + w1_specified = true; + // clamp to valid range [0, 1] + if (w1 < 0.0f) w1 = 0.0f; + if (w1 > 1.0f) w1 = 1.0f; + } else { + UngetToken(); + } + } if (!ExpectSymbol(',', true)) { SkipUntil(')'); @@ -7489,13 +7503,37 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) SkipUntil(')'); return CSSParseResult::Error; } - colorMix->mColor2 = color2; + + // parse optional weight for second color + bool w2_specified = false; + float w2 = 0.5f; // default to 50% + if (GetToken(true)) { + if (mToken.mType == eCSSToken_Percentage) { + w2 = mToken.mNumber; // percentage tokens are already normalized (0.0-1.0) + w2_specified = true; + // Clamp to valid range [0, 1] + if (w2 < 0.0f) w2 = 0.0f; + if (w2 > 1.0f) w2 = 1.0f; + } else { + UngetToken(); + } + } + + if (w1_specified && !w2_specified) { + // first specified, second should be complement + w2 = 1.0f - w1; + } else if (!w1_specified && w2_specified) { + // second specified, first should be complement + w1 = 1.0f - w2; + } if (!ExpectSymbol(')', true)) { SkipUntil(')'); return CSSParseResult::Error; } + RefPtr colorMix = new mozilla::css::ColorMixValue( + mozilla::css::ColorMixColorSpace::sRGB, color1, color2, w1, w2); aValue.SetColorMixValue(colorMix.forget()); return CSSParseResult::Ok; } From eb7eacc5657c6bf9910dd613b1c0094e512d35a9 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Fri, 25 Jul 2025 04:48:50 +0800 Subject: [PATCH 13/30] Issue #2489: Remove unnecessary initializations --- layout/style/nsCSSParser.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 0aad8add3f..cb8bac381e 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -7447,9 +7447,6 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) // check for color-mix function if (mToken.mIdent.LowerCaseEqualsLiteral("color-mix")) { // parse color-mix function - RefPtr colorMix = new mozilla::css::ColorMixValue( - mozilla::css::ColorMixColorSpace::sRGB, nsCSSValue(), nsCSSValue()); - if (!GetToken(true)) { SkipUntil(')'); return CSSParseResult::Error; @@ -7465,8 +7462,6 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) return CSSParseResult::Error; } - colorMix->mColorSpace = mozilla::css::ColorMixColorSpace::sRGB; - if (!ExpectSymbol(',', true)) { SkipUntil(')'); return CSSParseResult::Error; From ff5f3b2b2d1b6fd370c31fbbde0030939f15db9d Mon Sep 17 00:00:00 2001 From: erixreyes Date: Fri, 25 Jul 2025 04:49:35 +0800 Subject: [PATCH 14/30] Issue #2489: Improve color-mix() computation algorithm --- layout/style/nsRuleNode.cpp | 54 ++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 7 deletions(-) diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 53dfe3ef09..f3730979a7 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -1143,13 +1143,53 @@ static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, nscolor color1, color2; if (SetColor(colorMix->mColor1, aParentColor, aPresContext, aContext, color1, aConditions) && SetColor(colorMix->mColor2, aParentColor, aPresContext, aContext, color2, aConditions)) { - // simple linear interpolation (50/50 sRGB mix) - uint8_t r = (NS_GET_R(color1) + NS_GET_R(color2)) / 2; - uint8_t g = (NS_GET_G(color1) + NS_GET_G(color2)) / 2; - uint8_t b = (NS_GET_B(color1) + NS_GET_B(color2)) / 2; - uint8_t a = (NS_GET_A(color1) + NS_GET_A(color2)) / 2; - aResult = NS_RGBA(r, g, b, a); - result = true; + + // interpolate each RGBA component with proper percentage handling + float w1 = colorMix->mWeight1; + float w2 = colorMix->mWeight2; + + // edge case: if both weights are zero, return transparent black + if (w1 <= 0.0f && w2 <= 0.0f) { + aResult = NS_RGBA(0, 0, 0, 0); + result = true; + } else { + // extracts RGBA components from both colors + float r1 = NS_GET_R(color1); + float g1 = NS_GET_G(color1); + float b1 = NS_GET_B(color1); + float a1 = NS_GET_A(color1); + + float r2 = NS_GET_R(color2); + float g2 = NS_GET_G(color2); + float b2 = NS_GET_B(color2); + float a2 = NS_GET_A(color2); + + // normalize weights + float sum = w1 + w2; + if (sum <= 0.0f) { + // both weights zero - use equal weighting + w1 = w2 = 0.5f; + sum = 1.0f; + } + + float norm1 = w1 / sum; + float norm2 = w2 / sum; + + // perform linear interpolation + float r = r1 * norm1 + r2 * norm2; + float g = g1 * norm1 + g2 * norm2; + float b = b1 * norm1 + b2 * norm2; + float a = a1 * norm1 + a2 * norm2; + + // convert to integers with rounding + uint8_t rInt = (uint8_t)mozilla::clamped(r + 0.5f, 0.0f, 255.0f); + uint8_t gInt = (uint8_t)mozilla::clamped(g + 0.5f, 0.0f, 255.0f); + uint8_t bInt = (uint8_t)mozilla::clamped(b + 0.5f, 0.0f, 255.0f); + uint8_t aInt = (uint8_t)mozilla::clamped(a + 0.5f, 0.0f, 255.0f); + + aResult = NS_RGBA(rInt, gInt, bInt, aInt); + result = true; + } } } } else if (eCSSUnit_Enumerated == unit && From cb2547efa32a4f9b9f386c7e3d2b1b70582a1fae Mon Sep 17 00:00:00 2001 From: erixreyes Date: Fri, 25 Jul 2025 10:40:39 +0800 Subject: [PATCH 15/30] Issue #2489: Adding RGB to HSL function --- gfx/src/nsColor.cpp | 35 +++++++++++++++++++++++++++++++++++ gfx/src/nsColor.h | 5 +++++ 2 files changed, 40 insertions(+) diff --git a/gfx/src/nsColor.cpp b/gfx/src/nsColor.cpp index 359f9fde47..f8070ddfba 100644 --- a/gfx/src/nsColor.cpp +++ b/gfx/src/nsColor.cpp @@ -348,6 +348,41 @@ NS_HSL2RGB(float h, float s, float l) return NS_RGB(r, g, b); } +// RGB to HSL conversion function +// The uint8_t RGB parameters are expected to be in the range 0-255 +// Returns HSL values in the range: H[0-1], S[0-1], L[0-1] +void +NS_RGB2HSL(uint8_t aR, uint8_t aG, uint8_t aB, float* aH, float* aS, float* aL) +{ + float r = aR / 255.0f; + float g = aG / 255.0f; + float b = aB / 255.0f; + + float max = std::max(r, std::max(g, b)); + float min = std::min(r, std::min(g, b)); + float h, s, l = (max + min) / 2.0f; + + if (max == min) { + h = s = 0.0f; // achromatic + } else { + float d = max - min; + s = l > 0.5f ? d / (2.0f - max - min) : d / (max + min); + + if (max == r) { + h = (g - b) / d + (g < b ? 6.0f : 0.0f); + } else if (max == g) { + h = (b - r) / d + 2.0f; + } else { + h = (r - g) / d + 4.0f; + } + h /= 6.0f; + } + + *aH = h; + *aS = s; + *aL = l; +} + const char* NS_RGBToColorName(nscolor aColor) { diff --git a/gfx/src/nsColor.h b/gfx/src/nsColor.h index 2f21c91bf2..72ec5c7a86 100644 --- a/gfx/src/nsColor.h +++ b/gfx/src/nsColor.h @@ -113,6 +113,11 @@ const char * const * NS_AllColorNames(size_t *aSizeArray); // the float parameters are all expected to be in the range 0-1 nscolor NS_HSL2RGB(float h, float s, float l); +// function to convert from RGB color space to HSL color space +// the uint8_t RGB parameters are expected to be in the range 0-255 +// the float HSL parameters will be set to values in the range 0-1 +void NS_RGB2HSL(uint8_t aR, uint8_t aG, uint8_t aB, float* aH, float* aS, float* aL); + // Return a color name for the given nscolor. If there is no color // name for it, returns null. If there are multiple possible color // names for the given color, the first one in nsColorNameList.h From 01b0b8251d809687c8a65a02f49b7909cbf3c4b6 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Fri, 25 Jul 2025 10:41:30 +0800 Subject: [PATCH 16/30] Issue #2489: Allow color-mix() to differentiate color spaces --- layout/style/nsCSSParser.cpp | 15 +++++- layout/style/nsRuleNode.cpp | 91 ++++++++++++++++++++++++++---------- 2 files changed, 79 insertions(+), 27 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index cb8bac381e..f17474e7d6 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -7457,7 +7457,18 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) return CSSParseResult::Error; } - if (!GetToken(true) || mToken.mType != eCSSToken_Ident || !mToken.mIdent.LowerCaseEqualsLiteral("srgb")) { + // Check for supported color spaces: srgb or hsl + if (!GetToken(true) || mToken.mType != eCSSToken_Ident) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + mozilla::css::ColorMixColorSpace colorSpace; + if (mToken.mIdent.LowerCaseEqualsLiteral("srgb")) { + colorSpace = mozilla::css::ColorMixColorSpace::sRGB; + } else if (mToken.mIdent.LowerCaseEqualsLiteral("hsl")) { + colorSpace = mozilla::css::ColorMixColorSpace::HSL; + } else { SkipUntil(')'); return CSSParseResult::Error; } @@ -7528,7 +7539,7 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) } RefPtr colorMix = new mozilla::css::ColorMixValue( - mozilla::css::ColorMixColorSpace::sRGB, color1, color2, w1, w2); + colorSpace, color1, color2, w1, w2); aValue.SetColorMixValue(colorMix.forget()); return CSSParseResult::Ok; } diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index f3730979a7..4dc08e1763 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -1153,17 +1153,6 @@ static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, aResult = NS_RGBA(0, 0, 0, 0); result = true; } else { - // extracts RGBA components from both colors - float r1 = NS_GET_R(color1); - float g1 = NS_GET_G(color1); - float b1 = NS_GET_B(color1); - float a1 = NS_GET_A(color1); - - float r2 = NS_GET_R(color2); - float g2 = NS_GET_G(color2); - float b2 = NS_GET_B(color2); - float a2 = NS_GET_A(color2); - // normalize weights float sum = w1 + w2; if (sum <= 0.0f) { @@ -1175,20 +1164,72 @@ static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, float norm1 = w1 / sum; float norm2 = w2 / sum; - // perform linear interpolation - float r = r1 * norm1 + r2 * norm2; - float g = g1 * norm1 + g2 * norm2; - float b = b1 * norm1 + b2 * norm2; - float a = a1 * norm1 + a2 * norm2; - - // convert to integers with rounding - uint8_t rInt = (uint8_t)mozilla::clamped(r + 0.5f, 0.0f, 255.0f); - uint8_t gInt = (uint8_t)mozilla::clamped(g + 0.5f, 0.0f, 255.0f); - uint8_t bInt = (uint8_t)mozilla::clamped(b + 0.5f, 0.0f, 255.0f); - uint8_t aInt = (uint8_t)mozilla::clamped(a + 0.5f, 0.0f, 255.0f); - - aResult = NS_RGBA(rInt, gInt, bInt, aInt); - result = true; + if (colorMix->mColorSpace == mozilla::css::ColorMixColorSpace::HSL) { + // HSL color space mixing + float h1, s1, l1, h2, s2, l2; + + // Convert RGB colors to HSL + NS_RGB2HSL(NS_GET_R(color1), NS_GET_G(color1), NS_GET_B(color1), &h1, &s1, &l1); + NS_RGB2HSL(NS_GET_R(color2), NS_GET_G(color2), NS_GET_B(color2), &h2, &s2, &l2); + + // Handle hue interpolation (circular) + float h; + if (s1 == 0.0f || s2 == 0.0f) { + // If either color is achromatic, use the hue from the chromatic color + h = (s1 == 0.0f) ? h2 : h1; + } else { + float hue_diff = h2 - h1; + if (hue_diff > 0.5f) { + h1 += 1.0f; + } else if (hue_diff < -0.5f) { + h2 += 1.0f; + } + h = h1 * norm1 + h2 * norm2; + if (h >= 1.0f) h -= 1.0f; + } + + // Interpolate saturation and lightness linearly + float s = s1 * norm1 + s2 * norm2; + float l = l1 * norm1 + l2 * norm2; + + // Interpolate alpha in RGB space + float a1 = NS_GET_A(color1); + float a2 = NS_GET_A(color2); + float a = a1 * norm1 + a2 * norm2; + + // Convert back to RGB + nscolor hslResult = NS_HSL2RGB(h, s, l); + uint8_t aInt = (uint8_t)mozilla::clamped(a + 0.5f, 0.0f, 255.0f); + + aResult = NS_RGBA(NS_GET_R(hslResult), NS_GET_G(hslResult), NS_GET_B(hslResult), aInt); + result = true; + } else { + // sRGB color space mixing (existing logic) + float r1 = NS_GET_R(color1); + float g1 = NS_GET_G(color1); + float b1 = NS_GET_B(color1); + float a1 = NS_GET_A(color1); + + float r2 = NS_GET_R(color2); + float g2 = NS_GET_G(color2); + float b2 = NS_GET_B(color2); + float a2 = NS_GET_A(color2); + + // perform linear interpolation + float r = r1 * norm1 + r2 * norm2; + float g = g1 * norm1 + g2 * norm2; + float b = b1 * norm1 + b2 * norm2; + float a = a1 * norm1 + a2 * norm2; + + // convert to integers with rounding + uint8_t rInt = (uint8_t)mozilla::clamped(r + 0.5f, 0.0f, 255.0f); + uint8_t gInt = (uint8_t)mozilla::clamped(g + 0.5f, 0.0f, 255.0f); + uint8_t bInt = (uint8_t)mozilla::clamped(b + 0.5f, 0.0f, 255.0f); + uint8_t aInt = (uint8_t)mozilla::clamped(a + 0.5f, 0.0f, 255.0f); + + aResult = NS_RGBA(rInt, gInt, bInt, aInt); + result = true; + } } } } From 22be9f7fc6cf759019da8257ffbd2855b4578a1a Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 26 Jul 2025 01:00:17 +0800 Subject: [PATCH 17/30] Issue #2489: Allow HSL with alpha mixing --- layout/style/nsRuleNode.cpp | 59 +++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 22 deletions(-) diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 4dc08e1763..96ea71e683 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -1167,39 +1167,54 @@ static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, if (colorMix->mColorSpace == mozilla::css::ColorMixColorSpace::HSL) { // HSL color space mixing float h1, s1, l1, h2, s2, l2; + float a1 = NS_GET_A(color1) / 255.0f; + float a2 = NS_GET_A(color2) / 255.0f; // Convert RGB colors to HSL NS_RGB2HSL(NS_GET_R(color1), NS_GET_G(color1), NS_GET_B(color1), &h1, &s1, &l1); NS_RGB2HSL(NS_GET_R(color2), NS_GET_G(color2), NS_GET_B(color2), &h2, &s2, &l2); - // Handle hue interpolation (circular) - float h; - if (s1 == 0.0f || s2 == 0.0f) { - // If either color is achromatic, use the hue from the chromatic color - h = (s1 == 0.0f) ? h2 : h1; + // handle alpha premultiplication for HSL components + float alpha1_weight = norm1 * a1; + float alpha2_weight = norm2 * a2; + float total_alpha_weight = alpha1_weight + alpha2_weight; + + float h, s, l, a; + + if (total_alpha_weight <= 0.0f) { + // both colors are fully transparent + h = s = l = 0.0f; + a = 0.0f; } else { - float hue_diff = h2 - h1; - if (hue_diff > 0.5f) { - h1 += 1.0f; - } else if (hue_diff < -0.5f) { - h2 += 1.0f; + // normalize alpha-weighted contributions + float norm_alpha1 = alpha1_weight / total_alpha_weight; + float norm_alpha2 = alpha2_weight / total_alpha_weight; + + // handle hue interpolation (circular) + if (s1 == 0.0f || s2 == 0.0f) { + h = (s1 == 0.0f) ? h2 : h1; + } else { + float hue_diff = h2 - h1; + if (hue_diff > 0.5f) { + h1 += 1.0f; + } else if (hue_diff < -0.5f) { + h2 += 1.0f; + } + h = h1 * norm_alpha1 + h2 * norm_alpha2; + if (h >= 1.0f) h -= 1.0f; } - h = h1 * norm1 + h2 * norm2; - if (h >= 1.0f) h -= 1.0f; + + // interpolate saturation and lightness with alpha weighting + s = s1 * norm_alpha1 + s2 * norm_alpha2; + l = l1 * norm_alpha1 + l2 * norm_alpha2; + + // interpolate alpha normally (without premultiplication) + a = a1 * norm1 + a2 * norm2; } - // Interpolate saturation and lightness linearly - float s = s1 * norm1 + s2 * norm2; - float l = l1 * norm1 + l2 * norm2; - - // Interpolate alpha in RGB space - float a1 = NS_GET_A(color1); - float a2 = NS_GET_A(color2); - float a = a1 * norm1 + a2 * norm2; - // Convert back to RGB nscolor hslResult = NS_HSL2RGB(h, s, l); - uint8_t aInt = (uint8_t)mozilla::clamped(a + 0.5f, 0.0f, 255.0f); + uint8_t aInt = (uint8_t)mozilla::clamped(a * 255.0f + 0.5f, 0.0f, 255.0f); aResult = NS_RGBA(NS_GET_R(hslResult), NS_GET_G(hslResult), NS_GET_B(hslResult), aInt); result = true; From 5fe574de224cde848e33500ed45ae5e9f1488c46 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 26 Jul 2025 01:13:18 +0800 Subject: [PATCH 18/30] Issue #2489: Allow SRGB with alpha mixing --- layout/style/nsRuleNode.cpp | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 96ea71e683..25f19a2a1b 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -1219,28 +1219,46 @@ static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, aResult = NS_RGBA(NS_GET_R(hslResult), NS_GET_G(hslResult), NS_GET_B(hslResult), aInt); result = true; } else { - // sRGB color space mixing (existing logic) + // sRGB color space mixing with proper alpha premultiplication float r1 = NS_GET_R(color1); float g1 = NS_GET_G(color1); float b1 = NS_GET_B(color1); - float a1 = NS_GET_A(color1); + float a1 = NS_GET_A(color1) / 255.0f; float r2 = NS_GET_R(color2); float g2 = NS_GET_G(color2); float b2 = NS_GET_B(color2); - float a2 = NS_GET_A(color2); + float a2 = NS_GET_A(color2) / 255.0f; - // perform linear interpolation - float r = r1 * norm1 + r2 * norm2; - float g = g1 * norm1 + g2 * norm2; - float b = b1 * norm1 + b2 * norm2; - float a = a1 * norm1 + a2 * norm2; + // handle alpha premultiplication for RGB components + float alpha1_weight = norm1 * a1; + float alpha2_weight = norm2 * a2; + float total_alpha_weight = alpha1_weight + alpha2_weight; + + float r, g, b, a; + + if (total_alpha_weight <= 0.0f) { + // both colors are fully transparent + r = g = b = a = 0.0f; + } else { + // normalize alpha-weighted contributions + float norm_alpha1 = alpha1_weight / total_alpha_weight; + float norm_alpha2 = alpha2_weight / total_alpha_weight; + + // interpolate RGB components with alpha weighting + r = r1 * norm_alpha1 + r2 * norm_alpha2; + g = g1 * norm_alpha1 + g2 * norm_alpha2; + b = b1 * norm_alpha1 + b2 * norm_alpha2; + + // interpolate alpha normally (without premultiplication) + a = a1 * norm1 + a2 * norm2; + } // convert to integers with rounding uint8_t rInt = (uint8_t)mozilla::clamped(r + 0.5f, 0.0f, 255.0f); uint8_t gInt = (uint8_t)mozilla::clamped(g + 0.5f, 0.0f, 255.0f); uint8_t bInt = (uint8_t)mozilla::clamped(b + 0.5f, 0.0f, 255.0f); - uint8_t aInt = (uint8_t)mozilla::clamped(a + 0.5f, 0.0f, 255.0f); + uint8_t aInt = (uint8_t)mozilla::clamped(a * 255.0f + 0.5f, 0.0f, 255.0f); aResult = NS_RGBA(rInt, gInt, bInt, aInt); result = true; From 052b5d1ffafc7408a941d0a19ff1589bd16c5b93 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 26 Jul 2025 05:51:57 +0800 Subject: [PATCH 19/30] Issue #2489: Disallow percentage overflow and underflow --- layout/style/nsCSSParser.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index f17474e7d6..f885cb45e3 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -7491,9 +7491,11 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) if (mToken.mType == eCSSToken_Percentage) { w1 = mToken.mNumber; // percentage tokens are already normalized (0.0-1.0) w1_specified = true; - // clamp to valid range [0, 1] - if (w1 < 0.0f) w1 = 0.0f; - if (w1 > 1.0f) w1 = 1.0f; + // Reject invalid percentages (outside 0-100% range) + if (w1 < 0.0f || w1 > 1.0f) { + SkipUntil(')'); + return CSSParseResult::Error; + } } else { UngetToken(); } @@ -7517,9 +7519,11 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) if (mToken.mType == eCSSToken_Percentage) { w2 = mToken.mNumber; // percentage tokens are already normalized (0.0-1.0) w2_specified = true; - // Clamp to valid range [0, 1] - if (w2 < 0.0f) w2 = 0.0f; - if (w2 > 1.0f) w2 = 1.0f; + // Reject invalid percentages (outside 0-100% range) + if (w2 < 0.0f || w2 > 1.0f) { + SkipUntil(')'); + return CSSParseResult::Error; + } } else { UngetToken(); } From 2855cd6dedfd789af8603012e83f05424d6f4651 Mon Sep 17 00:00:00 2001 From: erixreyes Date: Sat, 26 Jul 2025 06:16:30 +0800 Subject: [PATCH 20/30] Issue #2489: Produce more accurate computations --- layout/style/nsRuleNode.cpp | 106 +++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 38 deletions(-) diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index 25f19a2a1b..780f62c1e9 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -1174,23 +1174,10 @@ static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, NS_RGB2HSL(NS_GET_R(color1), NS_GET_G(color1), NS_GET_B(color1), &h1, &s1, &l1); NS_RGB2HSL(NS_GET_R(color2), NS_GET_G(color2), NS_GET_B(color2), &h2, &s2, &l2); - // handle alpha premultiplication for HSL components - float alpha1_weight = norm1 * a1; - float alpha2_weight = norm2 * a2; - float total_alpha_weight = alpha1_weight + alpha2_weight; - float h, s, l, a; - if (total_alpha_weight <= 0.0f) { - // both colors are fully transparent - h = s = l = 0.0f; - a = 0.0f; - } else { - // normalize alpha-weighted contributions - float norm_alpha1 = alpha1_weight / total_alpha_weight; - float norm_alpha2 = alpha2_weight / total_alpha_weight; - - // handle hue interpolation (circular) + // check if both are opaque + if (a1 >= 1.0f && a2 >= 1.0f) { if (s1 == 0.0f || s2 == 0.0f) { h = (s1 == 0.0f) ? h2 : h1; } else { @@ -1200,16 +1187,50 @@ static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, } else if (hue_diff < -0.5f) { h2 += 1.0f; } - h = h1 * norm_alpha1 + h2 * norm_alpha2; + h = h1 * norm1 + h2 * norm2; if (h >= 1.0f) h -= 1.0f; } - // interpolate saturation and lightness with alpha weighting - s = s1 * norm_alpha1 + s2 * norm_alpha2; - l = l1 * norm_alpha1 + l2 * norm_alpha2; + // interpolate saturation and lightness normally + s = s1 * norm1 + s2 * norm2; + l = l1 * norm1 + l2 * norm2; + a = 1.0f; // Result is opaque + } else { + // handle alpha premultiplication for HSL components when transparency is involved + float alpha1_weight = norm1 * a1; + float alpha2_weight = norm2 * a2; + float total_alpha_weight = alpha1_weight + alpha2_weight; - // interpolate alpha normally (without premultiplication) - a = a1 * norm1 + a2 * norm2; + if (total_alpha_weight <= 0.0f) { + // both colors are fully transparent + h = s = l = 0.0f; + a = 0.0f; + } else { + // normalize alpha-weighted contributions + float norm_alpha1 = alpha1_weight / total_alpha_weight; + float norm_alpha2 = alpha2_weight / total_alpha_weight; + + // handle hue interpolation (circular) + if (s1 == 0.0f || s2 == 0.0f) { + h = (s1 == 0.0f) ? h2 : h1; + } else { + float hue_diff = h2 - h1; + if (hue_diff > 0.5f) { + h1 += 1.0f; + } else if (hue_diff < -0.5f) { + h2 += 1.0f; + } + h = h1 * norm_alpha1 + h2 * norm_alpha2; + if (h >= 1.0f) h -= 1.0f; + } + + // interpolate saturation and lightness with alpha weighting + s = s1 * norm_alpha1 + s2 * norm_alpha2; + l = l1 * norm_alpha1 + l2 * norm_alpha2; + + // interpolate alpha normally (without premultiplication) + a = a1 * norm1 + a2 * norm2; + } } // Convert back to RGB @@ -1230,28 +1251,37 @@ static bool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, float b2 = NS_GET_B(color2); float a2 = NS_GET_A(color2) / 255.0f; - // handle alpha premultiplication for RGB components - float alpha1_weight = norm1 * a1; - float alpha2_weight = norm2 * a2; - float total_alpha_weight = alpha1_weight + alpha2_weight; - float r, g, b, a; - if (total_alpha_weight <= 0.0f) { - // both colors are fully transparent - r = g = b = a = 0.0f; + // Check if both colors are opaque - use simple interpolation + if (a1 >= 1.0f && a2 >= 1.0f) { + // Simple linear interpolation for opaque colors + r = r1 * norm1 + r2 * norm2; + g = g1 * norm1 + g2 * norm2; + b = b1 * norm1 + b2 * norm2; + a = 1.0f; // Result is opaque } else { - // normalize alpha-weighted contributions - float norm_alpha1 = alpha1_weight / total_alpha_weight; - float norm_alpha2 = alpha2_weight / total_alpha_weight; + // handle alpha premultiplication for RGB components when transparency is involved + float alpha1_weight = norm1 * a1; + float alpha2_weight = norm2 * a2; + float total_alpha_weight = alpha1_weight + alpha2_weight; - // interpolate RGB components with alpha weighting - r = r1 * norm_alpha1 + r2 * norm_alpha2; - g = g1 * norm_alpha1 + g2 * norm_alpha2; - b = b1 * norm_alpha1 + b2 * norm_alpha2; + if (total_alpha_weight <= 0.0f) { + // both colors are fully transparent + r = g = b = a = 0.0f; + } else { + // normalize alpha-weighted contributions + float norm_alpha1 = alpha1_weight / total_alpha_weight; + float norm_alpha2 = alpha2_weight / total_alpha_weight; + + // interpolate RGB components with alpha weighting + r = r1 * norm_alpha1 + r2 * norm_alpha2; + g = g1 * norm_alpha1 + g2 * norm_alpha2; + b = b1 * norm_alpha1 + b2 * norm_alpha2; - // interpolate alpha normally (without premultiplication) - a = a1 * norm1 + a2 * norm2; + // interpolate alpha normally (without premultiplication) + a = a1 * norm1 + a2 * norm2; + } } // convert to integers with rounding From 13d1054046a4966ac4a64164bbabdbeccbb05453 Mon Sep 17 00:00:00 2001 From: MeladJM Date: Fri, 18 Jul 2025 06:28:23 +0800 Subject: [PATCH 21/30] Issue #2790 - Part 1: Add: event state, pseudo-class mapping, SetAutofilled methods --- dom/events/EventStates.h | 3 +++ dom/html/HTMLInputElement.cpp | 15 +++++++++++++++ dom/html/HTMLInputElement.h | 10 ++++++++++ dom/html/HTMLTextAreaElement.cpp | 15 +++++++++++++++ dom/html/HTMLTextAreaElement.h | 9 +++++++++ dom/webidl/HTMLInputElement.webidl | 7 +++++++ dom/webidl/HTMLTextAreaElement.webidl | 6 ++++++ layout/style/nsCSSPseudoClassList.h | 3 +++ .../formautofill/FormAutofillContentService.js | 5 +++++ 9 files changed, 73 insertions(+) diff --git a/dom/events/EventStates.h b/dom/events/EventStates.h index 291530d86b..38a3618ba7 100644 --- a/dom/events/EventStates.h +++ b/dom/events/EventStates.h @@ -293,6 +293,9 @@ private: // Modal element #define NS_EVENT_STATE_MODAL_DIALOG NS_DEFINE_EVENT_STATE_MACRO(54) +// Autofilled input element (for :autofill pseudo-class) +#define NS_EVENT_STATE_AUTOFILL NS_DEFINE_EVENT_STATE_MACRO(55) + #define DIR_ATTR_STATES (NS_EVENT_STATE_HAS_DIR_ATTR | \ NS_EVENT_STATE_DIR_ATTR_LTR | \ NS_EVENT_STATE_DIR_ATTR_RTL | \ diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 374fb14ef1..6a0979fce0 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2832,6 +2832,16 @@ HTMLInputElement::SetUserInput(const nsAString& aValue) return NS_OK; } +void +HTMLInputElement::SetAutofilled(bool aAutofilled) +{ + if (aAutofilled) { + AddStates(NS_EVENT_STATE_AUTOFILL); + } else { + RemoveStates(NS_EVENT_STATE_AUTOFILL); + } +} + nsIEditor* HTMLInputElement::GetEditor() { @@ -8507,6 +8517,11 @@ HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) { mLastValueChangeWasInteractive = aWasInteractiveUserChange; + // Clear autofilled state if this was an interactive user change + if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) { + RemoveStates(NS_EVENT_STATE_AUTOFILL); + } + UpdateAllValidityStates(aNotify); if (HasDirAuto()) { diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index e46be30ea7..36c4b06c0b 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -845,6 +845,16 @@ public: void SetUserInput(const nsAString& aInput, nsIPrincipal& aSubjectPrincipal); + /** + * Sets or clears the autofilled state of this input element. + * This is used by the browser's autofill system to indicate when + * a value has been automatically filled (e.g., from saved passwords + * or form data). + * + * @param aAutofilled Whether the element should be marked as autofilled + */ + void SetAutofilled(bool aAutofilled); + // XPCOM GetPhonetic() is OK /** diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp index 48b28d3fe4..e34e56975f 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -366,6 +366,16 @@ HTMLTextAreaElement::SetUserInput(const nsAString& aValue) return SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput); } +void +HTMLTextAreaElement::SetAutofilled(bool aAutofilled) +{ + if (aAutofilled) { + AddStates(NS_EVENT_STATE_AUTOFILL); + } else { + RemoveStates(NS_EVENT_STATE_AUTOFILL); + } +} + NS_IMETHODIMP HTMLTextAreaElement::SetValueChanged(bool aValueChanged) { @@ -1633,6 +1643,11 @@ HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange { mLastValueChangeWasInteractive = aWasInteractiveUserChange; + // Clear autofilled state if this was an interactive user change + if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) { + RemoveStates(NS_EVENT_STATE_AUTOFILL); + } + // Update the validity state bool validBefore = IsValid(); UpdateTooLongValidityState(); diff --git a/dom/html/HTMLTextAreaElement.h b/dom/html/HTMLTextAreaElement.h index cc9c2b7c5c..4efc264e0b 100644 --- a/dom/html/HTMLTextAreaElement.h +++ b/dom/html/HTMLTextAreaElement.h @@ -70,6 +70,15 @@ public: } NS_IMETHOD SetUserInput(const nsAString& aInput) override; + /** + * Sets or clears the autofilled state of this textarea element. + * This is used by the browser's autofill system to indicate when + * a value has been automatically filled (e.g., from saved form data). + * + * @param aAutofilled Whether the element should be marked as autofilled + */ + void SetAutofilled(bool aAutofilled); + // nsIFormControl NS_IMETHOD_(uint32_t) GetType() const override { return NS_FORM_TEXTAREA; } NS_IMETHOD Reset() override; diff --git a/dom/webidl/HTMLInputElement.webidl b/dom/webidl/HTMLInputElement.webidl index 1eefaea61e..5d0d7895b9 100644 --- a/dom/webidl/HTMLInputElement.webidl +++ b/dom/webidl/HTMLInputElement.webidl @@ -200,6 +200,13 @@ partial interface HTMLInputElement { // for example will be dispatched when focusing out the element. [Func="IsChromeOrXBL", NeedsSubjectPrincipal] void setUserInput(DOMString input); + + // Sets or clears the autofilled state of this input element. + // This is used by the browser's autofill system to indicate when + // a value has been automatically filled (e.g., from saved passwords + // or form data). + [ChromeOnly] + void setAutofilled(boolean autofilled); }; partial interface HTMLInputElement { diff --git a/dom/webidl/HTMLTextAreaElement.webidl b/dom/webidl/HTMLTextAreaElement.webidl index 7a181bf394..5eb31b1dfa 100644 --- a/dom/webidl/HTMLTextAreaElement.webidl +++ b/dom/webidl/HTMLTextAreaElement.webidl @@ -97,4 +97,10 @@ partial interface HTMLTextAreaElement { // element. [ChromeOnly] void setUserInput(DOMString input); + + // Sets or clears the autofilled state of this textarea element. + // This is used by the browser's autofill system to indicate when + // a value has been automatically filled (e.g., from saved form data). + [ChromeOnly] + void setAutofilled(boolean autofilled); }; diff --git a/layout/style/nsCSSPseudoClassList.h b/layout/style/nsCSSPseudoClassList.h index 485a1a428d..b763be1283 100644 --- a/layout/style/nsCSSPseudoClassList.h +++ b/layout/style/nsCSSPseudoClassList.h @@ -194,6 +194,9 @@ CSS_STATE_PSEUDO_CLASS(mozFullScreen, ":-moz-full-screen", 0, "", NS_EVENT_STATE CSS_STATE_PSEUDO_CLASS(modal, ":modal", 0, "", NS_EVENT_STATE_MODAL_DIALOG) CSS_STATE_PSEUDO_CLASS(mozModalDialog, ":-moz-modal-dialog", 0, "", NS_EVENT_STATE_MODAL_DIALOG) +// Matches autofilled input elements +CSS_STATE_PSEUDO_CLASS(autofill, ":autofill", 0, "", NS_EVENT_STATE_AUTOFILL) + // Matches if the element is focused and should show a focus ring CSS_STATE_PSEUDO_CLASS(mozFocusRing, ":-moz-focusring", 0, "", NS_EVENT_STATE_FOCUSRING) diff --git a/toolkit/components/formautofill/FormAutofillContentService.js b/toolkit/components/formautofill/FormAutofillContentService.js index ee8e978ad9..273697f221 100644 --- a/toolkit/components/formautofill/FormAutofillContentService.js +++ b/toolkit/components/formautofill/FormAutofillContentService.js @@ -238,6 +238,11 @@ FormHandler.prototype = { } fieldDetail.element.value = field.value; + + // Set the autofilled state on the element + if (typeof fieldDetail.element.setAutofilled === 'function') { + fieldDetail.element.setAutofilled(true); + } } }, From d99eab0d9db7673c0ca6473020d49d15ee146c89 Mon Sep 17 00:00:00 2001 From: MeladJM Date: Tue, 22 Jul 2025 03:21:45 +0800 Subject: [PATCH 22/30] Issue #2790 - Part 2: Address BZ bugs: 1355438 and 1341230 --- dom/base/nsDOMWindowUtils.cpp | 24 +++++++++++++++++++ dom/base/nsDOMWindowUtils.h | 2 ++ dom/html/HTMLInputElement.cpp | 8 +++++++ dom/html/HTMLTextAreaElement.cpp | 8 +++++++ dom/interfaces/base/nsIDOMWindowUtils.idl | 14 +++++++++++ layout/style/nsCSSPseudoClassList.h | 3 +++ layout/style/res/forms.css | 6 +++++ .../FormAutofillContentService.js | 20 ++++++++++++++++ 8 files changed, 85 insertions(+) diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 37e9018fa0..27791100dc 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -4058,3 +4058,27 @@ nsTranslationNodeList::GetLength(uint32_t* aRetVal) *aRetVal = mLength; return NS_OK; } + +NS_IMETHODIMP +nsDOMWindowUtils::AddElementEventState(nsIDOMElement* aElement, uint64_t aState) +{ + NS_ENSURE_ARG_POINTER(aElement); + nsCOMPtr content = do_QueryInterface(aElement); + if (!content) { + return NS_ERROR_INVALID_ARG; + } + content->AddStates(mozilla::EventStates(aState)); + return NS_OK; +} + +NS_IMETHODIMP +nsDOMWindowUtils::RemoveElementEventState(nsIDOMElement* aElement, uint64_t aState) +{ + NS_ENSURE_ARG_POINTER(aElement); + nsCOMPtr content = do_QueryInterface(aElement); + if (!content) { + return NS_ERROR_INVALID_ARG; + } + content->RemoveStates(mozilla::EventStates(aState)); + return NS_OK; +} diff --git a/dom/base/nsDOMWindowUtils.h b/dom/base/nsDOMWindowUtils.h index a398646a91..ef827be41c 100644 --- a/dom/base/nsDOMWindowUtils.h +++ b/dom/base/nsDOMWindowUtils.h @@ -63,6 +63,8 @@ public: explicit nsDOMWindowUtils(nsGlobalWindow *aWindow); NS_DECL_ISUPPORTS NS_DECL_NSIDOMWINDOWUTILS + NS_IMETHOD AddElementEventState(nsIDOMElement* aElement, uint64_t aState) override; + NS_IMETHOD RemoveElementEventState(nsIDOMElement* aElement, uint64_t aState) override; protected: ~nsDOMWindowUtils(); diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 6a0979fce0..c3287ba387 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2835,10 +2835,16 @@ HTMLInputElement::SetUserInput(const nsAString& aValue) void HTMLInputElement::SetAutofilled(bool aAutofilled) { + printf("🔍 AUTOFILL C++: SetAutofilled called with aAutofilled=%s\n", aAutofilled ? "true" : "false"); + if (aAutofilled) { + printf("🔍 AUTOFILL C++: Adding NS_EVENT_STATE_AUTOFILL state\n"); AddStates(NS_EVENT_STATE_AUTOFILL); + printf("🔍 AUTOFILL C++: State added successfully\n"); } else { + printf("🔍 AUTOFILL C++: Removing NS_EVENT_STATE_AUTOFILL state\n"); RemoveStates(NS_EVENT_STATE_AUTOFILL); + printf("🔍 AUTOFILL C++: State removed successfully\n"); } } @@ -8519,7 +8525,9 @@ HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) // Clear autofilled state if this was an interactive user change if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) { + printf("🔍 AUTOFILL C++: User changed autofilled input, clearing state\n"); RemoveStates(NS_EVENT_STATE_AUTOFILL); + printf("🔍 AUTOFILL C++: Autofill state cleared from input\n"); } UpdateAllValidityStates(aNotify); diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp index e34e56975f..8876a0cb44 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -369,10 +369,16 @@ HTMLTextAreaElement::SetUserInput(const nsAString& aValue) void HTMLTextAreaElement::SetAutofilled(bool aAutofilled) { + printf("🔍 AUTOFILL C++: HTMLTextAreaElement::SetAutofilled called with aAutofilled=%s\n", aAutofilled ? "true" : "false"); + if (aAutofilled) { + printf("🔍 AUTOFILL C++: Adding NS_EVENT_STATE_AUTOFILL state to textarea\n"); AddStates(NS_EVENT_STATE_AUTOFILL); + printf("🔍 AUTOFILL C++: State added successfully to textarea\n"); } else { + printf("🔍 AUTOFILL C++: Removing NS_EVENT_STATE_AUTOFILL state from textarea\n"); RemoveStates(NS_EVENT_STATE_AUTOFILL); + printf("🔍 AUTOFILL C++: State removed successfully from textarea\n"); } } @@ -1645,7 +1651,9 @@ HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange // Clear autofilled state if this was an interactive user change if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) { + printf("🔍 AUTOFILL C++: User changed autofilled textarea, clearing state\n"); RemoveStates(NS_EVENT_STATE_AUTOFILL); + printf("🔍 AUTOFILL C++: Autofill state cleared from textarea\n"); } // Update the validity state diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index 1289bd940d..cd695cf186 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -1944,6 +1944,20 @@ interface nsIDOMWindowUtils : nsISupports { const long MOUSE_BUTTONS_5TH_BUTTON = 0x10; // Buttons are not specified, will be calculated from |aButton|. const long MOUSE_BUTTONS_NOT_SPECIFIED = -1; + + /** + * Add an EventState bit to an element (privileged only). + * @param element The element to modify. + * @param state The EventState bit (see EventStates.h, e.g. NS_EVENT_STATE_AUTOFILL). + */ + void addElementEventState(in nsIDOMElement element, in unsigned long long state); + + /** + * Remove an EventState bit from an element (privileged only). + * @param element The element to modify. + * @param state The EventState bit. + */ + void removeElementEventState(in nsIDOMElement element, in unsigned long long state); }; [scriptable, uuid(c694e359-7227-4392-a138-33c0cc1f15a6)] diff --git a/layout/style/nsCSSPseudoClassList.h b/layout/style/nsCSSPseudoClassList.h index b763be1283..196c5d9f26 100644 --- a/layout/style/nsCSSPseudoClassList.h +++ b/layout/style/nsCSSPseudoClassList.h @@ -277,6 +277,9 @@ CSS_STATE_PSEUDO_CLASS(mozMeterSubSubOptimum, ":-moz-meter-sub-sub-optimum", 0, // Those values should be parsed but do nothing. CSS_STATE_PSEUDO_CLASS(mozPlaceholder, ":-moz-placeholder", 0, "", NS_EVENT_STATE_IGNORE) +// Internal-only pseudo-class for autofill highlight +CSS_STATE_PSEUDO_CLASS(mozAutofillHighlight, ":-moz-autofill-highlight", CSS_PSEUDO_CLASS_ENABLED_IN_UA_SHEETS, "", NS_EVENT_STATE_AUTOFILL) + #ifdef DEFINED_CSS_STATE_PSEUDO_CLASS #undef DEFINED_CSS_STATE_PSEUDO_CLASS #undef CSS_STATE_PSEUDO_CLASS diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css index e94d347687..8a09796f25 100644 --- a/layout/style/res/forms.css +++ b/layout/style/res/forms.css @@ -1149,3 +1149,9 @@ input[type="date"], input[type="time"] { overflow: hidden !important; } + +/* Autofill highlight for internal-only pseudo-class */ +input:-moz-autofill-highlight, +textarea:-moz-autofill-highlight { + background-color: #ffff99 !important; +} diff --git a/toolkit/components/formautofill/FormAutofillContentService.js b/toolkit/components/formautofill/FormAutofillContentService.js index 273697f221..d5afd8062f 100644 --- a/toolkit/components/formautofill/FormAutofillContentService.js +++ b/toolkit/components/formautofill/FormAutofillContentService.js @@ -11,6 +11,8 @@ "use strict"; +console.log('🔍 AUTOFILL: FormAutofillContentService.js loaded'); + const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; Cu.import("resource://gre/modules/Services.jsm"); @@ -130,7 +132,9 @@ FormHandler.prototype = { return "cancel"; } + console.log('🔍 AUTOFILL: About to call autofillFormFields with result:', result); this.autofillFormFields(result); + console.log('🔍 AUTOFILL: autofillFormFields completed'); return "success"; }), @@ -226,22 +230,38 @@ FormHandler.prototype = { * } */ autofillFormFields: function (aAutofillResult) { + console.log('🔍 AUTOFILL: autofillFormFields called with', aAutofillResult); + for (let field of aAutofillResult.fields) { + console.log('🔍 AUTOFILL: Processing field', field); + // Get the field details, if it was processed by the user interface. let fieldDetail = this.fieldDetails .find(f => f.section == field.section && f.addressType == field.addressType && f.contactType == field.contactType && f.fieldName == field.fieldName); + + console.log('🔍 AUTOFILL: Found fieldDetail?', !!fieldDetail, fieldDetail); + if (!fieldDetail) { + console.log('🔍 AUTOFILL: No fieldDetail found, skipping'); continue; } + console.log('🔍 AUTOFILL: Setting value on element', fieldDetail.element); fieldDetail.element.value = field.value; // Set the autofilled state on the element + console.log('🔍 AUTOFILL: Checking if setAutofilled exists on element'); + console.log('🔍 AUTOFILL: setAutofilled type:', typeof fieldDetail.element.setAutofilled); + if (typeof fieldDetail.element.setAutofilled === 'function') { + console.log('🔍 AUTOFILL: Calling setAutofilled(true) on element'); fieldDetail.element.setAutofilled(true); + console.log('🔍 AUTOFILL: setAutofilled(true) called successfully'); + } else { + console.log('🔍 AUTOFILL: setAutofilled is not a function on this element'); } } }, From 4cdfb9e16b0ae92dadb584014fabbea3a997bfa3 Mon Sep 17 00:00:00 2001 From: MeladJM Date: Tue, 22 Jul 2025 23:14:40 +0800 Subject: [PATCH 23/30] Issue #2790 - Part 3: Address BZ bug 1849122 and resolve build issues --- dom/base/Element.h | 10 ++++++++++ dom/base/nsDOMWindowUtils.cpp | 12 ++++++++++-- dom/base/nsDOMWindowUtils.h | 2 -- .../formautofill/FormAutofillContentService.js | 13 +++++++++++++ 4 files changed, 33 insertions(+), 4 deletions(-) diff --git a/dom/base/Element.h b/dom/base/Element.h index 611fde38f5..14c53ae465 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -1603,6 +1603,16 @@ private: // Data members EventStates mState; + +public: + // Public helper to add or remove event states + void SetEventState(mozilla::EventStates aState, bool aAdd) { + if (aAdd) { + this->AddStates(aState); + } else { + this->RemoveStates(aState); + } + } }; class RemoveFromBindingManagerRunnable : public mozilla::Runnable diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index 27791100dc..be9c16d0ec 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -4067,7 +4067,11 @@ nsDOMWindowUtils::AddElementEventState(nsIDOMElement* aElement, uint64_t aState) if (!content) { return NS_ERROR_INVALID_ARG; } - content->AddStates(mozilla::EventStates(aState)); + mozilla::dom::Element* element = static_cast(content.get()); + if (!element) { + return NS_ERROR_INVALID_ARG; + } + element->SetEventState(mozilla::EventStates(aState), true); return NS_OK; } @@ -4079,6 +4083,10 @@ nsDOMWindowUtils::RemoveElementEventState(nsIDOMElement* aElement, uint64_t aSta if (!content) { return NS_ERROR_INVALID_ARG; } - content->RemoveStates(mozilla::EventStates(aState)); + mozilla::dom::Element* element = static_cast(content.get()); + if (!element) { + return NS_ERROR_INVALID_ARG; + } + element->SetEventState(mozilla::EventStates(aState), false); return NS_OK; } diff --git a/dom/base/nsDOMWindowUtils.h b/dom/base/nsDOMWindowUtils.h index ef827be41c..a398646a91 100644 --- a/dom/base/nsDOMWindowUtils.h +++ b/dom/base/nsDOMWindowUtils.h @@ -63,8 +63,6 @@ public: explicit nsDOMWindowUtils(nsGlobalWindow *aWindow); NS_DECL_ISUPPORTS NS_DECL_NSIDOMWINDOWUTILS - NS_IMETHOD AddElementEventState(nsIDOMElement* aElement, uint64_t aState) override; - NS_IMETHOD RemoveElementEventState(nsIDOMElement* aElement, uint64_t aState) override; protected: ~nsDOMWindowUtils(); diff --git a/toolkit/components/formautofill/FormAutofillContentService.js b/toolkit/components/formautofill/FormAutofillContentService.js index d5afd8062f..2406be1ff2 100644 --- a/toolkit/components/formautofill/FormAutofillContentService.js +++ b/toolkit/components/formautofill/FormAutofillContentService.js @@ -31,6 +31,19 @@ function FormHandler(aForm, aWindow) { this.window = aWindow; this.fieldDetails = []; + + // Add a reset event listener to clear autofill state + this.form.addEventListener("reset", () => { + console.log('Form reset detected, clearing autofill state'); + for (let element of this.form.elements) { + if (typeof element.setAutofilled === "function") { + element.setAutofilled(false); + console.log('setAutofilled(false) called on', element); + } + } + // Optionally, clear fieldDetails if you want to force re-collection + // this.fieldDetails = []; + }); } FormHandler.prototype = { From bf8cfcc98099e6de976e856c311780f76f9a3c82 Mon Sep 17 00:00:00 2001 From: MeladJM Date: Fri, 25 Jul 2025 11:12:29 +0800 Subject: [PATCH 24/30] Issue #2790 - Part 4: Working non persistent autofill highlight --- dom/html/HTMLInputElement.cpp | 49 ++++++-- dom/html/HTMLInputElement.h | 5 +- dom/html/HTMLTextAreaElement.cpp | 111 +++++++++++++----- dom/html/HTMLTextAreaElement.h | 5 + dom/html/nsTextEditorState.h | 2 + .../core/nsIDOMNSEditableElement.idl | 6 + layout/style/RuleCascadeData.cpp | 8 ++ layout/style/nsCSSRuleUtils.cpp | 10 ++ layout/style/res/forms.css | 5 + .../FormAutofillContentService.js | 48 ++++---- .../passwordmgr/LoginManagerContent.jsm | 10 +- .../satchel/nsFormFillController.cpp | 16 ++- 12 files changed, 207 insertions(+), 68 deletions(-) diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index c3287ba387..eb2fdba516 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2835,16 +2835,11 @@ HTMLInputElement::SetUserInput(const nsAString& aValue) void HTMLInputElement::SetAutofilled(bool aAutofilled) { - printf("🔍 AUTOFILL C++: SetAutofilled called with aAutofilled=%s\n", aAutofilled ? "true" : "false"); - + if (aAutofilled) { - printf("🔍 AUTOFILL C++: Adding NS_EVENT_STATE_AUTOFILL state\n"); AddStates(NS_EVENT_STATE_AUTOFILL); - printf("🔍 AUTOFILL C++: State added successfully\n"); } else { - printf("🔍 AUTOFILL C++: Removing NS_EVENT_STATE_AUTOFILL state\n"); RemoveStates(NS_EVENT_STATE_AUTOFILL); - printf("🔍 AUTOFILL C++: State removed successfully\n"); } } @@ -3578,7 +3573,18 @@ HTMLInputElement::Blur(ErrorResult& aError) } nsGenericHTMLElement::Blur(aError); -} + + if (State().HasState(NS_EVENT_STATE_AUTOFILL)) { + // Force a complete restyle to ensure autofill pseudo-classes are processed + if (nsIDocument* doc = GetComposedDoc()) { + if (nsIPresShell* shell = doc->GetShell()) { + if (nsIFrame* frame = GetPrimaryFrame()) { + shell->FrameNeedsReflow(frame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); + } + } + } + } +} void HTMLInputElement::Focus(ErrorResult& aError) @@ -8521,13 +8527,16 @@ HTMLInputElement::InitializeKeyboardEventListeners() NS_IMETHODIMP_(void) HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) { + nsAutoString value; + GetValueInternal(value); mLastValueChangeWasInteractive = aWasInteractiveUserChange; - // Clear autofilled state if this was an interactive user change + // Only remove autofilled state if the value actually changed from autofilled value if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) { - printf("🔍 AUTOFILL C++: User changed autofilled input, clearing state\n"); - RemoveStates(NS_EVENT_STATE_AUTOFILL); - printf("🔍 AUTOFILL C++: Autofill state cleared from input\n"); + if (mAutofilledValue != value) { + RemoveStates(NS_EVENT_STATE_AUTOFILL); + mAutofilledValue.Truncate(); + } } UpdateAllValidityStates(aNotify); @@ -8560,6 +8569,24 @@ HTMLInputElement::HasCachedSelection() return isCached; } +NS_IMETHODIMP +HTMLInputElement::BeginProgrammaticValueSet() { + nsTextEditorState* state = GetEditorState(); + if (state) { + state->SettingValue(true); + } + return NS_OK; +} + +NS_IMETHODIMP +HTMLInputElement::EndProgrammaticValueSet() { + nsTextEditorState* state = GetEditorState(); + if (state) { + state->SettingValue(false); + } + return NS_OK; +} + void HTMLInputElement::FieldSetDisabledChanged(bool aNotify) { diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index 36c4b06c0b..1ee639ca5a 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -160,6 +160,8 @@ public: } NS_IMETHOD SetUserInput(const nsAString& aInput) override; + NS_IMETHOD BeginProgrammaticValueSet() override; + NS_IMETHOD EndProgrammaticValueSet() override; // Overriden nsIFormControl methods NS_IMETHOD_(uint32_t) GetType() const override { return mType; } @@ -1113,7 +1115,7 @@ protected: bool MinOrMaxLengthApplies() const { return IsSingleLineTextControl(false, mType); } void FreeData(); - nsTextEditorState *GetEditorState() const; + nsTextEditorState* GetEditorState() const; /** * Manages the internal data storage across type changes. @@ -1639,6 +1641,7 @@ protected: bool mNumberControlSpinnerSpinsUp : 1; bool mPickerRunning : 1; bool mSelectionCached : 1; + nsString mAutofilledValue; private: static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes, diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp index 8876a0cb44..fca321aec6 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -369,16 +369,46 @@ HTMLTextAreaElement::SetUserInput(const nsAString& aValue) void HTMLTextAreaElement::SetAutofilled(bool aAutofilled) { - printf("🔍 AUTOFILL C++: HTMLTextAreaElement::SetAutofilled called with aAutofilled=%s\n", aAutofilled ? "true" : "false"); - if (aAutofilled) { - printf("🔍 AUTOFILL C++: Adding NS_EVENT_STATE_AUTOFILL state to textarea\n"); AddStates(NS_EVENT_STATE_AUTOFILL); - printf("🔍 AUTOFILL C++: State added successfully to textarea\n"); + GetValueInternal(mAutofilledValue, true); // Store the autofilled value } else { - printf("🔍 AUTOFILL C++: Removing NS_EVENT_STATE_AUTOFILL state from textarea\n"); RemoveStates(NS_EVENT_STATE_AUTOFILL); - printf("🔍 AUTOFILL C++: State removed successfully from textarea\n"); + mAutofilledValue.Truncate(); + } +} + +NS_IMETHODIMP_(void) +HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) +{ + nsAutoString value; + GetValueInternal(value, true); + // printf("[TextArea] OnValueChanged: aWasInteractiveUserChange=%d, value='%s', autofilled='%s', autofill state=%d\n", + // aWasInteractiveUserChange, + // NS_ConvertUTF16toUTF8(value).get(), + // NS_ConvertUTF16toUTF8(mAutofilledValue).get(), + // State().HasState(NS_EVENT_STATE_AUTOFILL)); + + // Only remove autofilled state if the value actually changed from autofilled value + if (State().HasState(NS_EVENT_STATE_AUTOFILL) || !mAutofilledValue.IsEmpty()) { + if (aWasInteractiveUserChange && mAutofilledValue != value) { + RemoveStates(NS_EVENT_STATE_AUTOFILL); + mAutofilledValue.Truncate(); + } else if (aWasInteractiveUserChange && mAutofilledValue == value) { + // Defensive: re-add the autofill state if it was removed by something else + AddStates(NS_EVENT_STATE_AUTOFILL); + } + } + + // Update the validity state + bool validBefore = IsValid(); + UpdateTooLongValidityState(); + UpdateTooShortValidityState(); + UpdateValueMissingValidityState(); + + if (validBefore != IsValid() || + HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) { + UpdateState(aNotify); } } @@ -572,6 +602,19 @@ HTMLTextAreaElement::FireChangeEventIfNeeded() false); } +void +HTMLTextAreaElement::EnsureAutofillState() +{ + nsAutoString value; + GetValueInternal(value, true); + if (!mAutofilledValue.IsEmpty() && mAutofilledValue == value) { + if (!State().HasState(NS_EVENT_STATE_AUTOFILL)) { + AddStates(NS_EVENT_STATE_AUTOFILL); + UpdateState(true); // Force style system to re-evaluate + } + } +} + nsresult HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor) { @@ -596,6 +639,9 @@ HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor) } UpdateState(true); + + // Defensive: re-apply autofill state if value is still autofilled value + EnsureAutofillState(); } return NS_OK; @@ -1202,6 +1248,11 @@ HTMLTextAreaElement::IntrinsicState() const { EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState(); + // PATCH: Persist autofill state if autofilled + if (!mAutofilledValue.IsEmpty()) { + state |= NS_EVENT_STATE_AUTOFILL; + } + if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) { state |= NS_EVENT_STATE_REQUIRED; } else { @@ -1244,6 +1295,8 @@ HTMLTextAreaElement::IntrinsicState() const state |= NS_EVENT_STATE_PLACEHOLDERSHOWN; } + state |= NS_EVENT_STATE_AUTOFILL; + return state; } @@ -1644,30 +1697,6 @@ HTMLTextAreaElement::InitializeKeyboardEventListeners() mState.InitializeKeyboardEventListeners(); } -NS_IMETHODIMP_(void) -HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) -{ - mLastValueChangeWasInteractive = aWasInteractiveUserChange; - - // Clear autofilled state if this was an interactive user change - if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) { - printf("🔍 AUTOFILL C++: User changed autofilled textarea, clearing state\n"); - RemoveStates(NS_EVENT_STATE_AUTOFILL); - printf("🔍 AUTOFILL C++: Autofill state cleared from textarea\n"); - } - - // Update the validity state - bool validBefore = IsValid(); - UpdateTooLongValidityState(); - UpdateTooShortValidityState(); - UpdateValueMissingValidityState(); - - if (validBefore != IsValid() || - HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)) { - UpdateState(aNotify); - } -} - NS_IMETHODIMP_(bool) HTMLTextAreaElement::HasCachedSelection() { @@ -1683,11 +1712,33 @@ HTMLTextAreaElement::FieldSetDisabledChanged(bool aNotify) nsGenericHTMLFormElementWithState::FieldSetDisabledChanged(aNotify); } +NS_IMETHODIMP +HTMLTextAreaElement::BeginProgrammaticValueSet() { + nsTextEditorState* state = GetEditorState(); + if (state) { + state->SettingValue(true); + } + return NS_OK; +} + +NS_IMETHODIMP +HTMLTextAreaElement::EndProgrammaticValueSet() { + nsTextEditorState* state = GetEditorState(); + if (state) { + state->SettingValue(false); + } + return NS_OK; +} + JSObject* HTMLTextAreaElement::WrapNode(JSContext* aCx, JS::Handle aGivenProto) { return HTMLTextAreaElementBinding::Wrap(aCx, this, aGivenProto); } +nsTextEditorState* HTMLTextAreaElement::GetEditorState() const { + return const_cast(&mState); +} + } // namespace dom } // namespace mozilla diff --git a/dom/html/HTMLTextAreaElement.h b/dom/html/HTMLTextAreaElement.h index 4efc264e0b..8cc553e0d1 100644 --- a/dom/html/HTMLTextAreaElement.h +++ b/dom/html/HTMLTextAreaElement.h @@ -69,6 +69,8 @@ public: return nsGenericHTMLElement::GetEditor(aEditor); } NS_IMETHOD SetUserInput(const nsAString& aInput) override; + NS_IMETHOD BeginProgrammaticValueSet() override; + NS_IMETHOD EndProgrammaticValueSet() override; /** * Sets or clears the autofilled state of this textarea element. @@ -302,6 +304,7 @@ public: { return mState.GetEditor(); } + nsTextEditorState* GetEditorState() const; protected: virtual ~HTMLTextAreaElement() {} @@ -333,6 +336,7 @@ protected: void FireChangeEventIfNeeded(); nsString mFocusedValue; + nsString mAutofilledValue; /** The state of the text editor (selection controller and the editor) **/ nsTextEditorState mState; @@ -406,6 +410,7 @@ protected: private: static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes, nsRuleData* aData); + void EnsureAutofillState(); }; } // namespace dom diff --git a/dom/html/nsTextEditorState.h b/dom/html/nsTextEditorState.h index 5abc88d44e..66e5d7e593 100644 --- a/dom/html/nsTextEditorState.h +++ b/dom/html/nsTextEditorState.h @@ -163,6 +163,7 @@ public: // or reconsider fixing bug 597525 to remove these. void EmptyValue() { if (mValue) mValue->Truncate(); } bool IsEmpty() const { return mValue ? mValue->IsEmpty() : true; } + void SettingValue(bool aValue) { mSettingValue = aValue; } nsresult CreatePlaceholderNode(); @@ -348,6 +349,7 @@ private: mutable bool mSelectionRestoreEagerInit; // Whether we're eager initing because of selection restore bool mPlaceholderVisibility; bool mIsCommittingComposition; + bool mSettingValue; }; inline void diff --git a/dom/interfaces/core/nsIDOMNSEditableElement.idl b/dom/interfaces/core/nsIDOMNSEditableElement.idl index 67cb10488b..bfb0d84dd8 100644 --- a/dom/interfaces/core/nsIDOMNSEditableElement.idl +++ b/dom/interfaces/core/nsIDOMNSEditableElement.idl @@ -25,4 +25,10 @@ interface nsIDOMNSEditableElement : nsISupports // 'change' event for example will be dispatched when focusing out the // element. [noscript] void setUserInput(in DOMString input); + /** + * Call this before and after programmatically setting the value to prevent + * OnValueChanged from treating it as a user edit. + */ + void beginProgrammaticValueSet(); + void endProgrammaticValueSet(); }; diff --git a/layout/style/RuleCascadeData.cpp b/layout/style/RuleCascadeData.cpp index 7165a3967d..349f2ca6ff 100644 --- a/layout/style/RuleCascadeData.cpp +++ b/layout/style/RuleCascadeData.cpp @@ -1270,6 +1270,14 @@ ComputeSelectorStateDependence(nsCSSSelector& aSelector) continue; } + // --- BEGIN PATCH: Explicit autofill state dependence --- + if (pseudoClass->mType == CSSPseudoClassType::autofill || + pseudoClass->mType == CSSPseudoClassType::mozAutofillHighlight) { + states |= NS_EVENT_STATE_AUTOFILL; + continue; + } + // --- END PATCH --- + auto idx = static_cast(pseudoClass->mType); states |= nsCSSPseudoClasses::sPseudoClassStateDependences[idx]; } diff --git a/layout/style/nsCSSRuleUtils.cpp b/layout/style/nsCSSRuleUtils.cpp index 9717e42cb3..bbba88fbea 100644 --- a/layout/style/nsCSSRuleUtils.cpp +++ b/layout/style/nsCSSRuleUtils.cpp @@ -576,6 +576,16 @@ nsCSSRuleUtils::StateSelectorMatches(Element* aElement, for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; pseudoClass; pseudoClass = pseudoClass->mNext) { + // --- Autofill explicit matching --- + if (pseudoClass->mType == CSSPseudoClassType::autofill || + pseudoClass->mType == CSSPseudoClassType::mozAutofillHighlight) { + // Match if the element has the autofill state, regardless of focus + if (!aElement->State().HasState(NS_EVENT_STATE_AUTOFILL)) { + return false; + } + continue; // This pseudo-class matches + } + // --- End autofill explicit matching --- auto idx = static_cast(pseudoClass->mType); EventStates statesToCheck = nsCSSPseudoClasses::sPseudoClassStates[idx]; if (!statesToCheck.IsEmpty() && !StateSelectorMatches(aElement, diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css index 8a09796f25..bc07a8ff4a 100644 --- a/layout/style/res/forms.css +++ b/layout/style/res/forms.css @@ -1152,6 +1152,11 @@ input[type="time"] { /* Autofill highlight for internal-only pseudo-class */ input:-moz-autofill-highlight, +select:-moz-autofill-highlight, textarea:-moz-autofill-highlight { background-color: #ffff99 !important; } + +.custom-autofill-highlight { + background: yellow !important; +} diff --git a/toolkit/components/formautofill/FormAutofillContentService.js b/toolkit/components/formautofill/FormAutofillContentService.js index 2406be1ff2..4f96b361e3 100644 --- a/toolkit/components/formautofill/FormAutofillContentService.js +++ b/toolkit/components/formautofill/FormAutofillContentService.js @@ -9,9 +9,7 @@ * See the nsIFormAutofillContentService documentation for details. */ -"use strict"; - -console.log('🔍 AUTOFILL: FormAutofillContentService.js loaded'); +"use strict" const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; @@ -34,15 +32,11 @@ function FormHandler(aForm, aWindow) { // Add a reset event listener to clear autofill state this.form.addEventListener("reset", () => { - console.log('Form reset detected, clearing autofill state'); for (let element of this.form.elements) { if (typeof element.setAutofilled === "function") { element.setAutofilled(false); - console.log('setAutofilled(false) called on', element); } } - // Optionally, clear fieldDetails if you want to force re-collection - // this.fieldDetails = []; }); } @@ -145,9 +139,7 @@ FormHandler.prototype = { return "cancel"; } - console.log('🔍 AUTOFILL: About to call autofillFormFields with result:', result); this.autofillFormFields(result); - console.log('🔍 AUTOFILL: autofillFormFields completed'); return "success"; }), @@ -243,10 +235,8 @@ FormHandler.prototype = { * } */ autofillFormFields: function (aAutofillResult) { - console.log('🔍 AUTOFILL: autofillFormFields called with', aAutofillResult); for (let field of aAutofillResult.fields) { - console.log('🔍 AUTOFILL: Processing field', field); // Get the field details, if it was processed by the user interface. let fieldDetail = this.fieldDetails @@ -255,26 +245,36 @@ FormHandler.prototype = { f.contactType == field.contactType && f.fieldName == field.fieldName); - console.log('🔍 AUTOFILL: Found fieldDetail?', !!fieldDetail, fieldDetail); - if (!fieldDetail) { - console.log('🔍 AUTOFILL: No fieldDetail found, skipping'); continue; } - - console.log('🔍 AUTOFILL: Setting value on element', fieldDetail.element); fieldDetail.element.value = field.value; - // Set the autofilled state on the element - console.log('🔍 AUTOFILL: Checking if setAutofilled exists on element'); - console.log('🔍 AUTOFILL: setAutofilled type:', typeof fieldDetail.element.setAutofilled); + // Add event listeners for debugging + // try { + // fieldDetail.element.addEventListener('focus', () => console.log('[JS] input focused')); + // fieldDetail.element.addEventListener('blur', () => console.log('[JS] input blurred')); + // fieldDetail.element.addEventListener('input', () => console.log('[JS] input event, value:', fieldDetail.element.value)); + // } catch (e) { + // console.log('[JS] Could not add event listeners:', e); + // } + // if (typeof fieldDetail.element.setAutofilled === 'function') { + // if (field.value) { + // console.log('AUTOFILL: Calling setAutofilled(true) on element'); + // fieldDetail.element.setAutofilled(true); + // console.log('AUTOFILL: setAutofilled(true) called successfully'); + // } else { + // console.log('AUTOFILL: Calling setAutofilled(false) on element (empty value)'); + // fieldDetail.element.setAutofilled(false); + // } + // } else { + // console.log('AUTOFILL: setAutofilled is not a function on this element'); + // } + + // Highlight: Set autofilled state for all autofilled fields if (typeof fieldDetail.element.setAutofilled === 'function') { - console.log('🔍 AUTOFILL: Calling setAutofilled(true) on element'); - fieldDetail.element.setAutofilled(true); - console.log('🔍 AUTOFILL: setAutofilled(true) called successfully'); - } else { - console.log('🔍 AUTOFILL: setAutofilled is not a function on this element'); + fieldDetail.element.setAutofilled(!!field.value); } } }, diff --git a/toolkit/components/passwordmgr/LoginManagerContent.jsm b/toolkit/components/passwordmgr/LoginManagerContent.jsm index 8a2f340a6b..28eabafbd7 100644 --- a/toolkit/components/passwordmgr/LoginManagerContent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm @@ -1189,7 +1189,7 @@ var LoginManagerContent = { // Fill the form if (usernameField) { - // Don't modify the username field if it's disabled or readOnly so we preserve its case. + // Don't modify the username field if it's disabled or readOnly so we preserve its case. let disabledOrReadOnly = usernameField.disabled || usernameField.readOnly; let userNameDiffers = selectedLogin.username != usernameField.value; @@ -1202,6 +1202,10 @@ var LoginManagerContent = { if (!disabledOrReadOnly && !userEnteredDifferentCase && userNameDiffers) { usernameField.setUserInput(selectedLogin.username); } + //Set autofilled state if value is present + if (typeof usernameField.setAutofilled === "function" && usernameField.value) { + usernameField.setAutofilled(true); + } } let doc = form.ownerDocument; @@ -1216,6 +1220,10 @@ var LoginManagerContent = { }; log("Saving autoFilledLogin", autoFilledLogin.guid, "for", form.rootElement); this.stateForDocument(doc).fillsByRootElement.set(form.rootElement, autoFilledLogin); + // Patch: Set autofilled state if value is present + if (typeof passwordField.setAutofilled === "function" && passwordField.value) { + passwordField.setAutofilled(true); + } } log("_fillForm succeeded"); diff --git a/toolkit/components/satchel/nsFormFillController.cpp b/toolkit/components/satchel/nsFormFillController.cpp index 801af3287e..0d6498a73e 100644 --- a/toolkit/components/satchel/nsFormFillController.cpp +++ b/toolkit/components/satchel/nsFormFillController.cpp @@ -40,6 +40,7 @@ #include "nsIFrame.h" #include "nsIScriptSecurityManager.h" #include "nsFocusManager.h" +#include "mozilla/dom/HTMLInputElement.h" using namespace mozilla; using namespace mozilla::dom; @@ -541,9 +542,21 @@ nsFormFillController::SetTextValue(const nsAString & aTextValue) { nsCOMPtr editable = do_QueryInterface(mFocusedInput); if (editable) { + editable->BeginProgrammaticValueSet(); mSuppressOnInput = true; editable->SetUserInput(aTextValue); mSuppressOnInput = false; + editable->EndProgrammaticValueSet(); + + if (mFocusedInput) { + nsCOMPtr content = do_QueryInterface(mFocusedInput); + if (content) { + mozilla::dom::HTMLInputElement* htmlInput = mozilla::dom::HTMLInputElement::FromContentOrNull(content); + if (htmlInput) { + htmlInput->SetAutofilled(true); + } + } + } } return NS_OK; } @@ -1332,7 +1345,8 @@ nsFormFillController::StopControllingInput() nsCOMPtr formAutoComplete = do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv); if (formAutoComplete) { - formAutoComplete->StopControllingInput(mFocusedInput); + // PATCH: Do NOT call StopControllingInput here, so autofill state is NOT cleared on blur/focus. + // formAutoComplete->StopControllingInput(mFocusedInput); } mFocusedInputNode = nullptr; From 156c755085daa65c3e303ef8dd020cd7e3d4eea2 Mon Sep 17 00:00:00 2001 From: MeladJM Date: Sat, 26 Jul 2025 03:06:37 +0800 Subject: [PATCH 25/30] Issue #2790 - Part 5: Persistent highlight despite blur click --- dom/html/HTMLInputElement.cpp | 23 ++++++++++--- dom/html/HTMLInputElement.h | 13 ++++++-- dom/html/HTMLTextAreaElement.cpp | 9 ------ layout/style/res/forms.css | 32 +++++++++++++++++++ .../satchel/nsFormFillController.cpp | 3 ++ 5 files changed, 64 insertions(+), 16 deletions(-) diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index eb2fdba516..120e816eed 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2607,7 +2607,7 @@ HTMLInputElement::MozSetFileNameArray(const Sequence& aFileNames, NS_IMETHODIMP HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, - uint32_t aLength) + uint32_t aLength) { if (!nsContentUtils::IsCallerChrome()) { // setting the value of a "FILE" input widget requires chrome privilege @@ -2631,7 +2631,7 @@ HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, void HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath, - ErrorResult& aRv) + ErrorResult& aRv) { nsCOMPtr file; aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file)); @@ -2835,11 +2835,14 @@ HTMLInputElement::SetUserInput(const nsAString& aValue) void HTMLInputElement::SetAutofilled(bool aAutofilled) { - + nsAutoString value; + GetValueInternal(value); if (aAutofilled) { AddStates(NS_EVENT_STATE_AUTOFILL); + mAutofilledValue = value; } else { RemoveStates(NS_EVENT_STATE_AUTOFILL); + mAutofilledValue.Truncate(); } } @@ -7092,6 +7095,13 @@ HTMLInputElement::IntrinsicState() const state |= NS_EVENT_STATE_MOZ_SUBMITINVALID; } + // Autofill highlight should persist as long as the value matches the autofilled value + nsAutoString value; + GetValueInternal(value); + if (!mAutofilledValue.IsEmpty() && value == mAutofilledValue) { + state |= NS_EVENT_STATE_AUTOFILL; + } + return state; } @@ -8533,10 +8543,15 @@ HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) // Only remove autofilled state if the value actually changed from autofilled value if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) { - if (mAutofilledValue != value) { + if (!mAutofilledValue.IsEmpty() && mAutofilledValue != value) { RemoveStates(NS_EVENT_STATE_AUTOFILL); mAutofilledValue.Truncate(); } + } else if (aWasInteractiveUserChange && !State().HasState(NS_EVENT_STATE_AUTOFILL)) { + // If the value is changed back to the autofilled value, restore the state + if (!mAutofilledValue.IsEmpty() && mAutofilledValue == value) { + AddStates(NS_EVENT_STATE_AUTOFILL); + } } UpdateAllValidityStates(aNotify); diff --git a/dom/html/HTMLInputElement.h b/dom/html/HTMLInputElement.h index 1ee639ca5a..462d29b524 100644 --- a/dom/html/HTMLInputElement.h +++ b/dom/html/HTMLInputElement.h @@ -849,9 +849,8 @@ public: /** * Sets or clears the autofilled state of this input element. - * This is used by the browser's autofill system to indicate when - * a value has been automatically filled (e.g., from saved passwords - * or form data). + * When setting, also stores the autofilled value for persistence. + * When clearing, clears the stored autofilled value. * * @param aAutofilled Whether the element should be marked as autofilled */ @@ -872,6 +871,8 @@ public: void UpdateEntries(const nsTArray& aFilesOrDirectories); + void SetAutofilledValue(const nsAString& aValue) { mAutofilledValue = aValue; } + protected: virtual ~HTMLInputElement(); @@ -1641,6 +1642,10 @@ protected: bool mNumberControlSpinnerSpinsUp : 1; bool mPickerRunning : 1; bool mSelectionCached : 1; + /** + * The value that was autofilled by the browser. Used to persist the autofill + * highlight as long as the value matches, regardless of focus/blur. + */ nsString mAutofilledValue; private: @@ -1785,6 +1790,8 @@ private: nsCOMPtr mFilePicker; RefPtr mInput; }; + + void EnsureAutofillState(); }; } // namespace dom diff --git a/dom/html/HTMLTextAreaElement.cpp b/dom/html/HTMLTextAreaElement.cpp index fca321aec6..69a1fdea45 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -383,11 +383,6 @@ HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange { nsAutoString value; GetValueInternal(value, true); - // printf("[TextArea] OnValueChanged: aWasInteractiveUserChange=%d, value='%s', autofilled='%s', autofill state=%d\n", - // aWasInteractiveUserChange, - // NS_ConvertUTF16toUTF8(value).get(), - // NS_ConvertUTF16toUTF8(mAutofilledValue).get(), - // State().HasState(NS_EVENT_STATE_AUTOFILL)); // Only remove autofilled state if the value actually changed from autofilled value if (State().HasState(NS_EVENT_STATE_AUTOFILL) || !mAutofilledValue.IsEmpty()) { @@ -395,7 +390,6 @@ HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange RemoveStates(NS_EVENT_STATE_AUTOFILL); mAutofilledValue.Truncate(); } else if (aWasInteractiveUserChange && mAutofilledValue == value) { - // Defensive: re-add the autofill state if it was removed by something else AddStates(NS_EVENT_STATE_AUTOFILL); } } @@ -1248,7 +1242,6 @@ HTMLTextAreaElement::IntrinsicState() const { EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState(); - // PATCH: Persist autofill state if autofilled if (!mAutofilledValue.IsEmpty()) { state |= NS_EVENT_STATE_AUTOFILL; } @@ -1295,8 +1288,6 @@ HTMLTextAreaElement::IntrinsicState() const state |= NS_EVENT_STATE_PLACEHOLDERSHOWN; } - state |= NS_EVENT_STATE_AUTOFILL; - return state; } diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css index bc07a8ff4a..d0f2c101fd 100644 --- a/layout/style/res/forms.css +++ b/layout/style/res/forms.css @@ -82,8 +82,24 @@ label { /* Note: Values in nsNativeTheme IsWidgetStyled function need to match textfield background/border values here */ +/* Autofill persistent highlight styles */ +input:-moz-autofill { + background-color: #FFEB3B !important; + background-image: none !important; + color: fieldtext !important; +} + +input:-moz-autofill:hover, +input:-moz-autofill:focus, +input:-moz-autofill:active { + background-color: #FFEB3B !important; + background-image: none !important; + color: fieldtext !important; +} + input { -moz-appearance: textfield; + appearance: textfield; /* The sum of border and padding on block-start and block-end must be the same here, for buttons, and for (including its internal padding magic) */ @@ -107,22 +91,6 @@ input { border: 2px inset ThreeDLightShadow; background-color: -moz-Field; color: -moz-FieldText; -} - -/* Autofill styles - make highlighting persist */ -input:-moz-autofill { - background-color: #FFEB3B !important; - color: fieldtext !important; - background-image: none !important; -} - -/* Ensure highlight persists on focus/hover/active states */ -input:-moz-autofill:hover, -input:-moz-autofill:focus, -input:-moz-autofill:active { - background-color: #FFEB3B !important; - color: fieldtext !important; - background-image: none !important; font: -moz-field; text-rendering: optimizeLegibility; line-height: normal; diff --git a/toolkit/components/formautofill/FormAutofillContentService.js b/toolkit/components/formautofill/FormAutofillContentService.js index 4f96b361e3..cd9850804f 100644 --- a/toolkit/components/formautofill/FormAutofillContentService.js +++ b/toolkit/components/formautofill/FormAutofillContentService.js @@ -250,29 +250,7 @@ FormHandler.prototype = { } fieldDetail.element.value = field.value; - // Add event listeners for debugging - // try { - // fieldDetail.element.addEventListener('focus', () => console.log('[JS] input focused')); - // fieldDetail.element.addEventListener('blur', () => console.log('[JS] input blurred')); - // fieldDetail.element.addEventListener('input', () => console.log('[JS] input event, value:', fieldDetail.element.value)); - // } catch (e) { - // console.log('[JS] Could not add event listeners:', e); - // } - // if (typeof fieldDetail.element.setAutofilled === 'function') { - // if (field.value) { - // console.log('AUTOFILL: Calling setAutofilled(true) on element'); - // fieldDetail.element.setAutofilled(true); - // console.log('AUTOFILL: setAutofilled(true) called successfully'); - // } else { - // console.log('AUTOFILL: Calling setAutofilled(false) on element (empty value)'); - // fieldDetail.element.setAutofilled(false); - // } - // } else { - // console.log('AUTOFILL: setAutofilled is not a function on this element'); - // } - - // Highlight: Set autofilled state for all autofilled fields if (typeof fieldDetail.element.setAutofilled === 'function') { fieldDetail.element.setAutofilled(!!field.value); } diff --git a/toolkit/components/passwordmgr/LoginManagerContent.jsm b/toolkit/components/passwordmgr/LoginManagerContent.jsm index 28eabafbd7..a5b9abc288 100644 --- a/toolkit/components/passwordmgr/LoginManagerContent.jsm +++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm @@ -1220,7 +1220,7 @@ var LoginManagerContent = { }; log("Saving autoFilledLogin", autoFilledLogin.guid, "for", form.rootElement); this.stateForDocument(doc).fillsByRootElement.set(form.rootElement, autoFilledLogin); - // Patch: Set autofilled state if value is present + // Set autofilled state if value is present if (typeof passwordField.setAutofilled === "function" && passwordField.value) { passwordField.setAutofilled(true); } diff --git a/toolkit/components/satchel/nsFormFillController.cpp b/toolkit/components/satchel/nsFormFillController.cpp index 00bc070770..8da7f3ecfa 100644 --- a/toolkit/components/satchel/nsFormFillController.cpp +++ b/toolkit/components/satchel/nsFormFillController.cpp @@ -1347,10 +1347,6 @@ nsFormFillController::StopControllingInput() nsresult rv; nsCOMPtr formAutoComplete = do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv); - if (formAutoComplete) { - // PATCH: Do NOT call StopControllingInput here, so autofill state is NOT cleared on blur/focus. - // formAutoComplete->StopControllingInput(mFocusedInput); - } mFocusedInputNode = nullptr; mFocusedInput = nullptr; From 70cbf0dfd3e2997d7a3baee14f54e77886d17c74 Mon Sep 17 00:00:00 2001 From: MeladJM Date: Mon, 28 Jul 2025 01:01:27 +0800 Subject: [PATCH 27/30] Issue #2790 - Cleanup: Remove whitespaces --- dom/html/HTMLInputElement.cpp | 2 +- toolkit/components/formautofill/FormAutofillContentService.js | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 120e816eed..1a3b0a99ef 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2607,7 +2607,7 @@ HTMLInputElement::MozSetFileNameArray(const Sequence& aFileNames, NS_IMETHODIMP HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, - uint32_t aLength) + uint32_t aLength) { if (!nsContentUtils::IsCallerChrome()) { // setting the value of a "FILE" input widget requires chrome privilege diff --git a/toolkit/components/formautofill/FormAutofillContentService.js b/toolkit/components/formautofill/FormAutofillContentService.js index cd9850804f..b459736d3b 100644 --- a/toolkit/components/formautofill/FormAutofillContentService.js +++ b/toolkit/components/formautofill/FormAutofillContentService.js @@ -235,9 +235,7 @@ FormHandler.prototype = { * } */ autofillFormFields: function (aAutofillResult) { - for (let field of aAutofillResult.fields) { - // Get the field details, if it was processed by the user interface. let fieldDetail = this.fieldDetails .find(f => f.section == field.section && @@ -249,8 +247,6 @@ FormHandler.prototype = { continue; } fieldDetail.element.value = field.value; - - if (typeof fieldDetail.element.setAutofilled === 'function') { fieldDetail.element.setAutofilled(!!field.value); } From 61ad3b8d6efd32fdfe5907bdaceb6c73bb04b208 Mon Sep 17 00:00:00 2001 From: MeladJM Date: Mon, 28 Jul 2025 03:51:09 +0800 Subject: [PATCH 28/30] Issue #2790 - Part 6: Highlight color change and remove important --- layout/style/res/forms.css | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css index bc07a8ff4a..e704ba40fb 100644 --- a/layout/style/res/forms.css +++ b/layout/style/res/forms.css @@ -1150,13 +1150,20 @@ input[type="time"] { overflow: hidden !important; } -/* Autofill highlight for internal-only pseudo-class */ -input:-moz-autofill-highlight, +input[type="text"]:-moz-autofill-highlight, +input[type="email"]:-moz-autofill-highlight, +input[type="password"]:-moz-autofill-highlight, +input[type="search"]:-moz-autofill-highlight, +input[type="tel"]:-moz-autofill-highlight, +input[type="url"]:-moz-autofill-highlight, +input[type="number"]:-moz-autofill-highlight, +input[type="date"]:-moz-autofill-highlight, +input[type="time"]:-moz-autofill-highlight, +input[type="datetime-local"]:-moz-autofill-highlight, +input[type="month"]:-moz-autofill-highlight, +input[type="week"]:-moz-autofill-highlight, select:-moz-autofill-highlight, textarea:-moz-autofill-highlight { - background-color: #ffff99 !important; + background-color: #fffcd0 !important; } -.custom-autofill-highlight { - background: yellow !important; -} From 61f92f89ea9c26d8f55de6a1d89bdfeb87dce8f8 Mon Sep 17 00:00:00 2001 From: MeladJM Date: Tue, 29 Jul 2025 04:52:30 +0800 Subject: [PATCH 29/30] Issue #2790 - Part 7: Setting of contrasting color and fixing of last commit's issues --- layout/style/res/forms.css | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css index e704ba40fb..8d8c53e148 100644 --- a/layout/style/res/forms.css +++ b/layout/style/res/forms.css @@ -106,6 +106,14 @@ input { overflow-clip-box: content-box; } +/* Autofill highlight - placed immediately after base input styles */ +input:-moz-autofill-highlight, +select:-moz-autofill-highlight, +textarea:-moz-autofill-highlight { + background-color: #fffcd0 !important; + color: #090909; +} + input > .anonymous-div, input::placeholder { word-wrap: normal !important; @@ -1148,22 +1156,4 @@ input[type="number"] > div > div > div:hover { input[type="date"], input[type="time"] { overflow: hidden !important; -} - -input[type="text"]:-moz-autofill-highlight, -input[type="email"]:-moz-autofill-highlight, -input[type="password"]:-moz-autofill-highlight, -input[type="search"]:-moz-autofill-highlight, -input[type="tel"]:-moz-autofill-highlight, -input[type="url"]:-moz-autofill-highlight, -input[type="number"]:-moz-autofill-highlight, -input[type="date"]:-moz-autofill-highlight, -input[type="time"]:-moz-autofill-highlight, -input[type="datetime-local"]:-moz-autofill-highlight, -input[type="month"]:-moz-autofill-highlight, -input[type="week"]:-moz-autofill-highlight, -select:-moz-autofill-highlight, -textarea:-moz-autofill-highlight { - background-color: #fffcd0 !important; -} - +} \ No newline at end of file From 429e05c8a1ba3daf4c687a5fd837ccbfcc3482aa Mon Sep 17 00:00:00 2001 From: MeladJM Date: Tue, 29 Jul 2025 04:55:57 +0800 Subject: [PATCH 30/30] Issue #2790 - Cleanup: Correct whitespaces --- dom/html/HTMLInputElement.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dom/html/HTMLInputElement.cpp b/dom/html/HTMLInputElement.cpp index 1a3b0a99ef..56b7c36da2 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2631,7 +2631,7 @@ HTMLInputElement::MozSetFileNameArray(const char16_t** aFileNames, void HTMLInputElement::MozSetDirectory(const nsAString& aDirectoryPath, - ErrorResult& aRv) + ErrorResult& aRv) { nsCOMPtr file; aRv = NS_NewLocalFile(aDirectoryPath, true, getter_AddRefs(file));