Bug 981248 - Rewrite <input type=number> to avoid an anonymous input. r=masayuki,surkov,jwatt,ntim,jfkthame,smaug

Instead, subclass nsTextControlFrame. This simplifies the code and avoids
correctness issues.

I kept the localization functionality though it is not spec compliant. But I
filed a bug to remove it in a followup.

Differential Revision: https://phabricator.services.mozilla.com/D57193
This commit is contained in:
Emilio Cobos Álvarez
2020-01-14 15:05:22 +00:00
parent 47210f10c8
commit c442c1b9ab
42 changed files with 365 additions and 1159 deletions

View File

@@ -108,8 +108,9 @@ class nsTextControlFrame::nsAnonDivObserver final
};
nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle,
nsPresContext* aPresContext)
: nsContainerFrame(aStyle, aPresContext, kClassID),
nsPresContext* aPresContext,
nsIFrame::ClassID aClassID)
: nsContainerFrame(aStyle, aPresContext, aClassID),
mFirstBaseline(NS_INTRINSIC_ISIZE_UNKNOWN),
mEditorHasBeenInitialized(false),
mIsProcessing(false)
@@ -123,6 +124,13 @@ nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle,
nsTextControlFrame::~nsTextControlFrame() {}
nsIScrollableFrame* nsTextControlFrame::GetScrollTargetFrame() {
if (!mRootNode) {
return nullptr;
}
return do_QueryFrame(mRootNode->GetPrimaryFrame());
}
void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
PostDestroyData& aPostDestroyData) {
mScrollEvent.Revoke();
@@ -143,8 +151,15 @@ void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
mMutationObserver = nullptr;
}
// FIXME(emilio, bug 1400618): Do this after the child frames are destroyed.
aPostDestroyData.AddAnonymousContent(mRootNode.forget());
// If we're a subclass like nsNumberControlFrame, then it owns the root of the
// anonymous subtree where mRootNode is.
if (mClass == kClassID) {
aPostDestroyData.AddAnonymousContent(mRootNode.forget());
} else {
MOZ_ASSERT(!mRootNode || !mRootNode->IsRootOfAnonymousSubtree());
mRootNode = nullptr;
}
aPostDestroyData.AddAnonymousContent(mPlaceholderDiv.forget());
aPostDestroyData.AddAnonymousContent(mPreviewDiv.forget());
@@ -212,9 +227,7 @@ LogicalSize nsTextControlFrame::CalcIntrinsicSize(
// Add in the size of the scrollbars for textarea
if (IsTextArea()) {
nsIFrame* first = PrincipalChildList().FirstChild();
nsIScrollableFrame* scrollableFrame = do_QueryFrame(first);
nsIScrollableFrame* scrollableFrame = GetScrollTargetFrame();
NS_ASSERTION(scrollableFrame, "Child must be scrollable");
if (scrollableFrame) {
@@ -332,8 +345,6 @@ already_AddRefed<Element> nsTextControlFrame::CreateEmptyAnonymousDiv(
RefPtr<Element> divElement = NS_NewHTMLDivElement(nodeInfo.forget());
switch (aAnonymousDivType) {
case AnonymousDivType::Root: {
divElement->SetIsNativeAnonymousRoot();
// Make our root node editable
divElement->SetFlags(NODE_IS_EDITABLE);
@@ -410,12 +421,26 @@ nsresult nsTextControlFrame::CreateAnonymousContent(
RefPtr<TextControlElement> textControlElement =
TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
nsresult rv = CreateRootNode();
NS_ENSURE_SUCCESS(rv, rv);
mRootNode = CreateEmptyAnonymousDiv(AnonymousDivType::Root);
if (NS_WARN_IF(!mRootNode)) {
return NS_ERROR_FAILURE;
}
// Bind the frame to its text control
rv = textControlElement->BindToFrame(this);
NS_ENSURE_SUCCESS(rv, rv);
mMutationObserver = new nsAnonDivObserver(*this);
mRootNode->AddMutationObserver(mMutationObserver);
// Bind the frame to its text control.
//
// This can realistically fail in paginated mode, where we may replicate
// fixed-positioned elements and the replicated frame will not get the chance
// to get an editor.
nsresult rv = textControlElement->BindToFrame(this);
if (NS_WARN_IF(NS_FAILED(rv))) {
mRootNode->RemoveMutationObserver(mMutationObserver);
mMutationObserver = nullptr;
mRootNode = nullptr;
return rv;
}
aElements.AppendElement(mRootNode);
CreatePlaceholderIfNeeded();
@@ -477,18 +502,6 @@ void nsTextControlFrame::InitializeEagerlyIfNeeded() {
nsContentUtils::AddScriptRunner(initializer);
}
nsresult nsTextControlFrame::CreateRootNode() {
MOZ_ASSERT(!mRootNode);
mRootNode = CreateEmptyAnonymousDiv(AnonymousDivType::Root);
if (NS_WARN_IF(!mRootNode)) {
return NS_ERROR_FAILURE;
}
mMutationObserver = new nsAnonDivObserver(*this);
mRootNode->AddMutationObserver(mMutationObserver);
return NS_OK;
}
void nsTextControlFrame::CreatePlaceholderIfNeeded() {
MOZ_ASSERT(!mPlaceholderDiv);
@@ -589,6 +602,30 @@ LogicalSize nsTextControlFrame::ComputeAutoSize(
return autoSize;
}
void nsTextControlFrame::ComputeBaseline(const ReflowInput& aReflowInput,
ReflowOutput& aDesiredSize) {
// If we're layout-contained, we have no baseline.
if (aReflowInput.mStyleDisplay->IsContainLayout()) {
mFirstBaseline = NS_INTRINSIC_ISIZE_UNKNOWN;
return;
}
WritingMode wm = aReflowInput.GetWritingMode();
// Calculate the baseline and store it in mFirstBaseline.
nscoord lineHeight = aReflowInput.ComputedBSize();
float inflation = nsLayoutUtils::FontSizeInflationFor(this);
if (!IsSingleLineTextControl()) {
lineHeight = ReflowInput::CalcLineHeight(
GetContent(), Style(), PresContext(), NS_UNCONSTRAINEDSIZE, inflation);
}
RefPtr<nsFontMetrics> fontMet =
nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
mFirstBaseline = nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight,
wm.IsLineInverted()) +
aReflowInput.ComputedLogicalBorderPadding().BStart(wm);
aDesiredSize.SetBlockStartAscent(mFirstBaseline);
}
void nsTextControlFrame::Reflow(nsPresContext* aPresContext,
ReflowOutput& aDesiredSize,
const ReflowInput& aReflowInput,
@@ -613,22 +650,7 @@ void nsTextControlFrame::Reflow(nsPresContext* aPresContext,
aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm));
aDesiredSize.SetSize(wm, finalSize);
if (!aReflowInput.mStyleDisplay->IsContainLayout()) {
// Calculate the baseline and store it in mFirstBaseline.
nscoord lineHeight = aReflowInput.ComputedBSize();
float inflation = nsLayoutUtils::FontSizeInflationFor(this);
if (!IsSingleLineTextControl()) {
lineHeight =
ReflowInput::CalcLineHeight(GetContent(), Style(), PresContext(),
NS_UNCONSTRAINEDSIZE, inflation);
}
RefPtr<nsFontMetrics> fontMet =
nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
mFirstBaseline = nsLayoutUtils::GetCenteredFontBaseline(
fontMet, lineHeight, wm.IsLineInverted()) +
aReflowInput.ComputedLogicalBorderPadding().BStart(wm);
aDesiredSize.SetBlockStartAscent(mFirstBaseline);
} // else: we're layout-contained, and so we have no baseline.
ComputeBaseline(aReflowInput, aDesiredSize);
// overflow handling
aDesiredSize.SetOverflowAreasToDesiredBounds();
@@ -1183,6 +1205,21 @@ bool nsTextControlFrame::GetMaxLength(int32_t* aSize) {
// END IMPLEMENTING NS_IFORMCONTROLFRAME
// NOTE(emilio): This is needed because the root->primary frame map is not set
// up by the time this is called.
static nsIFrame* FindRootNodeFrame(const nsFrameList& aChildList,
const nsIContent* aRoot) {
for (nsIFrame* f : aChildList) {
if (f->GetContent() == aRoot) {
return f;
}
if (nsIFrame* root = FindRootNodeFrame(f->PrincipalChildList(), aRoot)) {
return root;
}
}
return nullptr;
}
void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
nsFrameList& aChildList) {
nsContainerFrame::SetInitialChildList(aListID, aChildList);
@@ -1190,22 +1227,20 @@ void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
return;
}
// Mark the scroll frame as being a reflow root. This will allow
// incremental reflows to be initiated at the scroll frame, rather
// than descending from the root frame of the frame hierarchy.
if (nsIFrame* first = PrincipalChildList().FirstChild()) {
first->AddStateBits(NS_FRAME_REFLOW_ROOT);
// Mark the scroll frame as being a reflow root. This will allow incremental
// reflows to be initiated at the scroll frame, rather than descending from
// the root frame of the frame hierarchy.
if (nsIFrame* frame = FindRootNodeFrame(PrincipalChildList(), mRootNode)) {
frame->AddStateBits(NS_FRAME_REFLOW_ROOT);
TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
auto* textControlElement = TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
textControlElement->InitializeKeyboardEventListeners();
nsPoint* contentScrollPos = GetProperty(ContentScrollPos());
if (contentScrollPos) {
if (nsPoint* contentScrollPos = GetProperty(ContentScrollPos())) {
// If we have a scroll pos stored to be passed to our anonymous
// div, do it here!
nsIStatefulFrame* statefulFrame = do_QueryFrame(first);
nsIStatefulFrame* statefulFrame = do_QueryFrame(frame);
NS_ASSERTION(statefulFrame,
"unexpected type of frame for the anonymous div");
UniquePtr<PresState> fakePresState = NewPresState();
@@ -1214,12 +1249,13 @@ void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
RemoveProperty(ContentScrollPos());
delete contentScrollPos;
}
} else {
MOZ_ASSERT(!mRootNode || PrincipalChildList().IsEmpty());
}
}
void nsTextControlFrame::SetValueChanged(bool aValueChanged) {
TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
auto* textControlElement = TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
if (mPlaceholderDiv) {
@@ -1284,8 +1320,7 @@ nsresult nsTextControlFrame::UpdateValueDisplay(bool aNotify,
}
if (aBeforeEditorInit && value.IsEmpty()) {
nsIContent* node = mRootNode->GetFirstChild();
if (node) {
if (nsIContent* node = mRootNode->GetFirstChild()) {
mRootNode->RemoveChildNode(node, true);
}
return NS_OK;
@@ -1310,20 +1345,15 @@ nsTextControlFrame::GetOwnedSelectionController(
}
nsFrameSelection* nsTextControlFrame::GetOwnedFrameSelection() {
TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
auto* textControlElement = TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
return textControlElement->GetConstFrameSelection();
}
UniquePtr<PresState> nsTextControlFrame::SaveState() {
if (mRootNode) {
// Query the nsIStatefulFrame from the HTMLScrollFrame
nsIStatefulFrame* scrollStateFrame =
do_QueryFrame(mRootNode->GetPrimaryFrame());
if (scrollStateFrame) {
return scrollStateFrame->SaveState();
}
if (nsIStatefulFrame* scrollStateFrame =
do_QueryFrame(GetScrollTargetFrame())) {
return scrollStateFrame->SaveState();
}
return nullptr;
@@ -1333,13 +1363,10 @@ NS_IMETHODIMP
nsTextControlFrame::RestoreState(PresState* aState) {
NS_ENSURE_ARG_POINTER(aState);
if (mRootNode) {
// Query the nsIStatefulFrame from the HTMLScrollFrame
nsIStatefulFrame* scrollStateFrame =
do_QueryFrame(mRootNode->GetPrimaryFrame());
if (scrollStateFrame) {
return scrollStateFrame->RestoreState(aState);
}
// Query the nsIStatefulFrame from the HTMLScrollFrame
if (nsIStatefulFrame* scrollStateFrame =
do_QueryFrame(GetScrollTargetFrame())) {
return scrollStateFrame->RestoreState(aState);
}
// Most likely, we don't have our anonymous content constructed yet, which
@@ -1363,29 +1390,42 @@ void nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
*/
DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame");
TextControlElement* textControlElement =
TextControlElement::FromNode(GetContent());
MOZ_ASSERT(textControlElement);
auto* control = TextControlElement::FromNode(GetContent());
MOZ_ASSERT(control);
DisplayBorderBackgroundOutline(aBuilder, aLists);
nsIFrame* kid = mFrames.FirstChild();
// Redirect all lists to the Content list so that nothing can escape, ie
// opacity creating stacking contexts that then get sorted with stacking
// contexts external to us.
nsDisplayList* content = aLists.Content();
nsDisplayListSet set(content, content, content, content, content, content);
while (kid) {
// If the frame is the placeholder or preview frame, we should only show
// it if it has to be visible.
if (!((kid->GetContent() == mPlaceholderDiv &&
!textControlElement->GetPlaceholderVisibility()) ||
(kid->GetContent() == mPreviewDiv &&
!textControlElement->GetPreviewVisibility()))) {
BuildDisplayListForChild(aBuilder, kid, set, 0);
for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
nsIContent* kidContent = kid->GetContent();
Maybe<DisplayListClipState::AutoSaveRestore> overlayTextClip;
if (kidContent == mPlaceholderDiv && !control->GetPlaceholderVisibility()) {
continue;
}
kid = kid->GetNextSibling();
if (kidContent == mPreviewDiv && !control->GetPreviewVisibility()) {
continue;
}
// Clip the preview text to the root box, so that it doesn't, e.g., overlay
// with our <input type="number"> spin buttons.
//
// For other input types, this will be a noop since we size the root via
// ReflowTextControlChild, which sets the same available space for all
// children.
if (kidContent == mPlaceholderDiv || kidContent == mPreviewDiv) {
if (mRootNode) {
if (auto* root = mRootNode->GetPrimaryFrame()) {
overlayTextClip.emplace(aBuilder);
nsRect rootBox(aBuilder->ToReferenceFrame(root), root->GetSize());
overlayTextClip->ClipContentDescendants(rootBox);
}
}
}
BuildDisplayListForChild(aBuilder, kid, set, 0);
}
}