1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 #include "mozilla/DebugOnly.h"
8
9 #include "gfxContext.h"
10 #include "nsCOMPtr.h"
11 #include "nsFontMetrics.h"
12 #include "nsTextControlFrame.h"
13 #include "nsIEditor.h"
14 #include "nsCaret.h"
15 #include "nsCSSPseudoElements.h"
16 #include "nsDisplayList.h"
17 #include "nsGenericHTMLElement.h"
18 #include "nsTextFragment.h"
19 #include "nsNameSpaceManager.h"
20
21 #include "nsIContent.h"
22 #include "nsIScrollableFrame.h"
23 #include "nsPresContext.h"
24 #include "nsGkAtoms.h"
25 #include "nsLayoutUtils.h"
26
27 #include <algorithm>
28 #include "nsRange.h" //for selection setting helper func
29 #include "nsINode.h"
30 #include "nsPIDOMWindow.h" //needed for notify selection changed to update the menus ect.
31 #include "nsQueryObject.h"
32 #include "nsILayoutHistoryState.h"
33
34 #include "nsFocusManager.h"
35 #include "mozilla/PresShell.h"
36 #include "mozilla/PresState.h"
37 #include "nsAttrValueInlines.h"
38 #include "mozilla/dom/Selection.h"
39 #include "nsContentUtils.h"
40 #include "nsTextNode.h"
41 #include "mozilla/dom/HTMLInputElement.h"
42 #include "mozilla/dom/HTMLTextAreaElement.h"
43 #include "mozilla/dom/ScriptSettings.h"
44 #include "mozilla/dom/Text.h"
45 #include "mozilla/MathAlgorithms.h"
46 #include "nsFrameSelection.h"
47
48 #define DEFAULT_COLUMN_WIDTH 20
49
50 using namespace mozilla;
51 using namespace mozilla::dom;
52
NS_NewTextControlFrame(PresShell * aPresShell,ComputedStyle * aStyle)53 nsIFrame* NS_NewTextControlFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
54 return new (aPresShell)
55 nsTextControlFrame(aStyle, aPresShell->GetPresContext());
56 }
57
58 NS_IMPL_FRAMEARENA_HELPERS(nsTextControlFrame)
59
NS_QUERYFRAME_HEAD(nsTextControlFrame)60 NS_QUERYFRAME_HEAD(nsTextControlFrame)
61 NS_QUERYFRAME_ENTRY(nsTextControlFrame)
62 NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
63 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
64 NS_QUERYFRAME_ENTRY(nsITextControlFrame)
65 NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
66 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
67
68 #ifdef ACCESSIBILITY
69 a11y::AccType nsTextControlFrame::AccessibleType() {
70 return a11y::eHTMLTextFieldType;
71 }
72 #endif
73
74 #ifdef DEBUG
75 class EditorInitializerEntryTracker {
76 public:
EditorInitializerEntryTracker(nsTextControlFrame & frame)77 explicit EditorInitializerEntryTracker(nsTextControlFrame& frame)
78 : mFrame(frame), mFirstEntry(false) {
79 if (!mFrame.mInEditorInitialization) {
80 mFrame.mInEditorInitialization = true;
81 mFirstEntry = true;
82 }
83 }
~EditorInitializerEntryTracker()84 ~EditorInitializerEntryTracker() {
85 if (mFirstEntry) {
86 mFrame.mInEditorInitialization = false;
87 }
88 }
EnteredMoreThanOnce() const89 bool EnteredMoreThanOnce() const { return !mFirstEntry; }
90
91 private:
92 nsTextControlFrame& mFrame;
93 bool mFirstEntry;
94 };
95 #endif
96
97 class nsTextControlFrame::nsAnonDivObserver final
98 : public nsStubMutationObserver {
99 public:
nsAnonDivObserver(nsTextControlFrame & aFrame)100 explicit nsAnonDivObserver(nsTextControlFrame& aFrame) : mFrame(aFrame) {}
101 NS_DECL_ISUPPORTS
102 NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
103 NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
104 NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
105 NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
106
107 private:
108 ~nsAnonDivObserver() = default;
109 nsTextControlFrame& mFrame;
110 };
111
nsTextControlFrame(ComputedStyle * aStyle,nsPresContext * aPresContext,nsIFrame::ClassID aClassID)112 nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle,
113 nsPresContext* aPresContext,
114 nsIFrame::ClassID aClassID)
115 : nsContainerFrame(aStyle, aPresContext, aClassID),
116 mFirstBaseline(NS_INTRINSIC_ISIZE_UNKNOWN),
117 mEditorHasBeenInitialized(false),
118 mIsProcessing(false)
119 #ifdef DEBUG
120 ,
121 mInEditorInitialization(false)
122 #endif
123 {
124 ClearCachedValue();
125 }
126
127 nsTextControlFrame::~nsTextControlFrame() = default;
128
GetScrollTargetFrame() const129 nsIScrollableFrame* nsTextControlFrame::GetScrollTargetFrame() const {
130 if (!mRootNode) {
131 return nullptr;
132 }
133 return do_QueryFrame(mRootNode->GetPrimaryFrame());
134 }
135
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)136 void nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot,
137 PostDestroyData& aPostDestroyData) {
138 RemoveProperty(TextControlInitializer());
139
140 // Unbind the text editor state object from the frame. The editor will live
141 // on, but things like controllers will be released.
142 RefPtr<TextControlElement> textControlElement =
143 TextControlElement::FromNode(GetContent());
144 MOZ_ASSERT(textControlElement);
145 textControlElement->UnbindFromFrame(this);
146
147 if (mMutationObserver) {
148 mRootNode->RemoveMutationObserver(mMutationObserver);
149 mMutationObserver = nullptr;
150 }
151
152 // If we're a subclass like nsNumberControlFrame, then it owns the root of the
153 // anonymous subtree where mRootNode is.
154 aPostDestroyData.AddAnonymousContent(mRootNode.forget());
155 aPostDestroyData.AddAnonymousContent(mPlaceholderDiv.forget());
156 aPostDestroyData.AddAnonymousContent(mPreviewDiv.forget());
157
158 nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
159 }
160
CalcIntrinsicSize(gfxContext * aRenderingContext,WritingMode aWM,float aFontSizeInflation) const161 LogicalSize nsTextControlFrame::CalcIntrinsicSize(
162 gfxContext* aRenderingContext, WritingMode aWM,
163 float aFontSizeInflation) const {
164 LogicalSize intrinsicSize(aWM);
165 // Get leading and the Average/MaxAdvance char width
166 nscoord lineHeight = 0;
167 nscoord charWidth = 0;
168 nscoord charMaxAdvance = 0;
169
170 RefPtr<nsFontMetrics> fontMet =
171 nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation);
172
173 lineHeight =
174 ReflowInput::CalcLineHeight(GetContent(), Style(), PresContext(),
175 NS_UNCONSTRAINEDSIZE, aFontSizeInflation);
176 charWidth = fontMet->AveCharWidth();
177 charMaxAdvance = fontMet->MaxAdvance();
178
179 // Set the width equal to the width in characters
180 int32_t cols = GetCols();
181 intrinsicSize.ISize(aWM) = cols * charWidth;
182
183 // To better match IE, take the maximum character width(in twips) and remove
184 // 4 pixels add this on as additional padding(internalPadding). But only do
185 // this if we think we have a fixed-width font.
186 if (mozilla::Abs(charWidth - charMaxAdvance) >
187 (unsigned)nsPresContext::CSSPixelsToAppUnits(1)) {
188 nscoord internalPadding =
189 std::max(0, charMaxAdvance - nsPresContext::CSSPixelsToAppUnits(4));
190 nscoord t = nsPresContext::CSSPixelsToAppUnits(1);
191 // Round to a multiple of t
192 nscoord rest = internalPadding % t;
193 if (rest < t - rest) {
194 internalPadding -= rest;
195 } else {
196 internalPadding += t - rest;
197 }
198 // Now add the extra padding on (so that small input sizes work well)
199 intrinsicSize.ISize(aWM) += internalPadding;
200 } else {
201 // This is to account for the anonymous <br> having a 1 twip width
202 // in Full Standards mode, see BRFrame::Reflow and bug 228752.
203 if (PresContext()->CompatibilityMode() == eCompatibility_FullStandards) {
204 intrinsicSize.ISize(aWM) += 1;
205 }
206 }
207
208 // Increment width with cols * letter-spacing.
209 {
210 const StyleLength& letterSpacing = StyleText()->mLetterSpacing;
211 if (!letterSpacing.IsZero()) {
212 intrinsicSize.ISize(aWM) += cols * letterSpacing.ToAppUnits();
213 }
214 }
215
216 // Set the height equal to total number of rows (times the height of each
217 // line, of course)
218 intrinsicSize.BSize(aWM) = lineHeight * GetRows();
219
220 // Add in the size of the scrollbars for textarea
221 if (IsTextArea()) {
222 nsIScrollableFrame* scrollableFrame = GetScrollTargetFrame();
223 NS_ASSERTION(scrollableFrame, "Child must be scrollable");
224
225 if (scrollableFrame) {
226 LogicalMargin scrollbarSizes(
227 aWM, scrollableFrame->GetDesiredScrollbarSizes(PresContext(),
228 aRenderingContext));
229
230 intrinsicSize.ISize(aWM) += scrollbarSizes.IStartEnd(aWM);
231 intrinsicSize.BSize(aWM) += scrollbarSizes.BStartEnd(aWM);
232 }
233 }
234 return intrinsicSize;
235 }
236
EnsureEditorInitialized()237 nsresult nsTextControlFrame::EnsureEditorInitialized() {
238 // This method initializes our editor, if needed.
239
240 // This code used to be called from CreateAnonymousContent(), but
241 // when the editor set the initial string, it would trigger a
242 // PresShell listener which called FlushPendingNotifications()
243 // during frame construction. This was causing other form controls
244 // to display wrong values. Additionally, calling this every time
245 // a text frame control is instantiated means that we're effectively
246 // instantiating the editor for all text fields, even if they
247 // never get used. So, now this method is being called lazily only
248 // when we actually need an editor.
249
250 if (mEditorHasBeenInitialized) return NS_OK;
251
252 Document* doc = mContent->GetComposedDoc();
253 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
254
255 AutoWeakFrame weakFrame(this);
256
257 // Flush out content on our document. Have to do this, because script
258 // blockers don't prevent the sink flushing out content and notifying in the
259 // process, which can destroy frames.
260 doc->FlushPendingNotifications(FlushType::ContentAndNotify);
261 NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_ERROR_FAILURE);
262
263 // Make sure that editor init doesn't do things that would kill us off
264 // (especially off the script blockers it'll create for its DOM mutations).
265 {
266 RefPtr<TextControlElement> textControlElement =
267 TextControlElement::FromNode(GetContent());
268 MOZ_ASSERT(textControlElement);
269
270 // Hide selection changes during the initialization, as webpages should not
271 // be aware of these initializations
272 AutoHideSelectionChanges hideSelectionChanges(
273 textControlElement->GetConstFrameSelection());
274
275 nsAutoScriptBlocker scriptBlocker;
276
277 // Time to mess with our security context... See comments in GetValue()
278 // for why this is needed.
279 mozilla::dom::AutoNoJSAPI nojsapi;
280
281 // Make sure that we try to focus the content even if the method fails
282 class EnsureSetFocus {
283 public:
284 explicit EnsureSetFocus(nsTextControlFrame* aFrame) : mFrame(aFrame) {}
285 ~EnsureSetFocus() {
286 if (nsContentUtils::IsFocusedContent(mFrame->GetContent()))
287 mFrame->SetFocus(true, false);
288 }
289
290 private:
291 nsTextControlFrame* mFrame;
292 };
293 EnsureSetFocus makeSureSetFocusHappens(this);
294
295 #ifdef DEBUG
296 // Make sure we are not being called again until we're finished.
297 // If reentrancy happens, just pretend that we don't have an editor.
298 const EditorInitializerEntryTracker tracker(*this);
299 NS_ASSERTION(!tracker.EnteredMoreThanOnce(),
300 "EnsureEditorInitialized has been called while a previous "
301 "call was in progress");
302 #endif
303
304 // Create an editor for the frame, if one doesn't already exist
305 nsresult rv = textControlElement->CreateEditor();
306 NS_ENSURE_SUCCESS(rv, rv);
307 NS_ENSURE_STATE(weakFrame.IsAlive());
308
309 // Set mEditorHasBeenInitialized so that subsequent calls will use the
310 // editor.
311 mEditorHasBeenInitialized = true;
312
313 if (weakFrame.IsAlive()) {
314 uint32_t position = 0;
315
316 // Set the selection to the end of the text field (bug 1287655),
317 // but only if the contents has changed (bug 1337392).
318 if (textControlElement->ValueChanged()) {
319 nsAutoString val;
320 textControlElement->GetTextEditorValue(val, true);
321 position = val.Length();
322 }
323
324 SetSelectionEndPoints(position, position);
325 }
326 }
327 NS_ENSURE_STATE(weakFrame.IsAlive());
328 return NS_OK;
329 }
330
MakeAnonElement(PseudoStyleType aPseudoType,Element * aParent,nsAtom * aTag) const331 already_AddRefed<Element> nsTextControlFrame::MakeAnonElement(
332 PseudoStyleType aPseudoType, Element* aParent, nsAtom* aTag) const {
333 MOZ_ASSERT(aPseudoType != PseudoStyleType::NotPseudo);
334 Document* doc = PresContext()->Document();
335 RefPtr<Element> element = doc->CreateHTMLElement(aTag);
336 element->SetPseudoElementType(aPseudoType);
337 if (aPseudoType == PseudoStyleType::mozTextControlEditingRoot) {
338 // Make our root node editable
339 element->SetFlags(NODE_IS_EDITABLE);
340 }
341
342 if (aPseudoType == PseudoStyleType::mozNumberSpinDown ||
343 aPseudoType == PseudoStyleType::mozNumberSpinUp) {
344 element->SetAttr(kNameSpaceID_None, nsGkAtoms::aria_hidden, u"true"_ns,
345 false);
346 }
347
348 if (aParent) {
349 aParent->AppendChildTo(element, false, IgnoreErrors());
350 }
351
352 return element.forget();
353 }
354
MakeAnonDivWithTextNode(PseudoStyleType aPseudoType) const355 already_AddRefed<Element> nsTextControlFrame::MakeAnonDivWithTextNode(
356 PseudoStyleType aPseudoType) const {
357 RefPtr<Element> div = MakeAnonElement(aPseudoType);
358
359 // Create the text node for the anonymous <div> element.
360 nsNodeInfoManager* nim = div->OwnerDoc()->NodeInfoManager();
361 RefPtr<nsTextNode> textNode = new (nim) nsTextNode(nim);
362 // If the anonymous div element is not for the placeholder, we should
363 // mark the text node as "maybe modified frequently" for avoiding ASCII
364 // range checks at every input.
365 if (aPseudoType != PseudoStyleType::placeholder) {
366 textNode->MarkAsMaybeModifiedFrequently();
367 // Additionally, this is a password field, the text node needs to be
368 // marked as "maybe masked" unless it's in placeholder.
369 if (IsPasswordTextControl()) {
370 textNode->MarkAsMaybeMasked();
371 }
372 }
373 div->AppendChildTo(textNode, false, IgnoreErrors());
374 return div.forget();
375 }
376
CreateAnonymousContent(nsTArray<ContentInfo> & aElements)377 nsresult nsTextControlFrame::CreateAnonymousContent(
378 nsTArray<ContentInfo>& aElements) {
379 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
380 MOZ_ASSERT(mContent, "We should have a content!");
381
382 AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
383
384 RefPtr<TextControlElement> textControlElement =
385 TextControlElement::FromNode(GetContent());
386 MOZ_ASSERT(textControlElement);
387 mRootNode = MakeAnonElement(PseudoStyleType::mozTextControlEditingRoot);
388 if (NS_WARN_IF(!mRootNode)) {
389 return NS_ERROR_FAILURE;
390 }
391
392 mMutationObserver = new nsAnonDivObserver(*this);
393 mRootNode->AddMutationObserver(mMutationObserver);
394
395 // Bind the frame to its text control.
396 //
397 // This can realistically fail in paginated mode, where we may replicate
398 // fixed-positioned elements and the replicated frame will not get the chance
399 // to get an editor.
400 nsresult rv = textControlElement->BindToFrame(this);
401 if (NS_WARN_IF(NS_FAILED(rv))) {
402 mRootNode->RemoveMutationObserver(mMutationObserver);
403 mMutationObserver = nullptr;
404 mRootNode = nullptr;
405 return rv;
406 }
407
408 CreatePlaceholderIfNeeded();
409 if (mPlaceholderDiv) {
410 aElements.AppendElement(mPlaceholderDiv);
411 }
412 CreatePreviewIfNeeded();
413 if (mPreviewDiv) {
414 aElements.AppendElement(mPreviewDiv);
415 }
416
417 // NOTE(emilio): We want the root node always after the placeholder so that
418 // background on the placeholder doesn't obscure the caret.
419 aElements.AppendElement(mRootNode);
420
421 rv = UpdateValueDisplay(false);
422 NS_ENSURE_SUCCESS(rv, rv);
423
424 InitializeEagerlyIfNeeded();
425 return NS_OK;
426 }
427
ShouldInitializeEagerly() const428 bool nsTextControlFrame::ShouldInitializeEagerly() const {
429 // textareas are eagerly initialized.
430 if (!IsSingleLineTextControl()) {
431 return true;
432 }
433
434 // Also, input elements which have a cached selection should get eager
435 // editor initialization.
436 TextControlElement* textControlElement =
437 TextControlElement::FromNode(GetContent());
438 MOZ_ASSERT(textControlElement);
439 if (textControlElement->HasCachedSelection()) {
440 return true;
441 }
442
443 // So do input text controls with spellcheck=true
444 if (auto* htmlElement = nsGenericHTMLElement::FromNode(mContent)) {
445 if (htmlElement->Spellcheck()) {
446 return true;
447 }
448 }
449
450 return false;
451 }
452
InitializeEagerlyIfNeeded()453 void nsTextControlFrame::InitializeEagerlyIfNeeded() {
454 MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
455 "Someone forgot a script blocker?");
456 if (!ShouldInitializeEagerly()) {
457 return;
458 }
459
460 EditorInitializer* initializer = new EditorInitializer(this);
461 SetProperty(TextControlInitializer(), initializer);
462 nsContentUtils::AddScriptRunner(initializer);
463 }
464
CreatePlaceholderIfNeeded()465 void nsTextControlFrame::CreatePlaceholderIfNeeded() {
466 MOZ_ASSERT(!mPlaceholderDiv);
467
468 // Do we need a placeholder node?
469 nsAutoString placeholder;
470 if (!mContent->AsElement()->GetAttr(nsGkAtoms::placeholder, placeholder)) {
471 return;
472 }
473
474 mPlaceholderDiv = MakeAnonDivWithTextNode(PseudoStyleType::placeholder);
475 UpdatePlaceholderText(placeholder, false);
476 }
477
PlaceholderChanged(const nsAttrValue * aOld,const nsAttrValue * aNew)478 void nsTextControlFrame::PlaceholderChanged(const nsAttrValue* aOld,
479 const nsAttrValue* aNew) {
480 if (!aOld || !aNew) {
481 return; // This should be handled by GetAttributeChangeHint.
482 }
483
484 // If we've changed the attribute but we still haven't reframed, there's
485 // nothing to do either.
486 if (!mPlaceholderDiv) {
487 return;
488 }
489
490 nsAutoString placeholder;
491 aNew->ToString(placeholder);
492 UpdatePlaceholderText(placeholder, true);
493 }
494
UpdatePlaceholderText(nsString & aPlaceholder,bool aNotify)495 void nsTextControlFrame::UpdatePlaceholderText(nsString& aPlaceholder,
496 bool aNotify) {
497 MOZ_DIAGNOSTIC_ASSERT(mPlaceholderDiv);
498 MOZ_DIAGNOSTIC_ASSERT(mPlaceholderDiv->GetFirstChild());
499
500 if (IsTextArea()) { // <textarea>s preserve newlines...
501 nsContentUtils::PlatformToDOMLineBreaks(aPlaceholder);
502 } else { // ...<input>s don't
503 nsContentUtils::RemoveNewlines(aPlaceholder);
504 }
505
506 mPlaceholderDiv->GetFirstChild()->AsText()->SetText(aPlaceholder, aNotify);
507 }
508
CreatePreviewIfNeeded()509 void nsTextControlFrame::CreatePreviewIfNeeded() {
510 RefPtr<TextControlElement> textControlElement =
511 TextControlElement::FromNode(GetContent());
512 MOZ_ASSERT(textControlElement);
513 if (!textControlElement->IsPreviewEnabled()) {
514 return;
515 }
516
517 mPreviewDiv = MakeAnonDivWithTextNode(PseudoStyleType::mozTextControlPreview);
518 }
519
AppendAnonymousContentTo(nsTArray<nsIContent * > & aElements,uint32_t aFilter)520 void nsTextControlFrame::AppendAnonymousContentTo(
521 nsTArray<nsIContent*>& aElements, uint32_t aFilter) {
522 if (mPlaceholderDiv && !(aFilter & nsIContent::eSkipPlaceholderContent)) {
523 aElements.AppendElement(mPlaceholderDiv);
524 }
525
526 if (mPreviewDiv) {
527 aElements.AppendElement(mPreviewDiv);
528 }
529
530 aElements.AppendElement(mRootNode);
531 }
532
GetPrefISize(gfxContext * aRenderingContext)533 nscoord nsTextControlFrame::GetPrefISize(gfxContext* aRenderingContext) {
534 nscoord result = 0;
535 DISPLAY_PREF_INLINE_SIZE(this, result);
536 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
537 WritingMode wm = GetWritingMode();
538 result = CalcIntrinsicSize(aRenderingContext, wm, inflation).ISize(wm);
539 return result;
540 }
541
GetMinISize(gfxContext * aRenderingContext)542 nscoord nsTextControlFrame::GetMinISize(gfxContext* aRenderingContext) {
543 // Our min inline size is just our preferred width if we have auto inline size
544 nscoord result;
545 DISPLAY_MIN_INLINE_SIZE(this, result);
546 result = GetPrefISize(aRenderingContext);
547 return result;
548 }
549
ComputeAutoSize(gfxContext * aRenderingContext,WritingMode aWM,const LogicalSize & aCBSize,nscoord aAvailableISize,const LogicalSize & aMargin,const LogicalSize & aBorderPadding,const StyleSizeOverrides & aSizeOverrides,ComputeSizeFlags aFlags)550 LogicalSize nsTextControlFrame::ComputeAutoSize(
551 gfxContext* aRenderingContext, WritingMode aWM, const LogicalSize& aCBSize,
552 nscoord aAvailableISize, const LogicalSize& aMargin,
553 const LogicalSize& aBorderPadding, const StyleSizeOverrides& aSizeOverrides,
554 ComputeSizeFlags aFlags) {
555 float inflation = nsLayoutUtils::FontSizeInflationFor(this);
556 LogicalSize autoSize = CalcIntrinsicSize(aRenderingContext, aWM, inflation);
557
558 // Note: nsContainerFrame::ComputeAutoSize only computes the inline-size (and
559 // only for 'auto'), the block-size it returns is always NS_UNCONSTRAINEDSIZE.
560 const auto& styleISize = aSizeOverrides.mStyleISize
561 ? *aSizeOverrides.mStyleISize
562 : StylePosition()->ISize(aWM);
563 if (styleISize.IsAuto()) {
564 if (aFlags.contains(ComputeSizeFlag::IClampMarginBoxMinSize)) {
565 // CalcIntrinsicSize isn't aware of grid-item margin-box clamping, so we
566 // fall back to nsContainerFrame's ComputeAutoSize to handle that.
567 // XXX maybe a font-inflation issue here? (per the assertion below).
568 autoSize.ISize(aWM) =
569 nsContainerFrame::ComputeAutoSize(
570 aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin,
571 aBorderPadding, aSizeOverrides, aFlags)
572 .ISize(aWM);
573 }
574 #ifdef DEBUG
575 else {
576 LogicalSize ancestorAutoSize = nsContainerFrame::ComputeAutoSize(
577 aRenderingContext, aWM, aCBSize, aAvailableISize, aMargin,
578 aBorderPadding, aSizeOverrides, aFlags);
579 // Disabled when there's inflation; see comment in GetXULPrefSize.
580 MOZ_ASSERT(inflation != 1.0f ||
581 ancestorAutoSize.ISize(aWM) == autoSize.ISize(aWM),
582 "Incorrect size computed by ComputeAutoSize?");
583 }
584 #endif
585 }
586 return autoSize;
587 }
588
ComputeBaseline(const nsIFrame * aFrame,const ReflowInput & aReflowInput,bool aForSingleLineControl)589 Maybe<nscoord> nsTextControlFrame::ComputeBaseline(
590 const nsIFrame* aFrame, const ReflowInput& aReflowInput,
591 bool aForSingleLineControl) {
592 // If we're layout-contained, we have no baseline.
593 if (aReflowInput.mStyleDisplay->IsContainLayout()) {
594 return Nothing();
595 }
596 WritingMode wm = aReflowInput.GetWritingMode();
597
598 // Calculate the baseline and store it in mFirstBaseline.
599 nscoord lineHeight = aReflowInput.ComputedBSize();
600 float inflation = nsLayoutUtils::FontSizeInflationFor(aFrame);
601 if (!aForSingleLineControl || lineHeight == NS_UNCONSTRAINEDSIZE) {
602 lineHeight = ReflowInput::CalcLineHeight(
603 aFrame->GetContent(), aFrame->Style(), aFrame->PresContext(),
604 NS_UNCONSTRAINEDSIZE, inflation);
605 }
606 RefPtr<nsFontMetrics> fontMet =
607 nsLayoutUtils::GetFontMetricsForFrame(aFrame, inflation);
608 return Some(nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight,
609 wm.IsLineInverted()) +
610 aReflowInput.ComputedLogicalBorderPadding(wm).BStart(wm));
611 }
612
IsButtonBox(const nsIFrame * aFrame)613 static bool IsButtonBox(const nsIFrame* aFrame) {
614 auto pseudoType = aFrame->Style()->GetPseudoType();
615 return pseudoType == PseudoStyleType::mozNumberSpinBox ||
616 pseudoType == PseudoStyleType::mozSearchClearButton;
617 }
618
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)619 void nsTextControlFrame::Reflow(nsPresContext* aPresContext,
620 ReflowOutput& aDesiredSize,
621 const ReflowInput& aReflowInput,
622 nsReflowStatus& aStatus) {
623 MarkInReflow();
624 DO_GLOBAL_REFLOW_COUNT("nsTextControlFrame");
625 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
626 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
627
628 // set values of reflow's out parameters
629 WritingMode wm = aReflowInput.GetWritingMode();
630 aDesiredSize.SetSize(wm, aReflowInput.ComputedSizeWithBorderPadding(wm));
631
632 {
633 auto baseline =
634 ComputeBaseline(this, aReflowInput, IsSingleLineTextControl());
635 mFirstBaseline = baseline.valueOr(NS_INTRINSIC_ISIZE_UNKNOWN);
636 if (baseline) {
637 aDesiredSize.SetBlockStartAscent(*baseline);
638 }
639 }
640
641 // overflow handling
642 aDesiredSize.SetOverflowAreasToDesiredBounds();
643
644 nsIFrame* buttonBox = [&]() -> nsIFrame* {
645 nsIFrame* last = mFrames.LastChild();
646 if (!last || !IsButtonBox(last)) {
647 return nullptr;
648 }
649 return last;
650 }();
651
652 // Reflow the button box first, so that we can use its size for the other
653 // frames.
654 nscoord buttonBoxISize = 0;
655 if (buttonBox) {
656 ReflowTextControlChild(buttonBox, aPresContext, aReflowInput, aStatus,
657 aDesiredSize, buttonBoxISize);
658 }
659
660 // perform reflow on all kids
661 nsIFrame* kid = mFrames.FirstChild();
662 while (kid) {
663 if (kid != buttonBox) {
664 MOZ_ASSERT(!IsButtonBox(kid),
665 "Should only have one button box, and should be last");
666 ReflowTextControlChild(kid, aPresContext, aReflowInput, aStatus,
667 aDesiredSize, buttonBoxISize);
668 }
669 kid = kid->GetNextSibling();
670 }
671
672 // take into account css properties that affect overflow handling
673 FinishAndStoreOverflow(&aDesiredSize);
674
675 aStatus.Reset(); // This type of frame can't be split.
676 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
677 }
678
ReflowTextControlChild(nsIFrame * aKid,nsPresContext * aPresContext,const ReflowInput & aReflowInput,nsReflowStatus & aStatus,ReflowOutput & aParentDesiredSize,nscoord & aButtonBoxISize)679 void nsTextControlFrame::ReflowTextControlChild(
680 nsIFrame* aKid, nsPresContext* aPresContext,
681 const ReflowInput& aReflowInput, nsReflowStatus& aStatus,
682 ReflowOutput& aParentDesiredSize, nscoord& aButtonBoxISize) {
683 const WritingMode outerWM = aReflowInput.GetWritingMode();
684 // compute available size and frame offsets for child
685 const WritingMode wm = aKid->GetWritingMode();
686 LogicalSize availSize = aReflowInput.ComputedSizeWithPadding(wm);
687 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
688
689 bool isButtonBox = IsButtonBox(aKid);
690
691 ReflowInput kidReflowInput(aPresContext, aReflowInput, aKid, availSize,
692 Nothing(), ReflowInput::InitFlag::CallerWillInit);
693
694 // Override padding with our computed padding in case we got it from theming
695 // or percentage, if we're not the button box.
696 auto overridePadding =
697 isButtonBox ? Nothing() : Some(aReflowInput.ComputedLogicalPadding(wm));
698 // We want to let our button box fill the frame in the block axis, up to the
699 // edge of the control's border. So, we use the control's padding-box as the
700 // containing block size for our button box.
701 auto overrideCBSize =
702 isButtonBox ? Some(aReflowInput.ComputedSizeWithPadding(wm)) : Nothing();
703 kidReflowInput.Init(aPresContext, overrideCBSize, Nothing(), overridePadding);
704
705 LogicalPoint position(wm);
706 const auto& bp = aReflowInput.ComputedLogicalBorderPadding(outerWM);
707
708 if (!isButtonBox) {
709 MOZ_ASSERT(wm == outerWM,
710 "Shouldn't have to care about orthogonal "
711 "writing-modes and such inside the control, "
712 "except for the number spin-box which forces "
713 "horizontal-tb");
714
715 // Offset the frame by the size of the parent's border
716 const auto& padding = aReflowInput.ComputedLogicalPadding(wm);
717 position.B(wm) = bp.BStart(wm) - padding.BStart(wm);
718 position.I(wm) = bp.IStart(wm) - padding.IStart(wm);
719
720 // Set computed width and computed height for the child (the button box is
721 // the only exception, which has an auto size).
722 kidReflowInput.SetComputedISize(
723 std::max(0, aReflowInput.ComputedISize() - aButtonBoxISize));
724 kidReflowInput.SetComputedBSize(aReflowInput.ComputedBSize());
725 }
726
727 // reflow the child
728 ReflowOutput desiredSize(aReflowInput);
729 const nsSize containerSize =
730 aReflowInput.ComputedSizeWithBorderPadding(outerWM).GetPhysicalSize(
731 outerWM);
732 ReflowChild(aKid, aPresContext, desiredSize, kidReflowInput, wm, position,
733 containerSize, ReflowChildFlags::Default, aStatus);
734
735 if (isButtonBox) {
736 auto size = desiredSize.Size(outerWM);
737 // Center button in the block axis of our content box. We do this
738 // computation in terms of outerWM for simplicity.
739 position = LogicalPoint(outerWM);
740 position.B(outerWM) =
741 bp.BStart(outerWM) +
742 (aReflowInput.ComputedBSize() - size.BSize(outerWM)) / 2;
743 // Align to the inline-end of the content box.
744 position.I(outerWM) =
745 bp.IStart(outerWM) + aReflowInput.ComputedISize() - size.ISize(outerWM);
746 position = position.ConvertTo(wm, outerWM, containerSize);
747 aButtonBoxISize = size.ISize(outerWM);
748 }
749
750 // place the child
751 FinishReflowChild(aKid, aPresContext, desiredSize, &kidReflowInput, wm,
752 position, containerSize, ReflowChildFlags::Default);
753
754 // consider the overflow
755 aParentDesiredSize.mOverflowAreas.UnionWith(desiredSize.mOverflowAreas);
756 }
757
GetXULMinSize(nsBoxLayoutState & aState)758 nsSize nsTextControlFrame::GetXULMinSize(nsBoxLayoutState& aState) {
759 // XXXbz why? Why not the nsBoxFrame sizes?
760 return nsIFrame::GetUncachedXULMinSize(aState);
761 }
762
IsXULCollapsed()763 bool nsTextControlFrame::IsXULCollapsed() {
764 // We're never collapsed in the box sense.
765 return false;
766 }
767
768 // IMPLEMENTING NS_IFORMCONTROLFRAME
SetFocus(bool aOn,bool aRepaint)769 void nsTextControlFrame::SetFocus(bool aOn, bool aRepaint) {
770 TextControlElement* textControlElement =
771 TextControlElement::FromNode(GetContent());
772 MOZ_ASSERT(textControlElement);
773
774 // If 'dom.placeholeder.show_on_focus' preference is 'false', focusing or
775 // blurring the frame can have an impact on the placeholder visibility.
776 if (!aOn) {
777 return;
778 }
779
780 nsISelectionController* selCon = textControlElement->GetSelectionController();
781 if (!selCon) {
782 return;
783 }
784
785 RefPtr<Selection> ourSel =
786 selCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
787 if (!ourSel) {
788 return;
789 }
790
791 mozilla::PresShell* presShell = PresContext()->GetPresShell();
792 RefPtr<nsCaret> caret = presShell->GetCaret();
793 if (!caret) {
794 return;
795 }
796
797 // Tell the caret to use our selection
798 caret->SetSelection(ourSel);
799
800 // mutual-exclusion: the selection is either controlled by the
801 // document or by the text input/area. Clear any selection in the
802 // document since the focus is now on our independent selection.
803
804 RefPtr<Selection> docSel =
805 presShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
806 if (!docSel) {
807 return;
808 }
809
810 if (!docSel->IsCollapsed()) {
811 docSel->RemoveAllRanges(IgnoreErrors());
812 }
813
814 // If the focus moved to a text control during text selection by pointer
815 // device, stop extending the selection.
816 if (RefPtr<nsFrameSelection> frameSelection = presShell->FrameSelection()) {
817 frameSelection->SetDragState(false);
818 }
819 }
820
SetFormProperty(nsAtom * aName,const nsAString & aValue)821 nsresult nsTextControlFrame::SetFormProperty(nsAtom* aName,
822 const nsAString& aValue) {
823 if (!mIsProcessing) { // some kind of lock.
824 mIsProcessing = true;
825 if (nsGkAtoms::select == aName) {
826 // Select all the text.
827 //
828 // XXX: This is lame, we can't call editor's SelectAll method
829 // because that triggers AutoCopies in unix builds.
830 // Instead, we have to call our own homegrown version
831 // of select all which merely builds a range that selects
832 // all of the content and adds that to the selection.
833
834 AutoWeakFrame weakThis = this;
835 SelectAllOrCollapseToEndOfText(true); // NOTE: can destroy the world
836 if (!weakThis.IsAlive()) {
837 return NS_OK;
838 }
839 }
840 mIsProcessing = false;
841 }
842 return NS_OK;
843 }
844
GetTextEditor()845 already_AddRefed<TextEditor> nsTextControlFrame::GetTextEditor() {
846 if (NS_WARN_IF(NS_FAILED(EnsureEditorInitialized()))) {
847 return nullptr;
848 }
849
850 RefPtr<TextControlElement> textControlElement =
851 TextControlElement::FromNode(GetContent());
852 MOZ_ASSERT(textControlElement);
853 RefPtr<TextEditor> textEditor = textControlElement->GetTextEditor();
854 return textEditor.forget();
855 }
856
SetSelectionInternal(nsINode * aStartNode,uint32_t aStartOffset,nsINode * aEndNode,uint32_t aEndOffset,nsITextControlFrame::SelectionDirection aDirection)857 nsresult nsTextControlFrame::SetSelectionInternal(
858 nsINode* aStartNode, uint32_t aStartOffset, nsINode* aEndNode,
859 uint32_t aEndOffset, nsITextControlFrame::SelectionDirection aDirection) {
860 // Get the selection, clear it and add the new range to it!
861 TextControlElement* textControlElement =
862 TextControlElement::FromNode(GetContent());
863 MOZ_ASSERT(textControlElement);
864 nsISelectionController* selCon = textControlElement->GetSelectionController();
865 NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
866
867 RefPtr<Selection> selection =
868 selCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
869 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
870
871 nsDirection direction;
872 if (aDirection == eNone) {
873 // Preserve the direction
874 direction = selection->GetDirection();
875 } else {
876 direction = (aDirection == eBackward) ? eDirPrevious : eDirNext;
877 }
878
879 MOZ_TRY(selection->SetStartAndEndInLimiter(*aStartNode, aStartOffset,
880 *aEndNode, aEndOffset, direction,
881 nsISelectionListener::JS_REASON));
882 return NS_OK;
883 }
884
ScrollSelectionIntoViewAsync(ScrollAncestors aScrollAncestors)885 void nsTextControlFrame::ScrollSelectionIntoViewAsync(
886 ScrollAncestors aScrollAncestors) {
887 auto* textControlElement = TextControlElement::FromNode(GetContent());
888 MOZ_ASSERT(textControlElement);
889 nsISelectionController* selCon = textControlElement->GetSelectionController();
890 if (!selCon) {
891 return;
892 }
893
894 int16_t flags = aScrollAncestors == ScrollAncestors::Yes
895 ? 0
896 : nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY;
897
898 // Scroll the selection into view (see bug 231389).
899 selCon->ScrollSelectionIntoView(
900 nsISelectionController::SELECTION_NORMAL,
901 nsISelectionController::SELECTION_FOCUS_REGION, flags);
902 }
903
SelectAllOrCollapseToEndOfText(bool aSelect)904 nsresult nsTextControlFrame::SelectAllOrCollapseToEndOfText(bool aSelect) {
905 nsresult rv = EnsureEditorInitialized();
906 if (NS_WARN_IF(NS_FAILED(rv))) {
907 return rv;
908 }
909
910 RefPtr<nsINode> rootNode = mRootNode;
911 NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE);
912
913 RefPtr<Text> text = Text::FromNodeOrNull(rootNode->GetFirstChild());
914 MOZ_ASSERT(text);
915
916 uint32_t length = text->Length();
917
918 rv = SetSelectionInternal(text, aSelect ? 0 : length, text, length);
919 NS_ENSURE_SUCCESS(rv, rv);
920
921 ScrollSelectionIntoViewAsync();
922 return NS_OK;
923 }
924
SetSelectionEndPoints(uint32_t aSelStart,uint32_t aSelEnd,nsITextControlFrame::SelectionDirection aDirection)925 nsresult nsTextControlFrame::SetSelectionEndPoints(
926 uint32_t aSelStart, uint32_t aSelEnd,
927 nsITextControlFrame::SelectionDirection aDirection) {
928 NS_ASSERTION(aSelStart <= aSelEnd, "Invalid selection offsets!");
929
930 if (aSelStart > aSelEnd) return NS_ERROR_FAILURE;
931
932 nsCOMPtr<nsINode> startNode, endNode;
933 uint32_t startOffset, endOffset;
934
935 // Calculate the selection start point.
936
937 nsresult rv =
938 OffsetToDOMPoint(aSelStart, getter_AddRefs(startNode), &startOffset);
939
940 NS_ENSURE_SUCCESS(rv, rv);
941
942 if (aSelStart == aSelEnd) {
943 // Collapsed selection, so start and end are the same!
944 endNode = startNode;
945 endOffset = startOffset;
946 } else {
947 // Selection isn't collapsed so we have to calculate
948 // the end point too.
949
950 rv = OffsetToDOMPoint(aSelEnd, getter_AddRefs(endNode), &endOffset);
951
952 NS_ENSURE_SUCCESS(rv, rv);
953 }
954
955 return SetSelectionInternal(startNode, startOffset, endNode, endOffset,
956 aDirection);
957 }
958
959 NS_IMETHODIMP
SetSelectionRange(uint32_t aSelStart,uint32_t aSelEnd,nsITextControlFrame::SelectionDirection aDirection)960 nsTextControlFrame::SetSelectionRange(
961 uint32_t aSelStart, uint32_t aSelEnd,
962 nsITextControlFrame::SelectionDirection aDirection) {
963 nsresult rv = EnsureEditorInitialized();
964 NS_ENSURE_SUCCESS(rv, rv);
965
966 if (aSelStart > aSelEnd) {
967 // Simulate what we'd see SetSelectionStart() was called, followed
968 // by a SetSelectionEnd().
969
970 aSelStart = aSelEnd;
971 }
972
973 return SetSelectionEndPoints(aSelStart, aSelEnd, aDirection);
974 }
975
OffsetToDOMPoint(uint32_t aOffset,nsINode ** aResult,uint32_t * aPosition)976 nsresult nsTextControlFrame::OffsetToDOMPoint(uint32_t aOffset,
977 nsINode** aResult,
978 uint32_t* aPosition) {
979 NS_ENSURE_ARG_POINTER(aResult && aPosition);
980
981 *aResult = nullptr;
982 *aPosition = 0;
983
984 nsresult rv = EnsureEditorInitialized();
985 if (NS_WARN_IF(NS_FAILED(rv))) {
986 return rv;
987 }
988
989 RefPtr<Element> rootNode = mRootNode;
990 NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE);
991
992 nsCOMPtr<nsINodeList> nodeList = rootNode->ChildNodes();
993 uint32_t length = nodeList->Length();
994
995 NS_ASSERTION(length <= 2,
996 "We should have one text node and one mozBR at most");
997
998 nsCOMPtr<nsINode> firstNode = nodeList->Item(0);
999 Text* textNode = firstNode ? firstNode->GetAsText() : nullptr;
1000
1001 if (length == 0) {
1002 rootNode.forget(aResult);
1003 *aPosition = 0;
1004 } else if (textNode) {
1005 uint32_t textLength = textNode->Length();
1006 firstNode.forget(aResult);
1007 *aPosition = std::min(aOffset, textLength);
1008 } else {
1009 rootNode.forget(aResult);
1010 *aPosition = 0;
1011 }
1012
1013 return NS_OK;
1014 }
1015
1016 /////END INTERFACE IMPLEMENTATIONS
1017
1018 ////NSIFRAME
AttributeChanged(int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType)1019 nsresult nsTextControlFrame::AttributeChanged(int32_t aNameSpaceID,
1020 nsAtom* aAttribute,
1021 int32_t aModType) {
1022 auto* textControlElement = TextControlElement::FromNode(GetContent());
1023 MOZ_ASSERT(textControlElement);
1024 nsISelectionController* selCon = textControlElement->GetSelectionController();
1025 const bool needEditor =
1026 nsGkAtoms::maxlength == aAttribute || nsGkAtoms::readonly == aAttribute ||
1027 nsGkAtoms::disabled == aAttribute || nsGkAtoms::spellcheck == aAttribute;
1028 RefPtr<TextEditor> textEditor = needEditor ? GetTextEditor() : nullptr;
1029 if ((needEditor && !textEditor) || !selCon) {
1030 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute,
1031 aModType);
1032 }
1033
1034 if (nsGkAtoms::maxlength == aAttribute) {
1035 if (textEditor) {
1036 textEditor->SetMaxTextLength(textControlElement->UsedMaxLength());
1037 }
1038 return NS_OK;
1039 }
1040
1041 if (nsGkAtoms::readonly == aAttribute || nsGkAtoms::disabled == aAttribute) {
1042 if (AttributeExists(aAttribute)) {
1043 if (nsContentUtils::IsFocusedContent(mContent)) {
1044 selCon->SetCaretEnabled(false);
1045 }
1046 textEditor->AddFlags(nsIEditor::eEditorReadonlyMask);
1047 } else {
1048 if (!AttributeExists(aAttribute == nsGkAtoms::readonly
1049 ? nsGkAtoms::disabled
1050 : nsGkAtoms::readonly)) {
1051 if (nsContentUtils::IsFocusedContent(mContent)) {
1052 selCon->SetCaretEnabled(true);
1053 }
1054 textEditor->RemoveFlags(nsIEditor::eEditorReadonlyMask);
1055 }
1056 }
1057 return NS_OK;
1058 }
1059
1060 if (!mEditorHasBeenInitialized && nsGkAtoms::value == aAttribute) {
1061 UpdateValueDisplay(true);
1062 return NS_OK;
1063 }
1064
1065 // Allow the base class to handle common attributes supported by all form
1066 // elements...
1067 return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
1068 }
1069
GetText(nsString & aText)1070 void nsTextControlFrame::GetText(nsString& aText) {
1071 if (HTMLInputElement* inputElement = HTMLInputElement::FromNode(mContent)) {
1072 if (IsSingleLineTextControl()) {
1073 // There will be no line breaks so we can ignore the wrap property.
1074 inputElement->GetTextEditorValue(aText, true);
1075 return;
1076 }
1077 aText.Truncate();
1078 return;
1079 }
1080
1081 MOZ_ASSERT(!IsSingleLineTextControl());
1082 if (HTMLTextAreaElement* textAreaElement =
1083 HTMLTextAreaElement::FromNode(mContent)) {
1084 textAreaElement->GetValue(aText);
1085 return;
1086 }
1087
1088 MOZ_ASSERT(aText.IsEmpty());
1089 aText.Truncate();
1090 }
1091
TextEquals(const nsAString & aText) const1092 bool nsTextControlFrame::TextEquals(const nsAString& aText) const {
1093 if (HTMLInputElement* inputElement = HTMLInputElement::FromNode(mContent)) {
1094 if (IsSingleLineTextControl()) {
1095 // There will be no line breaks so we can ignore the wrap property.
1096 return inputElement->TextEditorValueEquals(aText);
1097 }
1098 return aText.IsEmpty();
1099 }
1100
1101 MOZ_ASSERT(!IsSingleLineTextControl());
1102 if (HTMLTextAreaElement* textAreaElement =
1103 HTMLTextAreaElement::FromNode(mContent)) {
1104 return textAreaElement->ValueEquals(aText);
1105 }
1106
1107 return aText.IsEmpty();
1108 }
1109
1110 /// END NSIFRAME OVERLOADS
1111
1112 // NOTE(emilio): This is needed because the root->primary frame map is not set
1113 // up by the time this is called.
FindRootNodeFrame(const nsFrameList & aChildList,const nsIContent * aRoot)1114 static nsIFrame* FindRootNodeFrame(const nsFrameList& aChildList,
1115 const nsIContent* aRoot) {
1116 for (nsIFrame* f : aChildList) {
1117 if (f->GetContent() == aRoot) {
1118 return f;
1119 }
1120 if (nsIFrame* root = FindRootNodeFrame(f->PrincipalChildList(), aRoot)) {
1121 return root;
1122 }
1123 }
1124 return nullptr;
1125 }
1126
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)1127 void nsTextControlFrame::SetInitialChildList(ChildListID aListID,
1128 nsFrameList& aChildList) {
1129 nsContainerFrame::SetInitialChildList(aListID, aChildList);
1130 if (aListID != kPrincipalList) {
1131 return;
1132 }
1133
1134 // Mark the scroll frame as being a reflow root. This will allow incremental
1135 // reflows to be initiated at the scroll frame, rather than descending from
1136 // the root frame of the frame hierarchy.
1137 if (nsIFrame* frame = FindRootNodeFrame(PrincipalChildList(), mRootNode)) {
1138 frame->AddStateBits(NS_FRAME_REFLOW_ROOT);
1139
1140 auto* textControlElement = TextControlElement::FromNode(GetContent());
1141 MOZ_ASSERT(textControlElement);
1142 textControlElement->InitializeKeyboardEventListeners();
1143
1144 if (nsPoint* contentScrollPos = TakeProperty(ContentScrollPos())) {
1145 // If we have a scroll pos stored to be passed to our anonymous
1146 // div, do it here!
1147 nsIStatefulFrame* statefulFrame = do_QueryFrame(frame);
1148 NS_ASSERTION(statefulFrame,
1149 "unexpected type of frame for the anonymous div");
1150 UniquePtr<PresState> fakePresState = NewPresState();
1151 fakePresState->scrollState() = *contentScrollPos;
1152 statefulFrame->RestoreState(fakePresState.get());
1153 delete contentScrollPos;
1154 }
1155 } else {
1156 MOZ_ASSERT(!mRootNode || PrincipalChildList().IsEmpty());
1157 }
1158 }
1159
UpdateValueDisplay(bool aNotify,bool aBeforeEditorInit,const nsAString * aValue)1160 nsresult nsTextControlFrame::UpdateValueDisplay(bool aNotify,
1161 bool aBeforeEditorInit,
1162 const nsAString* aValue) {
1163 if (!IsSingleLineTextControl()) { // textareas don't use this
1164 return NS_OK;
1165 }
1166
1167 MOZ_ASSERT(mRootNode, "Must have a div content\n");
1168 MOZ_ASSERT(!mEditorHasBeenInitialized,
1169 "Do not call this after editor has been initialized");
1170
1171 nsIContent* childContent = mRootNode->GetFirstChild();
1172 Text* textContent;
1173 if (!childContent) {
1174 // Set up a textnode with our value
1175 RefPtr<nsTextNode> textNode = new (mContent->NodeInfo()->NodeInfoManager())
1176 nsTextNode(mContent->NodeInfo()->NodeInfoManager());
1177 textNode->MarkAsMaybeModifiedFrequently();
1178 if (IsPasswordTextControl()) {
1179 textNode->MarkAsMaybeMasked();
1180 }
1181
1182 mRootNode->AppendChildTo(textNode, aNotify, IgnoreErrors());
1183 textContent = textNode;
1184 } else {
1185 textContent = childContent->GetAsText();
1186 }
1187
1188 NS_ENSURE_TRUE(textContent, NS_ERROR_UNEXPECTED);
1189
1190 TextControlElement* textControlElement =
1191 TextControlElement::FromNode(GetContent());
1192 MOZ_ASSERT(textControlElement);
1193
1194 // Get the current value of the textfield from the content.
1195 nsAutoString value;
1196 if (aValue) {
1197 value = *aValue;
1198 } else {
1199 textControlElement->GetTextEditorValue(value, true);
1200 }
1201
1202 return textContent->SetText(value, aNotify);
1203 }
1204
1205 NS_IMETHODIMP
GetOwnedSelectionController(nsISelectionController ** aSelCon)1206 nsTextControlFrame::GetOwnedSelectionController(
1207 nsISelectionController** aSelCon) {
1208 NS_ENSURE_ARG_POINTER(aSelCon);
1209
1210 TextControlElement* textControlElement =
1211 TextControlElement::FromNode(GetContent());
1212 MOZ_ASSERT(textControlElement);
1213
1214 *aSelCon = textControlElement->GetSelectionController();
1215 NS_IF_ADDREF(*aSelCon);
1216
1217 return NS_OK;
1218 }
1219
GetOwnedFrameSelection()1220 nsFrameSelection* nsTextControlFrame::GetOwnedFrameSelection() {
1221 auto* textControlElement = TextControlElement::FromNode(GetContent());
1222 MOZ_ASSERT(textControlElement);
1223 return textControlElement->GetConstFrameSelection();
1224 }
1225
SaveState()1226 UniquePtr<PresState> nsTextControlFrame::SaveState() {
1227 if (nsIStatefulFrame* scrollStateFrame =
1228 do_QueryFrame(GetScrollTargetFrame())) {
1229 return scrollStateFrame->SaveState();
1230 }
1231
1232 return nullptr;
1233 }
1234
1235 NS_IMETHODIMP
RestoreState(PresState * aState)1236 nsTextControlFrame::RestoreState(PresState* aState) {
1237 NS_ENSURE_ARG_POINTER(aState);
1238
1239 // Query the nsIStatefulFrame from the HTMLScrollFrame
1240 if (nsIStatefulFrame* scrollStateFrame =
1241 do_QueryFrame(GetScrollTargetFrame())) {
1242 return scrollStateFrame->RestoreState(aState);
1243 }
1244
1245 // Most likely, we don't have our anonymous content constructed yet, which
1246 // would cause us to end up here. In this case, we'll just store the scroll
1247 // pos ourselves, and forward it to the scroll frame later when it's created.
1248 SetProperty(ContentScrollPos(), new nsPoint(aState->scrollState()));
1249 return NS_OK;
1250 }
1251
PeekOffset(nsPeekOffsetStruct * aPos)1252 nsresult nsTextControlFrame::PeekOffset(nsPeekOffsetStruct* aPos) {
1253 return NS_ERROR_FAILURE;
1254 }
1255
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)1256 void nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
1257 const nsDisplayListSet& aLists) {
1258 DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame");
1259
1260 DisplayBorderBackgroundOutline(aBuilder, aLists);
1261
1262 // Redirect all lists to the Content list so that nothing can escape, ie
1263 // opacity creating stacking contexts that then get sorted with stacking
1264 // contexts external to us.
1265 nsDisplayList* content = aLists.Content();
1266 nsDisplayListSet set(content, content, content, content, content, content);
1267
1268 for (auto* kid : mFrames) {
1269 BuildDisplayListForChild(aBuilder, kid, set);
1270 }
1271 }
1272
1273 NS_IMETHODIMP
Run()1274 nsTextControlFrame::EditorInitializer::Run() {
1275 if (!mFrame) {
1276 return NS_OK;
1277 }
1278
1279 // Need to block script to avoid bug 669767.
1280 nsAutoScriptBlocker scriptBlocker;
1281
1282 RefPtr<mozilla::PresShell> presShell = mFrame->PresShell();
1283 bool observes = presShell->ObservesNativeAnonMutationsForPrint();
1284 presShell->ObserveNativeAnonMutationsForPrint(true);
1285 // This can cause the frame to be destroyed (and call Revoke()).
1286 mFrame->EnsureEditorInitialized();
1287 presShell->ObserveNativeAnonMutationsForPrint(observes);
1288
1289 // The frame can *still* be destroyed even though we have a scriptblocker,
1290 // bug 682684.
1291 if (!mFrame) {
1292 return NS_ERROR_FAILURE;
1293 }
1294
1295 mFrame->FinishedInitializer();
1296 return NS_OK;
1297 }
1298
NS_IMPL_ISUPPORTS(nsTextControlFrame::nsAnonDivObserver,nsIMutationObserver)1299 NS_IMPL_ISUPPORTS(nsTextControlFrame::nsAnonDivObserver, nsIMutationObserver)
1300
1301 void nsTextControlFrame::nsAnonDivObserver::CharacterDataChanged(
1302 nsIContent* aContent, const CharacterDataChangeInfo&) {
1303 mFrame.ClearCachedValue();
1304 }
1305
ContentAppended(nsIContent * aFirstNewContent)1306 void nsTextControlFrame::nsAnonDivObserver::ContentAppended(
1307 nsIContent* aFirstNewContent) {
1308 mFrame.ClearCachedValue();
1309 }
1310
ContentInserted(nsIContent * aChild)1311 void nsTextControlFrame::nsAnonDivObserver::ContentInserted(
1312 nsIContent* aChild) {
1313 mFrame.ClearCachedValue();
1314 }
1315
ContentRemoved(nsIContent * aChild,nsIContent * aPreviousSibling)1316 void nsTextControlFrame::nsAnonDivObserver::ContentRemoved(
1317 nsIContent* aChild, nsIContent* aPreviousSibling) {
1318 mFrame.ClearCachedValue();
1319 }
1320