Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/layout/forms/nsTextControlFrame.cpp
Line
Count
Source (jump to first uncovered line)
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 "nsIPlaintextEditor.h"
14
#include "nsCaret.h"
15
#include "nsCSSPseudoElements.h"
16
#include "nsGenericHTMLElement.h"
17
#include "nsIEditor.h"
18
#include "nsTextFragment.h"
19
#include "nsNameSpaceManager.h"
20
#include "nsCheckboxRadioFrame.h" //for registering accesskeys
21
22
#include "nsIContent.h"
23
#include "nsPresContext.h"
24
#include "nsGkAtoms.h"
25
#include "nsLayoutUtils.h"
26
#include "nsIPresShell.h"
27
28
#include <algorithm>
29
#include "nsRange.h" //for selection setting helper func
30
#include "nsINode.h"
31
#include "nsPIDOMWindow.h" //needed for notify selection changed to update the menus ect.
32
#include "nsQueryObject.h"
33
#include "nsILayoutHistoryState.h"
34
35
#include "nsFocusManager.h"
36
#include "mozilla/PresState.h"
37
#include "nsAttrValueInlines.h"
38
#include "mozilla/dom/Selection.h"
39
#include "mozilla/TextEditRules.h"
40
#include "nsContentUtils.h"
41
#include "nsTextNode.h"
42
#include "mozilla/dom/HTMLInputElement.h"
43
#include "mozilla/dom/HTMLTextAreaElement.h"
44
#include "mozilla/dom/ScriptSettings.h"
45
#include "mozilla/dom/Text.h"
46
#include "mozilla/MathAlgorithms.h"
47
#include "nsFrameSelection.h"
48
49
#define DEFAULT_COLUMN_WIDTH 20
50
51
using namespace mozilla;
52
using namespace mozilla::dom;
53
54
nsIFrame*
55
NS_NewTextControlFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle)
56
0
{
57
0
  return new (aPresShell) nsTextControlFrame(aStyle);
58
0
}
59
60
NS_IMPL_FRAMEARENA_HELPERS(nsTextControlFrame)
61
62
0
NS_QUERYFRAME_HEAD(nsTextControlFrame)
63
0
  NS_QUERYFRAME_ENTRY(nsIFormControlFrame)
64
0
  NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
65
0
  NS_QUERYFRAME_ENTRY(nsITextControlFrame)
66
0
  NS_QUERYFRAME_ENTRY(nsIStatefulFrame)
67
0
NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
68
69
#ifdef ACCESSIBILITY
70
a11y::AccType
71
nsTextControlFrame::AccessibleType()
72
0
{
73
0
  return a11y::eHTMLTextFieldType;
74
0
}
75
#endif
76
77
#ifdef DEBUG
78
class EditorInitializerEntryTracker {
79
public:
80
  explicit EditorInitializerEntryTracker(nsTextControlFrame &frame)
81
    : mFrame(frame)
82
    , mFirstEntry(false)
83
  {
84
    if (!mFrame.mInEditorInitialization) {
85
      mFrame.mInEditorInitialization = true;
86
      mFirstEntry = true;
87
    }
88
  }
89
  ~EditorInitializerEntryTracker()
90
  {
91
    if (mFirstEntry) {
92
      mFrame.mInEditorInitialization = false;
93
    }
94
  }
95
  bool EnteredMoreThanOnce() const { return !mFirstEntry; }
96
private:
97
  nsTextControlFrame &mFrame;
98
  bool mFirstEntry;
99
};
100
#endif
101
102
class nsTextControlFrame::nsAnonDivObserver final : public nsStubMutationObserver
103
{
104
public:
105
  explicit nsAnonDivObserver(nsTextControlFrame& aFrame)
106
0
   : mFrame(aFrame) {}
107
  NS_DECL_ISUPPORTS
108
  NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
109
  NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
110
  NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
111
  NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
112
113
private:
114
0
  ~nsAnonDivObserver() {}
115
  nsTextControlFrame& mFrame;
116
};
117
118
nsTextControlFrame::nsTextControlFrame(ComputedStyle* aStyle)
119
  : nsContainerFrame(aStyle, kClassID)
120
  , mFirstBaseline(NS_INTRINSIC_WIDTH_UNKNOWN)
121
  , mEditorHasBeenInitialized(false)
122
  , mIsProcessing(false)
123
#ifdef DEBUG
124
  , mInEditorInitialization(false)
125
#endif
126
0
{
127
0
  ClearCachedValue();
128
0
}
129
130
nsTextControlFrame::~nsTextControlFrame()
131
0
{
132
0
}
133
134
void
135
nsTextControlFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
136
0
{
137
0
  mScrollEvent.Revoke();
138
0
139
0
  DeleteProperty(TextControlInitializer());
140
0
141
0
  // Unbind the text editor state object from the frame.  The editor will live
142
0
  // on, but things like controllers will be released.
143
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
144
0
  NS_ASSERTION(txtCtrl, "Content not a text control element");
145
0
  txtCtrl->UnbindFromFrame(this);
146
0
147
0
  nsCheckboxRadioFrame::RegUnRegAccessKey(static_cast<nsIFrame*>(this), false);
148
0
149
0
  if (mMutationObserver) {
150
0
    mRootNode->RemoveMutationObserver(mMutationObserver);
151
0
    mMutationObserver = nullptr;
152
0
  }
153
0
154
0
  // FIXME(emilio, bug 1400618): Do this after the child frames are destroyed.
155
0
  aPostDestroyData.AddAnonymousContent(mRootNode.forget());
156
0
  aPostDestroyData.AddAnonymousContent(mPlaceholderDiv.forget());
157
0
  aPostDestroyData.AddAnonymousContent(mPreviewDiv.forget());
158
0
159
0
  nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
160
0
}
161
162
LogicalSize
163
nsTextControlFrame::CalcIntrinsicSize(gfxContext* aRenderingContext,
164
                                      WritingMode aWM,
165
                                      float aFontSizeInflation) const
166
0
{
167
0
  LogicalSize intrinsicSize(aWM);
168
0
  // Get leading and the Average/MaxAdvance char width
169
0
  nscoord lineHeight  = 0;
170
0
  nscoord charWidth   = 0;
171
0
  nscoord charMaxAdvance  = 0;
172
0
173
0
  RefPtr<nsFontMetrics> fontMet =
174
0
    nsLayoutUtils::GetFontMetricsForFrame(this, aFontSizeInflation);
175
0
176
0
  lineHeight =
177
0
    ReflowInput::CalcLineHeight(GetContent(),
178
0
                                Style(),
179
0
                                PresContext(),
180
0
                                NS_AUTOHEIGHT,
181
0
                                aFontSizeInflation);
182
0
  charWidth = fontMet->AveCharWidth();
183
0
  charMaxAdvance = fontMet->MaxAdvance();
184
0
185
0
  // Set the width equal to the width in characters
186
0
  int32_t cols = GetCols();
187
0
  intrinsicSize.ISize(aWM) = cols * charWidth;
188
0
189
0
  // To better match IE, take the maximum character width(in twips) and remove
190
0
  // 4 pixels add this on as additional padding(internalPadding). But only do
191
0
  // this if we think we have a fixed-width font.
192
0
  if (mozilla::Abs(charWidth - charMaxAdvance) > (unsigned)nsPresContext::CSSPixelsToAppUnits(1)) {
193
0
    nscoord internalPadding =
194
0
      std::max(0, charMaxAdvance - nsPresContext::CSSPixelsToAppUnits(4));
195
0
    nscoord t = nsPresContext::CSSPixelsToAppUnits(1);
196
0
   // Round to a multiple of t
197
0
    nscoord rest = internalPadding % t;
198
0
    if (rest < t - rest) {
199
0
      internalPadding -= rest;
200
0
    } else {
201
0
      internalPadding += t - rest;
202
0
    }
203
0
    // Now add the extra padding on (so that small input sizes work well)
204
0
    intrinsicSize.ISize(aWM) += internalPadding;
205
0
  } else {
206
0
    // This is to account for the anonymous <br> having a 1 twip width
207
0
    // in Full Standards mode, see BRFrame::Reflow and bug 228752.
208
0
    if (PresContext()->CompatibilityMode() == eCompatibility_FullStandards) {
209
0
      intrinsicSize.ISize(aWM) += 1;
210
0
    }
211
0
  }
212
0
213
0
  // Increment width with cols * letter-spacing.
214
0
  {
215
0
    const nsStyleCoord& lsCoord = StyleText()->mLetterSpacing;
216
0
    if (eStyleUnit_Coord == lsCoord.GetUnit()) {
217
0
      nscoord letterSpacing = lsCoord.GetCoordValue();
218
0
      if (letterSpacing != 0) {
219
0
        intrinsicSize.ISize(aWM) += cols * letterSpacing;
220
0
      }
221
0
    }
222
0
  }
223
0
224
0
  // Set the height equal to total number of rows (times the height of each
225
0
  // line, of course)
226
0
  intrinsicSize.BSize(aWM) = lineHeight * GetRows();
227
0
228
0
  // Add in the size of the scrollbars for textarea
229
0
  if (IsTextArea()) {
230
0
    nsIFrame* first = PrincipalChildList().FirstChild();
231
0
232
0
    nsIScrollableFrame *scrollableFrame = do_QueryFrame(first);
233
0
    NS_ASSERTION(scrollableFrame, "Child must be scrollable");
234
0
235
0
    if (scrollableFrame) {
236
0
      LogicalMargin scrollbarSizes(aWM,
237
0
        scrollableFrame->GetDesiredScrollbarSizes(PresContext(),
238
0
                                                  aRenderingContext));
239
0
240
0
      intrinsicSize.ISize(aWM) += scrollbarSizes.IStartEnd(aWM);
241
0
      intrinsicSize.BSize(aWM) += scrollbarSizes.BStartEnd(aWM);
242
0
    }
243
0
  }
244
0
  return intrinsicSize;
245
0
}
246
247
nsresult
248
nsTextControlFrame::EnsureEditorInitialized()
249
0
{
250
0
  // This method initializes our editor, if needed.
251
0
252
0
  // This code used to be called from CreateAnonymousContent(), but
253
0
  // when the editor set the initial string, it would trigger a
254
0
  // PresShell listener which called FlushPendingNotifications()
255
0
  // during frame construction. This was causing other form controls
256
0
  // to display wrong values.  Additionally, calling this every time
257
0
  // a text frame control is instantiated means that we're effectively
258
0
  // instantiating the editor for all text fields, even if they
259
0
  // never get used.  So, now this method is being called lazily only
260
0
  // when we actually need an editor.
261
0
262
0
  if (mEditorHasBeenInitialized)
263
0
    return NS_OK;
264
0
265
0
  nsIDocument* doc = mContent->GetComposedDoc();
266
0
  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
267
0
268
0
  AutoWeakFrame weakFrame(this);
269
0
270
0
  // Flush out content on our document.  Have to do this, because script
271
0
  // blockers don't prevent the sink flushing out content and notifying in the
272
0
  // process, which can destroy frames.
273
0
  doc->FlushPendingNotifications(FlushType::ContentAndNotify);
274
0
  NS_ENSURE_TRUE(weakFrame.IsAlive(), NS_ERROR_FAILURE);
275
0
276
0
  // Make sure that editor init doesn't do things that would kill us off
277
0
  // (especially off the script blockers it'll create for its DOM mutations).
278
0
  {
279
0
    nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
280
0
    MOZ_ASSERT(txtCtrl, "Content not a text control element");
281
0
282
0
    // Hide selection changes during the initialization, as webpages should not
283
0
    // be aware of these initializations
284
0
    AutoHideSelectionChanges hideSelectionChanges(txtCtrl->GetConstFrameSelection());
285
0
286
0
    nsAutoScriptBlocker scriptBlocker;
287
0
288
0
    // Time to mess with our security context... See comments in GetValue()
289
0
    // for why this is needed.
290
0
    mozilla::dom::AutoNoJSAPI nojsapi;
291
0
292
0
    // Make sure that we try to focus the content even if the method fails
293
0
    class EnsureSetFocus {
294
0
    public:
295
0
      explicit EnsureSetFocus(nsTextControlFrame* aFrame)
296
0
        : mFrame(aFrame) {}
297
0
      ~EnsureSetFocus() {
298
0
        if (nsContentUtils::IsFocusedContent(mFrame->GetContent()))
299
0
          mFrame->SetFocus(true, false);
300
0
      }
301
0
    private:
302
0
      nsTextControlFrame *mFrame;
303
0
    };
304
0
    EnsureSetFocus makeSureSetFocusHappens(this);
305
0
306
#ifdef DEBUG
307
    // Make sure we are not being called again until we're finished.
308
    // If reentrancy happens, just pretend that we don't have an editor.
309
    const EditorInitializerEntryTracker tracker(*this);
310
    NS_ASSERTION(!tracker.EnteredMoreThanOnce(),
311
                 "EnsureEditorInitialized has been called while a previous call was in progress");
312
#endif
313
314
0
    // Create an editor for the frame, if one doesn't already exist
315
0
    nsresult rv = txtCtrl->CreateEditor();
316
0
    NS_ENSURE_SUCCESS(rv, rv);
317
0
    NS_ENSURE_STATE(weakFrame.IsAlive());
318
0
319
0
    // Set mEditorHasBeenInitialized so that subsequent calls will use the
320
0
    // editor.
321
0
    mEditorHasBeenInitialized = true;
322
0
323
0
    if (weakFrame.IsAlive()) {
324
0
      uint32_t position = 0;
325
0
326
0
      // Set the selection to the end of the text field (bug 1287655),
327
0
      // but only if the contents has changed (bug 1337392).
328
0
      if (txtCtrl->ValueChanged()) {
329
0
        nsAutoString val;
330
0
        txtCtrl->GetTextEditorValue(val, true);
331
0
        position = val.Length();
332
0
      }
333
0
334
0
      SetSelectionEndPoints(position, position);
335
0
    }
336
0
  }
337
0
  NS_ENSURE_STATE(weakFrame.IsAlive());
338
0
  return NS_OK;
339
0
}
340
341
static already_AddRefed<Element>
342
CreateEmptyDiv(const nsTextControlFrame& aOwnerFrame)
343
0
{
344
0
  nsIDocument* doc = aOwnerFrame.PresContext()->Document();
345
0
  RefPtr<mozilla::dom::NodeInfo> nodeInfo =
346
0
    doc->NodeInfoManager()->GetNodeInfo(nsGkAtoms::div, nullptr,
347
0
                                        kNameSpaceID_XHTML,
348
0
                                        nsINode::ELEMENT_NODE);
349
0
350
0
  RefPtr<Element> element = NS_NewHTMLDivElement(nodeInfo.forget());
351
0
  return element.forget();
352
0
}
353
354
static already_AddRefed<Element>
355
CreateEmptyDivWithTextNode(const nsTextControlFrame& aOwnerFrame)
356
0
{
357
0
  RefPtr<Element> element = CreateEmptyDiv(aOwnerFrame);
358
0
359
0
  // Create the text node for DIV
360
0
  RefPtr<nsTextNode> textNode =
361
0
    new nsTextNode(element->OwnerDoc()->NodeInfoManager());
362
0
  textNode->MarkAsMaybeModifiedFrequently();
363
0
364
0
  element->AppendChildTo(textNode, false);
365
0
366
0
  return element.forget();
367
0
}
368
369
nsresult
370
nsTextControlFrame::CreateAnonymousContent(nsTArray<ContentInfo>& aElements)
371
0
{
372
0
  MOZ_ASSERT(mContent, "We should have a content!");
373
0
374
0
  AddStateBits(NS_FRAME_INDEPENDENT_SELECTION);
375
0
376
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
377
0
  MOZ_ASSERT(txtCtrl, "Content not a text control element");
378
0
379
0
  nsresult rv = CreateRootNode();
380
0
  NS_ENSURE_SUCCESS(rv, rv);
381
0
382
0
  // Bind the frame to its text control
383
0
  rv = txtCtrl->BindToFrame(this);
384
0
  NS_ENSURE_SUCCESS(rv, rv);
385
0
386
0
  aElements.AppendElement(mRootNode);
387
0
  CreatePlaceholderIfNeeded();
388
0
  if (mPlaceholderDiv) {
389
0
    if (!IsSingleLineTextControl()) {
390
0
      // For textareas, UpdateValueDisplay doesn't initialize the visibility
391
0
      // status of the placeholder because it returns early, so we have to
392
0
      // do that manually here.
393
0
      txtCtrl->UpdateOverlayTextVisibility(true);
394
0
    }
395
0
    aElements.AppendElement(mPlaceholderDiv);
396
0
  }
397
0
  CreatePreviewIfNeeded();
398
0
  if (mPreviewDiv) {
399
0
    aElements.AppendElement(mPreviewDiv);
400
0
  }
401
0
402
0
  rv = UpdateValueDisplay(false);
403
0
  NS_ENSURE_SUCCESS(rv, rv);
404
0
405
0
  InitializeEagerlyIfNeeded();
406
0
  return NS_OK;
407
0
}
408
409
bool
410
nsTextControlFrame::ShouldInitializeEagerly() const
411
0
{
412
0
  // textareas are eagerly initialized.
413
0
  if (!IsSingleLineTextControl()) {
414
0
    return true;
415
0
  }
416
0
417
0
  // Also, input elements which have a cached selection should get eager
418
0
  // editor initialization.
419
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
420
0
  if (txtCtrl->HasCachedSelection()) {
421
0
    return true;
422
0
  }
423
0
424
0
  // So do input text controls with spellcheck=true
425
0
  if (auto* htmlElement = nsGenericHTMLElement::FromNode(mContent)) {
426
0
    if (htmlElement->Spellcheck()) {
427
0
      return true;
428
0
    }
429
0
  }
430
0
431
0
  return false;
432
0
}
433
434
void
435
nsTextControlFrame::InitializeEagerlyIfNeeded()
436
0
{
437
0
  MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
438
0
             "Someone forgot a script blocker?");
439
0
  if (!ShouldInitializeEagerly()) {
440
0
    return;
441
0
  }
442
0
443
0
  EditorInitializer* initializer = new EditorInitializer(this);
444
0
  SetProperty(TextControlInitializer(), initializer);
445
0
  nsContentUtils::AddScriptRunner(initializer);
446
0
}
447
448
nsresult
449
nsTextControlFrame::CreateRootNode()
450
0
{
451
0
  MOZ_ASSERT(!mRootNode);
452
0
453
0
  mRootNode = CreateEmptyDiv(*this);
454
0
  mRootNode->SetIsNativeAnonymousRoot();
455
0
456
0
  mMutationObserver = new nsAnonDivObserver(*this);
457
0
  mRootNode->AddMutationObserver(mMutationObserver);
458
0
459
0
  // Make our root node editable
460
0
  mRootNode->SetFlags(NODE_IS_EDITABLE);
461
0
462
0
  // Set the necessary classes on the text control. We use class values instead
463
0
  // of a 'style' attribute so that the style comes from a user-agent style
464
0
  // sheet and is still applied even if author styles are disabled.
465
0
  nsAutoString classValue;
466
0
  classValue.AppendLiteral("anonymous-div");
467
0
468
0
  if (!IsSingleLineTextControl()) {
469
0
    // We can't just inherit the overflow because setting visible overflow will
470
0
    // crash when the number of lines exceeds the height of the textarea and
471
0
    // setting -moz-hidden-unscrollable overflow (NS_STYLE_OVERFLOW_CLIP)
472
0
    // doesn't paint the caret for some reason.
473
0
    const nsStyleDisplay* disp = StyleDisplay();
474
0
    if (disp->mOverflowX != NS_STYLE_OVERFLOW_VISIBLE &&
475
0
        disp->mOverflowX != NS_STYLE_OVERFLOW_CLIP) {
476
0
      classValue.AppendLiteral(" inherit-overflow");
477
0
    }
478
0
    classValue.AppendLiteral(" inherit-scroll-behavior");
479
0
  }
480
0
  nsresult rv = mRootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
481
0
                                   classValue, false);
482
0
  NS_ENSURE_SUCCESS(rv, rv);
483
0
484
0
  return NS_OK;
485
0
}
486
487
void
488
nsTextControlFrame::CreatePlaceholderIfNeeded()
489
0
{
490
0
  MOZ_ASSERT(!mPlaceholderDiv);
491
0
492
0
  // Do we need a placeholder node?
493
0
  nsAutoString placeholderTxt;
494
0
  mContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder,
495
0
                                 placeholderTxt);
496
0
  if (IsTextArea()) { // <textarea>s preserve newlines...
497
0
    nsContentUtils::PlatformToDOMLineBreaks(placeholderTxt);
498
0
  } else { // ...<input>s don't
499
0
    nsContentUtils::RemoveNewlines(placeholderTxt);
500
0
  }
501
0
502
0
  if (placeholderTxt.IsEmpty()) {
503
0
    return;
504
0
  }
505
0
506
0
  mPlaceholderDiv = CreateEmptyDivWithTextNode(*this);
507
0
  // Associate ::placeholder pseudo-element with the placeholder node.
508
0
  mPlaceholderDiv->SetPseudoElementType(CSSPseudoElementType::placeholder);
509
0
  mPlaceholderDiv->GetFirstChild()->AsText()->SetText(placeholderTxt, false);
510
0
}
511
512
void
513
nsTextControlFrame::CreatePreviewIfNeeded()
514
0
{
515
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
516
0
  if (!txtCtrl->IsPreviewEnabled()) {
517
0
    return;
518
0
  }
519
0
520
0
  mPreviewDiv = CreateEmptyDivWithTextNode(*this);
521
0
  mPreviewDiv->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
522
0
                       NS_LITERAL_STRING("preview-div"), false);
523
0
}
524
525
void
526
nsTextControlFrame::AppendAnonymousContentTo(
527
  nsTArray<nsIContent*>& aElements,
528
  uint32_t aFilter)
529
0
{
530
0
  aElements.AppendElement(mRootNode);
531
0
532
0
  if (mPlaceholderDiv && !(aFilter & nsIContent::eSkipPlaceholderContent)) {
533
0
    aElements.AppendElement(mPlaceholderDiv);
534
0
  }
535
0
536
0
  if (mPreviewDiv) {
537
0
    aElements.AppendElement(mPreviewDiv);
538
0
  }
539
0
}
540
541
nscoord
542
nsTextControlFrame::GetPrefISize(gfxContext* aRenderingContext)
543
0
{
544
0
  nscoord result = 0;
545
0
  DISPLAY_PREF_INLINE_SIZE(this, result);
546
0
  float inflation = nsLayoutUtils::FontSizeInflationFor(this);
547
0
  WritingMode wm = GetWritingMode();
548
0
  result = CalcIntrinsicSize(aRenderingContext, wm, inflation).ISize(wm);
549
0
  return result;
550
0
}
551
552
nscoord
553
nsTextControlFrame::GetMinISize(gfxContext* aRenderingContext)
554
0
{
555
0
  // Our min inline size is just our preferred width if we have auto inline size
556
0
  nscoord result;
557
0
  DISPLAY_MIN_INLINE_SIZE(this, result);
558
0
  result = GetPrefISize(aRenderingContext);
559
0
  return result;
560
0
}
561
562
LogicalSize
563
nsTextControlFrame::ComputeAutoSize(gfxContext*         aRenderingContext,
564
                                    WritingMode         aWM,
565
                                    const LogicalSize&  aCBSize,
566
                                    nscoord             aAvailableISize,
567
                                    const LogicalSize&  aMargin,
568
                                    const LogicalSize&  aBorder,
569
                                    const LogicalSize&  aPadding,
570
                                    ComputeSizeFlags    aFlags)
571
0
{
572
0
  float inflation = nsLayoutUtils::FontSizeInflationFor(this);
573
0
  LogicalSize autoSize = CalcIntrinsicSize(aRenderingContext, aWM, inflation);
574
0
575
0
  // Note: nsContainerFrame::ComputeAutoSize only computes the inline-size (and
576
0
  // only for 'auto'), the block-size it returns is always NS_UNCONSTRAINEDSIZE.
577
0
  const nsStyleCoord& iSizeCoord = StylePosition()->ISize(aWM);
578
0
  if (iSizeCoord.GetUnit() == eStyleUnit_Auto) {
579
0
    if (aFlags & ComputeSizeFlags::eIClampMarginBoxMinSize) {
580
0
      // CalcIntrinsicSize isn't aware of grid-item margin-box clamping, so we
581
0
      // fall back to nsContainerFrame's ComputeAutoSize to handle that.
582
0
      // XXX maybe a font-inflation issue here? (per the assertion below).
583
0
      autoSize.ISize(aWM) =
584
0
        nsContainerFrame::ComputeAutoSize(aRenderingContext, aWM,
585
0
                                          aCBSize, aAvailableISize,
586
0
                                          aMargin, aBorder,
587
0
                                          aPadding, aFlags).ISize(aWM);
588
0
    }
589
#ifdef DEBUG
590
    else {
591
      LogicalSize ancestorAutoSize =
592
        nsContainerFrame::ComputeAutoSize(aRenderingContext, aWM,
593
                                          aCBSize, aAvailableISize,
594
                                          aMargin, aBorder,
595
                                          aPadding, aFlags);
596
      // Disabled when there's inflation; see comment in GetXULPrefSize.
597
      MOZ_ASSERT(inflation != 1.0f ||
598
                 ancestorAutoSize.ISize(aWM) == autoSize.ISize(aWM),
599
                 "Incorrect size computed by ComputeAutoSize?");
600
    }
601
#endif
602
  }
603
0
  return autoSize;
604
0
}
605
606
void
607
nsTextControlFrame::Reflow(nsPresContext*   aPresContext,
608
                           ReflowOutput&     aDesiredSize,
609
                           const ReflowInput& aReflowInput,
610
                           nsReflowStatus&          aStatus)
611
0
{
612
0
  MarkInReflow();
613
0
  DO_GLOBAL_REFLOW_COUNT("nsTextControlFrame");
614
0
  DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
615
0
  MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
616
0
617
0
  // make sure that the form registers itself on the initial/first reflow
618
0
  if (mState & NS_FRAME_FIRST_REFLOW) {
619
0
    nsCheckboxRadioFrame::RegUnRegAccessKey(this, true);
620
0
  }
621
0
622
0
  // set values of reflow's out parameters
623
0
  WritingMode wm = aReflowInput.GetWritingMode();
624
0
  LogicalSize
625
0
    finalSize(wm,
626
0
              aReflowInput.ComputedISize() +
627
0
              aReflowInput.ComputedLogicalBorderPadding().IStartEnd(wm),
628
0
              aReflowInput.ComputedBSize() +
629
0
              aReflowInput.ComputedLogicalBorderPadding().BStartEnd(wm));
630
0
  aDesiredSize.SetSize(wm, finalSize);
631
0
632
0
  // Calculate the baseline and store it in mFirstBaseline.
633
0
  nscoord lineHeight = aReflowInput.ComputedBSize();
634
0
  float inflation = nsLayoutUtils::FontSizeInflationFor(this);
635
0
  if (!IsSingleLineTextControl()) {
636
0
    lineHeight = ReflowInput::CalcLineHeight(GetContent(),
637
0
                                             Style(),
638
0
                                             PresContext(),
639
0
                                             NS_AUTOHEIGHT,
640
0
                                             inflation);
641
0
  }
642
0
  RefPtr<nsFontMetrics> fontMet =
643
0
    nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
644
0
  mFirstBaseline =
645
0
    nsLayoutUtils::GetCenteredFontBaseline(fontMet, lineHeight,
646
0
                                           wm.IsLineInverted()) +
647
0
    aReflowInput.ComputedLogicalBorderPadding().BStart(wm);
648
0
  aDesiredSize.SetBlockStartAscent(mFirstBaseline);
649
0
650
0
  // overflow handling
651
0
  aDesiredSize.SetOverflowAreasToDesiredBounds();
652
0
  // perform reflow on all kids
653
0
  nsIFrame* kid = mFrames.FirstChild();
654
0
  while (kid) {
655
0
    ReflowTextControlChild(kid, aPresContext, aReflowInput, aStatus, aDesiredSize);
656
0
    kid = kid->GetNextSibling();
657
0
  }
658
0
659
0
  // take into account css properties that affect overflow handling
660
0
  FinishAndStoreOverflow(&aDesiredSize);
661
0
662
0
  aStatus.Reset(); // This type of frame can't be split.
663
0
  NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
664
0
}
665
666
void
667
nsTextControlFrame::ReflowTextControlChild(nsIFrame*                aKid,
668
                                           nsPresContext*           aPresContext,
669
                                           const ReflowInput& aReflowInput,
670
                                           nsReflowStatus&          aStatus,
671
                                           ReflowOutput& aParentDesiredSize)
672
0
{
673
0
  // compute available size and frame offsets for child
674
0
  WritingMode wm = aKid->GetWritingMode();
675
0
  LogicalSize availSize = aReflowInput.ComputedSizeWithPadding(wm);
676
0
  availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
677
0
678
0
  ReflowInput kidReflowInput(aPresContext, aReflowInput,
679
0
                                   aKid, availSize, nullptr,
680
0
                                   ReflowInput::CALLER_WILL_INIT);
681
0
  // Override padding with our computed padding in case we got it from theming or percentage
682
0
  kidReflowInput.Init(aPresContext, nullptr, nullptr, &aReflowInput.ComputedPhysicalPadding());
683
0
684
0
  // Set computed width and computed height for the child
685
0
  kidReflowInput.SetComputedWidth(aReflowInput.ComputedWidth());
686
0
  kidReflowInput.SetComputedHeight(aReflowInput.ComputedHeight());
687
0
688
0
  // Offset the frame by the size of the parent's border
689
0
  nscoord xOffset = aReflowInput.ComputedPhysicalBorderPadding().left -
690
0
                    aReflowInput.ComputedPhysicalPadding().left;
691
0
  nscoord yOffset = aReflowInput.ComputedPhysicalBorderPadding().top -
692
0
                    aReflowInput.ComputedPhysicalPadding().top;
693
0
694
0
  // reflow the child
695
0
  ReflowOutput desiredSize(aReflowInput);
696
0
  ReflowChild(aKid, aPresContext, desiredSize, kidReflowInput,
697
0
              xOffset, yOffset, 0, aStatus);
698
0
699
0
  // place the child
700
0
  FinishReflowChild(aKid, aPresContext, desiredSize,
701
0
                    &kidReflowInput, xOffset, yOffset, 0);
702
0
703
0
  // consider the overflow
704
0
  aParentDesiredSize.mOverflowAreas.UnionWith(desiredSize.mOverflowAreas);
705
0
}
706
707
nsSize
708
nsTextControlFrame::GetXULMinSize(nsBoxLayoutState& aState)
709
0
{
710
0
  // XXXbz why?  Why not the nsBoxFrame sizes?
711
0
  return nsBox::GetXULMinSize(aState);
712
0
}
713
714
bool
715
nsTextControlFrame::IsXULCollapsed()
716
0
{
717
0
  // We're never collapsed in the box sense.
718
0
  return false;
719
0
}
720
721
NS_IMETHODIMP
722
nsTextControlFrame::ScrollOnFocusEvent::Run()
723
0
{
724
0
  if (mFrame) {
725
0
    nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(mFrame->GetContent());
726
0
    NS_ASSERTION(txtCtrl, "Content not a text control element");
727
0
    nsISelectionController* selCon = txtCtrl->GetSelectionController();
728
0
    if (selCon) {
729
0
      mFrame->mScrollEvent.Forget();
730
0
      selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
731
0
                                      nsISelectionController::SELECTION_FOCUS_REGION,
732
0
                                      nsISelectionController::SCROLL_SYNCHRONOUS);
733
0
    }
734
0
  }
735
0
  return NS_OK;
736
0
}
737
738
//IMPLEMENTING NS_IFORMCONTROLFRAME
739
void nsTextControlFrame::SetFocus(bool aOn, bool aRepaint)
740
0
{
741
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
742
0
  NS_ASSERTION(txtCtrl, "Content not a text control element");
743
0
744
0
  // Revoke the previous scroll event if one exists
745
0
  mScrollEvent.Revoke();
746
0
747
0
  // If 'dom.placeholeder.show_on_focus' preference is 'false', focusing or
748
0
  // blurring the frame can have an impact on the placeholder visibility.
749
0
  if (mPlaceholderDiv) {
750
0
    txtCtrl->UpdateOverlayTextVisibility(true);
751
0
  }
752
0
753
0
  if (!aOn) {
754
0
    return;
755
0
  }
756
0
757
0
  nsISelectionController* selCon = txtCtrl->GetSelectionController();
758
0
  if (!selCon) {
759
0
    return;
760
0
  }
761
0
762
0
  RefPtr<Selection> ourSel =
763
0
    selCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
764
0
  if (!ourSel) {
765
0
    return;
766
0
  }
767
0
768
0
  nsIPresShell* presShell = PresContext()->GetPresShell();
769
0
  RefPtr<nsCaret> caret = presShell->GetCaret();
770
0
  if (!caret) {
771
0
    return;
772
0
  }
773
0
774
0
  // Scroll the current selection into view
775
0
  Selection* caretSelection = caret->GetSelection();
776
0
  const bool isFocusedRightNow = ourSel == caretSelection;
777
0
  if (!isFocusedRightNow) {
778
0
    // Don't scroll the current selection if we've been focused using the mouse.
779
0
    uint32_t lastFocusMethod = 0;
780
0
    nsIDocument* doc = GetContent()->GetComposedDoc();
781
0
    if (doc) {
782
0
      nsIFocusManager* fm = nsFocusManager::GetFocusManager();
783
0
      if (fm) {
784
0
        fm->GetLastFocusMethod(doc->GetWindow(), &lastFocusMethod);
785
0
      }
786
0
    }
787
0
    if (!(lastFocusMethod & nsIFocusManager::FLAG_BYMOUSE)) {
788
0
      RefPtr<ScrollOnFocusEvent> event = new ScrollOnFocusEvent(this);
789
0
      nsresult rv = mContent->OwnerDoc()->Dispatch(TaskCategory::Other,
790
0
                                                   do_AddRef(event));
791
0
      if (NS_SUCCEEDED(rv)) {
792
0
        mScrollEvent = std::move(event);
793
0
      }
794
0
    }
795
0
  }
796
0
797
0
  // tell the caret to use our selection
798
0
  caret->SetSelection(ourSel);
799
0
800
0
  // mutual-exclusion: the selection is either controlled by the
801
0
  // document or by the text input/area. Clear any selection in the
802
0
  // document since the focus is now on our independent selection.
803
0
804
0
  nsCOMPtr<nsISelectionController> selcon = do_QueryInterface(presShell);
805
0
  RefPtr<Selection> docSel =
806
0
    selcon->GetSelection(nsISelectionController::SELECTION_NORMAL);
807
0
  if (!docSel) {
808
0
    return;
809
0
  }
810
0
811
0
  if (!docSel->IsCollapsed()) {
812
0
    docSel->RemoveAllRanges(IgnoreErrors());
813
0
  }
814
0
}
815
816
nsresult nsTextControlFrame::SetFormProperty(nsAtom* aName, const nsAString& aValue)
817
0
{
818
0
  if (!mIsProcessing)//some kind of lock.
819
0
  {
820
0
    mIsProcessing = true;
821
0
    if (nsGkAtoms::select == aName)
822
0
    {
823
0
      // Select all the text.
824
0
      //
825
0
      // XXX: This is lame, we can't call editor's SelectAll method
826
0
      //      because that triggers AutoCopies in unix builds.
827
0
      //      Instead, we have to call our own homegrown version
828
0
      //      of select all which merely builds a range that selects
829
0
      //      all of the content and adds that to the selection.
830
0
831
0
      AutoWeakFrame weakThis = this;
832
0
      SelectAllOrCollapseToEndOfText(true);  // NOTE: can destroy the world
833
0
      if (!weakThis.IsAlive()) {
834
0
        return NS_OK;
835
0
      }
836
0
    }
837
0
    mIsProcessing = false;
838
0
  }
839
0
  return NS_OK;
840
0
}
841
842
NS_IMETHODIMP_(already_AddRefed<TextEditor>)
843
nsTextControlFrame::GetTextEditor()
844
0
{
845
0
  if (NS_WARN_IF(NS_FAILED(EnsureEditorInitialized()))) {
846
0
    return nullptr;
847
0
  }
848
0
849
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
850
0
  MOZ_ASSERT(txtCtrl, "Content not a text control element");
851
0
  RefPtr<TextEditor> textEditor = txtCtrl->GetTextEditor();
852
0
  return textEditor.forget();
853
0
}
854
855
nsresult
856
nsTextControlFrame::SetSelectionInternal(nsINode* aStartNode,
857
                                         uint32_t aStartOffset,
858
                                         nsINode* aEndNode,
859
                                         uint32_t aEndOffset,
860
                                         nsITextControlFrame::SelectionDirection aDirection)
861
0
{
862
0
  // Create a new range to represent the new selection.
863
0
  // Note that we use a new range to avoid having to do
864
0
  // isIncreasing checks to avoid possible errors.
865
0
866
0
  RefPtr<nsRange> range = new nsRange(mContent);
867
0
  // Be careful to use internal nsRange methods which do not check to make sure
868
0
  // we have access to the node.
869
0
  // XXXbz nsRange::SetStartAndEnd takes int32_t (and ranges generally work on
870
0
  // int32_t), but we're passing uint32_t.  The good news is that at this point
871
0
  // our endpoints should really be within our length, so not really that big.
872
0
  // And if they _are_ that big, SetStartAndEnd() will simply error out, which
873
0
  // is not too bad for a case we don't expect to happen.
874
0
  nsresult rv = range->SetStartAndEnd(aStartNode, aStartOffset,
875
0
              aEndNode, aEndOffset);
876
0
  NS_ENSURE_SUCCESS(rv, rv);
877
0
878
0
  // Get the selection, clear it and add the new range to it!
879
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
880
0
  NS_ASSERTION(txtCtrl, "Content not a text control element");
881
0
  nsISelectionController* selCon = txtCtrl->GetSelectionController();
882
0
  NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
883
0
884
0
  RefPtr<Selection> selection =
885
0
    selCon->GetSelection(nsISelectionController::SELECTION_NORMAL);
886
0
  NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
887
0
888
0
  nsDirection direction;
889
0
  if (aDirection == eNone) {
890
0
    // Preserve the direction
891
0
    direction = selection->GetDirection();
892
0
  } else {
893
0
    direction = (aDirection == eBackward) ? eDirPrevious : eDirNext;
894
0
  }
895
0
896
0
  ErrorResult err;
897
0
  selection->RemoveAllRanges(err);
898
0
  if (NS_WARN_IF(err.Failed())) {
899
0
    return err.StealNSResult();
900
0
  }
901
0
902
0
  selection->AddRange(*range, err);  // NOTE: can destroy the world
903
0
  if (NS_WARN_IF(err.Failed())) {
904
0
    return err.StealNSResult();
905
0
  }
906
0
907
0
  selection->SetDirection(direction);
908
0
  return rv;
909
0
}
910
911
nsresult
912
nsTextControlFrame::ScrollSelectionIntoView()
913
0
{
914
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
915
0
  NS_ASSERTION(txtCtrl, "Content not a text control element");
916
0
  nsISelectionController* selCon = txtCtrl->GetSelectionController();
917
0
  if (selCon) {
918
0
    // Scroll the selection into view (see bug 231389).
919
0
    return selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
920
0
                                           nsISelectionController::SELECTION_FOCUS_REGION,
921
0
                                           nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY);
922
0
  }
923
0
924
0
  return NS_ERROR_FAILURE;
925
0
}
926
927
nsresult
928
nsTextControlFrame::SelectAllOrCollapseToEndOfText(bool aSelect)
929
0
{
930
0
  nsresult rv = EnsureEditorInitialized();
931
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
932
0
    return rv;
933
0
  }
934
0
935
0
  nsCOMPtr<nsINode> rootNode;
936
0
  rootNode= mRootNode;
937
0
938
0
  NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE);
939
0
940
0
  int32_t numChildren = mRootNode->GetChildCount();
941
0
942
0
  if (numChildren > 0) {
943
0
    // We never want to place the selection after the last
944
0
    // br under the root node!
945
0
    nsIContent *child = mRootNode->GetLastChild();
946
0
    if (child) {
947
0
      if (child->IsHTMLElement(nsGkAtoms::br)) {
948
0
        child = child->GetPreviousSibling();
949
0
        --numChildren;
950
0
      } else if (child->IsText() && !child->Length()) {
951
0
        // Editor won't remove text node when empty value.
952
0
        --numChildren;
953
0
      }
954
0
    }
955
0
    if (!aSelect && numChildren) {
956
0
      child = child->GetPreviousSibling();
957
0
      if (child && child->IsText()) {
958
0
        rootNode = child;
959
0
        const nsTextFragment* fragment = child->GetText();
960
0
        numChildren = fragment ? fragment->GetLength() : 0;
961
0
      }
962
0
    }
963
0
  }
964
0
965
0
  rv = SetSelectionInternal(rootNode, aSelect ? 0 : numChildren,
966
0
                            rootNode, numChildren);
967
0
  NS_ENSURE_SUCCESS(rv, rv);
968
0
969
0
  return ScrollSelectionIntoView();
970
0
}
971
972
nsresult
973
nsTextControlFrame::SetSelectionEndPoints(uint32_t aSelStart, uint32_t aSelEnd,
974
                                          nsITextControlFrame::SelectionDirection aDirection)
975
0
{
976
0
  NS_ASSERTION(aSelStart <= aSelEnd, "Invalid selection offsets!");
977
0
978
0
  if (aSelStart > aSelEnd)
979
0
    return NS_ERROR_FAILURE;
980
0
981
0
  nsCOMPtr<nsINode> startNode, endNode;
982
0
  uint32_t startOffset, endOffset;
983
0
984
0
  // Calculate the selection start point.
985
0
986
0
  nsresult rv = OffsetToDOMPoint(aSelStart, getter_AddRefs(startNode), &startOffset);
987
0
988
0
  NS_ENSURE_SUCCESS(rv, rv);
989
0
990
0
  if (aSelStart == aSelEnd) {
991
0
    // Collapsed selection, so start and end are the same!
992
0
    endNode   = startNode;
993
0
    endOffset = startOffset;
994
0
  }
995
0
  else {
996
0
    // Selection isn't collapsed so we have to calculate
997
0
    // the end point too.
998
0
999
0
    rv = OffsetToDOMPoint(aSelEnd, getter_AddRefs(endNode), &endOffset);
1000
0
1001
0
    NS_ENSURE_SUCCESS(rv, rv);
1002
0
  }
1003
0
1004
0
  return SetSelectionInternal(startNode, startOffset, endNode, endOffset, aDirection);
1005
0
}
1006
1007
NS_IMETHODIMP
1008
nsTextControlFrame::SetSelectionRange(uint32_t aSelStart, uint32_t aSelEnd,
1009
                                      nsITextControlFrame::SelectionDirection aDirection)
1010
0
{
1011
0
  nsresult rv = EnsureEditorInitialized();
1012
0
  NS_ENSURE_SUCCESS(rv, rv);
1013
0
1014
0
  if (aSelStart > aSelEnd) {
1015
0
    // Simulate what we'd see SetSelectionStart() was called, followed
1016
0
    // by a SetSelectionEnd().
1017
0
1018
0
    aSelStart   = aSelEnd;
1019
0
  }
1020
0
1021
0
  return SetSelectionEndPoints(aSelStart, aSelEnd, aDirection);
1022
0
}
1023
1024
1025
nsresult
1026
nsTextControlFrame::OffsetToDOMPoint(uint32_t aOffset,
1027
                                     nsINode** aResult,
1028
                                     uint32_t* aPosition)
1029
0
{
1030
0
  NS_ENSURE_ARG_POINTER(aResult && aPosition);
1031
0
1032
0
  *aResult = nullptr;
1033
0
  *aPosition = 0;
1034
0
1035
0
  nsresult rv = EnsureEditorInitialized();
1036
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1037
0
    return rv;
1038
0
  }
1039
0
1040
0
  RefPtr<Element> rootNode = mRootNode;
1041
0
  NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE);
1042
0
1043
0
  nsCOMPtr<nsINodeList> nodeList = rootNode->ChildNodes();
1044
0
  uint32_t length = nodeList->Length();
1045
0
1046
0
  NS_ASSERTION(length <= 2, "We should have one text node and one mozBR at most");
1047
0
1048
0
  nsCOMPtr<nsINode> firstNode = nodeList->Item(0);
1049
0
  Text* textNode = firstNode ? firstNode->GetAsText() : nullptr;
1050
0
1051
0
  if (length == 0) {
1052
0
    rootNode.forget(aResult);
1053
0
    *aPosition = 0;
1054
0
  } else if (textNode) {
1055
0
    uint32_t textLength = textNode->Length();
1056
0
    if (length == 2 && aOffset == textLength) {
1057
0
      // If we're at the end of the text node and we have a trailing BR node,
1058
0
      // set the selection on the BR node.
1059
0
      rootNode.forget(aResult);
1060
0
      *aPosition = 1;
1061
0
    } else {
1062
0
      // Otherwise, set the selection on the textnode itself.
1063
0
      firstNode.forget(aResult);
1064
0
      *aPosition = std::min(aOffset, textLength);
1065
0
    }
1066
0
  } else {
1067
0
    rootNode.forget(aResult);
1068
0
    *aPosition = 0;
1069
0
  }
1070
0
1071
0
  return NS_OK;
1072
0
}
1073
1074
/////END INTERFACE IMPLEMENTATIONS
1075
1076
////NSIFRAME
1077
nsresult
1078
nsTextControlFrame::AttributeChanged(int32_t         aNameSpaceID,
1079
                                     nsAtom*        aAttribute,
1080
                                     int32_t         aModType)
1081
0
{
1082
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
1083
0
  NS_ASSERTION(txtCtrl, "Content not a text control element");
1084
0
  nsISelectionController* selCon = txtCtrl->GetSelectionController();
1085
0
  const bool needEditor = nsGkAtoms::maxlength == aAttribute ||
1086
0
                            nsGkAtoms::readonly == aAttribute ||
1087
0
                            nsGkAtoms::disabled == aAttribute ||
1088
0
                            nsGkAtoms::spellcheck == aAttribute;
1089
0
  RefPtr<TextEditor> textEditor = needEditor ? GetTextEditor() : nullptr;
1090
0
  if ((needEditor && !textEditor) || !selCon) {
1091
0
    return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
1092
0
  }
1093
0
1094
0
  if (nsGkAtoms::maxlength == aAttribute) {
1095
0
    int32_t maxLength;
1096
0
    bool maxDefined = GetMaxLength(&maxLength);
1097
0
    if (textEditor) {
1098
0
      if (maxDefined) { // set the maxLength attribute
1099
0
        textEditor->SetMaxTextLength(maxLength);
1100
0
        // if maxLength>docLength, we need to truncate the doc content
1101
0
      } else { // unset the maxLength attribute
1102
0
        textEditor->SetMaxTextLength(-1);
1103
0
      }
1104
0
    }
1105
0
    return NS_OK;
1106
0
  }
1107
0
1108
0
  if (nsGkAtoms::readonly == aAttribute) {
1109
0
    if (AttributeExists(nsGkAtoms::readonly)) { // set readonly
1110
0
      if (nsContentUtils::IsFocusedContent(mContent)) {
1111
0
        selCon->SetCaretEnabled(false);
1112
0
      }
1113
0
      textEditor->AddFlags(nsIPlaintextEditor::eEditorReadonlyMask);
1114
0
    } else { // unset readonly
1115
0
      if (!textEditor->IsDisabled() &&
1116
0
          nsContentUtils::IsFocusedContent(mContent)) {
1117
0
        selCon->SetCaretEnabled(true);
1118
0
      }
1119
0
      textEditor->RemoveFlags(nsIPlaintextEditor::eEditorReadonlyMask);
1120
0
    }
1121
0
    return NS_OK;
1122
0
  }
1123
0
1124
0
  if (nsGkAtoms::disabled == aAttribute) {
1125
0
    int16_t displaySelection = nsISelectionController::SELECTION_OFF;
1126
0
    const bool focused = nsContentUtils::IsFocusedContent(mContent);
1127
0
    const bool hasAttr = AttributeExists(nsGkAtoms::disabled);
1128
0
    bool disable;
1129
0
    if (hasAttr) { // set disabled
1130
0
      disable = true;
1131
0
    } else { // unset disabled
1132
0
      disable = false;
1133
0
      displaySelection = focused ? nsISelectionController::SELECTION_ON
1134
0
                                 : nsISelectionController::SELECTION_HIDDEN;
1135
0
    }
1136
0
    selCon->SetDisplaySelection(displaySelection);
1137
0
    if (focused) {
1138
0
      selCon->SetCaretEnabled(!hasAttr);
1139
0
    }
1140
0
    if (disable) {
1141
0
      textEditor->AddFlags(nsIPlaintextEditor::eEditorDisabledMask);
1142
0
    } else {
1143
0
      textEditor->RemoveFlags(nsIPlaintextEditor::eEditorDisabledMask);
1144
0
    }
1145
0
    return NS_OK;
1146
0
  }
1147
0
1148
0
  if (!mEditorHasBeenInitialized && nsGkAtoms::value == aAttribute) {
1149
0
    UpdateValueDisplay(true);
1150
0
    return NS_OK;
1151
0
  }
1152
0
1153
0
  // Allow the base class to handle common attributes supported by all form
1154
0
  // elements...
1155
0
  return nsContainerFrame::AttributeChanged(aNameSpaceID, aAttribute, aModType);
1156
0
}
1157
1158
1159
void
1160
nsTextControlFrame::GetText(nsString& aText)
1161
0
{
1162
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
1163
0
  NS_ASSERTION(txtCtrl, "Content not a text control element");
1164
0
  if (IsSingleLineTextControl()) {
1165
0
    // There will be no line breaks so we can ignore the wrap property.
1166
0
    txtCtrl->GetTextEditorValue(aText, true);
1167
0
  } else {
1168
0
    HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(mContent);
1169
0
    if (textArea) {
1170
0
      textArea->GetValue(aText);
1171
0
    }
1172
0
  }
1173
0
}
1174
1175
1176
///END NSIFRAME OVERLOADS
1177
/////BEGIN PROTECTED METHODS
1178
1179
bool
1180
nsTextControlFrame::GetMaxLength(int32_t* aSize)
1181
0
{
1182
0
  *aSize = -1;
1183
0
1184
0
  nsGenericHTMLElement *content = nsGenericHTMLElement::FromNode(mContent);
1185
0
  if (content) {
1186
0
    const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::maxlength);
1187
0
    if (attr && attr->Type() == nsAttrValue::eInteger) {
1188
0
      *aSize = attr->GetIntegerValue();
1189
0
1190
0
      return true;
1191
0
    }
1192
0
  }
1193
0
  return false;
1194
0
}
1195
1196
// END IMPLEMENTING NS_IFORMCONTROLFRAME
1197
1198
void
1199
nsTextControlFrame::SetInitialChildList(ChildListID     aListID,
1200
                                        nsFrameList&    aChildList)
1201
0
{
1202
0
  nsContainerFrame::SetInitialChildList(aListID, aChildList);
1203
0
  if (aListID != kPrincipalList) {
1204
0
    return;
1205
0
  }
1206
0
1207
0
  // Mark the scroll frame as being a reflow root. This will allow
1208
0
  // incremental reflows to be initiated at the scroll frame, rather
1209
0
  // than descending from the root frame of the frame hierarchy.
1210
0
  if (nsIFrame* first = PrincipalChildList().FirstChild()) {
1211
0
    first->AddStateBits(NS_FRAME_REFLOW_ROOT);
1212
0
1213
0
    nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
1214
0
    NS_ASSERTION(txtCtrl, "Content not a text control element");
1215
0
    txtCtrl->InitializeKeyboardEventListeners();
1216
0
1217
0
    nsPoint* contentScrollPos = GetProperty(ContentScrollPos());
1218
0
    if (contentScrollPos) {
1219
0
      // If we have a scroll pos stored to be passed to our anonymous
1220
0
      // div, do it here!
1221
0
      nsIStatefulFrame* statefulFrame = do_QueryFrame(first);
1222
0
      NS_ASSERTION(statefulFrame, "unexpected type of frame for the anonymous div");
1223
0
      UniquePtr<PresState> fakePresState = NewPresState();
1224
0
      fakePresState->scrollState() = *contentScrollPos;
1225
0
      statefulFrame->RestoreState(fakePresState.get());
1226
0
      RemoveProperty(ContentScrollPos());
1227
0
      delete contentScrollPos;
1228
0
    }
1229
0
  }
1230
0
}
1231
1232
void
1233
nsTextControlFrame::SetValueChanged(bool aValueChanged)
1234
0
{
1235
0
  nsCOMPtr<nsITextControlElement> txtCtrl = HTMLInputElement::FromNode(GetContent());
1236
0
  if (!txtCtrl) {
1237
0
    txtCtrl = HTMLTextAreaElement::FromNode(GetContent());
1238
0
  }
1239
0
  MOZ_ASSERT(txtCtrl, "Content not a text control element");
1240
0
1241
0
  if (mPlaceholderDiv) {
1242
0
    AutoWeakFrame weakFrame(this);
1243
0
    txtCtrl->UpdateOverlayTextVisibility(true);
1244
0
    if (!weakFrame.IsAlive()) {
1245
0
      return;
1246
0
    }
1247
0
  }
1248
0
1249
0
  txtCtrl->SetValueChanged(aValueChanged);
1250
0
}
1251
1252
1253
nsresult
1254
nsTextControlFrame::UpdateValueDisplay(bool aNotify,
1255
                                       bool aBeforeEditorInit,
1256
                                       const nsAString *aValue)
1257
0
{
1258
0
  if (!IsSingleLineTextControl()) // textareas don't use this
1259
0
    return NS_OK;
1260
0
1261
0
  MOZ_ASSERT(mRootNode, "Must have a div content\n");
1262
0
  MOZ_ASSERT(!mEditorHasBeenInitialized,
1263
0
             "Do not call this after editor has been initialized");
1264
0
1265
0
  nsIContent* childContent = mRootNode->GetFirstChild();
1266
0
  Text* textContent;
1267
0
  if (!childContent) {
1268
0
    // Set up a textnode with our value
1269
0
    RefPtr<nsTextNode> textNode =
1270
0
      new nsTextNode(mContent->NodeInfo()->NodeInfoManager());
1271
0
    textNode->MarkAsMaybeModifiedFrequently();
1272
0
1273
0
    mRootNode->AppendChildTo(textNode, aNotify);
1274
0
    textContent = textNode;
1275
0
  } else {
1276
0
    textContent = childContent->GetAsText();
1277
0
  }
1278
0
1279
0
  NS_ENSURE_TRUE(textContent, NS_ERROR_UNEXPECTED);
1280
0
1281
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
1282
0
  MOZ_ASSERT(txtCtrl);
1283
0
1284
0
  // Get the current value of the textfield from the content.
1285
0
  nsAutoString value;
1286
0
  if (aValue) {
1287
0
    value = *aValue;
1288
0
  } else {
1289
0
    txtCtrl->GetTextEditorValue(value, true);
1290
0
  }
1291
0
1292
0
  // Update the display of the placeholder value and preview text if needed.
1293
0
  // We don't need to do this if we're about to initialize the editor, since
1294
0
  // EnsureEditorInitialized takes care of this.
1295
0
  if ((mPlaceholderDiv || mPreviewDiv) && !aBeforeEditorInit) {
1296
0
    AutoWeakFrame weakFrame(this);
1297
0
    txtCtrl->UpdateOverlayTextVisibility(aNotify);
1298
0
    NS_ENSURE_STATE(weakFrame.IsAlive());
1299
0
  }
1300
0
1301
0
  if (aBeforeEditorInit && value.IsEmpty()) {
1302
0
    nsIContent* node = mRootNode->GetFirstChild();
1303
0
    if (node) {
1304
0
      mRootNode->RemoveChildNode(node, true);
1305
0
    }
1306
0
    return NS_OK;
1307
0
  }
1308
0
1309
0
  if (!value.IsEmpty() && IsPasswordTextControl()) {
1310
0
    TextEditRules::FillBufWithPWChars(&value, value.Length());
1311
0
  }
1312
0
  return textContent->SetText(value, aNotify);
1313
0
}
1314
1315
NS_IMETHODIMP
1316
nsTextControlFrame::GetOwnedSelectionController(nsISelectionController** aSelCon)
1317
0
{
1318
0
  NS_ENSURE_ARG_POINTER(aSelCon);
1319
0
1320
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
1321
0
  NS_ASSERTION(txtCtrl, "Content not a text control element");
1322
0
1323
0
  *aSelCon = txtCtrl->GetSelectionController();
1324
0
  NS_IF_ADDREF(*aSelCon);
1325
0
1326
0
  return NS_OK;
1327
0
}
1328
1329
nsFrameSelection*
1330
nsTextControlFrame::GetOwnedFrameSelection()
1331
0
{
1332
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
1333
0
  NS_ASSERTION(txtCtrl, "Content not a text control element");
1334
0
1335
0
  return txtCtrl->GetConstFrameSelection();
1336
0
}
1337
1338
UniquePtr<PresState>
1339
nsTextControlFrame::SaveState()
1340
0
{
1341
0
  if (mRootNode) {
1342
0
    // Query the nsIStatefulFrame from the HTMLScrollFrame
1343
0
    nsIStatefulFrame* scrollStateFrame =
1344
0
      do_QueryFrame(mRootNode->GetPrimaryFrame());
1345
0
    if (scrollStateFrame) {
1346
0
      return scrollStateFrame->SaveState();
1347
0
    }
1348
0
  }
1349
0
1350
0
  return nullptr;
1351
0
}
1352
1353
NS_IMETHODIMP
1354
nsTextControlFrame::RestoreState(PresState* aState)
1355
0
{
1356
0
  NS_ENSURE_ARG_POINTER(aState);
1357
0
1358
0
  if (mRootNode) {
1359
0
    // Query the nsIStatefulFrame from the HTMLScrollFrame
1360
0
    nsIStatefulFrame* scrollStateFrame =
1361
0
      do_QueryFrame(mRootNode->GetPrimaryFrame());
1362
0
    if (scrollStateFrame) {
1363
0
      return scrollStateFrame->RestoreState(aState);
1364
0
    }
1365
0
  }
1366
0
1367
0
  // Most likely, we don't have our anonymous content constructed yet, which
1368
0
  // would cause us to end up here.  In this case, we'll just store the scroll
1369
0
  // pos ourselves, and forward it to the scroll frame later when it's created.
1370
0
  SetProperty(ContentScrollPos(), new nsPoint(aState->scrollState()));
1371
0
  return NS_OK;
1372
0
}
1373
1374
nsresult
1375
nsTextControlFrame::PeekOffset(nsPeekOffsetStruct *aPos)
1376
0
{
1377
0
  return NS_ERROR_FAILURE;
1378
0
}
1379
1380
void
1381
nsTextControlFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
1382
                                     const nsDisplayListSet& aLists)
1383
0
{
1384
0
  /*
1385
0
   * The implementation of this method is equivalent as:
1386
0
   * nsContainerFrame::BuildDisplayList()
1387
0
   * with the difference that we filter-out the placeholder frame when it
1388
0
   * should not be visible.
1389
0
   */
1390
0
  DO_GLOBAL_REFLOW_COUNT_DSP("nsTextControlFrame");
1391
0
1392
0
  nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
1393
0
  NS_ASSERTION(txtCtrl, "Content not a text control element!");
1394
0
1395
0
  DisplayBorderBackgroundOutline(aBuilder, aLists);
1396
0
1397
0
  nsIFrame* kid = mFrames.FirstChild();
1398
0
  // Redirect all lists to the Content list so that nothing can escape, ie
1399
0
  // opacity creating stacking contexts that then get sorted with stacking
1400
0
  // contexts external to us.
1401
0
  nsDisplayList* content = aLists.Content();
1402
0
  nsDisplayListSet set(content, content, content, content, content, content);
1403
0
1404
0
  while (kid) {
1405
0
    // If the frame is the placeholder or preview frame, we should only show
1406
0
    // it if it has to be visible.
1407
0
    if (!((kid->GetContent() == mPlaceholderDiv &&
1408
0
           !txtCtrl->GetPlaceholderVisibility()) ||
1409
0
          (kid->GetContent() == mPreviewDiv &&
1410
0
           !txtCtrl->GetPreviewVisibility()))) {
1411
0
      BuildDisplayListForChild(aBuilder, kid, set, 0);
1412
0
    }
1413
0
    kid = kid->GetNextSibling();
1414
0
  }
1415
0
}
1416
1417
NS_IMETHODIMP
1418
nsTextControlFrame::EditorInitializer::Run()
1419
0
{
1420
0
  if (!mFrame) {
1421
0
    return NS_OK;
1422
0
  }
1423
0
1424
0
  // Need to block script to avoid bug 669767.
1425
0
  nsAutoScriptBlocker scriptBlocker;
1426
0
1427
0
  nsCOMPtr<nsIPresShell> shell =
1428
0
    mFrame->PresContext()->GetPresShell();
1429
0
  bool observes = shell->ObservesNativeAnonMutationsForPrint();
1430
0
  shell->ObserveNativeAnonMutationsForPrint(true);
1431
0
  // This can cause the frame to be destroyed (and call Revoke()).
1432
0
  mFrame->EnsureEditorInitialized();
1433
0
  shell->ObserveNativeAnonMutationsForPrint(observes);
1434
0
1435
0
  // The frame can *still* be destroyed even though we have a scriptblocker,
1436
0
  // bug 682684.
1437
0
  if (!mFrame) {
1438
0
    return NS_ERROR_FAILURE;
1439
0
  }
1440
0
1441
0
  mFrame->FinishedInitializer();
1442
0
  return NS_OK;
1443
0
}
1444
1445
NS_IMPL_ISUPPORTS(nsTextControlFrame::nsAnonDivObserver, nsIMutationObserver)
1446
1447
void
1448
nsTextControlFrame::nsAnonDivObserver::CharacterDataChanged(
1449
  nsIContent* aContent,
1450
  const CharacterDataChangeInfo&)
1451
0
{
1452
0
  mFrame.ClearCachedValue();
1453
0
}
1454
1455
void
1456
nsTextControlFrame::nsAnonDivObserver::ContentAppended(
1457
  nsIContent* aFirstNewContent)
1458
0
{
1459
0
  mFrame.ClearCachedValue();
1460
0
}
1461
1462
void
1463
nsTextControlFrame::nsAnonDivObserver::ContentInserted(nsIContent* aChild)
1464
0
{
1465
0
  mFrame.ClearCachedValue();
1466
0
}
1467
1468
void
1469
nsTextControlFrame::nsAnonDivObserver::ContentRemoved(
1470
  nsIContent* aChild,
1471
  nsIContent* aPreviousSibling)
1472
0
{
1473
0
  mFrame.ClearCachedValue();
1474
0
}