mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
400 lines
14 KiB
C++
400 lines
14 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* 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 "HTMLInputElement.h"
|
|
#include "nsIFocusManager.h"
|
|
#include "nsIPresShell.h"
|
|
#include "nsFocusManager.h"
|
|
#include "nsFontMetrics.h"
|
|
#include "nsFormControlFrame.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsINodeInfo.h"
|
|
#include "nsINameSpaceManager.h"
|
|
#include "mozilla/BasicEvents.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsContentCreatorFunctions.h"
|
|
#include "nsContentList.h"
|
|
#include "nsStyleSet.h"
|
|
#include "nsIDOMMutationEvent.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::dom;
|
|
|
|
nsIFrame*
|
|
NS_NewNumberControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
|
|
{
|
|
return new (aPresShell) nsNumberControlFrame(aContext);
|
|
}
|
|
|
|
NS_IMPL_FRAMEARENA_HELPERS(nsNumberControlFrame)
|
|
|
|
NS_QUERYFRAME_HEAD(nsNumberControlFrame)
|
|
NS_QUERYFRAME_ENTRY(nsNumberControlFrame)
|
|
NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
|
|
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
|
|
|
|
nsNumberControlFrame::nsNumberControlFrame(nsStyleContext* aContext)
|
|
: nsContainerFrame(aContext)
|
|
, mHandlingInputEvent(false)
|
|
{
|
|
}
|
|
|
|
void
|
|
nsNumberControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
|
|
{
|
|
NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
|
|
"nsNumberControlFrame should not have continuations; if it does we "
|
|
"need to call RegUnregAccessKey only for the first");
|
|
nsFormControlFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
|
|
nsContentUtils::DestroyAnonymousContent(&mOuterWrapper);
|
|
nsContainerFrame::DestroyFrom(aDestructRoot);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNumberControlFrame::Reflow(nsPresContext* aPresContext,
|
|
nsHTMLReflowMetrics& aDesiredSize,
|
|
const nsHTMLReflowState& aReflowState,
|
|
nsReflowStatus& aStatus)
|
|
{
|
|
DO_GLOBAL_REFLOW_COUNT("nsNumberControlFrame");
|
|
DISPLAY_REFLOW(aPresContext, this, aReflowState, aDesiredSize, aStatus);
|
|
|
|
NS_ASSERTION(mOuterWrapper, "Outer wrapper div must exist!");
|
|
|
|
NS_ASSERTION(!GetPrevContinuation() && !GetNextContinuation(),
|
|
"nsNumberControlFrame should not have continuations; if it does we "
|
|
"need to call RegUnregAccessKey only for the first");
|
|
|
|
NS_ASSERTION(!mFrames.FirstChild() ||
|
|
!mFrames.FirstChild()->GetNextSibling(),
|
|
"We expect at most one direct child frame");
|
|
|
|
if (mState & NS_FRAME_FIRST_REFLOW) {
|
|
nsFormControlFrame::RegUnRegAccessKey(this, true);
|
|
}
|
|
|
|
nsHTMLReflowMetrics wrappersDesiredSize;
|
|
nsIFrame* outerWrapperFrame = mOuterWrapper->GetPrimaryFrame();
|
|
if (outerWrapperFrame) { // display:none?
|
|
NS_ASSERTION(outerWrapperFrame == mFrames.FirstChild(), "huh?");
|
|
nsresult rv =
|
|
ReflowAnonymousContent(aPresContext, wrappersDesiredSize,
|
|
aReflowState, outerWrapperFrame);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
ConsiderChildOverflow(aDesiredSize.mOverflowAreas, outerWrapperFrame);
|
|
}
|
|
|
|
nscoord computedHeight = aReflowState.ComputedHeight();
|
|
if (computedHeight == NS_AUTOHEIGHT) {
|
|
computedHeight =
|
|
outerWrapperFrame ? outerWrapperFrame->GetSize().height : 0;
|
|
}
|
|
aDesiredSize.width = aReflowState.ComputedWidth() +
|
|
aReflowState.mComputedBorderPadding.LeftRight();
|
|
aDesiredSize.height = computedHeight +
|
|
aReflowState.mComputedBorderPadding.TopBottom();
|
|
|
|
if (outerWrapperFrame) {
|
|
aDesiredSize.ascent = wrappersDesiredSize.ascent +
|
|
outerWrapperFrame->GetPosition().y;
|
|
}
|
|
|
|
aDesiredSize.SetOverflowAreasToDesiredBounds();
|
|
|
|
FinishAndStoreOverflow(&aDesiredSize);
|
|
|
|
aStatus = NS_FRAME_COMPLETE;
|
|
|
|
NS_FRAME_SET_TRUNCATION(aStatus, aReflowState, aDesiredSize);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsNumberControlFrame::
|
|
ReflowAnonymousContent(nsPresContext* aPresContext,
|
|
nsHTMLReflowMetrics& aWrappersDesiredSize,
|
|
const nsHTMLReflowState& aParentReflowState,
|
|
nsIFrame* aOuterWrapperFrame)
|
|
{
|
|
MOZ_ASSERT(aOuterWrapperFrame);
|
|
|
|
// The width of our content box, which is the available width
|
|
// for our anonymous content:
|
|
nscoord inputFrameContentBoxWidth = aParentReflowState.ComputedWidth();
|
|
|
|
nsHTMLReflowState wrapperReflowState(aPresContext, aParentReflowState,
|
|
aOuterWrapperFrame,
|
|
nsSize(inputFrameContentBoxWidth,
|
|
NS_UNCONSTRAINEDSIZE));
|
|
|
|
nscoord xoffset = aParentReflowState.mComputedBorderPadding.left +
|
|
wrapperReflowState.mComputedMargin.left;
|
|
nscoord yoffset = aParentReflowState.mComputedBorderPadding.top +
|
|
wrapperReflowState.mComputedMargin.top;
|
|
|
|
nsReflowStatus childStatus;
|
|
nsresult rv = ReflowChild(aOuterWrapperFrame, aPresContext,
|
|
aWrappersDesiredSize, wrapperReflowState,
|
|
xoffset, yoffset, 0, childStatus);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
MOZ_ASSERT(NS_FRAME_IS_FULLY_COMPLETE(childStatus),
|
|
"We gave our child unconstrained height, so it should be complete");
|
|
return FinishReflowChild(aOuterWrapperFrame, aPresContext,
|
|
&wrapperReflowState, aWrappersDesiredSize,
|
|
xoffset, yoffset, 0);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNumberControlFrame::AttributeChanged(int32_t aNameSpaceID,
|
|
nsIAtom* aAttribute,
|
|
int32_t aModType)
|
|
{
|
|
if (aNameSpaceID == kNameSpaceID_None) {
|
|
if (aAttribute == nsGkAtoms::placeholder ||
|
|
aAttribute == nsGkAtoms::readonly ||
|
|
aAttribute == nsGkAtoms::tabindex) {
|
|
if (aModType == nsIDOMMutationEvent::REMOVAL) {
|
|
mTextField->UnsetAttr(aNameSpaceID, aAttribute, true);
|
|
} else {
|
|
MOZ_ASSERT(aModType == nsIDOMMutationEvent::ADDITION ||
|
|
aModType == nsIDOMMutationEvent::MODIFICATION);
|
|
nsAutoString value;
|
|
mContent->GetAttr(aNameSpaceID, aAttribute, value);
|
|
mTextField->SetAttr(aNameSpaceID, aAttribute, value, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
|
|
aModType);
|
|
}
|
|
|
|
nsresult
|
|
nsNumberControlFrame::MakeAnonymousElement(Element** aResult,
|
|
nsTArray<ContentInfo>& aElements,
|
|
nsIAtom* aTagName,
|
|
nsCSSPseudoElements::Type aPseudoType,
|
|
nsStyleContext* aParentContext)
|
|
{
|
|
// Get the NodeInfoManager and tag necessary to create the anonymous divs.
|
|
nsCOMPtr<nsIDocument> doc = mContent->GetDocument();
|
|
nsRefPtr<Element> resultElement = doc->CreateHTMLElement(aTagName);
|
|
|
|
// If we legitimately fail this assertion and need to allow
|
|
// non-pseudo-element anonymous children, then we'll need to add a branch
|
|
// that calls ResolveStyleFor((*aResult)->AsElement(), aParentContext)") to
|
|
// set newStyleContext.
|
|
NS_ASSERTION(aPseudoType != nsCSSPseudoElements::ePseudo_NotPseudoElement,
|
|
"Expecting anonymous children to all be pseudo-elements");
|
|
// Associate the pseudo-element with the anonymous child
|
|
nsRefPtr<nsStyleContext> newStyleContext =
|
|
PresContext()->StyleSet()->ResolvePseudoElementStyle(mContent->AsElement(),
|
|
aPseudoType,
|
|
aParentContext,
|
|
resultElement);
|
|
|
|
if (!aElements.AppendElement(ContentInfo(resultElement, newStyleContext))) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
resultElement.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsNumberControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
|
|
{
|
|
nsresult rv;
|
|
|
|
// We create an anonymous tree for our input element that is structured as
|
|
// follows:
|
|
//
|
|
// input
|
|
// div - outer wrapper with "display:flex" by default
|
|
// input - text input field
|
|
// div - spin box wrapping up/down arrow buttons
|
|
// div - spin up (up arrow button)
|
|
// div - spin down (down arrow button)
|
|
//
|
|
// If you change this, be careful to change the destruction order in
|
|
// nsNumberControlFrame::DestroyFrom.
|
|
|
|
|
|
// Create the anonymous outer wrapper:
|
|
rv = MakeAnonymousElement(getter_AddRefs(mOuterWrapper),
|
|
aElements,
|
|
nsGkAtoms::div,
|
|
nsCSSPseudoElements::ePseudo_mozNumberWrapper,
|
|
mStyleContext);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
ContentInfo& outerWrapperCI = aElements.LastElement();
|
|
|
|
// Create the ::-moz-number-text pseudo-element:
|
|
rv = MakeAnonymousElement(getter_AddRefs(mTextField),
|
|
outerWrapperCI.mChildren,
|
|
nsGkAtoms::input,
|
|
nsCSSPseudoElements::ePseudo_mozNumberText,
|
|
outerWrapperCI.mStyleContext);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
|
|
NS_LITERAL_STRING("text"), PR_FALSE);
|
|
|
|
HTMLInputElement* content = HTMLInputElement::FromContent(mContent);
|
|
HTMLInputElement* textField = HTMLInputElement::FromContent(mTextField);
|
|
|
|
// Initialize the text field value:
|
|
nsAutoString value;
|
|
content->GetValue(value);
|
|
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::value, value, false);
|
|
|
|
// If we're readonly, make sure our anonymous text control is too:
|
|
nsAutoString readonly;
|
|
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly)) {
|
|
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::readonly, readonly, false);
|
|
}
|
|
|
|
// Propogate our tabindex:
|
|
int32_t tabIndex;
|
|
content->GetTabIndex(&tabIndex);
|
|
textField->SetTabIndex(tabIndex);
|
|
|
|
// Initialize the text field's placeholder, if ours is set:
|
|
nsAutoString placeholder;
|
|
if (mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder)) {
|
|
mTextField->SetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, placeholder, false);
|
|
}
|
|
|
|
if (mContent->AsElement()->State().HasState(NS_EVENT_STATE_FOCUS)) {
|
|
// We don't want to focus the frame but the text field.
|
|
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
|
|
nsCOMPtr<nsIDOMElement> element = do_QueryInterface(mTextField);
|
|
NS_ASSERTION(element, "Really, this should be a nsIDOMElement!");
|
|
fm->SetFocus(element, 0);
|
|
}
|
|
|
|
// Create the ::-moz-number-spin-box pseudo-element:
|
|
rv = MakeAnonymousElement(getter_AddRefs(mSpinBox),
|
|
outerWrapperCI.mChildren,
|
|
nsGkAtoms::div,
|
|
nsCSSPseudoElements::ePseudo_mozNumberSpinBox,
|
|
outerWrapperCI.mStyleContext);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
ContentInfo& spinBoxCI = outerWrapperCI.mChildren.LastElement();
|
|
|
|
// Create the ::-moz-number-spin-up pseudo-element:
|
|
rv = MakeAnonymousElement(getter_AddRefs(mSpinUp),
|
|
spinBoxCI.mChildren,
|
|
nsGkAtoms::div,
|
|
nsCSSPseudoElements::ePseudo_mozNumberSpinUp,
|
|
spinBoxCI.mStyleContext);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Create the ::-moz-number-spin-down pseudo-element:
|
|
rv = MakeAnonymousElement(getter_AddRefs(mSpinDown),
|
|
spinBoxCI.mChildren,
|
|
nsGkAtoms::div,
|
|
nsCSSPseudoElements::ePseudo_mozNumberSpinDown,
|
|
spinBoxCI.mStyleContext);
|
|
return rv;
|
|
}
|
|
|
|
nsIAtom*
|
|
nsNumberControlFrame::GetType() const
|
|
{
|
|
return nsGkAtoms::numberControlFrame;
|
|
}
|
|
|
|
HTMLInputElement*
|
|
nsNumberControlFrame::GetAnonTextControl()
|
|
{
|
|
return mTextField ? HTMLInputElement::FromContent(mTextField) : nullptr;
|
|
}
|
|
|
|
int32_t
|
|
nsNumberControlFrame::GetSpinButtonForPointerEvent(WidgetGUIEvent* aEvent) const
|
|
{
|
|
MOZ_ASSERT(aEvent->eventStructType == NS_MOUSE_EVENT,
|
|
"Unexpected event type");
|
|
|
|
if (aEvent->originalTarget == mSpinUp) {
|
|
return eSpinButtonUp;
|
|
}
|
|
if (aEvent->originalTarget == mSpinDown) {
|
|
return eSpinButtonDown;
|
|
}
|
|
return eSpinButtonNone;
|
|
}
|
|
|
|
void
|
|
nsNumberControlFrame::HandleFocusEvent(WidgetEvent* aEvent)
|
|
{
|
|
if (aEvent->originalTarget != mTextField) {
|
|
// Move focus to our text field
|
|
HTMLInputElement::FromContent(mTextField)->Focus();
|
|
}
|
|
}
|
|
|
|
void
|
|
nsNumberControlFrame::AppendAnonymousContentTo(nsBaseContentList& aElements,
|
|
uint32_t aFilter)
|
|
{
|
|
// Only one direct anonymous child:
|
|
aElements.MaybeAppendElement(mOuterWrapper);
|
|
}
|
|
|
|
void
|
|
nsNumberControlFrame::UpdateForValueChange(const nsAString& aValue)
|
|
{
|
|
if (mHandlingInputEvent) {
|
|
// We have been called while our HTMLInputElement is processing a DOM
|
|
// 'input' event targeted at our anonymous text control. Our
|
|
// HTMLInputElement has taken the value of our anon text control and
|
|
// called SetValueInternal on itself to keep its own value in sync. As a
|
|
// result SetValueInternal has called us. In this one case we do not want
|
|
// to update our anon text control, especially since aValue will be the
|
|
// sanitized value, and only the internal value should be sanitized (not
|
|
// the value shown to the user, and certainly we shouldn't change it as
|
|
// they type).
|
|
return;
|
|
}
|
|
// We need to update the value of our anonymous text control here. Note that
|
|
// this must be its value, and not its 'value' attribute (the default value),
|
|
// since the default value is ignored once a user types into the text
|
|
// control.
|
|
HTMLInputElement::FromContent(mTextField)->SetValue(aValue);
|
|
}
|
|
|
|
Element*
|
|
nsNumberControlFrame::GetPseudoElement(nsCSSPseudoElements::Type aType)
|
|
{
|
|
if (aType == nsCSSPseudoElements::ePseudo_mozNumberWrapper) {
|
|
return mOuterWrapper;
|
|
}
|
|
|
|
if (aType == nsCSSPseudoElements::ePseudo_mozNumberText) {
|
|
return mTextField;
|
|
}
|
|
|
|
if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinBox) {
|
|
return mSpinBox;
|
|
}
|
|
|
|
if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinUp) {
|
|
return mSpinUp;
|
|
}
|
|
|
|
if (aType == nsCSSPseudoElements::ePseudo_mozNumberSpinDown) {
|
|
return mSpinDown;
|
|
}
|
|
|
|
return nsContainerFrame::GetPseudoElement(aType);
|
|
}
|