diff --git a/content/html/content/src/HTMLInputElement.cpp b/content/html/content/src/HTMLInputElement.cpp index c1d7fd81c36..c22bc218395 100644 --- a/content/html/content/src/HTMLInputElement.cpp +++ b/content/html/content/src/HTMLInputElement.cpp @@ -3314,6 +3314,8 @@ HTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor) nsNumberControlFrame* numberControlFrame = do_QueryFrame(GetPrimaryFrame()); if (numberControlFrame) { + bool oldNumberControlSpinTimerSpinsUpValue = + mNumberControlSpinnerSpinsUp; switch (numberControlFrame->GetSpinButtonForPointerEvent( aVisitor.mEvent->AsMouseEvent())) { case nsNumberControlFrame::eSpinButtonUp: @@ -3325,6 +3327,14 @@ HTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor) stopSpin = false; break; } + if (mNumberControlSpinnerSpinsUp != + oldNumberControlSpinTimerSpinsUpValue) { + nsNumberControlFrame* numberControlFrame = + do_QueryFrame(GetPrimaryFrame()); + if (numberControlFrame) { + numberControlFrame->SpinnerStateChanged(); + } + } } if (stopSpin) { StopNumberControlSpinnerSpin(); @@ -3346,6 +3356,15 @@ HTMLInputElement::PreHandleEvent(nsEventChainPreVisitor& aVisitor) numberControlFrame->HandleFocusEvent(aVisitor.mEvent); } } + if (frame->IsThemed()) { + // Our frame's nested will be invalidated when it + // loses focus, but since we are also native themed we need to make + // sure that our entire area is repainted since any focus highlight + // from the theme should be removed from us (the repainting of the + // sub-area occupied by the anon text control is not enough to do + // that). + frame->InvalidateFrame(); + } } } else if (aVisitor.mEvent->message == NS_KEY_UP) { WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent(); @@ -3497,6 +3516,12 @@ HTMLInputElement::StartNumberControlSpinnerSpin() // Capture the mouse so that we can tell if the pointer moves from one // spin button to the other, or to some other element: nsIPresShell::SetCapturingContent(this, CAPTURE_IGNOREALLOWED); + + nsNumberControlFrame* numberControlFrame = + do_QueryFrame(GetPrimaryFrame()); + if (numberControlFrame) { + numberControlFrame->SpinnerStateChanged(); + } } void @@ -3512,6 +3537,12 @@ HTMLInputElement::StopNumberControlSpinnerSpin() mNumberControlSpinnerIsSpinning = false; FireChangeEventIfNeeded(); + + nsNumberControlFrame* numberControlFrame = + do_QueryFrame(GetPrimaryFrame()); + if (numberControlFrame) { + numberControlFrame->SpinnerStateChanged(); + } } } diff --git a/content/html/content/src/HTMLInputElement.h b/content/html/content/src/HTMLInputElement.h index ade169fdecb..e154baa4363 100644 --- a/content/html/content/src/HTMLInputElement.h +++ b/content/html/content/src/HTMLInputElement.h @@ -688,6 +688,16 @@ public: */ static void HandleNumberControlSpin(void* aData); + bool NumberSpinnerUpButtonIsDepressed() const + { + return mNumberControlSpinnerIsSpinning && mNumberControlSpinnerSpinsUp; + } + + bool NumberSpinnerDownButtonIsDepressed() const + { + return mNumberControlSpinnerIsSpinning && !mNumberControlSpinnerSpinsUp; + } + bool MozIsTextField(bool aExcludePassword); nsIEditor* GetEditor(); diff --git a/layout/forms/nsNumberControlFrame.cpp b/layout/forms/nsNumberControlFrame.cpp index b64ff247768..c720dc8e288 100644 --- a/layout/forms/nsNumberControlFrame.cpp +++ b/layout/forms/nsNumberControlFrame.cpp @@ -318,6 +318,49 @@ nsNumberControlFrame::GetAnonTextControl() return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr; } +/* static */ nsNumberControlFrame* +nsNumberControlFrame::GetNumberControlFrameForTextField(nsIFrame* aFrame) +{ + // If aFrame is the anon text field for an then we expect + // the frame of its mContent's grandparent to be that input's frame. We + // have to check for this via the content tree because we don't know whether + // extra frames will be wrapped around any of the elements between aFrame and + // the nsNumberControlFrame that we're looking for (e.g. flex wrappers). + nsIContent* content = aFrame->GetContent(); + if (content->IsInNativeAnonymousSubtree() && + content->GetParent() && content->GetParent()->GetParent()) { + nsIContent* grandparent = content->GetParent()->GetParent(); + if (grandparent->IsHTML(nsGkAtoms::input) && + grandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::number, eCaseMatters)) { + return do_QueryFrame(grandparent->GetPrimaryFrame()); + } + } + return nullptr; +} + +/* static */ nsNumberControlFrame* +nsNumberControlFrame::GetNumberControlFrameForSpinButton(nsIFrame* aFrame) +{ + // If aFrame is a spin button for an then we expect the + // frame of its mContent's great-grandparent to be that input's frame. We + // have to check for this via the content tree because we don't know whether + // extra frames will be wrapped around any of the elements between aFrame and + // the nsNumberControlFrame that we're looking for (e.g. flex wrappers). + nsIContent* content = aFrame->GetContent(); + if (content->IsInNativeAnonymousSubtree() && + content->GetParent() && content->GetParent()->GetParent() && + content->GetParent()->GetParent()->GetParent()) { + nsIContent* greatgrandparent = content->GetParent()->GetParent()->GetParent(); + if (greatgrandparent->IsHTML(nsGkAtoms::input) && + greatgrandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + nsGkAtoms::number, eCaseMatters)) { + return do_QueryFrame(greatgrandparent->GetPrimaryFrame()); + } + } + return nullptr; +} + int32_t nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const { @@ -330,9 +373,64 @@ nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const if (aEvent->originalTarget == mSpinDown) { return eSpinButtonDown; } + if (aEvent->originalTarget == mSpinBox) { + // In the case that the up/down buttons are hidden (display:none) we use + // just the spin box element, spinning up if the pointer is over the top + // half of the element, or down if it's over the bottom half. This is + // important to handle since this is the state things are in for the + // default UA style sheet. See the comment in forms.css for why. + LayoutDeviceIntPoint absPoint = aEvent->refPoint; + nsPoint point = + nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, + LayoutDeviceIntPoint::ToUntyped(absPoint), + mSpinBox->GetPrimaryFrame()); + if (point != nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) { + if (point.y < mSpinBox->GetPrimaryFrame()->GetSize().height / 2) { + return eSpinButtonUp; + } + return eSpinButtonDown; + } + } return eSpinButtonNone; } +void +nsNumberControlFrame::SpinnerStateChanged() const +{ + nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame(); + if (spinUpFrame && spinUpFrame->IsThemed()) { + spinUpFrame->InvalidateFrame(); + } + nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame(); + if (spinDownFrame && spinDownFrame->IsThemed()) { + spinDownFrame->InvalidateFrame(); + } +} + +bool +nsNumberControlFrame::SpinnerUpButtonIsDepressed() const +{ + return HTMLInputElement::FromContent(mContent)-> + NumberSpinnerUpButtonIsDepressed(); +} + +bool +nsNumberControlFrame::SpinnerDownButtonIsDepressed() const +{ + return HTMLInputElement::FromContent(mContent)-> + NumberSpinnerDownButtonIsDepressed(); +} + +bool +nsNumberControlFrame::IsFocused() const +{ + // Normally this depends on the state of our anonymous text control (which + // takes focus for us), but in the case that it does not have a frame we will + // have focus ourself. + return mTextField->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS) || + mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS); +} + void nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent) { @@ -342,6 +440,27 @@ nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent) } } +#define STYLES_DISABLING_NATIVE_THEMING \ + NS_AUTHOR_SPECIFIED_BACKGROUND | \ + NS_AUTHOR_SPECIFIED_PADDING | \ + NS_AUTHOR_SPECIFIED_BORDER + +bool +nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const +{ + nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame(); + nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame(); + + return spinUpFrame && + spinUpFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_UP_BUTTON && + !PresContext()->HasAuthorSpecifiedRules(spinUpFrame, + STYLES_DISABLING_NATIVE_THEMING) && + spinDownFrame && + spinDownFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_DOWN_BUTTON && + !PresContext()->HasAuthorSpecifiedRules(spinDownFrame, + STYLES_DISABLING_NATIVE_THEMING); +} + void nsNumberControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, uint32_t aFilter) diff --git a/layout/forms/nsNumberControlFrame.h b/layout/forms/nsNumberControlFrame.h index 7d85e8fc5bc..a82c24043e3 100644 --- a/layout/forms/nsNumberControlFrame.h +++ b/layout/forms/nsNumberControlFrame.h @@ -44,7 +44,7 @@ public: virtual void DestroyFrom(nsIFrame* aDestructRoot) MOZ_OVERRIDE; - virtual bool IsLeaf() const MOZ_OVERRIDE { return true; } + virtual bool IsLeaf() const MOZ_OVERRIDE { return false; } NS_IMETHOD Reflow(nsPresContext* aPresContext, nsHTMLReflowMetrics& aDesiredSize, @@ -91,6 +91,18 @@ public: HTMLInputElement* GetAnonTextControl(); + /** + * If the frame is the frame for an nsNumberControlFrame's anonymous text + * field, returns the nsNumberControlFrame. Else returns nullptr. + */ + static nsNumberControlFrame* GetNumberControlFrameForTextField(nsIFrame* aFrame); + + /** + * If the frame is the frame for an nsNumberControlFrame's up or down spin + * button, returns the nsNumberControlFrame. Else returns nullptr. + */ + static nsNumberControlFrame* GetNumberControlFrameForSpinButton(nsIFrame* aFrame); + enum SpinButtonEnum { eSpinButtonNone, eSpinButtonUp, @@ -104,10 +116,19 @@ public: */ int32_t GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const; + void SpinnerStateChanged() const; + + bool SpinnerUpButtonIsDepressed() const; + bool SpinnerDownButtonIsDepressed() const; + + bool IsFocused() const; + void HandleFocusEvent(WidgetEvent* aEvent); virtual Element* GetPseudoElement(nsCSSPseudoElements::Type aType) MOZ_OVERRIDE; + bool ShouldUseNativeStyleForSpinner() const; + private: nsresult MakeAnonymousElement(Element** aResult, diff --git a/layout/style/forms.css b/layout/style/forms.css index 90ffc318fb0..7dcc8409e95 100644 --- a/layout/style/forms.css +++ b/layout/style/forms.css @@ -897,9 +897,6 @@ input[type=number]::-moz-number-wrapper { display: flex; float: none !important; position: static !important; - -moz-box-sizing: border-box; - width: 100%; - height: 100%; } input[type=number]::-moz-number-text { @@ -922,18 +919,23 @@ input[type=number]::-moz-number-text { input[type=number]::-moz-number-spin-box { display: flex; flex-direction: column; - flex: 0 8px; - cursor: default; - padding: 1px; +%ifdef XP_WIN + /* The Window's Theme's spin buttons have a very narrow minimum width, so + * make it something reasonable: + */ + width: 16px; +%endif + height: 0; + align-self: center; + justify-content: center; } input[type=number]::-moz-number-spin-up { - /* We should be "display:block" so that we don't get wrapped in an anonymous - * flex item that will prevent the setting of the "flex" property below from - * working. - */ - display: block; - flex: 1; + -moz-appearance: spinner-upbutton; + display: block; /* bug 926670 */ + flex: none; + cursor: default; + /* Style for when native theming is off: */ background-image: url('data:image/svg+xml,'); background-repeat: no-repeat; background-position: center bottom; @@ -944,12 +946,11 @@ input[type=number]::-moz-number-spin-up { } input[type=number]::-moz-number-spin-down { - /* We should be "display:block" so that we don't get wrapped in an anonymous - * flex item that will prevent the setting of the "flex" property below from - * working. - */ - display: block; - flex: 1; + -moz-appearance: spinner-downbutton; + display: block; /* bug 926670 */ + flex: none; + cursor: default; + /* Style for when native theming is off: */ background-image: url('data:image/svg+xml,'); background-repeat: no-repeat; background-position: center top; diff --git a/widget/cocoa/nsNativeThemeCocoa.h b/widget/cocoa/nsNativeThemeCocoa.h index bdd6cc2c2bc..f17be845057 100644 --- a/widget/cocoa/nsNativeThemeCocoa.h +++ b/widget/cocoa/nsNativeThemeCocoa.h @@ -104,6 +104,10 @@ protected: const HIRect& inBoxRect, ThemeDrawState inDrawState, ThemeButtonAdornment inAdornment, nsEventStates inState, nsIFrame* aFrame); + void DrawSpinButton(CGContextRef context, ThemeButtonKind inKind, + const HIRect& inBoxRect, ThemeDrawState inDrawState, + ThemeButtonAdornment inAdornment, nsEventStates inState, + nsIFrame* aFrame, uint8_t aWidgetType); void DrawUnifiedToolbar(CGContextRef cgContext, const HIRect& inBoxRect, NSWindow* aWindow); void DrawStatusBar(CGContextRef cgContext, const HIRect& inBoxRect, diff --git a/widget/cocoa/nsNativeThemeCocoa.mm b/widget/cocoa/nsNativeThemeCocoa.mm index 5ece8b31c18..e094bd56162 100644 --- a/widget/cocoa/nsNativeThemeCocoa.mm +++ b/widget/cocoa/nsNativeThemeCocoa.mm @@ -5,6 +5,7 @@ #include "nsNativeThemeCocoa.h" #include "nsObjCExceptions.h" +#include "nsNumberControlFrame.h" #include "nsRangeFrame.h" #include "nsRenderingContext.h" #include "nsRect.h" @@ -1210,6 +1211,26 @@ nsNativeThemeCocoa::DrawDropdown(CGContextRef cgContext, const HIRect& inBoxRect NS_OBJC_END_TRY_ABORT_BLOCK; } +static const CellRenderSettings spinnerSettings = { + { + NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border) + NSMakeSize(15, 22), // small + NSMakeSize(19, 27) // regular + }, + { + NSMakeSize(11, 16), // mini (width trimmed by 2px to reduce blank border) + NSMakeSize(15, 22), // small + NSMakeSize(19, 27) // regular + }, + { + { // Leopard + {0, 0, 0, 0}, // mini + {0, 0, 0, 0}, // small + {0, 0, 0, 0} // regular + } + } +}; + void nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKind, const HIRect& inBoxRect, ThemeDrawState inDrawState, @@ -1234,6 +1255,56 @@ nsNativeThemeCocoa::DrawSpinButtons(CGContextRef cgContext, ThemeButtonKind inKi NS_OBJC_END_TRY_ABORT_BLOCK; } +void +nsNativeThemeCocoa::DrawSpinButton(CGContextRef cgContext, + ThemeButtonKind inKind, + const HIRect& inBoxRect, + ThemeDrawState inDrawState, + ThemeButtonAdornment inAdornment, + nsEventStates inState, + nsIFrame* aFrame, + uint8_t aWidgetType) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + MOZ_ASSERT(aWidgetType == NS_THEME_SPINNER_UP_BUTTON || + aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON); + + HIThemeButtonDrawInfo bdi; + bdi.version = 0; + bdi.kind = inKind; + bdi.value = kThemeButtonOff; + bdi.adornment = inAdornment; + + if (IsDisabled(aFrame, inState)) + bdi.state = kThemeStateUnavailable; + else + bdi.state = FrameIsInActiveWindow(aFrame) ? inDrawState : kThemeStateActive; + + // Cocoa only allows kThemeIncDecButton to paint the up and down spin buttons + // together as a single unit (presumably because when one button is active, + // the appearance of both changes (in different ways)). Here we have to paint + // both buttons, using clip to hide the one we don't want to paint. + HIRect drawRect = inBoxRect; + drawRect.size.height *= 2; + if (aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON) { + drawRect.origin.y -= inBoxRect.size.height; + } + + // Shift the drawing a little to the left, since cocoa paints with more + // blank space around the visual buttons than we'd like: + drawRect.origin.x -= 1; + + CGContextSaveGState(cgContext); + CGContextClipToRect(cgContext, inBoxRect); + + HIThemeDrawButton(&drawRect, &bdi, cgContext, HITHEME_ORIENTATION, NULL); + + CGContextRestoreGState(cgContext); + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + void nsNativeThemeCocoa::DrawFrame(CGContextRef cgContext, HIThemeFrameKind inKind, const HIRect& inBoxRect, bool inDisabled, @@ -2135,8 +2206,14 @@ nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext, break; case NS_THEME_SPINNER: { - ThemeDrawState state = kThemeStateActive; nsIContent* content = aFrame->GetContent(); + if (content->IsHTML()) { + // In HTML the theming for the spin buttons is drawn individually into + // their own backgrounds instead of being drawn into the background of + // their spinner parent as it is for XUL. + break; + } + ThemeDrawState state = kThemeStateActive; if (content->AttrValueIs(kNameSpaceID_None, nsGkAtoms::state, NS_LITERAL_STRING("up"), eCaseMatters)) { state = kThemeStatePressedUp; @@ -2151,6 +2228,23 @@ nsNativeThemeCocoa::DrawWidgetBackground(nsRenderingContext* aContext, } break; + case NS_THEME_SPINNER_UP_BUTTON: + case NS_THEME_SPINNER_DOWN_BUTTON: { + nsNumberControlFrame* numberControlFrame = + nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame); + if (numberControlFrame) { + ThemeDrawState state = kThemeStateActive; + if (numberControlFrame->SpinnerUpButtonIsDepressed()) { + state = kThemeStatePressedUp; + } else if (numberControlFrame->SpinnerDownButtonIsDepressed()) { + state = kThemeStatePressedDown; + } + DrawSpinButton(cgContext, kThemeIncDecButtonMini, macRect, state, + kThemeAdornmentNone, eventState, aFrame, aWidgetType); + } + } + break; + case NS_THEME_TOOLBAR_BUTTON: DrawSegment(cgContext, macRect, eventState, aFrame, toolbarButtonRenderSettings); break; @@ -2747,12 +2841,25 @@ nsNativeThemeCocoa::GetMinimumWidgetSize(nsRenderingContext* aContext, } case NS_THEME_SPINNER: + case NS_THEME_SPINNER_UP_BUTTON: + case NS_THEME_SPINNER_DOWN_BUTTON: { SInt32 buttonHeight = 0, buttonWidth = 0; - ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth); - ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight); + if (aFrame->GetContent()->IsXUL()) { + ::GetThemeMetric(kThemeMetricLittleArrowsWidth, &buttonWidth); + ::GetThemeMetric(kThemeMetricLittleArrowsHeight, &buttonHeight); + } else { + NSSize size = + spinnerSettings.minimumSizes[EnumSizeForCocoaSize(NSMiniControlSize)]; + buttonWidth = size.width; + buttonHeight = size.height; + if (aWidgetType != NS_THEME_SPINNER) { + // the buttons are half the height of the spinner + buttonHeight /= 2; + } + } aResult->SizeTo(buttonWidth, buttonHeight); - *aIsOverridable = false; + *aIsOverridable = true; break; } @@ -3100,6 +3207,8 @@ nsNativeThemeCocoa::ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* a case NS_THEME_BUTTON_BEVEL: case NS_THEME_TOOLBAR_BUTTON: case NS_THEME_SPINNER: + case NS_THEME_SPINNER_UP_BUTTON: + case NS_THEME_SPINNER_DOWN_BUTTON: case NS_THEME_TOOLBAR: case NS_THEME_MOZ_MAC_UNIFIED_TOOLBAR: case NS_THEME_STATUSBAR: @@ -3224,6 +3333,8 @@ nsNativeThemeCocoa::WidgetAppearanceDependsOnWindowFocus(uint8_t aWidgetType) case NS_THEME_MENUSEPARATOR: case NS_THEME_TOOLTIP: case NS_THEME_SPINNER: + case NS_THEME_SPINNER_UP_BUTTON: + case NS_THEME_SPINNER_DOWN_BUTTON: case NS_THEME_TOOLBAR_SEPARATOR: case NS_THEME_TOOLBOX: case NS_THEME_TEXTFIELD: diff --git a/widget/xpwidgets/nsNativeTheme.cpp b/widget/xpwidgets/nsNativeTheme.cpp index b764fbc89d9..b9b6cef9df0 100644 --- a/widget/xpwidgets/nsNativeTheme.cpp +++ b/widget/xpwidgets/nsNativeTheme.cpp @@ -9,6 +9,7 @@ #include "nsIContent.h" #include "nsIFrame.h" #include "nsIPresShell.h" +#include "nsNumberControlFrame.h" #include "nsPresContext.h" #include "nsEventStateManager.h" #include "nsString.h" @@ -74,6 +75,16 @@ nsNativeTheme::GetContentState(nsIFrame* aFrame, uint8_t aWidgetType) nsEventStates flags; if (frameContent->IsElement()) { flags = frameContent->AsElement()->State(); + + // needs special handling since its nested native + // anonymous takes focus for it. + if (aWidgetType == NS_THEME_TEXTFIELD && + frameContent->IsHTML(nsGkAtoms::input)) { + nsNumberControlFrame *numberControlFrame = do_QueryFrame(aFrame); + if (numberControlFrame && numberControlFrame->IsFocused()) { + flags |= NS_EVENT_STATE_FOCUS; + } + } } if (isXULCheckboxRadio && aWidgetType == NS_THEME_RADIO) { @@ -313,6 +324,15 @@ nsNativeTheme::IsWidgetStyled(nsPresContext* aPresContext, nsIFrame* aFrame, } } + if (aWidgetType == NS_THEME_SPINNER_UP_BUTTON || + aWidgetType == NS_THEME_SPINNER_DOWN_BUTTON) { + nsNumberControlFrame* numberControlFrame = + nsNumberControlFrame::GetNumberControlFrameForSpinButton(aFrame); + if (numberControlFrame) { + return !numberControlFrame->ShouldUseNativeStyleForSpinner(); + } + } + return (aWidgetType == NS_THEME_BUTTON || aWidgetType == NS_THEME_TEXTFIELD || aWidgetType == NS_THEME_TEXTFIELD_MULTILINE ||