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": [ 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 37e9018fa0..be9c16d0ec 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -4058,3 +4058,35 @@ 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; + } + mozilla::dom::Element* element = static_cast(content.get()); + if (!element) { + return NS_ERROR_INVALID_ARG; + } + element->SetEventState(mozilla::EventStates(aState), true); + 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; + } + 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/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..56b7c36da2 100644 --- a/dom/html/HTMLInputElement.cpp +++ b/dom/html/HTMLInputElement.cpp @@ -2832,6 +2832,20 @@ HTMLInputElement::SetUserInput(const nsAString& aValue) return NS_OK; } +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(); + } +} + nsIEditor* HTMLInputElement::GetEditor() { @@ -3562,7 +3576,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) @@ -7070,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; } @@ -8505,8 +8537,23 @@ HTMLInputElement::InitializeKeyboardEventListeners() NS_IMETHODIMP_(void) HTMLInputElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) { + nsAutoString value; + GetValueInternal(value); mLastValueChangeWasInteractive = aWasInteractiveUserChange; + // Only remove autofilled state if the value actually changed from autofilled value + if (aWasInteractiveUserChange && State().HasState(NS_EVENT_STATE_AUTOFILL)) { + 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); if (HasDirAuto()) { @@ -8537,6 +8584,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 e46be30ea7..462d29b524 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; } @@ -845,6 +847,15 @@ public: void SetUserInput(const nsAString& aInput, nsIPrincipal& aSubjectPrincipal); + /** + * Sets or clears the autofilled state of this input element. + * 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 + */ + void SetAutofilled(bool aAutofilled); + // XPCOM GetPhonetic() is OK /** @@ -860,6 +871,8 @@ public: void UpdateEntries(const nsTArray& aFilesOrDirectories); + void SetAutofilledValue(const nsAString& aValue) { mAutofilledValue = aValue; } + protected: virtual ~HTMLInputElement(); @@ -1103,7 +1116,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. @@ -1629,6 +1642,11 @@ 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: static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes, @@ -1772,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 48b28d3fe4..69a1fdea45 100644 --- a/dom/html/HTMLTextAreaElement.cpp +++ b/dom/html/HTMLTextAreaElement.cpp @@ -366,6 +366,46 @@ HTMLTextAreaElement::SetUserInput(const nsAString& aValue) return SetValueInternal(aValue, nsTextEditorState::eSetValue_BySetUserInput); } +void +HTMLTextAreaElement::SetAutofilled(bool aAutofilled) +{ + if (aAutofilled) { + AddStates(NS_EVENT_STATE_AUTOFILL); + GetValueInternal(mAutofilledValue, true); // Store the autofilled value + } else { + RemoveStates(NS_EVENT_STATE_AUTOFILL); + mAutofilledValue.Truncate(); + } +} + +NS_IMETHODIMP_(void) +HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) +{ + nsAutoString value; + GetValueInternal(value, true); + + // 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) { + 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); + } +} + NS_IMETHODIMP HTMLTextAreaElement::SetValueChanged(bool aValueChanged) { @@ -556,6 +596,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) { @@ -580,6 +633,9 @@ HTMLTextAreaElement::PostHandleEvent(EventChainPostVisitor& aVisitor) } UpdateState(true); + + // Defensive: re-apply autofill state if value is still autofilled value + EnsureAutofillState(); } return NS_OK; @@ -1186,6 +1242,10 @@ HTMLTextAreaElement::IntrinsicState() const { EventStates state = nsGenericHTMLFormElementWithState::IntrinsicState(); + if (!mAutofilledValue.IsEmpty()) { + state |= NS_EVENT_STATE_AUTOFILL; + } + if (HasAttr(kNameSpaceID_None, nsGkAtoms::required)) { state |= NS_EVENT_STATE_REQUIRED; } else { @@ -1628,23 +1688,6 @@ HTMLTextAreaElement::InitializeKeyboardEventListeners() mState.InitializeKeyboardEventListeners(); } -NS_IMETHODIMP_(void) -HTMLTextAreaElement::OnValueChanged(bool aNotify, bool aWasInteractiveUserChange) -{ - mLastValueChangeWasInteractive = aWasInteractiveUserChange; - - // 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() { @@ -1660,11 +1703,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 cc9c2b7c5c..8cc553e0d1 100644 --- a/dom/html/HTMLTextAreaElement.h +++ b/dom/html/HTMLTextAreaElement.h @@ -69,6 +69,17 @@ 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. + * 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; } @@ -293,6 +304,7 @@ public: { return mState.GetEditor(); } + nsTextEditorState* GetEditorState() const; protected: virtual ~HTMLTextAreaElement() {} @@ -324,6 +336,7 @@ protected: void FireChangeEventIfNeeded(); nsString mFocusedValue; + nsString mAutofilledValue; /** The state of the text editor (selection controller and the editor) **/ nsTextEditorState mState; @@ -397,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/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/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/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/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 diff --git a/layout/style/RuleCascadeData.cpp b/layout/style/RuleCascadeData.cpp index 7165a3967d..da6a4e70de 100644 --- a/layout/style/RuleCascadeData.cpp +++ b/layout/style/RuleCascadeData.cpp @@ -1270,6 +1270,12 @@ ComputeSelectorStateDependence(nsCSSSelector& aSelector) continue; } + if (pseudoClass->mType == CSSPseudoClassType::autofill || + pseudoClass->mType == CSSPseudoClassType::mozAutofillHighlight) { + states |= NS_EVENT_STATE_AUTOFILL; + continue; + } + auto idx = static_cast(pseudoClass->mType); states |= nsCSSPseudoClasses::sPseudoClassStateDependences[idx]; } diff --git a/layout/style/nsCSSKeywordList.h b/layout/style/nsCSSKeywordList.h index d389f4fe35..05218ccc51 100644 --- a/layout/style/nsCSSKeywordList.h +++ b/layout/style/nsCSSKeywordList.h @@ -198,6 +198,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) @@ -313,6 +314,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) diff --git a/layout/style/nsCSSParser.cpp b/layout/style/nsCSSParser.cpp index 71ebb83b85..0fcf81e9f6 100644 --- a/layout/style/nsCSSParser.cpp +++ b/layout/style/nsCSSParser.cpp @@ -7444,6 +7444,110 @@ CSSParserImpl::ParseColor(nsCSSValue& aValue) } break; case eCSSToken_Function: { + // check for color-mix function + if (mToken.mIdent.LowerCaseEqualsLiteral("color-mix")) { + // parse color-mix function + if (!GetToken(true)) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + if (mToken.mType != eCSSToken_Ident || !mToken.mIdent.LowerCaseEqualsLiteral("in")) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + // 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; + } + + if (!ExpectSymbol(',', true)) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + nsCSSValue color1; + if (ParseColor(color1) != CSSParseResult::Ok) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + // 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; + // Reject invalid percentages (outside 0-100% range) + if (w1 < 0.0f || w1 > 1.0f) { + SkipUntil(')'); + return CSSParseResult::Error; + } + } else { + UngetToken(); + } + } + + if (!ExpectSymbol(',', true)) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + nsCSSValue color2; + if (ParseColor(color2) != CSSParseResult::Ok) { + SkipUntil(')'); + return CSSParseResult::Error; + } + + // 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; + // Reject invalid percentages (outside 0-100% range) + if (w2 < 0.0f || w2 > 1.0f) { + SkipUntil(')'); + return CSSParseResult::Error; + } + } 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( + colorSpace, color1, color2, w1, w2); + aValue.SetColorMixValue(colorMix.forget()); + return CSSParseResult::Ok; + } + bool isRGB; bool isHSL; if ((isRGB = mToken.mIdent.LowerCaseEqualsLiteral("rgb")) || @@ -8650,7 +8754,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/nsCSSPseudoClassList.h b/layout/style/nsCSSPseudoClassList.h index 485a1a428d..196c5d9f26 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) @@ -274,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/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/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); diff --git a/layout/style/nsCSSRuleUtils.cpp b/layout/style/nsCSSRuleUtils.cpp index 996e1f5223..1b987cbab8 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() @@ -566,6 +576,14 @@ nsCSSRuleUtils::StateSelectorMatches(Element* aElement, for (nsPseudoClassList* pseudoClass = aSelector->mPseudoClassList; pseudoClass; pseudoClass = pseudoClass->mNext) { + 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 + } auto idx = static_cast(pseudoClass->mType); EventStates statesToCheck = nsCSSPseudoClasses::sPseudoClassStates[idx]; if (!statesToCheck.IsEmpty() && !StateSelectorMatches(aElement, 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/layout/style/nsCSSValue.cpp b/layout/style/nsCSSValue.cpp index 24e804d800..1e43377460 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) { @@ -1709,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 mozilla::css::ColorMixColorSpace::sRGB: + aResult.AppendLiteral("srgb"); + break; + case mozilla::css::ColorMixColorSpace::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( @@ -2007,6 +2051,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 +2266,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 +3125,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), diff --git a/layout/style/nsCSSValue.h b/layout/style/nsCSSValue.h index 680e26732a..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; @@ -804,6 +812,12 @@ public: MOZ_ASSERT(mUnit == eCSSUnit_ComplexColor); return mValue.mComplexColor->ToComplexColor(); } + + mozilla::css::ColorMixValue* GetColorMixValue() const + { + MOZ_ASSERT(mUnit == eCSSUnit_ColorMix); + return mValue.mColorMix; + } Array* GetArrayValue() const { @@ -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___ */ diff --git a/layout/style/nsRuleNode.cpp b/layout/style/nsRuleNode.cpp index a0a5ec1fd6..780f62c1e9 100644 --- a/layout/style/nsRuleNode.cpp +++ b/layout/style/nsRuleNode.cpp @@ -1137,6 +1137,165 @@ 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)) { + + // 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 { + // 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; + + 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); + + float h, s, l, a; + + // 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 { + 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 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; + + 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 + nscolor hslResult = NS_HSL2RGB(h, s, l); + 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; + } else { + // 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) / 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) / 255.0f; + + float r, g, b, a; + + // 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 { + // 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; + + 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 * 255.0f + 0.5f, 0.0f, 255.0f); + + aResult = NS_RGBA(rInt, gInt, bInt, aInt); + result = true; + } + } + } + } } else if (eCSSUnit_Enumerated == unit && aValue.GetIntValue() == NS_STYLE_COLOR_INHERIT_FROM_BODY) { NS_ASSERTION(aPresContext->CompatibilityMode() == eCompatibility_NavQuirks, diff --git a/layout/style/res/forms.css b/layout/style/res/forms.css index e94d347687..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,4 +1156,4 @@ input[type="number"] > div > div > div:hover { input[type="date"], input[type="time"] { overflow: hidden !important; -} +} \ No newline at end of file diff --git a/modules/libpref/init/all.js b/modules/libpref/init/all.js index 350ddbcd8d..a1f5a4b1bd 100644 --- a/modules/libpref/init/all.js +++ b/modules/libpref/init/all.js @@ -2713,6 +2713,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 diff --git a/toolkit/components/formautofill/FormAutofillContentService.js b/toolkit/components/formautofill/FormAutofillContentService.js index ee8e978ad9..b459736d3b 100644 --- a/toolkit/components/formautofill/FormAutofillContentService.js +++ b/toolkit/components/formautofill/FormAutofillContentService.js @@ -9,7 +9,7 @@ * See the nsIFormAutofillContentService documentation for details. */ -"use strict"; +"use strict" const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; @@ -29,6 +29,15 @@ function FormHandler(aForm, aWindow) { this.window = aWindow; this.fieldDetails = []; + + // Add a reset event listener to clear autofill state + this.form.addEventListener("reset", () => { + for (let element of this.form.elements) { + if (typeof element.setAutofilled === "function") { + element.setAutofilled(false); + } + } + }); } FormHandler.prototype = { @@ -233,11 +242,14 @@ FormHandler.prototype = { f.addressType == field.addressType && f.contactType == field.contactType && f.fieldName == field.fieldName); + if (!fieldDetail) { continue; } - fieldDetail.element.value = field.value; + 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 8a2f340a6b..a5b9abc288 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); + // 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..8da7f3ecfa 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,24 @@ 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); + nsAutoString value; + htmlInput->GetValue(value); + htmlInput->SetAutofilledValue(value); + } + } + } } return NS_OK; } @@ -1331,9 +1347,6 @@ nsFormFillController::StopControllingInput() nsresult rv; nsCOMPtr formAutoComplete = do_GetService("@mozilla.org/satchel/form-autocomplete;1", &rv); - if (formAutoComplete) { - formAutoComplete->StopControllingInput(mFocusedInput); - } mFocusedInputNode = nullptr; mFocusedInput = nullptr;