michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsNumberControlFrame.h" michael@0: michael@0: #include "HTMLInputElement.h" michael@0: #include "ICUUtils.h" michael@0: #include "nsIFocusManager.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsFocusManager.h" michael@0: #include "nsFontMetrics.h" michael@0: #include "nsFormControlFrame.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "nsINodeInfo.h" michael@0: #include "nsNameSpaceManager.h" michael@0: #include "nsThemeConstants.h" michael@0: #include "mozilla/BasicEvents.h" michael@0: #include "mozilla/EventStates.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsContentCreatorFunctions.h" michael@0: #include "nsContentList.h" michael@0: #include "nsStyleSet.h" michael@0: #include "nsIDOMMutationEvent.h" michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: #include "mozilla/a11y/AccTypes.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: nsIFrame* michael@0: NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) michael@0: { michael@0: return new (aPresShell) nsNumberControlFrame(aContext); michael@0: } michael@0: michael@0: NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame) michael@0: michael@0: NS_QUERYFRAME_HEAD(nsNumberControlFrame) michael@0: NS_QUERYFRAME_ENTRY(nsNumberControlFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator) michael@0: NS_QUERYFRAME_ENTRY(nsITextControlFrame) michael@0: NS_QUERYFRAME_ENTRY(nsIFormControlFrame) michael@0: NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame) michael@0: michael@0: nsNumberControlFrame::nsNumberControlFrame(nsStyleContext* aContext) michael@0: : nsContainerFrame(aContext) michael@0: , mHandlingInputEvent(false) michael@0: { michael@0: } michael@0: michael@0: void michael@0: nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot) michael@0: { michael@0: NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(), michael@0: "nsNumberControlFrame should not have continuations; if it does we " michael@0: "need to call RegUnregAccessKey only for the first"); michael@0: nsFormControlFrame::RegUnRegAccessKey(static_cast(this), false); michael@0: nsContentUtils::DestroyAnonymousContent(&mOuterWrapper); michael@0: nsContainerFrame::DestroyFrom(aDestructRoot); michael@0: } michael@0: michael@0: nscoord michael@0: nsNumberControlFrame::GetMinWidth(nsRenderingContext* aRenderingContext) michael@0: { michael@0: nscoord result; michael@0: DISPLAY_MIN_WIDTH(this, result); michael@0: michael@0: nsIFrame* kid = mFrames.FirstChild(); michael@0: if (kid) { // display:none? michael@0: result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, michael@0: kid, michael@0: nsLayoutUtils::MIN_WIDTH); michael@0: } else { michael@0: result = 0; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nscoord michael@0: nsNumberControlFrame::GetPrefWidth(nsRenderingContext* aRenderingContext) michael@0: { michael@0: nscoord result; michael@0: DISPLAY_PREF_WIDTH(this, result); michael@0: michael@0: nsIFrame* kid = mFrames.FirstChild(); michael@0: if (kid) { // display:none? michael@0: result = nsLayoutUtils::IntrinsicForContainer(aRenderingContext, michael@0: kid, michael@0: nsLayoutUtils::PREF_WIDTH); michael@0: } else { michael@0: result = 0; michael@0: } michael@0: michael@0: return result; michael@0: } michael@0: michael@0: nsresult michael@0: nsNumberControlFrame::Reflow(nsPresContext* aPresContext, michael@0: nsHTMLReflowMetrics& aDesiredSize, michael@0: const nsHTMLReflowState& aReflowState, michael@0: nsReflowStatus& aStatus) michael@0: { michael@0: DO_GLOBAL_REFLOW_COUNT("nsNumberControlFrame"); michael@0: DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus); michael@0: michael@0: NS_ASSERTION(mOuterWrapper, "Outer wrapper div must exist!"); michael@0: michael@0: NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(), michael@0: "nsNumberControlFrame should not have continuations; if it does we " michael@0: "need to call RegUnregAccessKey only for the first"); michael@0: michael@0: NS_ASSERTION(!mFrames.FirstChild() || michael@0: !mFrames.FirstChild()->GetNextSibling(), michael@0: "We expect at most one direct child frame"); michael@0: michael@0: if (mState & NS_FRAME_FIRST_REFLOW) { michael@0: nsFormControlFrame::RegUnRegAccessKey(this, true); michael@0: } michael@0: michael@0: // The width of our content box, which is the available width michael@0: // for our anonymous content: michael@0: const nscoord contentBoxWidth = aReflowState.ComputedWidth(); michael@0: nscoord contentBoxHeight = aReflowState.ComputedHeight(); michael@0: michael@0: nsIFrame* outerWrapperFrame = mOuterWrapper->GetPrimaryFrame(); michael@0: michael@0: if (!outerWrapperFrame) { // display:none? michael@0: if (contentBoxHeight == NS_INTRINSICSIZE) { michael@0: contentBoxHeight = 0; michael@0: } michael@0: } else { michael@0: NS_ASSERTION(outerWrapperFrame == mFrames.FirstChild(), "huh?"); michael@0: michael@0: nsHTMLReflowMetrics wrappersDesiredSize(aReflowState); michael@0: michael@0: nsHTMLReflowState wrapperReflowState(aPresContext, aReflowState, michael@0: outerWrapperFrame, michael@0: nsSize(contentBoxWidth, michael@0: NS_UNCONSTRAINEDSIZE)); michael@0: michael@0: // offsets of wrapper frame michael@0: nscoord xoffset = aReflowState.ComputedPhysicalBorderPadding().left + michael@0: wrapperReflowState.ComputedPhysicalMargin().left; michael@0: nscoord yoffset = aReflowState.ComputedPhysicalBorderPadding().top + michael@0: wrapperReflowState.ComputedPhysicalMargin().top; michael@0: michael@0: nsReflowStatus childStatus; michael@0: nsresult rv = ReflowChild(outerWrapperFrame, aPresContext, michael@0: wrappersDesiredSize, wrapperReflowState, michael@0: xoffset, yoffset, 0, childStatus); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus), michael@0: "We gave our child unconstrained height, so it should be complete"); michael@0: michael@0: nscoord wrappersMarginBoxHeight = wrappersDesiredSize.Height() + michael@0: wrapperReflowState.ComputedPhysicalMargin().TopBottom(); michael@0: michael@0: if (contentBoxHeight == NS_INTRINSICSIZE) { michael@0: // We are intrinsically sized -- we should shrinkwrap the outer wrapper's michael@0: // height: michael@0: contentBoxHeight = wrappersMarginBoxHeight; michael@0: michael@0: // Make sure we obey min/max-height in the case when we're doing intrinsic michael@0: // sizing (we get it for free when we have a non-intrinsic michael@0: // aReflowState.ComputedHeight()). Note that we do this before michael@0: // adjusting for borderpadding, since mComputedMaxHeight and michael@0: // mComputedMinHeight are content heights. michael@0: contentBoxHeight = michael@0: NS_CSS_MINMAX(contentBoxHeight, michael@0: aReflowState.ComputedMinHeight(), michael@0: aReflowState.ComputedMaxHeight()); michael@0: } michael@0: michael@0: // Center child vertically michael@0: nscoord extraSpace = contentBoxHeight - wrappersMarginBoxHeight; michael@0: yoffset += std::max(0, extraSpace / 2); michael@0: michael@0: // Place the child michael@0: rv = FinishReflowChild(outerWrapperFrame, aPresContext, michael@0: wrappersDesiredSize, &wrapperReflowState, michael@0: xoffset, yoffset, 0); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: aDesiredSize.SetTopAscent(wrappersDesiredSize.TopAscent() + michael@0: outerWrapperFrame->GetPosition().y); michael@0: } michael@0: michael@0: aDesiredSize.Width() = contentBoxWidth + michael@0: aReflowState.ComputedPhysicalBorderPadding().LeftRight(); michael@0: aDesiredSize.Height() = contentBoxHeight + michael@0: aReflowState.ComputedPhysicalBorderPadding().TopBottom(); michael@0: michael@0: aDesiredSize.SetOverflowAreasToDesiredBounds(); michael@0: michael@0: if (outerWrapperFrame) { michael@0: ConsiderChildOverflow(aDesiredSize.mOverflowAreas, outerWrapperFrame); michael@0: } michael@0: michael@0: FinishAndStoreOverflow(&aDesiredSize); michael@0: michael@0: aStatus = NS_FRAME_COMPLETE; michael@0: michael@0: NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsNumberControlFrame::SyncDisabledState() michael@0: { michael@0: EventStates eventStates = mContent->AsElement()->State(); michael@0: if (eventStates.HasState(NS_EVENT_STATE_DISABLED)) { michael@0: mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::disabled, EmptyString(), michael@0: true); michael@0: } else { michael@0: mTextField->UnsetAttr(kNameSpaceID_None, nsGkAtoms::disabled, true); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsNumberControlFrame::AttributeChanged(int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: // nsGkAtoms::disabled is handled by SyncDisabledState michael@0: if (aNameSpaceID == kNameSpaceID_None) { michael@0: if (aAttribute == nsGkAtoms::placeholder || michael@0: aAttribute == nsGkAtoms::readonly || michael@0: aAttribute == nsGkAtoms::tabindex) { michael@0: if (aModType == nsIDOMMutationEvent::REMOVAL) { michael@0: mTextField->UnsetAttr(aNameSpaceID, aAttribute, true); michael@0: } else { michael@0: MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION || michael@0: aModType == nsIDOMMutationEvent::MODIFICATION); michael@0: nsAutoString value; michael@0: mContent->GetAttr(aNameSpaceID, aAttribute, value); michael@0: mTextField->SetAttr(aNameSpaceID, aAttribute, value, true); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, michael@0: aModType); michael@0: } michael@0: michael@0: void michael@0: nsNumberControlFrame::ContentStatesChanged(EventStates aStates) michael@0: { michael@0: if (aStates.HasState(NS_EVENT_STATE_DISABLED)) { michael@0: nsContentUtils::AddScriptRunner(new SyncDisabledStateEvent(this)); michael@0: } michael@0: } michael@0: michael@0: nsITextControlFrame* michael@0: nsNumberControlFrame::GetTextFieldFrame() michael@0: { michael@0: return do_QueryFrame(GetAnonTextControl()->GetPrimaryFrame()); michael@0: } michael@0: michael@0: nsresult michael@0: nsNumberControlFrame::MakeAnonymousElement(Element** aResult, michael@0: nsTArray& aElements, michael@0: nsIAtom* aTagName, michael@0: nsCSSPseudoElements::Type aPseudoType, michael@0: nsStyleContext* aParentContext) michael@0: { michael@0: // Get the NodeInfoManager and tag necessary to create the anonymous divs. michael@0: nsCOMPtr doc = mContent->GetDocument(); michael@0: nsRefPtr resultElement = doc->CreateHTMLElement(aTagName); michael@0: michael@0: // If we legitimately fail this assertion and need to allow michael@0: // non-pseudo-element anonymous children, then we'll need to add a branch michael@0: // that calls ResolveStyleFor((*aResult)->AsElement(), aParentContext)") to michael@0: // set newStyleContext. michael@0: NS_ASSERTION(aPseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement, michael@0: "Expecting anonymous children to all be pseudo-elements"); michael@0: // Associate the pseudo-element with the anonymous child michael@0: nsRefPtr newStyleContext = michael@0: PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(), michael@0: aPseudoType, michael@0: aParentContext, michael@0: resultElement); michael@0: michael@0: if (!aElements.AppendElement(ContentInfo(resultElement, newStyleContext))) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: if (aPseudoType == nsCSSPseudoElements::ePseudo_mozNumberSpinDown || michael@0: aPseudoType == nsCSSPseudoElements::ePseudo_mozNumberSpinUp) { michael@0: resultElement->SetAttr(kNameSpaceID_None, nsGkAtoms::role, michael@0: NS_LITERAL_STRING("button"), false); michael@0: } michael@0: michael@0: resultElement.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsNumberControlFrame::CreateAnonymousContent(nsTArray& aElements) michael@0: { michael@0: nsresult rv; michael@0: michael@0: // We create an anonymous tree for our input element that is structured as michael@0: // follows: michael@0: // michael@0: // input michael@0: // div - outer wrapper with "display:flex" by default michael@0: // input - text input field michael@0: // div - spin box wrapping up/down arrow buttons michael@0: // div - spin up (up arrow button) michael@0: // div - spin down (down arrow button) michael@0: // michael@0: // If you change this, be careful to change the destruction order in michael@0: // nsNumberControlFrame::DestroyFrom. michael@0: michael@0: michael@0: // Create the anonymous outer wrapper: michael@0: rv = MakeAnonymousElement(getter_AddRefs(mOuterWrapper), michael@0: aElements, michael@0: nsGkAtoms::div, michael@0: nsCSSPseudoElements::ePseudo_mozNumberWrapper, michael@0: mStyleContext); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: ContentInfo& outerWrapperCI = aElements.LastElement(); michael@0: michael@0: // Create the ::-moz-number-text pseudo-element: michael@0: rv = MakeAnonymousElement(getter_AddRefs(mTextField), michael@0: outerWrapperCI.mChildren, michael@0: nsGkAtoms::input, michael@0: nsCSSPseudoElements::ePseudo_mozNumberText, michael@0: outerWrapperCI.mStyleContext); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::type, michael@0: NS_LITERAL_STRING("text"), PR_FALSE); michael@0: michael@0: HTMLInputElement* content = HTMLInputElement::FromContent(mContent); michael@0: HTMLInputElement* textField = HTMLInputElement::FromContent(mTextField); michael@0: michael@0: // Initialize the text field value: michael@0: nsAutoString value; michael@0: content->GetValue(value); michael@0: SetValueOfAnonTextControl(value); michael@0: michael@0: // If we're readonly, make sure our anonymous text control is too: michael@0: nsAutoString readonly; michael@0: if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) { michael@0: mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly, false); michael@0: } michael@0: michael@0: // Propogate our tabindex: michael@0: int32_t tabIndex; michael@0: content->GetTabIndex(&tabIndex); michael@0: textField->SetTabIndex(tabIndex); michael@0: michael@0: // Initialize the text field's placeholder, if ours is set: michael@0: nsAutoString placeholder; michael@0: if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder)) { michael@0: mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder, false); michael@0: } michael@0: michael@0: if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) { michael@0: // We don't want to focus the frame but the text field. michael@0: nsIFocusManager* fm = nsFocusManager::GetFocusManager(); michael@0: nsCOMPtr element = do_QueryInterface(mTextField); michael@0: NS_ASSERTION(element, "Really, this should be a nsIDOMElement!"); michael@0: fm->SetFocus(element, 0); michael@0: } michael@0: michael@0: if (StyleDisplay()->mAppearance == NS_THEME_TEXTFIELD) { michael@0: // The author has elected to hide the spinner by setting this michael@0: // -moz-appearance. We will reframe if it changes. michael@0: return rv; michael@0: } michael@0: michael@0: // Create the ::-moz-number-spin-box pseudo-element: michael@0: rv = MakeAnonymousElement(getter_AddRefs(mSpinBox), michael@0: outerWrapperCI.mChildren, michael@0: nsGkAtoms::div, michael@0: nsCSSPseudoElements::ePseudo_mozNumberSpinBox, michael@0: outerWrapperCI.mStyleContext); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: ContentInfo& spinBoxCI = outerWrapperCI.mChildren.LastElement(); michael@0: michael@0: // Create the ::-moz-number-spin-up pseudo-element: michael@0: rv = MakeAnonymousElement(getter_AddRefs(mSpinUp), michael@0: spinBoxCI.mChildren, michael@0: nsGkAtoms::div, michael@0: nsCSSPseudoElements::ePseudo_mozNumberSpinUp, michael@0: spinBoxCI.mStyleContext); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Create the ::-moz-number-spin-down pseudo-element: michael@0: rv = MakeAnonymousElement(getter_AddRefs(mSpinDown), michael@0: spinBoxCI.mChildren, michael@0: nsGkAtoms::div, michael@0: nsCSSPseudoElements::ePseudo_mozNumberSpinDown, michael@0: spinBoxCI.mStyleContext); michael@0: michael@0: SyncDisabledState(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsIAtom* michael@0: nsNumberControlFrame::GetType() const michael@0: { michael@0: return nsGkAtoms::numberControlFrame; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNumberControlFrame::GetEditor(nsIEditor **aEditor) michael@0: { michael@0: return GetTextFieldFrame()->GetEditor(aEditor); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNumberControlFrame::SetSelectionStart(int32_t aSelectionStart) michael@0: { michael@0: return GetTextFieldFrame()->SetSelectionStart(aSelectionStart); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNumberControlFrame::SetSelectionEnd(int32_t aSelectionEnd) michael@0: { michael@0: return GetTextFieldFrame()->SetSelectionEnd(aSelectionEnd); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNumberControlFrame::SetSelectionRange(int32_t aSelectionStart, michael@0: int32_t aSelectionEnd, michael@0: SelectionDirection aDirection) michael@0: { michael@0: return GetTextFieldFrame()->SetSelectionRange(aSelectionStart, aSelectionEnd, michael@0: aDirection); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNumberControlFrame::GetSelectionRange(int32_t* aSelectionStart, michael@0: int32_t* aSelectionEnd, michael@0: SelectionDirection* aDirection) michael@0: { michael@0: return GetTextFieldFrame()->GetSelectionRange(aSelectionStart, aSelectionEnd, michael@0: aDirection); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNumberControlFrame::GetOwnedSelectionController(nsISelectionController** aSelCon) michael@0: { michael@0: return GetTextFieldFrame()->GetOwnedSelectionController(aSelCon); michael@0: } michael@0: michael@0: nsFrameSelection* michael@0: nsNumberControlFrame::GetOwnedFrameSelection() michael@0: { michael@0: return GetTextFieldFrame()->GetOwnedFrameSelection(); michael@0: } michael@0: michael@0: nsresult michael@0: nsNumberControlFrame::GetPhonetic(nsAString& aPhonetic) michael@0: { michael@0: return GetTextFieldFrame()->GetPhonetic(aPhonetic); michael@0: } michael@0: michael@0: nsresult michael@0: nsNumberControlFrame::EnsureEditorInitialized() michael@0: { michael@0: return GetTextFieldFrame()->EnsureEditorInitialized(); michael@0: } michael@0: michael@0: nsresult michael@0: nsNumberControlFrame::ScrollSelectionIntoView() michael@0: { michael@0: return GetTextFieldFrame()->ScrollSelectionIntoView(); michael@0: } michael@0: michael@0: void michael@0: nsNumberControlFrame::SetFocus(bool aOn, bool aRepaint) michael@0: { michael@0: GetTextFieldFrame()->SetFocus(aOn, aRepaint); michael@0: } michael@0: michael@0: nsresult michael@0: nsNumberControlFrame::SetFormProperty(nsIAtom* aName, const nsAString& aValue) michael@0: { michael@0: return GetTextFieldFrame()->SetFormProperty(aName, aValue); michael@0: } michael@0: michael@0: HTMLInputElement* michael@0: nsNumberControlFrame::GetAnonTextControl() michael@0: { michael@0: return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr; michael@0: } michael@0: michael@0: /* static */ nsNumberControlFrame* michael@0: nsNumberControlFrame::GetNumberControlFrameForTextField(nsIFrame* aFrame) michael@0: { michael@0: // If aFrame is the anon text field for an then we expect michael@0: // the frame of its mContent's grandparent to be that input's frame. We michael@0: // have to check for this via the content tree because we don't know whether michael@0: // extra frames will be wrapped around any of the elements between aFrame and michael@0: // the nsNumberControlFrame that we're looking for (e.g. flex wrappers). michael@0: nsIContent* content = aFrame->GetContent(); michael@0: if (content->IsInNativeAnonymousSubtree() && michael@0: content->GetParent() && content->GetParent()->GetParent()) { michael@0: nsIContent* grandparent = content->GetParent()->GetParent(); michael@0: if (grandparent->IsHTML(nsGkAtoms::input) && michael@0: grandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::number, eCaseMatters)) { michael@0: return do_QueryFrame(grandparent->GetPrimaryFrame()); michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: /* static */ nsNumberControlFrame* michael@0: nsNumberControlFrame::GetNumberControlFrameForSpinButton(nsIFrame* aFrame) michael@0: { michael@0: // If aFrame is a spin button for an then we expect the michael@0: // frame of its mContent's great-grandparent to be that input's frame. We michael@0: // have to check for this via the content tree because we don't know whether michael@0: // extra frames will be wrapped around any of the elements between aFrame and michael@0: // the nsNumberControlFrame that we're looking for (e.g. flex wrappers). michael@0: nsIContent* content = aFrame->GetContent(); michael@0: if (content->IsInNativeAnonymousSubtree() && michael@0: content->GetParent() && content->GetParent()->GetParent() && michael@0: content->GetParent()->GetParent()->GetParent()) { michael@0: nsIContent* greatgrandparent = content->GetParent()->GetParent()->GetParent(); michael@0: if (greatgrandparent->IsHTML(nsGkAtoms::input) && michael@0: greatgrandparent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, michael@0: nsGkAtoms::number, eCaseMatters)) { michael@0: return do_QueryFrame(greatgrandparent->GetPrimaryFrame()); michael@0: } michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: int32_t michael@0: nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const michael@0: { michael@0: MOZ_ASSERT(aEvent->eventStructType == NS_MOUSE_EVENT, michael@0: "Unexpected event type"); michael@0: michael@0: if (!mSpinBox) { michael@0: // we don't have a spinner michael@0: return eSpinButtonNone; michael@0: } michael@0: if (aEvent->originalTarget == mSpinUp) { michael@0: return eSpinButtonUp; michael@0: } michael@0: if (aEvent->originalTarget == mSpinDown) { michael@0: return eSpinButtonDown; michael@0: } michael@0: if (aEvent->originalTarget == mSpinBox) { michael@0: // In the case that the up/down buttons are hidden (display:none) we use michael@0: // just the spin box element, spinning up if the pointer is over the top michael@0: // half of the element, or down if it's over the bottom half. This is michael@0: // important to handle since this is the state things are in for the michael@0: // default UA style sheet. See the comment in forms.css for why. michael@0: LayoutDeviceIntPoint absPoint = aEvent->refPoint; michael@0: nsPoint point = michael@0: nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, michael@0: LayoutDeviceIntPoint::ToUntyped(absPoint), michael@0: mSpinBox->GetPrimaryFrame()); michael@0: if (point != nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) { michael@0: if (point.y < mSpinBox->GetPrimaryFrame()->GetSize().height / 2) { michael@0: return eSpinButtonUp; michael@0: } michael@0: return eSpinButtonDown; michael@0: } michael@0: } michael@0: return eSpinButtonNone; michael@0: } michael@0: michael@0: void michael@0: nsNumberControlFrame::SpinnerStateChanged() const michael@0: { michael@0: MOZ_ASSERT(mSpinUp && mSpinDown, michael@0: "We should not be called when we have no spinner"); michael@0: michael@0: nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame(); michael@0: if (spinUpFrame && spinUpFrame->IsThemed()) { michael@0: spinUpFrame->InvalidateFrame(); michael@0: } michael@0: nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame(); michael@0: if (spinDownFrame && spinDownFrame->IsThemed()) { michael@0: spinDownFrame->InvalidateFrame(); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsNumberControlFrame::SpinnerUpButtonIsDepressed() const michael@0: { michael@0: return HTMLInputElement::FromContent(mContent)-> michael@0: NumberSpinnerUpButtonIsDepressed(); michael@0: } michael@0: michael@0: bool michael@0: nsNumberControlFrame::SpinnerDownButtonIsDepressed() const michael@0: { michael@0: return HTMLInputElement::FromContent(mContent)-> michael@0: NumberSpinnerDownButtonIsDepressed(); michael@0: } michael@0: michael@0: bool michael@0: nsNumberControlFrame::IsFocused() const michael@0: { michael@0: // Normally this depends on the state of our anonymous text control (which michael@0: // takes focus for us), but in the case that it does not have a frame we will michael@0: // have focus ourself. michael@0: return mTextField->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS) || michael@0: mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS); michael@0: } michael@0: michael@0: void michael@0: nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent) michael@0: { michael@0: if (aEvent->originalTarget != mTextField) { michael@0: // Move focus to our text field michael@0: HTMLInputElement::FromContent(mTextField)->Focus(); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsNumberControlFrame::HandleSelectCall() michael@0: { michael@0: return HTMLInputElement::FromContent(mTextField)->Select(); michael@0: } michael@0: michael@0: #define STYLES_DISABLING_NATIVE_THEMING \ michael@0: NS_AUTHOR_SPECIFIED_BACKGROUND | \ michael@0: NS_AUTHOR_SPECIFIED_PADDING | \ michael@0: NS_AUTHOR_SPECIFIED_BORDER michael@0: michael@0: bool michael@0: nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const michael@0: { michael@0: MOZ_ASSERT(mSpinUp && mSpinDown, michael@0: "We should not be called when we have no spinner"); michael@0: michael@0: nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame(); michael@0: nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame(); michael@0: michael@0: return spinUpFrame && michael@0: spinUpFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_UP_BUTTON && michael@0: !PresContext()->HasAuthorSpecifiedRules(spinUpFrame, michael@0: STYLES_DISABLING_NATIVE_THEMING) && michael@0: spinDownFrame && michael@0: spinDownFrame->StyleDisplay()->mAppearance == NS_THEME_SPINNER_DOWN_BUTTON && michael@0: !PresContext()->HasAuthorSpecifiedRules(spinDownFrame, michael@0: STYLES_DISABLING_NATIVE_THEMING); michael@0: } michael@0: michael@0: void michael@0: nsNumberControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements, michael@0: uint32_t aFilter) michael@0: { michael@0: // Only one direct anonymous child: michael@0: aElements.MaybeAppendElement(mOuterWrapper); michael@0: } michael@0: michael@0: void michael@0: nsNumberControlFrame::SetValueOfAnonTextControl(const nsAString& aValue) michael@0: { michael@0: if (mHandlingInputEvent) { michael@0: // We have been called while our HTMLInputElement is processing a DOM michael@0: // 'input' event targeted at our anonymous text control. Our michael@0: // HTMLInputElement has taken the value of our anon text control and michael@0: // called SetValueInternal on itself to keep its own value in sync. As a michael@0: // result SetValueInternal has called us. In this one case we do not want michael@0: // to update our anon text control, especially since aValue will be the michael@0: // sanitized value, and only the internal value should be sanitized (not michael@0: // the value shown to the user, and certainly we shouldn't change it as michael@0: // they type). michael@0: return; michael@0: } michael@0: michael@0: // Init to aValue so that we set aValue as the value of our text control if michael@0: // aValue isn't a valid number (in which case the HTMLInputElement's validity michael@0: // state will be set to invalid) or if aValue can't be localized: michael@0: nsAutoString localizedValue(aValue); michael@0: michael@0: #ifdef ENABLE_INTL_API michael@0: // Try and localize the value we will set: michael@0: Decimal val = HTMLInputElement::StringToDecimal(aValue); michael@0: if (val.isFinite()) { michael@0: ICUUtils::LanguageTagIterForContent langTagIter(mContent); michael@0: ICUUtils::LocalizeNumber(val.toDouble(), langTagIter, localizedValue); michael@0: } michael@0: #endif michael@0: michael@0: // We need to update the value of our anonymous text control here. Note that michael@0: // this must be its value, and not its 'value' attribute (the default value), michael@0: // since the default value is ignored once a user types into the text michael@0: // control. michael@0: HTMLInputElement::FromContent(mTextField)->SetValue(localizedValue); michael@0: } michael@0: michael@0: void michael@0: nsNumberControlFrame::GetValueOfAnonTextControl(nsAString& aValue) michael@0: { michael@0: if (!mTextField) { michael@0: aValue.Truncate(); michael@0: return; michael@0: } michael@0: michael@0: HTMLInputElement::FromContent(mTextField)->GetValue(aValue); michael@0: michael@0: #ifdef ENABLE_INTL_API michael@0: // Here we need to de-localize any number typed in by the user. That is, we michael@0: // need to convert it from the number format of the user's language, region, michael@0: // etc. to the format that the HTML 5 spec defines to be a "valid michael@0: // floating-point number": michael@0: // michael@0: // http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#floating-point-numbers michael@0: // michael@0: // so that it can be parsed by functions like HTMLInputElement:: michael@0: // StringToDecimal (the HTML-5-conforming parsing function) which don't know michael@0: // how to handle numbers that are formatted differently (for example, with michael@0: // non-ASCII digits, with grouping separator characters or with a decimal michael@0: // separator character other than '.'). michael@0: // michael@0: // We need to be careful to avoid normalizing numbers that are already michael@0: // formatted for a locale that matches the format of HTML 5's "valid michael@0: // floating-point number" and have no grouping separator characters. (In michael@0: // other words we want to return the number as specified by the user, not the michael@0: // de-localized serialization, since the latter will normalize the value.) michael@0: // For example, if the user's locale is English and the user types in "2e2" michael@0: // then inputElement.value should be "2e2" and not "100". This is because michael@0: // content (and tests) expect us to avoid "normalizing" the number that the michael@0: // user types in if it's not necessary in order to make sure it conforms to michael@0: // HTML 5's "valid floating-point number" format. michael@0: // michael@0: // Note that we also need to be careful when trying to avoid normalization. michael@0: // For example, just because "1.234" _looks_ like a valid floating-point michael@0: // number according to the spec does not mean that it should be returned michael@0: // as-is. If the user's locale is German, then this represents the value michael@0: // 1234, not 1.234, so it still needs to be de-localized. Alternatively, if michael@0: // the user's locale is English and they type in "1,234" we _do_ need to michael@0: // normalize the number to "1234" because HTML 5's valid floating-point michael@0: // number format does not allow the ',' grouping separator. We can detect all michael@0: // the cases where we need to convert by seeing if the locale-specific michael@0: // parsing function understands the user input to mean the same thing as the michael@0: // HTML-5-conforming parsing function. If so, then we should return the value michael@0: // as-is to avoid normalization. Otherwise, we return the de-localized michael@0: // serialization. michael@0: ICUUtils::LanguageTagIterForContent langTagIter(mContent); michael@0: double value = ICUUtils::ParseNumber(aValue, langTagIter); michael@0: if (NS_finite(value) && michael@0: value != HTMLInputElement::StringToDecimal(aValue).toDouble()) { michael@0: aValue.Truncate(); michael@0: aValue.AppendFloat(value); michael@0: } michael@0: #endif michael@0: // else, we return whatever FromContent put into aValue (the number as typed michael@0: // in by the user) michael@0: } michael@0: michael@0: bool michael@0: nsNumberControlFrame::AnonTextControlIsEmpty() michael@0: { michael@0: if (!mTextField) { michael@0: return true; michael@0: } michael@0: nsAutoString value; michael@0: HTMLInputElement::FromContent(mTextField)->GetValue(value); michael@0: return value.IsEmpty(); michael@0: } michael@0: michael@0: Element* michael@0: nsNumberControlFrame::GetPseudoElement(nsCSSPseudoElements::Type aType) michael@0: { michael@0: if (aType == nsCSSPseudoElements::ePseudo_mozNumberWrapper) { michael@0: return mOuterWrapper; michael@0: } michael@0: michael@0: if (aType == nsCSSPseudoElements::ePseudo_mozNumberText) { michael@0: return mTextField; michael@0: } michael@0: michael@0: if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinBox) { michael@0: MOZ_ASSERT(mSpinBox); michael@0: return mSpinBox; michael@0: } michael@0: michael@0: if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinUp) { michael@0: MOZ_ASSERT(mSpinUp); michael@0: return mSpinUp; michael@0: } michael@0: michael@0: if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinDown) { michael@0: MOZ_ASSERT(mSpinDown); michael@0: return mSpinDown; michael@0: } michael@0: michael@0: return nsContainerFrame::GetPseudoElement(aType); michael@0: } michael@0: michael@0: #ifdef ACCESSIBILITY michael@0: a11y::AccType michael@0: nsNumberControlFrame::AccessibleType() michael@0: { michael@0: return a11y::eHTMLSpinnerType; michael@0: } michael@0: #endif