diff options
Diffstat (limited to 'layout/forms/nsNumberControlFrame.cpp')
-rw-r--r-- | layout/forms/nsNumberControlFrame.cpp | 249 |
1 files changed, 249 insertions, 0 deletions
diff --git a/layout/forms/nsNumberControlFrame.cpp b/layout/forms/nsNumberControlFrame.cpp new file mode 100644 index 0000000000..93ecf66ed2 --- /dev/null +++ b/layout/forms/nsNumberControlFrame.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNumberControlFrame.h" + +#include "mozilla/BasicEvents.h" +#include "mozilla/EventStates.h" +#include "mozilla/FloatingPoint.h" +#include "mozilla/PresShell.h" +#include "HTMLInputElement.h" +#include "nsGkAtoms.h" +#include "nsNameSpaceManager.h" +#include "nsStyleConsts.h" +#include "nsContentUtils.h" +#include "nsContentCreatorFunctions.h" +#include "nsCSSPseudoElements.h" + +#ifdef ACCESSIBILITY +# include "mozilla/a11y/AccTypes.h" +#endif + +using namespace mozilla; +using namespace mozilla::dom; + +nsIFrame* NS_NewNumberControlFrame(PresShell* aPresShell, + ComputedStyle* aStyle) { + return new (aPresShell) + nsNumberControlFrame(aStyle, aPresShell->GetPresContext()); +} + +NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame) + +NS_QUERYFRAME_HEAD(nsNumberControlFrame) + NS_QUERYFRAME_ENTRY(nsNumberControlFrame) +NS_QUERYFRAME_TAIL_INHERITING(nsTextControlFrame) + +nsNumberControlFrame::nsNumberControlFrame(ComputedStyle* aStyle, + nsPresContext* aPresContext) + : nsTextControlFrame(aStyle, aPresContext, kClassID) {} + +void nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot, + PostDestroyData& aPostDestroyData) { + aPostDestroyData.AddAnonymousContent(mOuterWrapper.forget()); + nsTextControlFrame::DestroyFrom(aDestructRoot, aPostDestroyData); +} + +nsresult nsNumberControlFrame::CreateAnonymousContent( + nsTArray<ContentInfo>& aElements) { + // We create an anonymous tree for our input element that is structured as + // follows: + // + // input + // div - outer wrapper with "display:flex" by default + // div - editor root + // div - spin box wrapping up/down arrow buttons + // div - spin up (up arrow button) + // div - spin down (down arrow button) + // div - placeholder + // div - preview div + // + // If you change this, be careful to change the destruction order in + // nsNumberControlFrame::DestroyFrom. + + // Create the anonymous outer wrapper: + mOuterWrapper = MakeAnonElement(PseudoStyleType::mozComplexControlWrapper); + + // We want to do this now, rather than on the caller, so that the + // AppendChildTo calls below know that they are anonymous already. This is + // important for the NODE_IS_EDITABLE flag handling, for example. + mOuterWrapper->SetIsNativeAnonymousRoot(); + + aElements.AppendElement(mOuterWrapper); + + nsTArray<ContentInfo> nestedContent; + nsTextControlFrame::CreateAnonymousContent(nestedContent); + for (auto& content : nestedContent) { + // The root goes inside the container. + if (content.mContent == mRootNode) { + mOuterWrapper->AppendChildTo(content.mContent, false); + } else { + // The rest (placeholder and preview), directly under us. + aElements.AppendElement(std::move(content)); + } + } + + if (StyleDisplay()->EffectiveAppearance() == StyleAppearance::Textfield) { + // The author has elected to hide the spinner by setting this + // -moz-appearance. We will reframe if it changes. + return NS_OK; + } + + // Create the ::-moz-number-spin-box pseudo-element: + mSpinBox = MakeAnonElement(PseudoStyleType::mozNumberSpinBox, mOuterWrapper); + + // Create the ::-moz-number-spin-up pseudo-element: + mSpinUp = MakeAnonElement(PseudoStyleType::mozNumberSpinUp, mSpinBox); + + // Create the ::-moz-number-spin-down pseudo-element: + mSpinDown = MakeAnonElement(PseudoStyleType::mozNumberSpinDown, mSpinBox); + + return NS_OK; +} + +/* static */ +nsNumberControlFrame* nsNumberControlFrame::GetNumberControlFrameForTextField( + nsIFrame* aFrame) { + // If aFrame is the anon text field for an <input type=number> 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->IsHTMLElement(nsGkAtoms::input) && + grandparent->AsElement()->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 <input type=number> 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->IsHTMLElement(nsGkAtoms::input) && + greatgrandparent->AsElement()->AttrValueIs( + kNameSpaceID_None, nsGkAtoms::type, nsGkAtoms::number, + eCaseMatters)) { + return do_QueryFrame(greatgrandparent->GetPrimaryFrame()); + } + } + return nullptr; +} + +int32_t nsNumberControlFrame::GetSpinButtonForPointerEvent( + WidgetGUIEvent* aEvent) const { + MOZ_ASSERT(aEvent->mClass == eMouseEventClass, "Unexpected event type"); + + if (!mSpinBox) { + // we don't have a spinner + return eSpinButtonNone; + } + if (aEvent->mOriginalTarget == mSpinUp) { + return eSpinButtonUp; + } + if (aEvent->mOriginalTarget == mSpinDown) { + return eSpinButtonDown; + } + if (aEvent->mOriginalTarget == 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->mRefPoint; + nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo( + aEvent, absPoint, RelativeTo{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 { + MOZ_ASSERT(mSpinUp && mSpinDown, + "We should not be called when we have no spinner"); + + 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::FromNode(mContent) + ->NumberSpinnerUpButtonIsDepressed(); +} + +bool nsNumberControlFrame::SpinnerDownButtonIsDepressed() const { + return HTMLInputElement::FromNode(mContent) + ->NumberSpinnerDownButtonIsDepressed(); +} + +#define STYLES_DISABLING_NATIVE_THEMING \ + NS_AUTHOR_SPECIFIED_BORDER_OR_BACKGROUND | NS_AUTHOR_SPECIFIED_PADDING + +bool nsNumberControlFrame::ShouldUseNativeStyleForSpinner() const { + MOZ_ASSERT(mSpinUp && mSpinDown, + "We should not be called when we have no spinner"); + + nsIFrame* spinUpFrame = mSpinUp->GetPrimaryFrame(); + nsIFrame* spinDownFrame = mSpinDown->GetPrimaryFrame(); + + return spinUpFrame && + spinUpFrame->StyleDisplay()->EffectiveAppearance() == + StyleAppearance::SpinnerUpbutton && + !PresContext()->HasAuthorSpecifiedRules( + spinUpFrame, STYLES_DISABLING_NATIVE_THEMING) && + spinDownFrame && + spinDownFrame->StyleDisplay()->EffectiveAppearance() == + StyleAppearance::SpinnerDownbutton && + !PresContext()->HasAuthorSpecifiedRules( + spinDownFrame, STYLES_DISABLING_NATIVE_THEMING); +} + +void nsNumberControlFrame::AppendAnonymousContentTo( + nsTArray<nsIContent*>& aElements, uint32_t aFilter) { + if (mOuterWrapper) { + aElements.AppendElement(mOuterWrapper); + } + if (mPlaceholderDiv) { + aElements.AppendElement(mPlaceholderDiv); + } + if (mPreviewDiv) { + aElements.AppendElement(mPreviewDiv); + } +} + +#ifdef ACCESSIBILITY +a11y::AccType nsNumberControlFrame::AccessibleType() { + return a11y::eHTMLSpinnerType; +} +#endif |