Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/dom/html/nsTextEditorState.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 "nsTextEditorState.h"
8
#include "mozilla/TextInputListener.h"
9
10
#include "nsCOMPtr.h"
11
#include "nsIPresShell.h"
12
#include "nsView.h"
13
#include "nsCaret.h"
14
#include "nsLayoutCID.h"
15
#include "nsITextControlFrame.h"
16
#include "nsContentCreatorFunctions.h"
17
#include "nsTextControlFrame.h"
18
#include "nsIControllers.h"
19
#include "nsITransactionManager.h"
20
#include "nsIControllerContext.h"
21
#include "nsAttrValue.h"
22
#include "nsAttrValueInlines.h"
23
#include "nsGenericHTMLElement.h"
24
#include "nsIDOMEventListener.h"
25
#include "nsIEditorObserver.h"
26
#include "nsIWidget.h"
27
#include "nsIDocumentEncoder.h"
28
#include "nsPIDOMWindow.h"
29
#include "nsServiceManagerUtils.h"
30
#include "mozilla/dom/Selection.h"
31
#include "mozilla/TextEditRules.h"
32
#include "mozilla/EventListenerManager.h"
33
#include "nsContentUtils.h"
34
#include "mozilla/Preferences.h"
35
#include "nsTextNode.h"
36
#include "nsIController.h"
37
#include "mozilla/AutoRestore.h"
38
#include "mozilla/TextEvents.h"
39
#include "mozilla/dom/Event.h"
40
#include "mozilla/dom/ScriptSettings.h"
41
#include "mozilla/dom/HTMLInputElement.h"
42
#include "mozilla/dom/HTMLTextAreaElement.h"
43
#include "mozilla/dom/Text.h"
44
#include "nsNumberControlFrame.h"
45
#include "nsFrameSelection.h"
46
#include "mozilla/ErrorResult.h"
47
#include "mozilla/Telemetry.h"
48
49
using namespace mozilla;
50
using namespace mozilla::dom;
51
52
inline nsresult
53
SetEditorFlagsIfNecessary(EditorBase& aEditorBase, uint32_t aFlags)
54
0
{
55
0
  if (aEditorBase.Flags() == aFlags) {
56
0
    return NS_OK;
57
0
  }
58
0
  return aEditorBase.SetFlags(aFlags);
59
0
}
60
61
class MOZ_STACK_CLASS ValueSetter
62
{
63
public:
64
  explicit ValueSetter(TextEditor* aTextEditor)
65
    : mTextEditor(aTextEditor)
66
    // To protect against a reentrant call to SetValue, we check whether
67
    // another SetValue is already happening for this editor.  If it is,
68
    // we must wait until we unwind to re-enable oninput events.
69
    , mOuterTransaction(aTextEditor->IsSuppressingDispatchingInputEvent())
70
0
  {
71
0
    MOZ_ASSERT(aTextEditor);
72
0
  }
73
  ~ValueSetter()
74
0
  {
75
0
    mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction);
76
0
  }
77
  void Init()
78
0
  {
79
0
    mTextEditor->SuppressDispatchingInputEvent(true);
80
0
  }
81
82
private:
83
  RefPtr<TextEditor> mTextEditor;
84
  bool mOuterTransaction;
85
};
86
87
class RestoreSelectionState : public Runnable {
88
public:
89
  RestoreSelectionState(nsTextEditorState* aState, nsTextControlFrame* aFrame)
90
    : mozilla::Runnable("RestoreSelectionState")
91
    , mFrame(aFrame)
92
    , mTextEditorState(aState)
93
0
  {
94
0
  }
95
96
0
  NS_IMETHOD Run() override {
97
0
    if (!mTextEditorState) {
98
0
      return NS_OK;
99
0
    }
100
0
101
0
    AutoHideSelectionChanges hideSelectionChanges
102
0
      (mFrame->GetConstFrameSelection());
103
0
104
0
    if (mFrame) {
105
0
      // SetSelectionRange leads to Selection::AddRange which flushes Layout -
106
0
      // need to block script to avoid nested PrepareEditor calls (bug 642800).
107
0
      nsAutoScriptBlocker scriptBlocker;
108
0
      nsTextEditorState::SelectionProperties& properties =
109
0
        mTextEditorState->GetSelectionProperties();
110
0
      if (properties.IsDirty()) {
111
0
        mFrame->SetSelectionRange(properties.GetStart(),
112
0
                                  properties.GetEnd(),
113
0
                                  properties.GetDirection());
114
0
      }
115
0
      if (!mTextEditorState->mSelectionRestoreEagerInit) {
116
0
        mTextEditorState->HideSelectionIfBlurred();
117
0
      }
118
0
      mTextEditorState->mSelectionRestoreEagerInit = false;
119
0
    }
120
0
121
0
    if (mTextEditorState) {
122
0
      mTextEditorState->FinishedRestoringSelection();
123
0
    }
124
0
    return NS_OK;
125
0
  }
126
127
  // Let the text editor tell us we're no longer relevant - avoids use of AutoWeakFrame
128
0
  void Revoke() {
129
0
    mFrame = nullptr;
130
0
    mTextEditorState = nullptr;
131
0
  }
132
133
private:
134
  nsTextControlFrame* mFrame;
135
  nsTextEditorState* mTextEditorState;
136
};
137
138
class MOZ_RAII AutoRestoreEditorState final
139
{
140
public:
141
  explicit AutoRestoreEditorState(TextEditor* aTextEditor
142
                                  MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
143
    : mTextEditor(aTextEditor)
144
    , mSavedFlags(mTextEditor->Flags())
145
    , mSavedMaxLength(mTextEditor->MaxTextLength())
146
0
  {
147
0
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
148
0
    MOZ_ASSERT(mTextEditor);
149
0
150
0
    // EditorBase::SetFlags() is a virtual method.  Even though it does nothing
151
0
    // if new flags and current flags are same, the calling cost causes
152
0
    // appearing the method in profile.  So, this class should check if it's
153
0
    // necessary to call.
154
0
    uint32_t flags = mSavedFlags;
155
0
    flags &= ~(nsIPlaintextEditor::eEditorDisabledMask);
156
0
    flags &= ~(nsIPlaintextEditor::eEditorReadonlyMask);
157
0
    flags |= nsIPlaintextEditor::eEditorDontEchoPassword;
158
0
    if (mSavedFlags != flags) {
159
0
      mTextEditor->SetFlags(flags);
160
0
    }
161
0
    mTextEditor->SetMaxTextLength(-1);
162
0
  }
163
164
  ~AutoRestoreEditorState()
165
0
  {
166
0
     mTextEditor->SetMaxTextLength(mSavedMaxLength);
167
0
     SetEditorFlagsIfNecessary(*mTextEditor, mSavedFlags);
168
0
  }
169
170
private:
171
  TextEditor* mTextEditor;
172
  uint32_t mSavedFlags;
173
  int32_t mSavedMaxLength;
174
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
175
};
176
177
class MOZ_RAII AutoDisableUndo final
178
{
179
public:
180
  explicit AutoDisableUndo(TextEditor* aTextEditor
181
                           MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
182
    : mTextEditor(aTextEditor)
183
    , mNumberOfMaximumTransactions(0)
184
0
  {
185
0
    MOZ_GUARD_OBJECT_NOTIFIER_INIT;
186
0
    MOZ_ASSERT(mTextEditor);
187
0
188
0
    mNumberOfMaximumTransactions =
189
0
      mTextEditor ? mTextEditor->NumberOfMaximumTransactions() : 0;
190
0
    DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
191
0
    NS_WARNING_ASSERTION(disabledUndoRedo,
192
0
      "Failed to disable undo/redo transactions");
193
0
  }
194
195
  ~AutoDisableUndo()
196
0
  {
197
0
    // Don't change enable/disable of undo/redo if it's enabled after
198
0
    // it's disabled by the constructor because we shouldn't change
199
0
    // the maximum undo/redo count to the old value.
200
0
    if (mTextEditor->IsUndoRedoEnabled()) {
201
0
      return;
202
0
    }
203
0
    // If undo/redo was enabled, mNumberOfMaximumTransactions is -1 or lager
204
0
    // than 0.  Only when it's 0, it was disabled.
205
0
    if (mNumberOfMaximumTransactions) {
206
0
      DebugOnly<bool> enabledUndoRedo =
207
0
        mTextEditor->EnableUndoRedo(mNumberOfMaximumTransactions);
208
0
      NS_WARNING_ASSERTION(enabledUndoRedo,
209
0
        "Failed to enable undo/redo transactions");
210
0
    } else {
211
0
      DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
212
0
      NS_WARNING_ASSERTION(disabledUndoRedo,
213
0
        "Failed to disable undo/redo transactions");
214
0
    }
215
0
  }
216
217
private:
218
  TextEditor* mTextEditor;
219
  int32_t mNumberOfMaximumTransactions;
220
  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
221
};
222
223
/*static*/
224
bool
225
nsITextControlElement::GetWrapPropertyEnum(nsIContent* aContent,
226
  nsITextControlElement::nsHTMLTextWrap& aWrapProp)
227
0
{
228
0
  // soft is the default; "physical" defaults to soft as well because all other
229
0
  // browsers treat it that way and there is no real reason to maintain physical
230
0
  // and virtual as separate entities if no one else does.  Only hard and off
231
0
  // do anything different.
232
0
  aWrapProp = eHTMLTextWrap_Soft; // the default
233
0
234
0
  nsAutoString wrap;
235
0
  if (aContent->IsHTMLElement()) {
236
0
    static Element::AttrValuesArray strings[] =
237
0
      {&nsGkAtoms::HARD, &nsGkAtoms::OFF, nullptr};
238
0
239
0
    switch (aContent->AsElement()->FindAttrValueIn(kNameSpaceID_None,
240
0
                                                   nsGkAtoms::wrap, strings,
241
0
                                                   eIgnoreCase)) {
242
0
      case 0: aWrapProp = eHTMLTextWrap_Hard; break;
243
0
      case 1: aWrapProp = eHTMLTextWrap_Off; break;
244
0
    }
245
0
246
0
    return true;
247
0
  }
248
0
249
0
  return false;
250
0
}
251
252
/*static*/
253
already_AddRefed<nsITextControlElement>
254
nsITextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost)
255
0
{
256
0
  if (!aHost) {
257
0
    return nullptr;
258
0
  }
259
0
260
0
  nsCOMPtr<nsITextControlElement> parent =
261
0
    do_QueryInterface(aHost->GetParent());
262
0
263
0
  return parent.forget();
264
0
}
265
266
static bool
267
SuppressEventHandlers(nsPresContext* aPresContext)
268
0
{
269
0
  bool suppressHandlers = false;
270
0
271
0
  if (aPresContext)
272
0
  {
273
0
    // Right now we only suppress event handlers and controller manipulation
274
0
    // when in a print preview or print context!
275
0
276
0
    // In the current implementation, we only paginate when
277
0
    // printing or in print preview.
278
0
279
0
    suppressHandlers = aPresContext->IsPaginated();
280
0
  }
281
0
282
0
  return suppressHandlers;
283
0
}
284
285
class nsAnonDivObserver final : public nsStubMutationObserver
286
{
287
public:
288
  explicit nsAnonDivObserver(nsTextEditorState* aTextEditorState)
289
0
  : mTextEditorState(aTextEditorState) {}
290
  NS_DECL_ISUPPORTS
291
  NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
292
  NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
293
  NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
294
  NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
295
296
private:
297
0
  ~nsAnonDivObserver() {}
298
  nsTextEditorState* mTextEditorState;
299
};
300
301
class nsTextInputSelectionImpl final : public nsSupportsWeakReference
302
                                     , public nsISelectionController
303
{
304
0
  ~nsTextInputSelectionImpl(){}
305
306
public:
307
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
308
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsTextInputSelectionImpl, nsISelectionController)
309
310
  nsTextInputSelectionImpl(nsFrameSelection *aSel, nsIPresShell *aShell, nsIContent *aLimiter);
311
312
  void SetScrollableFrame(nsIScrollableFrame *aScrollableFrame);
313
  nsFrameSelection* GetConstFrameSelection()
314
0
    { return mFrameSelection; }
315
  // Will return null if !mFrameSelection.
316
  Selection* GetSelection(SelectionType aSelectionType);
317
318
  //NSISELECTIONCONTROLLER INTERFACES
319
  NS_IMETHOD SetDisplaySelection(int16_t toggle) override;
320
  NS_IMETHOD GetDisplaySelection(int16_t* _retval) override;
321
  NS_IMETHOD SetSelectionFlags(int16_t aInEnable) override;
322
  NS_IMETHOD GetSelectionFlags(int16_t *aOutEnable) override;
323
  NS_IMETHOD GetSelectionFromScript(RawSelectionType aRawSelectionType,
324
                                    Selection** aSelection) override;
325
  Selection* GetSelection(RawSelectionType aRawSelectionType) override;
326
  NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
327
                                     int16_t aRegion, int16_t aFlags) override;
328
  NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override;
329
  nsresult RepaintSelection(nsPresContext* aPresContext,
330
                            SelectionType aSelectionType);
331
  NS_IMETHOD SetCaretEnabled(bool enabled) override;
332
  NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override;
333
  NS_IMETHOD GetCaretEnabled(bool* _retval) override;
334
  NS_IMETHOD GetCaretVisible(bool* _retval) override;
335
  NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override;
336
  NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) override;
337
  NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override;
338
  NS_IMETHOD CharacterExtendForDelete() override;
339
  NS_IMETHOD CharacterExtendForBackspace() override;
340
  NS_IMETHOD WordMove(bool aForward, bool aExtend) override;
341
  NS_IMETHOD WordExtendForDelete(bool aForward) override;
342
  NS_IMETHOD LineMove(bool aForward, bool aExtend) override;
343
  NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override;
344
  NS_IMETHOD PageMove(bool aForward, bool aExtend) override;
345
  NS_IMETHOD CompleteScroll(bool aForward) override;
346
  NS_IMETHOD CompleteMove(bool aForward, bool aExtend) override;
347
  NS_IMETHOD ScrollPage(bool aForward) override;
348
  NS_IMETHOD ScrollLine(bool aForward) override;
349
  NS_IMETHOD ScrollCharacter(bool aRight) override;
350
  NS_IMETHOD SelectAll(void) override;
351
  NS_IMETHOD CheckVisibility(nsINode *node, int16_t startOffset, int16_t EndOffset, bool* _retval) override;
352
  virtual nsresult CheckVisibilityContent(nsIContent* aNode, int16_t aStartOffset, int16_t aEndOffset, bool* aRetval) override;
353
354
private:
355
  RefPtr<nsFrameSelection> mFrameSelection;
356
  nsCOMPtr<nsIContent>       mLimiter;
357
  nsIScrollableFrame        *mScrollFrame;
358
  nsWeakPtr mPresShellWeak;
359
};
360
361
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextInputSelectionImpl)
362
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextInputSelectionImpl)
363
0
NS_INTERFACE_TABLE_HEAD(nsTextInputSelectionImpl)
364
0
  NS_INTERFACE_TABLE(nsTextInputSelectionImpl,
365
0
                     nsISelectionController,
366
0
                     nsISelectionDisplay,
367
0
                     nsISupportsWeakReference)
368
0
  NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsTextInputSelectionImpl)
369
0
NS_INTERFACE_MAP_END
370
371
NS_IMPL_CYCLE_COLLECTION(nsTextInputSelectionImpl, mFrameSelection, mLimiter)
372
373
374
// BEGIN nsTextInputSelectionImpl
375
376
nsTextInputSelectionImpl::nsTextInputSelectionImpl(nsFrameSelection *aSel,
377
                                                   nsIPresShell *aShell,
378
                                                   nsIContent *aLimiter)
379
  : mScrollFrame(nullptr)
380
0
{
381
0
  if (aSel && aShell)
382
0
  {
383
0
    mFrameSelection = aSel;//we are the owner now!
384
0
    mLimiter = aLimiter;
385
0
    bool accessibleCaretEnabled =
386
0
      PresShell::AccessibleCaretEnabled(aLimiter->OwnerDoc()->GetDocShell());
387
0
    mFrameSelection->Init(aShell, mLimiter, accessibleCaretEnabled);
388
0
    mPresShellWeak = do_GetWeakReference(aShell);
389
0
  }
390
0
}
391
392
void
393
nsTextInputSelectionImpl::SetScrollableFrame(nsIScrollableFrame *aScrollableFrame)
394
0
{
395
0
  mScrollFrame = aScrollableFrame;
396
0
  if (!mScrollFrame && mFrameSelection) {
397
0
    mFrameSelection->DisconnectFromPresShell();
398
0
    mFrameSelection = nullptr;
399
0
  }
400
0
}
401
402
Selection*
403
nsTextInputSelectionImpl::GetSelection(SelectionType aSelectionType)
404
0
{
405
0
  if (!mFrameSelection) {
406
0
    return nullptr;
407
0
  }
408
0
409
0
  return mFrameSelection->GetSelection(aSelectionType);
410
0
}
411
412
NS_IMETHODIMP
413
nsTextInputSelectionImpl::SetDisplaySelection(int16_t aToggle)
414
0
{
415
0
  if (!mFrameSelection)
416
0
    return NS_ERROR_NULL_POINTER;
417
0
418
0
  mFrameSelection->SetDisplaySelection(aToggle);
419
0
  return NS_OK;
420
0
}
421
422
NS_IMETHODIMP
423
nsTextInputSelectionImpl::GetDisplaySelection(int16_t *aToggle)
424
0
{
425
0
  if (!mFrameSelection)
426
0
    return NS_ERROR_NULL_POINTER;
427
0
428
0
  *aToggle = mFrameSelection->GetDisplaySelection();
429
0
  return NS_OK;
430
0
}
431
432
NS_IMETHODIMP
433
nsTextInputSelectionImpl::SetSelectionFlags(int16_t aToggle)
434
0
{
435
0
  return NS_OK;//stub this out. not used in input
436
0
}
437
438
NS_IMETHODIMP
439
nsTextInputSelectionImpl::GetSelectionFlags(int16_t *aOutEnable)
440
0
{
441
0
  *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT;
442
0
  return NS_OK;
443
0
}
444
445
NS_IMETHODIMP
446
nsTextInputSelectionImpl::GetSelectionFromScript(RawSelectionType aRawSelectionType,
447
                                                 Selection** aSelection)
448
0
{
449
0
  if (!mFrameSelection)
450
0
    return NS_ERROR_NULL_POINTER;
451
0
452
0
  *aSelection =
453
0
    mFrameSelection->GetSelection(ToSelectionType(aRawSelectionType));
454
0
455
0
  // GetSelection() fails only when aRawSelectionType is invalid value.
456
0
  if (!(*aSelection)) {
457
0
    return NS_ERROR_INVALID_ARG;
458
0
  }
459
0
460
0
  NS_ADDREF(*aSelection);
461
0
  return NS_OK;
462
0
}
463
464
Selection*
465
nsTextInputSelectionImpl::GetSelection(RawSelectionType aRawSelectionType)
466
0
{
467
0
  return GetSelection(ToSelectionType(aRawSelectionType));
468
0
}
469
470
NS_IMETHODIMP
471
nsTextInputSelectionImpl::ScrollSelectionIntoView(
472
                            RawSelectionType aRawSelectionType,
473
                            int16_t aRegion,
474
                            int16_t aFlags)
475
0
{
476
0
  if (!mFrameSelection)
477
0
    return NS_ERROR_FAILURE;
478
0
479
0
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
480
0
  return frameSelection->ScrollSelectionIntoView(
481
0
                           ToSelectionType(aRawSelectionType),
482
0
                           aRegion, aFlags);
483
0
}
484
485
NS_IMETHODIMP
486
nsTextInputSelectionImpl::RepaintSelection(RawSelectionType aRawSelectionType)
487
0
{
488
0
  if (!mFrameSelection)
489
0
    return NS_ERROR_FAILURE;
490
0
491
0
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
492
0
  return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
493
0
}
494
495
nsresult
496
nsTextInputSelectionImpl::RepaintSelection(nsPresContext* aPresContext,
497
                                           SelectionType aSelectionType)
498
0
{
499
0
  if (!mFrameSelection)
500
0
    return NS_ERROR_FAILURE;
501
0
502
0
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
503
0
  return frameSelection->RepaintSelection(aSelectionType);
504
0
}
505
506
NS_IMETHODIMP
507
nsTextInputSelectionImpl::SetCaretEnabled(bool enabled)
508
0
{
509
0
  if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
510
0
511
0
  nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak);
512
0
  if (!shell) return NS_ERROR_FAILURE;
513
0
514
0
  // tell the pres shell to enable the caret, rather than settings its visibility directly.
515
0
  // this way the presShell's idea of caret visibility is maintained.
516
0
  nsCOMPtr<nsISelectionController> selCon = do_QueryInterface(shell);
517
0
  if (!selCon) return NS_ERROR_NO_INTERFACE;
518
0
  selCon->SetCaretEnabled(enabled);
519
0
520
0
  return NS_OK;
521
0
}
522
523
NS_IMETHODIMP
524
nsTextInputSelectionImpl::SetCaretReadOnly(bool aReadOnly)
525
0
{
526
0
  if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
527
0
  nsresult result;
528
0
  nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result);
529
0
  if (shell)
530
0
  {
531
0
    RefPtr<nsCaret> caret = shell->GetCaret();
532
0
    if (caret) {
533
0
      Selection* selection =
534
0
        mFrameSelection->GetSelection(SelectionType::eNormal);
535
0
      if (selection) {
536
0
        caret->SetCaretReadOnly(aReadOnly);
537
0
      }
538
0
      return NS_OK;
539
0
    }
540
0
  }
541
0
  return NS_ERROR_FAILURE;
542
0
}
543
544
NS_IMETHODIMP
545
nsTextInputSelectionImpl::GetCaretEnabled(bool *_retval)
546
0
{
547
0
  return GetCaretVisible(_retval);
548
0
}
549
550
NS_IMETHODIMP
551
nsTextInputSelectionImpl::GetCaretVisible(bool *_retval)
552
0
{
553
0
  if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
554
0
  nsresult result;
555
0
  nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result);
556
0
  if (shell)
557
0
  {
558
0
    RefPtr<nsCaret> caret = shell->GetCaret();
559
0
    if (caret) {
560
0
      *_retval = caret->IsVisible();
561
0
      return NS_OK;
562
0
    }
563
0
  }
564
0
  return NS_ERROR_FAILURE;
565
0
}
566
567
NS_IMETHODIMP
568
nsTextInputSelectionImpl::SetCaretVisibilityDuringSelection(bool aVisibility)
569
0
{
570
0
  if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
571
0
  nsresult result;
572
0
  nsCOMPtr<nsIPresShell> shell = do_QueryReferent(mPresShellWeak, &result);
573
0
  if (shell)
574
0
  {
575
0
    RefPtr<nsCaret> caret = shell->GetCaret();
576
0
    if (caret) {
577
0
      Selection* selection =
578
0
        mFrameSelection->GetSelection(SelectionType::eNormal);
579
0
      if (selection) {
580
0
        caret->SetVisibilityDuringSelection(aVisibility);
581
0
      }
582
0
      return NS_OK;
583
0
    }
584
0
  }
585
0
  return NS_ERROR_FAILURE;
586
0
}
587
588
NS_IMETHODIMP
589
nsTextInputSelectionImpl::PhysicalMove(int16_t aDirection, int16_t aAmount,
590
                                       bool aExtend)
591
0
{
592
0
  if (mFrameSelection) {
593
0
    RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
594
0
    return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
595
0
  }
596
0
  return NS_ERROR_NULL_POINTER;
597
0
}
598
599
NS_IMETHODIMP
600
nsTextInputSelectionImpl::CharacterMove(bool aForward, bool aExtend)
601
0
{
602
0
  if (mFrameSelection) {
603
0
    RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
604
0
    return frameSelection->CharacterMove(aForward, aExtend);
605
0
  }
606
0
  return NS_ERROR_NULL_POINTER;
607
0
}
608
609
NS_IMETHODIMP
610
nsTextInputSelectionImpl::CharacterExtendForDelete()
611
0
{
612
0
  if (mFrameSelection) {
613
0
    RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
614
0
    return frameSelection->CharacterExtendForDelete();
615
0
  }
616
0
  return NS_ERROR_NULL_POINTER;
617
0
}
618
619
NS_IMETHODIMP
620
nsTextInputSelectionImpl::CharacterExtendForBackspace()
621
0
{
622
0
  if (mFrameSelection) {
623
0
    RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
624
0
    return frameSelection->CharacterExtendForBackspace();
625
0
  }
626
0
  return NS_ERROR_NULL_POINTER;
627
0
}
628
629
NS_IMETHODIMP
630
nsTextInputSelectionImpl::WordMove(bool aForward, bool aExtend)
631
0
{
632
0
  if (mFrameSelection) {
633
0
    RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
634
0
    return frameSelection->WordMove(aForward, aExtend);
635
0
  }
636
0
  return NS_ERROR_NULL_POINTER;
637
0
}
638
639
NS_IMETHODIMP
640
nsTextInputSelectionImpl::WordExtendForDelete(bool aForward)
641
0
{
642
0
  if (mFrameSelection) {
643
0
    RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
644
0
    return frameSelection->WordExtendForDelete(aForward);
645
0
  }
646
0
  return NS_ERROR_NULL_POINTER;
647
0
}
648
649
NS_IMETHODIMP
650
nsTextInputSelectionImpl::LineMove(bool aForward, bool aExtend)
651
0
{
652
0
  if (mFrameSelection)
653
0
  {
654
0
    RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
655
0
    nsresult result = frameSelection->LineMove(aForward, aExtend);
656
0
    if (NS_FAILED(result))
657
0
      result = CompleteMove(aForward,aExtend);
658
0
    return result;
659
0
  }
660
0
  return NS_ERROR_NULL_POINTER;
661
0
}
662
663
664
NS_IMETHODIMP
665
nsTextInputSelectionImpl::IntraLineMove(bool aForward, bool aExtend)
666
0
{
667
0
  if (mFrameSelection) {
668
0
    RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
669
0
    return frameSelection->IntraLineMove(aForward, aExtend);
670
0
  }
671
0
  return NS_ERROR_NULL_POINTER;
672
0
}
673
674
675
NS_IMETHODIMP
676
nsTextInputSelectionImpl::PageMove(bool aForward, bool aExtend)
677
0
{
678
0
  // expected behavior for PageMove is to scroll AND move the caret
679
0
  // and to remain relative position of the caret in view. see Bug 4302.
680
0
  if (mScrollFrame)
681
0
  {
682
0
    RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
683
0
    frameSelection->CommonPageMove(aForward, aExtend, mScrollFrame);
684
0
  }
685
0
  // After ScrollSelectionIntoView(), the pending notifications might be
686
0
  // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
687
0
  return ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
688
0
                                 nsISelectionController::SELECTION_FOCUS_REGION,
689
0
                                 nsISelectionController::SCROLL_SYNCHRONOUS |
690
0
                                 nsISelectionController::SCROLL_FOR_CARET_MOVE);
691
0
}
692
693
NS_IMETHODIMP
694
nsTextInputSelectionImpl::CompleteScroll(bool aForward)
695
0
{
696
0
  if (!mScrollFrame)
697
0
    return NS_ERROR_NOT_INITIALIZED;
698
0
699
0
  mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
700
0
                         nsIScrollableFrame::WHOLE,
701
0
                         nsIScrollableFrame::INSTANT);
702
0
  return NS_OK;
703
0
}
704
705
NS_IMETHODIMP
706
nsTextInputSelectionImpl::CompleteMove(bool aForward, bool aExtend)
707
0
{
708
0
  NS_ENSURE_STATE(mFrameSelection);
709
0
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
710
0
711
0
  // grab the parent / root DIV for this text widget
712
0
  nsIContent* parentDIV = frameSelection->GetLimiter();
713
0
  if (!parentDIV)
714
0
    return NS_ERROR_UNEXPECTED;
715
0
716
0
  // make the caret be either at the very beginning (0) or the very end
717
0
  int32_t offset = 0;
718
0
  CaretAssociationHint hint = CARET_ASSOCIATE_BEFORE;
719
0
  if (aForward)
720
0
  {
721
0
    offset = parentDIV->GetChildCount();
722
0
723
0
    // Prevent the caret from being placed after the last
724
0
    // BR node in the content tree!
725
0
726
0
    if (offset > 0)
727
0
    {
728
0
      nsIContent *child = parentDIV->GetLastChild();
729
0
730
0
      if (child->IsHTMLElement(nsGkAtoms::br))
731
0
      {
732
0
        --offset;
733
0
        hint = CARET_ASSOCIATE_AFTER; // for Bug 106855
734
0
      }
735
0
    }
736
0
  }
737
0
738
0
  frameSelection->HandleClick(parentDIV, offset, offset, aExtend,
739
0
                               false, hint);
740
0
741
0
  // if we got this far, attempt to scroll no matter what the above result is
742
0
  return CompleteScroll(aForward);
743
0
}
744
745
NS_IMETHODIMP
746
nsTextInputSelectionImpl::ScrollPage(bool aForward)
747
0
{
748
0
  if (!mScrollFrame)
749
0
    return NS_ERROR_NOT_INITIALIZED;
750
0
751
0
  mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
752
0
                         nsIScrollableFrame::PAGES,
753
0
                         nsIScrollableFrame::SMOOTH);
754
0
  return NS_OK;
755
0
}
756
757
NS_IMETHODIMP
758
nsTextInputSelectionImpl::ScrollLine(bool aForward)
759
0
{
760
0
  if (!mScrollFrame)
761
0
    return NS_ERROR_NOT_INITIALIZED;
762
0
763
0
  mScrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
764
0
                         nsIScrollableFrame::LINES,
765
0
                         nsIScrollableFrame::SMOOTH);
766
0
  return NS_OK;
767
0
}
768
769
NS_IMETHODIMP
770
nsTextInputSelectionImpl::ScrollCharacter(bool aRight)
771
0
{
772
0
  if (!mScrollFrame)
773
0
    return NS_ERROR_NOT_INITIALIZED;
774
0
775
0
  mScrollFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0),
776
0
                         nsIScrollableFrame::LINES,
777
0
                         nsIScrollableFrame::SMOOTH);
778
0
  return NS_OK;
779
0
}
780
781
NS_IMETHODIMP
782
nsTextInputSelectionImpl::SelectAll()
783
0
{
784
0
  if (mFrameSelection) {
785
0
    RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
786
0
    return frameSelection->SelectAll();
787
0
  }
788
0
  return NS_ERROR_NULL_POINTER;
789
0
}
790
791
NS_IMETHODIMP
792
nsTextInputSelectionImpl::CheckVisibility(nsINode *node, int16_t startOffset, int16_t EndOffset, bool *_retval)
793
0
{
794
0
  if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
795
0
  nsresult result;
796
0
  nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak, &result);
797
0
  if (shell)
798
0
  {
799
0
    return shell->CheckVisibility(node,startOffset,EndOffset, _retval);
800
0
  }
801
0
  return NS_ERROR_FAILURE;
802
0
803
0
}
804
805
nsresult
806
nsTextInputSelectionImpl::CheckVisibilityContent(nsIContent* aNode,
807
                                                 int16_t aStartOffset,
808
                                                 int16_t aEndOffset,
809
                                                 bool* aRetval)
810
0
{
811
0
  if (!mPresShellWeak) {
812
0
    return NS_ERROR_NOT_INITIALIZED;
813
0
  }
814
0
815
0
  nsCOMPtr<nsISelectionController> shell = do_QueryReferent(mPresShellWeak);
816
0
  NS_ENSURE_TRUE(shell, NS_ERROR_FAILURE);
817
0
818
0
  return shell->CheckVisibilityContent(aNode, aStartOffset, aEndOffset, aRetval);
819
0
}
820
821
/*
822
 * mozilla::TextInputListener implementation
823
 */
824
825
TextInputListener::TextInputListener(nsITextControlElement* aTxtCtrlElement)
826
  : mFrame(nullptr)
827
  , mTxtCtrlElement(aTxtCtrlElement)
828
  , mSelectionWasCollapsed(true)
829
  , mHadUndoItems(false)
830
  , mHadRedoItems(false)
831
  , mSettingValue(false)
832
  , mSetValueChanged(true)
833
  , mListeningToSelectionChange(false)
834
0
{
835
0
}
836
837
NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputListener)
838
NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputListener)
839
840
0
NS_INTERFACE_MAP_BEGIN(TextInputListener)
841
0
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
842
0
  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
843
0
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
844
0
  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextInputListener)
845
0
NS_INTERFACE_MAP_END
846
847
NS_IMPL_CYCLE_COLLECTION_0(TextInputListener)
848
849
void
850
TextInputListener::OnSelectionChange(Selection& aSelection,
851
                                     int16_t aReason)
852
0
{
853
0
  if (!mListeningToSelectionChange) {
854
0
    return;
855
0
  }
856
0
857
0
  AutoWeakFrame weakFrame = mFrame;
858
0
859
0
  // Fire the select event
860
0
  // The specs don't exactly say when we should fire the select event.
861
0
  // IE: Whenever you add/remove a character to/from the selection. Also
862
0
  //     each time for select all. Also if you get to the end of the text
863
0
  //     field you will get new event for each keypress or a continuous
864
0
  //     stream of events if you use the mouse. IE will fire select event
865
0
  //     when the selection collapses to nothing if you are holding down
866
0
  //     the shift or mouse button.
867
0
  // Mozilla: If we have non-empty selection we will fire a new event for each
868
0
  //          keypress (or mouseup) if the selection changed. Mozilla will also
869
0
  //          create the event each time select all is called, even if everything
870
0
  //          was previously selected, becase technically select all will first collapse
871
0
  //          and then extend. Mozilla will never create an event if the selection
872
0
  //          collapses to nothing.
873
0
  bool collapsed = aSelection.IsCollapsed();
874
0
  if (!collapsed && (aReason & (nsISelectionListener::MOUSEUP_REASON |
875
0
                                nsISelectionListener::KEYPRESS_REASON |
876
0
                                nsISelectionListener::SELECTALL_REASON))) {
877
0
    nsIContent* content = mFrame->GetContent();
878
0
    if (content) {
879
0
      nsCOMPtr<nsIDocument> doc = content->GetComposedDoc();
880
0
      if (doc) {
881
0
        nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
882
0
        if (presShell) {
883
0
          nsEventStatus status = nsEventStatus_eIgnore;
884
0
          WidgetEvent event(true, eFormSelect);
885
0
886
0
          presShell->HandleEventWithTarget(&event, mFrame, content, &status);
887
0
        }
888
0
      }
889
0
    }
890
0
  }
891
0
892
0
  // if the collapsed state did not change, don't fire notifications
893
0
  if (collapsed == mSelectionWasCollapsed) {
894
0
    return;
895
0
  }
896
0
897
0
  mSelectionWasCollapsed = collapsed;
898
0
899
0
  if (!weakFrame.IsAlive() || !mFrame ||
900
0
      !nsContentUtils::IsFocusedContent(mFrame->GetContent())) {
901
0
    return;
902
0
  }
903
0
904
0
  UpdateTextInputCommands(NS_LITERAL_STRING("select"),
905
0
                          &aSelection, aReason);
906
0
}
907
908
static void
909
DoCommandCallback(Command aCommand, void* aData)
910
0
{
911
0
  nsTextControlFrame *frame = static_cast<nsTextControlFrame*>(aData);
912
0
  nsIContent *content = frame->GetContent();
913
0
914
0
  nsCOMPtr<nsIControllers> controllers;
915
0
  HTMLInputElement* input = HTMLInputElement::FromNode(content);
916
0
  if (input) {
917
0
    input->GetControllers(getter_AddRefs(controllers));
918
0
  } else {
919
0
    HTMLTextAreaElement* textArea =
920
0
      HTMLTextAreaElement::FromNode(content);
921
0
922
0
    if (textArea) {
923
0
      textArea->GetControllers(getter_AddRefs(controllers));
924
0
    }
925
0
  }
926
0
927
0
  if (!controllers) {
928
0
    NS_WARNING("Could not get controllers");
929
0
    return;
930
0
  }
931
0
932
0
  const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
933
0
934
0
  nsCOMPtr<nsIController> controller;
935
0
  controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller));
936
0
  if (!controller) {
937
0
    return;
938
0
  }
939
0
940
0
  bool commandEnabled;
941
0
  nsresult rv = controller->IsCommandEnabled(commandStr, &commandEnabled);
942
0
  NS_ENSURE_SUCCESS_VOID(rv);
943
0
  if (commandEnabled) {
944
0
    controller->DoCommand(commandStr);
945
0
  }
946
0
}
947
948
NS_IMETHODIMP
949
TextInputListener::HandleEvent(Event* aEvent)
950
0
{
951
0
  if (aEvent->DefaultPrevented()) {
952
0
    return NS_OK;
953
0
  }
954
0
955
0
  if (!aEvent->IsTrusted()) {
956
0
    return NS_OK;
957
0
  }
958
0
959
0
  WidgetKeyboardEvent* keyEvent =
960
0
    aEvent->WidgetEventPtr()->AsKeyboardEvent();
961
0
  if (!keyEvent) {
962
0
    return NS_ERROR_UNEXPECTED;
963
0
  }
964
0
965
0
  if (keyEvent->mMessage != eKeyPress) {
966
0
    return NS_OK;
967
0
  }
968
0
969
0
  nsIWidget::NativeKeyBindingsType nativeKeyBindingsType =
970
0
    mTxtCtrlElement->IsTextArea() ?
971
0
      nsIWidget::NativeKeyBindingsForMultiLineEditor :
972
0
      nsIWidget::NativeKeyBindingsForSingleLineEditor;
973
0
974
0
  nsIWidget* widget = keyEvent->mWidget;
975
0
  // If the event is created by chrome script, the widget is nullptr.
976
0
  if (!widget) {
977
0
    widget = mFrame->GetNearestWidget();
978
0
    NS_ENSURE_TRUE(widget, NS_OK);
979
0
  }
980
0
981
0
  // WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget.
982
0
  // If the event is created by chrome script, it is nullptr but we need to
983
0
  // execute native key bindings.  Therefore, we need to set widget to
984
0
  // WidgetEvent::mWidget temporarily.
985
0
  AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(keyEvent->mWidget);
986
0
  keyEvent->mWidget = widget;
987
0
  if (keyEvent->ExecuteEditCommands(nativeKeyBindingsType,
988
0
                                    DoCommandCallback, mFrame)) {
989
0
    aEvent->PreventDefault();
990
0
  }
991
0
  return NS_OK;
992
0
}
993
994
void
995
TextInputListener::OnEditActionHandled()
996
0
{
997
0
  if (!mFrame) {
998
0
    // We've been disconnected from the nsTextEditorState object, nothing to do
999
0
    // here.
1000
0
    return;
1001
0
  }
1002
0
1003
0
  AutoWeakFrame weakFrame = mFrame;
1004
0
1005
0
  nsITextControlFrame* frameBase = do_QueryFrame(mFrame);
1006
0
  nsTextControlFrame* frame = static_cast<nsTextControlFrame*> (frameBase);
1007
0
  NS_ASSERTION(frame, "Where is our frame?");
1008
0
  //
1009
0
  // Update the undo / redo menus
1010
0
  //
1011
0
  RefPtr<TextEditor> textEditor = frame->GetTextEditor();
1012
0
1013
0
  // Get the number of undo / redo items
1014
0
  size_t numUndoItems = textEditor->NumberOfUndoItems();
1015
0
  size_t numRedoItems = textEditor->NumberOfRedoItems();
1016
0
  if ((numUndoItems && !mHadUndoItems) || (!numUndoItems && mHadUndoItems) ||
1017
0
      (numRedoItems && !mHadRedoItems) || (!numRedoItems && mHadRedoItems)) {
1018
0
    // Modify the menu if undo or redo items are different
1019
0
    UpdateTextInputCommands(NS_LITERAL_STRING("undo"));
1020
0
1021
0
    mHadUndoItems = numUndoItems != 0;
1022
0
    mHadRedoItems = numRedoItems != 0;
1023
0
  }
1024
0
1025
0
  if (!weakFrame.IsAlive()) {
1026
0
    return;
1027
0
  }
1028
0
1029
0
  HandleValueChanged(frame);
1030
0
}
1031
1032
void
1033
TextInputListener::HandleValueChanged(nsTextControlFrame* aFrame)
1034
0
{
1035
0
  // Make sure we know we were changed (do NOT set this to false if there are
1036
0
  // no undo items; JS could change the value and we'd still need to save it)
1037
0
  if (mSetValueChanged) {
1038
0
    if (!aFrame) {
1039
0
      nsITextControlFrame* frameBase = do_QueryFrame(mFrame);
1040
0
      aFrame = static_cast<nsTextControlFrame*> (frameBase);
1041
0
      NS_ASSERTION(aFrame, "Where is our frame?");
1042
0
    }
1043
0
    aFrame->SetValueChanged(true);
1044
0
  }
1045
0
1046
0
  if (!mSettingValue) {
1047
0
    mTxtCtrlElement->OnValueChanged(/* aNotify = */ true,
1048
0
                                    /* aWasInteractiveUserChange = */ true);
1049
0
  }
1050
0
}
1051
1052
nsresult
1053
TextInputListener::UpdateTextInputCommands(const nsAString& aCommandsToUpdate,
1054
                                           Selection* aSelection,
1055
                                           int16_t aReason)
1056
0
{
1057
0
  nsIContent* content = mFrame->GetContent();
1058
0
  NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
1059
0
1060
0
  nsCOMPtr<nsIDocument> doc = content->GetComposedDoc();
1061
0
  NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
1062
0
1063
0
  nsPIDOMWindowOuter* domWindow = doc->GetWindow();
1064
0
  NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE);
1065
0
1066
0
  domWindow->UpdateCommands(aCommandsToUpdate, aSelection, aReason);
1067
0
  return NS_OK;
1068
0
}
1069
1070
// END mozilla::TextInputListener
1071
1072
// nsTextEditorState
1073
1074
nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement)
1075
  : mTextCtrlElement(aOwningElement)
1076
  , mBoundFrame(nullptr)
1077
  , mEverInited(false)
1078
  , mEditorInitialized(false)
1079
  , mInitializing(false)
1080
  , mValueTransferInProgress(false)
1081
  , mSelectionCached(true)
1082
  , mSelectionRestoreEagerInit(false)
1083
  , mPlaceholderVisibility(false)
1084
  , mPreviewVisibility(false)
1085
  , mIsCommittingComposition(false)
1086
  // When adding more member variable initializations here, add the same
1087
  // also to ::Construct.
1088
0
{
1089
0
  MOZ_COUNT_CTOR(nsTextEditorState);
1090
0
}
1091
1092
nsTextEditorState*
1093
nsTextEditorState::Construct(nsITextControlElement* aOwningElement,
1094
                             nsTextEditorState** aReusedState)
1095
0
{
1096
0
  if (*aReusedState) {
1097
0
    nsTextEditorState* state = *aReusedState;
1098
0
    *aReusedState = nullptr;
1099
0
    state->mTextCtrlElement = aOwningElement;
1100
0
    state->mBoundFrame = nullptr;
1101
0
    state->mSelectionProperties = SelectionProperties();
1102
0
    state->mEverInited = false;
1103
0
    state->mEditorInitialized = false;
1104
0
    state->mInitializing = false;
1105
0
    state->mValueTransferInProgress = false;
1106
0
    state->mSelectionCached = true;
1107
0
    state->mSelectionRestoreEagerInit = false;
1108
0
    state->mPlaceholderVisibility = false;
1109
0
    state->mPreviewVisibility = false;
1110
0
    state->mIsCommittingComposition = false;
1111
0
    // When adding more member variable initializations here, add the same
1112
0
    // also to the constructor.
1113
0
    return state;
1114
0
  }
1115
0
1116
0
  return new nsTextEditorState(aOwningElement);
1117
0
}
1118
1119
nsTextEditorState::~nsTextEditorState()
1120
0
{
1121
0
  MOZ_COUNT_DTOR(nsTextEditorState);
1122
0
  Clear();
1123
0
}
1124
1125
Element*
1126
nsTextEditorState::GetRootNode()
1127
0
{
1128
0
  return mBoundFrame ? mBoundFrame->GetRootNode() : nullptr;
1129
0
}
1130
1131
Element*
1132
nsTextEditorState::GetPreviewNode()
1133
0
{
1134
0
  return mBoundFrame ? mBoundFrame->GetPreviewNode() : nullptr;
1135
0
}
1136
1137
void
1138
nsTextEditorState::Clear()
1139
0
{
1140
0
  if (mTextEditor) {
1141
0
    mTextEditor->SetTextInputListener(nullptr);
1142
0
  }
1143
0
1144
0
  if (mBoundFrame) {
1145
0
    // Oops, we still have a frame!
1146
0
    // This should happen when the type of a text input control is being changed
1147
0
    // to something which is not a text control.  In this case, we should pretend
1148
0
    // that a frame is being destroyed, and clean up after ourselves properly.
1149
0
    UnbindFromFrame(mBoundFrame);
1150
0
    mTextEditor = nullptr;
1151
0
  } else {
1152
0
    // If we have a bound frame around, UnbindFromFrame will call DestroyEditor
1153
0
    // for us.
1154
0
    DestroyEditor();
1155
0
  }
1156
0
  mTextListener = nullptr;
1157
0
}
1158
1159
void nsTextEditorState::Unlink()
1160
0
{
1161
0
  nsTextEditorState* tmp = this;
1162
0
  tmp->Clear();
1163
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon)
1164
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextEditor)
1165
0
}
1166
1167
void nsTextEditorState::Traverse(nsCycleCollectionTraversalCallback& cb)
1168
0
{
1169
0
  nsTextEditorState* tmp = this;
1170
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon)
1171
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextEditor)
1172
0
}
1173
1174
nsFrameSelection*
1175
0
nsTextEditorState::GetConstFrameSelection() {
1176
0
  if (mSelCon)
1177
0
    return mSelCon->GetConstFrameSelection();
1178
0
  return nullptr;
1179
0
}
1180
1181
TextEditor*
1182
nsTextEditorState::GetTextEditor()
1183
0
{
1184
0
  if (!mTextEditor) {
1185
0
    nsresult rv = PrepareEditor();
1186
0
    NS_ENSURE_SUCCESS(rv, nullptr);
1187
0
  }
1188
0
  return mTextEditor;
1189
0
}
1190
1191
nsISelectionController*
1192
nsTextEditorState::GetSelectionController() const
1193
0
{
1194
0
  return mSelCon;
1195
0
}
1196
1197
// Helper class, used below in BindToFrame().
1198
class PrepareEditorEvent : public Runnable {
1199
public:
1200
  PrepareEditorEvent(nsTextEditorState& aState,
1201
                     nsIContent* aOwnerContent,
1202
                     const nsAString& aCurrentValue)
1203
    : mozilla::Runnable("PrepareEditorEvent")
1204
    , mState(&aState)
1205
    , mOwnerContent(aOwnerContent)
1206
    , mCurrentValue(aCurrentValue)
1207
0
  {
1208
0
    aState.mValueTransferInProgress = true;
1209
0
  }
1210
1211
0
  NS_IMETHOD Run() override {
1212
0
    NS_ENSURE_TRUE(mState, NS_ERROR_NULL_POINTER);
1213
0
1214
0
    // Transfer the saved value to the editor if we have one
1215
0
    const nsAString *value = nullptr;
1216
0
    if (!mCurrentValue.IsEmpty()) {
1217
0
      value = &mCurrentValue;
1218
0
    }
1219
0
1220
0
    nsAutoScriptBlocker scriptBlocker;
1221
0
1222
0
    mState->PrepareEditor(value);
1223
0
1224
0
    mState->mValueTransferInProgress = false;
1225
0
1226
0
    return NS_OK;
1227
0
  }
1228
1229
private:
1230
  WeakPtr<nsTextEditorState> mState;
1231
  nsCOMPtr<nsIContent> mOwnerContent; // strong reference
1232
  nsAutoString mCurrentValue;
1233
};
1234
1235
nsresult
1236
nsTextEditorState::BindToFrame(nsTextControlFrame* aFrame)
1237
0
{
1238
0
  NS_ASSERTION(aFrame, "The frame to bind to should be valid");
1239
0
  NS_ENSURE_ARG_POINTER(aFrame);
1240
0
1241
0
  NS_ASSERTION(!mBoundFrame, "Cannot bind twice, need to unbind first");
1242
0
  NS_ENSURE_TRUE(!mBoundFrame, NS_ERROR_FAILURE);
1243
0
1244
0
  // If we'll need to transfer our current value to the editor, save it before
1245
0
  // binding to the frame.
1246
0
  nsAutoString currentValue;
1247
0
  if (mTextEditor) {
1248
0
    GetValue(currentValue, true);
1249
0
  }
1250
0
1251
0
  mBoundFrame = aFrame;
1252
0
1253
0
  Element* rootNode = aFrame->GetRootNode();
1254
0
  MOZ_ASSERT(rootNode);
1255
0
1256
0
  nsIPresShell* shell = aFrame->PresContext()->GetPresShell();
1257
0
  MOZ_ASSERT(shell);
1258
0
1259
0
  // Create selection
1260
0
  RefPtr<nsFrameSelection> frameSel = new nsFrameSelection();
1261
0
1262
0
  // Create a SelectionController
1263
0
  mSelCon = new nsTextInputSelectionImpl(frameSel, shell, rootNode);
1264
0
  MOZ_ASSERT(!mTextListener, "Should not overwrite the object");
1265
0
  mTextListener = new TextInputListener(mTextCtrlElement);
1266
0
1267
0
  mTextListener->SetFrame(mBoundFrame);
1268
0
  mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
1269
0
1270
0
  // Get the caret and make it a selection listener.
1271
0
  // FYI: It's safe to use raw pointer for calling
1272
0
  //      Selection::AddSelectionListner() because it only appends the listener
1273
0
  //      to its internal array.
1274
0
  Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
1275
0
  if (selection) {
1276
0
    RefPtr<nsCaret> caret = shell->GetCaret();
1277
0
    if (caret) {
1278
0
      selection->AddSelectionListener(caret);
1279
0
    }
1280
0
    mTextListener->StartToListenToSelectionChange();
1281
0
  }
1282
0
1283
0
  // If an editor exists from before, prepare it for usage
1284
0
  if (mTextEditor) {
1285
0
    nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
1286
0
    NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
1287
0
1288
0
    // Set the correct direction on the newly created root node
1289
0
    if (mTextEditor->IsRightToLeft()) {
1290
0
      rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("rtl"), false);
1291
0
    } else if (mTextEditor->IsLeftToRight()) {
1292
0
      rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("ltr"), false);
1293
0
    } else {
1294
0
      // otherwise, inherit the content node's direction
1295
0
    }
1296
0
1297
0
    nsContentUtils::AddScriptRunner(
1298
0
      new PrepareEditorEvent(*this, content, currentValue));
1299
0
  }
1300
0
1301
0
  return NS_OK;
1302
0
}
1303
1304
struct PreDestroyer
1305
{
1306
0
  void Init(TextEditor* aTextEditor) { mTextEditor = aTextEditor; }
1307
  ~PreDestroyer()
1308
0
  {
1309
0
    if (mTextEditor) {
1310
0
      mTextEditor->PreDestroy(true);
1311
0
    }
1312
0
  }
1313
  void Swap(RefPtr<TextEditor>& aTextEditor)
1314
0
  {
1315
0
    return mTextEditor.swap(aTextEditor);
1316
0
  }
1317
private:
1318
  RefPtr<TextEditor> mTextEditor;
1319
};
1320
1321
nsresult
1322
nsTextEditorState::PrepareEditor(const nsAString *aValue)
1323
0
{
1324
0
  if (!mBoundFrame) {
1325
0
    // Cannot create an editor without a bound frame.
1326
0
    // Don't return a failure code, because js callers can't handle that.
1327
0
    return NS_OK;
1328
0
  }
1329
0
1330
0
  if (mEditorInitialized) {
1331
0
    // Do not initialize the editor multiple times.
1332
0
    return NS_OK;
1333
0
  }
1334
0
1335
0
  AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection());
1336
0
1337
0
  // Don't attempt to initialize recursively!
1338
0
  InitializationGuard guard(*this);
1339
0
  if (guard.IsInitializingRecursively()) {
1340
0
    return NS_ERROR_NOT_INITIALIZED;
1341
0
  }
1342
0
1343
0
  // Note that we don't check mTextEditor here, because we might already have
1344
0
  // one around, in which case we don't create a new one, and we'll just tie
1345
0
  // the required machinery to it.
1346
0
1347
0
  nsPresContext *presContext = mBoundFrame->PresContext();
1348
0
  nsIPresShell *shell = presContext->GetPresShell();
1349
0
1350
0
  // Setup the editor flags
1351
0
  uint32_t editorFlags = nsIPlaintextEditor::eEditorPlaintextMask;
1352
0
  if (IsSingleLineTextControl())
1353
0
    editorFlags |= nsIPlaintextEditor::eEditorSingleLineMask;
1354
0
  if (IsPasswordTextControl())
1355
0
    editorFlags |= nsIPlaintextEditor::eEditorPasswordMask;
1356
0
1357
0
  // All nsTextControlFrames are widgets
1358
0
  editorFlags |= nsIPlaintextEditor::eEditorWidgetMask;
1359
0
1360
0
  // Spell check is diabled at creation time. It is enabled once
1361
0
  // the editor comes into focus.
1362
0
  editorFlags |= nsIPlaintextEditor::eEditorSkipSpellCheck;
1363
0
1364
0
  bool shouldInitializeEditor = false;
1365
0
  RefPtr<TextEditor> newTextEditor; // the editor that we might create
1366
0
  nsresult rv = NS_OK;
1367
0
  PreDestroyer preDestroyer;
1368
0
  if (!mTextEditor) {
1369
0
    shouldInitializeEditor = true;
1370
0
1371
0
    // Create an editor
1372
0
    newTextEditor = new TextEditor();
1373
0
    preDestroyer.Init(newTextEditor);
1374
0
1375
0
    // Make sure we clear out the non-breaking space before we initialize the editor
1376
0
    rv = mBoundFrame->UpdateValueDisplay(true, true);
1377
0
    NS_ENSURE_SUCCESS(rv, rv);
1378
0
  } else {
1379
0
    if (aValue || !mEditorInitialized) {
1380
0
      // Set the correct value in the root node
1381
0
      rv = mBoundFrame->UpdateValueDisplay(true, !mEditorInitialized, aValue);
1382
0
      NS_ENSURE_SUCCESS(rv, rv);
1383
0
    }
1384
0
1385
0
    newTextEditor = mTextEditor; // just pretend that we have a new editor!
1386
0
1387
0
    // Don't lose application flags in the process.
1388
0
    if (newTextEditor->IsMailEditor()) {
1389
0
      editorFlags |= nsIPlaintextEditor::eEditorMailMask;
1390
0
    }
1391
0
  }
1392
0
1393
0
  // Get the current value of the textfield from the content.
1394
0
  // Note that if we've created a new editor, mTextEditor is null at this stage,
1395
0
  // so we will get the real value from the content.
1396
0
  nsAutoString defaultValue;
1397
0
  if (aValue) {
1398
0
    defaultValue = *aValue;
1399
0
  } else {
1400
0
    GetValue(defaultValue, true);
1401
0
  }
1402
0
1403
0
  if (!mEditorInitialized) {
1404
0
    // Now initialize the editor.
1405
0
    //
1406
0
    // NOTE: Conversion of '\n' to <BR> happens inside the
1407
0
    //       editor's Init() call.
1408
0
1409
0
    // Get the DOM document
1410
0
    nsCOMPtr<nsIDocument> doc = shell->GetDocument();
1411
0
    if (NS_WARN_IF(!doc)) {
1412
0
      return NS_ERROR_FAILURE;
1413
0
    }
1414
0
1415
0
    // What follows is a bit of a hack.  The editor uses the public DOM APIs
1416
0
    // for its content manipulations, and it causes it to fail some security
1417
0
    // checks deep inside when initializing. So we explictly make it clear that
1418
0
    // we're native code.
1419
0
    // Note that any script that's directly trying to access our value
1420
0
    // has to be going through some scriptable object to do that and that
1421
0
    // already does the relevant security checks.
1422
0
    AutoNoJSAPI nojsapi;
1423
0
1424
0
    rv = newTextEditor->Init(*doc, GetRootNode(), mSelCon, editorFlags,
1425
0
                             defaultValue);
1426
0
    NS_ENSURE_SUCCESS(rv, rv);
1427
0
  }
1428
0
1429
0
  // Initialize the controller for the editor
1430
0
1431
0
  if (!SuppressEventHandlers(presContext)) {
1432
0
    nsCOMPtr<nsIControllers> controllers;
1433
0
    nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
1434
0
    HTMLInputElement* inputElement =
1435
0
      HTMLInputElement::FromNodeOrNull(content);
1436
0
    if (inputElement) {
1437
0
      rv = inputElement->GetControllers(getter_AddRefs(controllers));
1438
0
    } else {
1439
0
      HTMLTextAreaElement* textAreaElement =
1440
0
        HTMLTextAreaElement::FromNodeOrNull(content);
1441
0
1442
0
      if (!textAreaElement)
1443
0
        return NS_ERROR_FAILURE;
1444
0
1445
0
      rv = textAreaElement->GetControllers(getter_AddRefs(controllers));
1446
0
    }
1447
0
1448
0
    NS_ENSURE_SUCCESS(rv, rv);
1449
0
1450
0
    if (controllers) {
1451
0
      uint32_t numControllers;
1452
0
      bool found = false;
1453
0
      rv = controllers->GetControllerCount(&numControllers);
1454
0
      for (uint32_t i = 0; i < numControllers; i ++) {
1455
0
        nsCOMPtr<nsIController> controller;
1456
0
        rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
1457
0
        if (NS_SUCCEEDED(rv) && controller) {
1458
0
          nsCOMPtr<nsIControllerContext> editController =
1459
0
            do_QueryInterface(controller);
1460
0
          if (editController) {
1461
0
            editController->SetCommandContext(
1462
0
              static_cast<nsIEditor*>(newTextEditor));
1463
0
            found = true;
1464
0
          }
1465
0
        }
1466
0
      }
1467
0
      if (!found)
1468
0
        rv = NS_ERROR_FAILURE;
1469
0
    }
1470
0
  }
1471
0
1472
0
  // Initialize the plaintext editor
1473
0
  if (shouldInitializeEditor) {
1474
0
    // Set up wrapping
1475
0
    newTextEditor->SetWrapColumn(GetWrapCols());
1476
0
  }
1477
0
1478
0
  // Set max text field length
1479
0
  newTextEditor->SetMaxTextLength(GetMaxLength());
1480
0
1481
0
  if (nsCOMPtr<Element> element = do_QueryInterface(mTextCtrlElement)) {
1482
0
    editorFlags = newTextEditor->Flags();
1483
0
1484
0
    // Check if the readonly attribute is set.
1485
0
    if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly))
1486
0
      editorFlags |= nsIPlaintextEditor::eEditorReadonlyMask;
1487
0
1488
0
    // Check if the disabled attribute is set.
1489
0
    // TODO: call IsDisabled() here!
1490
0
    if (element->HasAttr(kNameSpaceID_None, nsGkAtoms::disabled))
1491
0
      editorFlags |= nsIPlaintextEditor::eEditorDisabledMask;
1492
0
1493
0
    // Disable the selection if necessary.
1494
0
    if (newTextEditor->IsDisabled()) {
1495
0
      mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_OFF);
1496
0
    }
1497
0
1498
0
    SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1499
0
  }
1500
0
1501
0
  if (shouldInitializeEditor) {
1502
0
    // Hold on to the newly created editor
1503
0
    preDestroyer.Swap(mTextEditor);
1504
0
  }
1505
0
1506
0
  // If we have a default value, insert it under the div we created
1507
0
  // above, but be sure to use the editor so that '*' characters get
1508
0
  // displayed for password fields, etc. SetValue() will call the
1509
0
  // editor for us.
1510
0
1511
0
  if (!defaultValue.IsEmpty()) {
1512
0
    rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1513
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1514
0
      return rv;
1515
0
    }
1516
0
1517
0
    // Now call SetValue() which will make the necessary editor calls to set
1518
0
    // the default value.  Make sure to turn off undo before setting the default
1519
0
    // value, and turn it back on afterwards. This will make sure we can't undo
1520
0
    // past the default value.
1521
0
    // So, we use eSetValue_Internal flag only that it will turn off undo.
1522
0
1523
0
    bool success = SetValue(defaultValue, eSetValue_Internal);
1524
0
    NS_ENSURE_TRUE(success, NS_ERROR_OUT_OF_MEMORY);
1525
0
1526
0
    // Now restore the original editor flags.
1527
0
    rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
1528
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1529
0
      return rv;
1530
0
    }
1531
0
  }
1532
0
1533
0
  if (IsPasswordTextControl()) {
1534
0
    // Disable undo for <input type="password">.  Note that we want to do this
1535
0
    // at the very end of InitEditor(), so the calls to EnableUndoRedo() when
1536
0
    // setting the default value don't screw us up.  Since changing the
1537
0
    // control type does a reframe, we don't have to worry about dynamic type
1538
0
    // changes here.
1539
0
    DebugOnly<bool> disabledUndoRedo = newTextEditor->DisableUndoRedo();
1540
0
    NS_WARNING_ASSERTION(disabledUndoRedo,
1541
0
      "Failed to disable undo/redo transaction");
1542
0
  } else {
1543
0
    DebugOnly<bool> enabledUndoRedo =
1544
0
      newTextEditor->EnableUndoRedo(nsITextControlElement::DEFAULT_UNDO_CAP);
1545
0
    NS_WARNING_ASSERTION(enabledUndoRedo,
1546
0
      "Failed to enable undo/redo transaction");
1547
0
  }
1548
0
1549
0
  if (!mEditorInitialized) {
1550
0
    newTextEditor->PostCreate();
1551
0
    mEverInited = true;
1552
0
    mEditorInitialized = true;
1553
0
  }
1554
0
1555
0
  if (mTextListener) {
1556
0
    newTextEditor->SetTextInputListener(mTextListener);
1557
0
  }
1558
0
1559
0
  // Restore our selection after being bound to a new frame
1560
0
  HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
1561
0
  if (number ? number->IsSelectionCached() : mSelectionCached) {
1562
0
    if (mRestoringSelection) // paranoia
1563
0
      mRestoringSelection->Revoke();
1564
0
    mRestoringSelection = new RestoreSelectionState(this, mBoundFrame);
1565
0
    if (mRestoringSelection) {
1566
0
      nsContentUtils::AddScriptRunner(mRestoringSelection);
1567
0
    }
1568
0
  }
1569
0
1570
0
  // The selection cache is no longer going to be valid.
1571
0
  //
1572
0
  // XXXbz Shouldn't we do this at the point when we're actually about to
1573
0
  // restore the properties or something?  As things stand, if UnbindFromFrame
1574
0
  // happens before our RestoreSelectionState runs, it looks like we'll lose our
1575
0
  // selection info, because we will think we don't have it cached and try to
1576
0
  // read it from the selection controller, which will not have it yet.
1577
0
  if (number) {
1578
0
    number->ClearSelectionCached();
1579
0
  } else {
1580
0
    mSelectionCached = false;
1581
0
  }
1582
0
1583
0
  return rv;
1584
0
}
1585
1586
void
1587
nsTextEditorState::FinishedRestoringSelection()
1588
0
{
1589
0
  mRestoringSelection = nullptr;
1590
0
}
1591
1592
bool
1593
nsTextEditorState::IsSelectionCached() const
1594
0
{
1595
0
  if (mBoundFrame) {
1596
0
    HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
1597
0
    if (number) {
1598
0
      return number->IsSelectionCached();
1599
0
    }
1600
0
  }
1601
0
  return mSelectionCached;
1602
0
}
1603
1604
nsTextEditorState::SelectionProperties&
1605
nsTextEditorState::GetSelectionProperties()
1606
0
{
1607
0
  if (mBoundFrame) {
1608
0
    HTMLInputElement* number = GetParentNumberControl(mBoundFrame);
1609
0
    if (number) {
1610
0
      return number->GetSelectionProperties();
1611
0
    }
1612
0
  }
1613
0
  return mSelectionProperties;
1614
0
}
1615
1616
void
1617
nsTextEditorState::SyncUpSelectionPropertiesBeforeDestruction()
1618
0
{
1619
0
  if (mBoundFrame) {
1620
0
    UnbindFromFrame(mBoundFrame);
1621
0
  }
1622
0
}
1623
1624
void
1625
nsTextEditorState::SetSelectionProperties(nsTextEditorState::SelectionProperties& aProps)
1626
0
{
1627
0
  if (mBoundFrame) {
1628
0
    mBoundFrame->SetSelectionRange(aProps.GetStart(),
1629
0
                                   aProps.GetEnd(),
1630
0
                                   aProps.GetDirection());
1631
0
  } else {
1632
0
    mSelectionProperties = aProps;
1633
0
  }
1634
0
}
1635
1636
void
1637
nsTextEditorState::GetSelectionRange(uint32_t* aSelectionStart,
1638
                                     uint32_t* aSelectionEnd,
1639
                                     ErrorResult& aRv)
1640
0
{
1641
0
  MOZ_ASSERT(aSelectionStart);
1642
0
  MOZ_ASSERT(aSelectionEnd);
1643
0
  MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
1644
0
             "How can we not have a cached selection if we have no selection "
1645
0
             "controller?");
1646
0
1647
0
  // Note that we may have both IsSelectionCached() _and_
1648
0
  // GetSelectionController() if we haven't initialized our editor yet.
1649
0
  if (IsSelectionCached()) {
1650
0
    const SelectionProperties& props = GetSelectionProperties();
1651
0
    *aSelectionStart = props.GetStart();
1652
0
    *aSelectionEnd = props.GetEnd();
1653
0
    return;
1654
0
  }
1655
0
1656
0
  Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
1657
0
  if (NS_WARN_IF(!sel)) {
1658
0
    aRv.Throw(NS_ERROR_FAILURE);
1659
0
    return;
1660
0
  }
1661
0
1662
0
  mozilla::dom::Element* root = GetRootNode();
1663
0
  if (NS_WARN_IF(!root)) {
1664
0
    aRv.Throw(NS_ERROR_UNEXPECTED);
1665
0
    return;
1666
0
  }
1667
0
  nsContentUtils::GetSelectionInTextControl(sel, root,
1668
0
                                            *aSelectionStart, *aSelectionEnd);
1669
0
}
1670
1671
nsITextControlFrame::SelectionDirection
1672
nsTextEditorState::GetSelectionDirection(ErrorResult& aRv)
1673
0
{
1674
0
  MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
1675
0
             "How can we not have a cached selection if we have no selection "
1676
0
             "controller?");
1677
0
1678
0
  // Note that we may have both IsSelectionCached() _and_
1679
0
  // GetSelectionController() if we haven't initialized our editor yet.
1680
0
  if (IsSelectionCached()) {
1681
0
    return GetSelectionProperties().GetDirection();
1682
0
  }
1683
0
1684
0
  Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
1685
0
  if (NS_WARN_IF(!sel)) {
1686
0
    aRv.Throw(NS_ERROR_FAILURE);
1687
0
    return nsITextControlFrame::eForward; // Doesn't really matter
1688
0
  }
1689
0
1690
0
  nsDirection direction = sel->GetDirection();
1691
0
  if (direction == eDirNext) {
1692
0
    return nsITextControlFrame::eForward;
1693
0
  }
1694
0
1695
0
  MOZ_ASSERT(direction == eDirPrevious);
1696
0
  return nsITextControlFrame::eBackward;
1697
0
}
1698
1699
void
1700
nsTextEditorState::SetSelectionRange(uint32_t aStart, uint32_t aEnd,
1701
                                     nsITextControlFrame::SelectionDirection aDirection,
1702
                                     ErrorResult& aRv)
1703
0
{
1704
0
  MOZ_ASSERT(IsSelectionCached() || mBoundFrame,
1705
0
             "How can we have a non-cached selection but no frame?");
1706
0
1707
0
  if (aStart > aEnd) {
1708
0
    aStart = aEnd;
1709
0
  }
1710
0
1711
0
  bool changed = false;
1712
0
  nsresult rv = NS_OK; // For the ScrollSelectionIntoView() return value.
1713
0
  if (IsSelectionCached()) {
1714
0
    nsAutoString value;
1715
0
    // XXXbz is "false" the right thing to pass here?  Hard to tell, given the
1716
0
    // various mismatches between our impl and the spec.
1717
0
    GetValue(value, false);
1718
0
    uint32_t length = value.Length();
1719
0
    if (aStart > length) {
1720
0
      aStart = length;
1721
0
    }
1722
0
    if (aEnd > length) {
1723
0
      aEnd = length;
1724
0
    }
1725
0
    SelectionProperties& props = GetSelectionProperties();
1726
0
    changed = props.GetStart() != aStart ||
1727
0
              props.GetEnd() != aEnd ||
1728
0
              props.GetDirection() != aDirection;
1729
0
    props.SetStart(aStart);
1730
0
    props.SetEnd(aEnd);
1731
0
    props.SetDirection(aDirection);
1732
0
  } else {
1733
0
    WeakPtr<nsTextEditorState> self(this);
1734
0
    aRv = mBoundFrame->SetSelectionRange(aStart, aEnd, aDirection);
1735
0
    if (aRv.Failed() || !self.get()) {
1736
0
      return;
1737
0
    }
1738
0
    rv = mBoundFrame->ScrollSelectionIntoView();
1739
0
    // Press on to firing the event even if that failed, like our old code did.
1740
0
    // But is that really what we want?  Firing the event _and_ throwing from
1741
0
    // here is weird.  Maybe we should just ignore ScrollSelectionIntoView
1742
0
    // failures?
1743
0
1744
0
    // XXXbz This is preserving our current behavior of firing a "select" event
1745
0
    // on all mutations when we have an editor, but we should really consider
1746
0
    // fixing that...
1747
0
    changed = true;
1748
0
  }
1749
0
1750
0
  if (changed) {
1751
0
    // It sure would be nice if we had an existing Element* or so to work with.
1752
0
    nsCOMPtr<nsINode> node = do_QueryInterface(mTextCtrlElement);
1753
0
    RefPtr<AsyncEventDispatcher> asyncDispatcher =
1754
0
      new AsyncEventDispatcher(node,
1755
0
                               NS_LITERAL_STRING("select"),
1756
0
                               CanBubble::eYes,
1757
0
                               ChromeOnlyDispatch::eNo);
1758
0
    asyncDispatcher->PostDOMEvent();
1759
0
  }
1760
0
1761
0
  if (NS_FAILED(rv)) {
1762
0
    aRv.Throw(rv);
1763
0
  }
1764
0
}
1765
1766
void
1767
nsTextEditorState::SetSelectionStart(const Nullable<uint32_t>& aStart,
1768
                                     ErrorResult& aRv)
1769
0
{
1770
0
  uint32_t start = 0;
1771
0
  if (!aStart.IsNull()) {
1772
0
    start = aStart.Value();
1773
0
  }
1774
0
1775
0
  uint32_t ignored, end;
1776
0
  GetSelectionRange(&ignored, &end, aRv);
1777
0
  if (aRv.Failed()) {
1778
0
    return;
1779
0
  }
1780
0
1781
0
  nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
1782
0
  if (aRv.Failed()) {
1783
0
    return;
1784
0
  }
1785
0
1786
0
  if (end < start) {
1787
0
    end = start;
1788
0
  }
1789
0
1790
0
  SetSelectionRange(start, end, dir, aRv);
1791
0
}
1792
1793
void
1794
nsTextEditorState::SetSelectionEnd(const Nullable<uint32_t>& aEnd,
1795
                                   ErrorResult& aRv)
1796
0
{
1797
0
  uint32_t end = 0;
1798
0
  if (!aEnd.IsNull()) {
1799
0
    end = aEnd.Value();
1800
0
  }
1801
0
1802
0
  uint32_t start, ignored;
1803
0
  GetSelectionRange(&start, &ignored, aRv);
1804
0
  if (aRv.Failed()) {
1805
0
    return;
1806
0
  }
1807
0
1808
0
  nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
1809
0
  if (aRv.Failed()) {
1810
0
    return;
1811
0
  }
1812
0
1813
0
  SetSelectionRange(start, end, dir, aRv);
1814
0
}
1815
1816
static void
1817
DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
1818
0
{
1819
0
  if (dir == nsITextControlFrame::eNone) {
1820
0
    NS_WARNING("We don't actually support this... how did we get it?");
1821
0
    aDirection.AssignLiteral("none");
1822
0
  } else if (dir == nsITextControlFrame::eForward) {
1823
0
    aDirection.AssignLiteral("forward");
1824
0
  } else if (dir == nsITextControlFrame::eBackward) {
1825
0
    aDirection.AssignLiteral("backward");
1826
0
  } else {
1827
0
    MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value");
1828
0
  }
1829
0
}
1830
1831
void
1832
nsTextEditorState::GetSelectionDirectionString(nsAString& aDirection,
1833
                                               ErrorResult& aRv)
1834
0
{
1835
0
  nsITextControlFrame::SelectionDirection dir = GetSelectionDirection(aRv);
1836
0
  if (aRv.Failed()) {
1837
0
    return;
1838
0
  }
1839
0
  DirectionToName(dir, aDirection);
1840
0
}
1841
1842
static nsITextControlFrame::SelectionDirection
1843
DirectionStringToSelectionDirection(const nsAString& aDirection)
1844
0
{
1845
0
  if (aDirection.EqualsLiteral("backward")) {
1846
0
    return nsITextControlFrame::eBackward;
1847
0
  }
1848
0
1849
0
  // We don't support directionless selections.
1850
0
  return nsITextControlFrame::eForward;
1851
0
}
1852
1853
void
1854
nsTextEditorState::SetSelectionDirection(const nsAString& aDirection,
1855
                                         ErrorResult& aRv)
1856
0
{
1857
0
  nsITextControlFrame::SelectionDirection dir =
1858
0
    DirectionStringToSelectionDirection(aDirection);
1859
0
1860
0
  if (IsSelectionCached()) {
1861
0
    GetSelectionProperties().SetDirection(dir);
1862
0
    return;
1863
0
  }
1864
0
1865
0
  uint32_t start, end;
1866
0
  GetSelectionRange(&start, &end, aRv);
1867
0
  if (aRv.Failed()) {
1868
0
    return;
1869
0
  }
1870
0
1871
0
  SetSelectionRange(start, end, dir, aRv);
1872
0
}
1873
1874
static nsITextControlFrame::SelectionDirection
1875
DirectionStringToSelectionDirection(const Optional<nsAString>& aDirection)
1876
0
{
1877
0
  if (!aDirection.WasPassed()) {
1878
0
    // We don't support directionless selections.
1879
0
    return nsITextControlFrame::eForward;
1880
0
  }
1881
0
1882
0
  return DirectionStringToSelectionDirection(aDirection.Value());
1883
0
}
1884
1885
void
1886
nsTextEditorState::SetSelectionRange(uint32_t aSelectionStart,
1887
                                     uint32_t aSelectionEnd,
1888
                                     const Optional<nsAString>& aDirection,
1889
                                     ErrorResult& aRv)
1890
0
{
1891
0
  nsITextControlFrame::SelectionDirection dir =
1892
0
    DirectionStringToSelectionDirection(aDirection);
1893
0
1894
0
  SetSelectionRange(aSelectionStart, aSelectionEnd, dir, aRv);
1895
0
}
1896
1897
void
1898
nsTextEditorState::SetRangeText(const nsAString& aReplacement,
1899
                                ErrorResult& aRv)
1900
0
{
1901
0
  uint32_t start, end;
1902
0
  GetSelectionRange(&start, &end, aRv);
1903
0
  if (aRv.Failed()) {
1904
0
    return;
1905
0
  }
1906
0
1907
0
  SetRangeText(aReplacement, start, end, SelectionMode::Preserve,
1908
0
               aRv, Some(start), Some(end));
1909
0
}
1910
1911
void
1912
nsTextEditorState::SetRangeText(const nsAString& aReplacement, uint32_t aStart,
1913
                                uint32_t aEnd, SelectionMode aSelectMode,
1914
                                ErrorResult& aRv,
1915
                                const Maybe<uint32_t>& aSelectionStart,
1916
                                const Maybe<uint32_t>& aSelectionEnd)
1917
0
{
1918
0
  if (aStart > aEnd) {
1919
0
    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
1920
0
    return;
1921
0
  }
1922
0
1923
0
  nsAutoString value;
1924
0
  mTextCtrlElement->GetValueFromSetRangeText(value);
1925
0
  uint32_t inputValueLength = value.Length();
1926
0
1927
0
  if (aStart > inputValueLength) {
1928
0
    aStart = inputValueLength;
1929
0
  }
1930
0
1931
0
  if (aEnd > inputValueLength) {
1932
0
    aEnd = inputValueLength;
1933
0
  }
1934
0
1935
0
  uint32_t selectionStart, selectionEnd;
1936
0
  if (!aSelectionStart) {
1937
0
    MOZ_ASSERT(!aSelectionEnd);
1938
0
    GetSelectionRange(&selectionStart, &selectionEnd, aRv);
1939
0
    if (aRv.Failed()) {
1940
0
      return;
1941
0
    }
1942
0
  } else {
1943
0
    MOZ_ASSERT(aSelectionEnd);
1944
0
    selectionStart = *aSelectionStart;
1945
0
    selectionEnd = *aSelectionEnd;
1946
0
  }
1947
0
1948
0
  MOZ_ASSERT(aStart <= aEnd);
1949
0
  value.Replace(aStart, aEnd - aStart, aReplacement);
1950
0
  nsresult rv = mTextCtrlElement->SetValueFromSetRangeText(value);
1951
0
  if (NS_FAILED(rv)) {
1952
0
    aRv.Throw(rv);
1953
0
    return;
1954
0
  }
1955
0
1956
0
  uint32_t newEnd = aStart + aReplacement.Length();
1957
0
  int32_t delta =  aReplacement.Length() - (aEnd - aStart);
1958
0
1959
0
  switch (aSelectMode) {
1960
0
    case mozilla::dom::SelectionMode::Select:
1961
0
    {
1962
0
      selectionStart = aStart;
1963
0
      selectionEnd = newEnd;
1964
0
    }
1965
0
    break;
1966
0
    case mozilla::dom::SelectionMode::Start:
1967
0
    {
1968
0
      selectionStart = selectionEnd = aStart;
1969
0
    }
1970
0
    break;
1971
0
    case mozilla::dom::SelectionMode::End:
1972
0
    {
1973
0
      selectionStart = selectionEnd = newEnd;
1974
0
    }
1975
0
    break;
1976
0
    case mozilla::dom::SelectionMode::Preserve:
1977
0
    {
1978
0
      if (selectionStart > aEnd) {
1979
0
        selectionStart += delta;
1980
0
      } else if (selectionStart > aStart) {
1981
0
        selectionStart = aStart;
1982
0
      }
1983
0
1984
0
      if (selectionEnd > aEnd) {
1985
0
        selectionEnd += delta;
1986
0
      } else if (selectionEnd > aStart) {
1987
0
        selectionEnd = newEnd;
1988
0
      }
1989
0
    }
1990
0
    break;
1991
0
    default:
1992
0
      MOZ_CRASH("Unknown mode!");
1993
0
  }
1994
0
1995
0
  SetSelectionRange(selectionStart, selectionEnd, Optional<nsAString>(), aRv);
1996
0
}
1997
1998
HTMLInputElement*
1999
nsTextEditorState::GetParentNumberControl(nsFrame* aFrame) const
2000
0
{
2001
0
  MOZ_ASSERT(aFrame);
2002
0
  nsIContent* content = aFrame->GetContent();
2003
0
  MOZ_ASSERT(content);
2004
0
  nsIContent* parent = content->GetParent();
2005
0
  if (!parent) {
2006
0
    return nullptr;
2007
0
  }
2008
0
  nsIContent* parentOfParent = parent->GetParent();
2009
0
  if (!parentOfParent) {
2010
0
    return nullptr;
2011
0
  }
2012
0
  HTMLInputElement* input = HTMLInputElement::FromNode(parentOfParent);
2013
0
  if (input) {
2014
0
    // This function might be called during frame reconstruction as a result
2015
0
    // of changing the input control's type from number to something else. In
2016
0
    // that situation, the type of the control has changed, but its frame has
2017
0
    // not been reconstructed yet.  So we need to check the type of the input
2018
0
    // control in addition to the type of the frame.
2019
0
    return (input->ControlType() == NS_FORM_INPUT_NUMBER) ? input : nullptr;
2020
0
  }
2021
0
2022
0
  return nullptr;
2023
0
}
2024
2025
void
2026
nsTextEditorState::DestroyEditor()
2027
0
{
2028
0
  // notify the editor that we are going away
2029
0
  if (mEditorInitialized) {
2030
0
    mTextEditor->PreDestroy(true);
2031
0
    mEditorInitialized = false;
2032
0
  }
2033
0
}
2034
2035
void
2036
nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame)
2037
0
{
2038
0
  NS_ENSURE_TRUE_VOID(mBoundFrame);
2039
0
2040
0
  // If it was, however, it should be unbounded from the same frame.
2041
0
  MOZ_ASSERT(aFrame == mBoundFrame, "Unbinding from the wrong frame");
2042
0
  NS_ENSURE_TRUE_VOID(!aFrame || aFrame == mBoundFrame);
2043
0
2044
0
  // If the editor is modified but nsIEditorObserver::EditAction() hasn't been
2045
0
  // called yet, we need to notify it here because editor may be destroyed
2046
0
  // before EditAction() is called if selection listener causes flushing layout.
2047
0
  if (mTextListener && mTextEditor && mEditorInitialized &&
2048
0
      mTextEditor->IsInEditSubAction()) {
2049
0
    mTextListener->OnEditActionHandled();
2050
0
  }
2051
0
2052
0
  // We need to start storing the value outside of the editor if we're not
2053
0
  // going to use it anymore, so retrieve it for now.
2054
0
  nsAutoString value;
2055
0
  GetValue(value, true);
2056
0
2057
0
  if (mRestoringSelection) {
2058
0
    mRestoringSelection->Revoke();
2059
0
    mRestoringSelection = nullptr;
2060
0
  }
2061
0
2062
0
  // Save our selection state if needed.
2063
0
  // Note that GetSelectionRange will attempt to work with our selection
2064
0
  // controller, so we should make sure we do it before we start doing things
2065
0
  // like destroying our editor (if we have one), tearing down the selection
2066
0
  // controller, and so forth.
2067
0
  if (!IsSelectionCached()) {
2068
0
    // Go ahead and cache it now.
2069
0
    uint32_t start = 0, end = 0;
2070
0
    GetSelectionRange(&start, &end, IgnoreErrors());
2071
0
2072
0
    nsITextControlFrame::SelectionDirection direction =
2073
0
      GetSelectionDirection(IgnoreErrors());
2074
0
2075
0
    SelectionProperties& props = GetSelectionProperties();
2076
0
    props.SetStart(start);
2077
0
    props.SetEnd(end);
2078
0
    props.SetDirection(direction);
2079
0
    HTMLInputElement* number = GetParentNumberControl(aFrame);
2080
0
    if (number) {
2081
0
      // If we are inside a number control, cache the selection on the
2082
0
      // parent control, because this text editor state will be destroyed
2083
0
      // together with the native anonymous text control.
2084
0
      number->SetSelectionCached();
2085
0
    } else {
2086
0
      mSelectionCached = true;
2087
0
    }
2088
0
  }
2089
0
2090
0
  // Destroy our editor
2091
0
  DestroyEditor();
2092
0
2093
0
  // Clean up the controller
2094
0
  if (!SuppressEventHandlers(mBoundFrame->PresContext()))
2095
0
  {
2096
0
    nsCOMPtr<nsIControllers> controllers;
2097
0
    nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2098
0
    HTMLInputElement* inputElement =
2099
0
      HTMLInputElement::FromNodeOrNull(content);
2100
0
    if (inputElement)
2101
0
      inputElement->GetControllers(getter_AddRefs(controllers));
2102
0
    else
2103
0
    {
2104
0
      HTMLTextAreaElement* textAreaElement =
2105
0
        HTMLTextAreaElement::FromNodeOrNull(content);
2106
0
      if (textAreaElement) {
2107
0
        textAreaElement->GetControllers(getter_AddRefs(controllers));
2108
0
      }
2109
0
    }
2110
0
2111
0
    if (controllers)
2112
0
    {
2113
0
      uint32_t numControllers;
2114
0
      nsresult rv = controllers->GetControllerCount(&numControllers);
2115
0
      NS_ASSERTION((NS_SUCCEEDED(rv)), "bad result in gfx text control destructor");
2116
0
      for (uint32_t i = 0; i < numControllers; i ++)
2117
0
      {
2118
0
        nsCOMPtr<nsIController> controller;
2119
0
        rv = controllers->GetControllerAt(i, getter_AddRefs(controller));
2120
0
        if (NS_SUCCEEDED(rv) && controller)
2121
0
        {
2122
0
          nsCOMPtr<nsIControllerContext> editController = do_QueryInterface(controller);
2123
0
          if (editController)
2124
0
          {
2125
0
            editController->SetCommandContext(nullptr);
2126
0
          }
2127
0
        }
2128
0
      }
2129
0
    }
2130
0
  }
2131
0
2132
0
  if (mSelCon) {
2133
0
    if (mTextListener) {
2134
0
      mTextListener->EndListeningToSelectionChange();
2135
0
    }
2136
0
2137
0
    mSelCon->SetScrollableFrame(nullptr);
2138
0
    mSelCon = nullptr;
2139
0
  }
2140
0
2141
0
  if (mTextListener)
2142
0
  {
2143
0
    mTextListener->SetFrame(nullptr);
2144
0
2145
0
    nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement);
2146
0
    EventListenerManager* manager = target->GetExistingListenerManager();
2147
0
    if (manager) {
2148
0
      manager->RemoveEventListenerByType(mTextListener,
2149
0
        NS_LITERAL_STRING("keydown"),
2150
0
        TrustedEventsAtSystemGroupBubble());
2151
0
      manager->RemoveEventListenerByType(mTextListener,
2152
0
        NS_LITERAL_STRING("keypress"),
2153
0
        TrustedEventsAtSystemGroupBubble());
2154
0
      manager->RemoveEventListenerByType(mTextListener,
2155
0
        NS_LITERAL_STRING("keyup"),
2156
0
        TrustedEventsAtSystemGroupBubble());
2157
0
    }
2158
0
2159
0
    mTextListener = nullptr;
2160
0
  }
2161
0
2162
0
  mBoundFrame = nullptr;
2163
0
2164
0
  // Now that we don't have a frame any more, store the value in the text buffer.
2165
0
  // The only case where we don't do this is if a value transfer is in progress.
2166
0
  if (!mValueTransferInProgress) {
2167
0
    bool success = SetValue(value, eSetValue_Internal);
2168
0
    // TODO Find something better to do if this fails...
2169
0
    NS_ENSURE_TRUE_VOID(success);
2170
0
  }
2171
0
}
2172
2173
int32_t
2174
nsTextEditorState::GetMaxLength()
2175
0
{
2176
0
  nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2177
0
  nsGenericHTMLElement* element =
2178
0
    nsGenericHTMLElement::FromNodeOrNull(content);
2179
0
  if (NS_WARN_IF(!element)) {
2180
0
    return -1;
2181
0
  }
2182
0
2183
0
  const nsAttrValue* attr = element->GetParsedAttr(nsGkAtoms::maxlength);
2184
0
  if (attr && attr->Type() == nsAttrValue::eInteger) {
2185
0
    return attr->GetIntegerValue();
2186
0
  }
2187
0
2188
0
  return -1;
2189
0
}
2190
2191
void
2192
nsTextEditorState::GetValue(nsAString& aValue, bool aIgnoreWrap) const
2193
0
{
2194
0
  // While SetValue() is being called and requesting to commit composition to
2195
0
  // IME, GetValue() may be called for appending text or something.  Then, we
2196
0
  // need to return the latest aValue of SetValue() since the value hasn't
2197
0
  // been set to the editor yet.
2198
0
  if (mIsCommittingComposition) {
2199
0
    aValue = mValueBeingSet;
2200
0
    return;
2201
0
  }
2202
0
2203
0
  if (mTextEditor && mBoundFrame &&
2204
0
      (mEditorInitialized || !IsSingleLineTextControl())) {
2205
0
    if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) {
2206
0
      aValue = mBoundFrame->CachedValue();
2207
0
      return;
2208
0
    }
2209
0
2210
0
    aValue.Truncate(); // initialize out param
2211
0
2212
0
    uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
2213
0
                      nsIDocumentEncoder::OutputPreformatted |
2214
0
                      nsIDocumentEncoder::OutputPersistNBSP |
2215
0
                      nsIDocumentEncoder::OutputBodyOnly);
2216
0
    if (!aIgnoreWrap) {
2217
0
      nsITextControlElement::nsHTMLTextWrap wrapProp;
2218
0
      nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2219
0
      if (content &&
2220
0
          nsITextControlElement::GetWrapPropertyEnum(content, wrapProp) &&
2221
0
          wrapProp == nsITextControlElement::eHTMLTextWrap_Hard) {
2222
0
        flags |= nsIDocumentEncoder::OutputWrap;
2223
0
      }
2224
0
    }
2225
0
2226
0
    // What follows is a bit of a hack.  The problem is that we could be in
2227
0
    // this method because we're being destroyed for whatever reason while
2228
0
    // script is executing.  If that happens, editor will run with the
2229
0
    // privileges of the executing script, which means it may not be able to
2230
0
    // access its own DOM nodes!  Let's try to deal with that by pushing a null
2231
0
    // JSContext on the JSContext stack to make it clear that we're native
2232
0
    // code.  Note that any script that's directly trying to access our value
2233
0
    // has to be going through some scriptable object to do that and that
2234
0
    // already does the relevant security checks.
2235
0
    // XXXbz if we could just get the textContent of our anonymous content (eg
2236
0
    // if plaintext editor didn't create <br> nodes all over), we wouldn't need
2237
0
    // this.
2238
0
    { /* Scope for AutoNoJSAPI. */
2239
0
      AutoNoJSAPI nojsapi;
2240
0
2241
0
      DebugOnly<nsresult> rv = mTextEditor->ComputeTextValue(flags, aValue);
2242
0
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get value");
2243
0
    }
2244
0
    // Only when the result doesn't include line breaks caused by hard-wrap,
2245
0
    // mCacheValue should cache the value.
2246
0
    if (!(flags & nsIDocumentEncoder::OutputWrap)) {
2247
0
      mBoundFrame->CacheValue(aValue);
2248
0
    } else {
2249
0
      mBoundFrame->ClearCachedValue();
2250
0
    }
2251
0
  } else {
2252
0
    if (!mTextCtrlElement->ValueChanged() || !mValue) {
2253
0
      mTextCtrlElement->GetDefaultValueFromContent(aValue);
2254
0
    } else {
2255
0
      aValue = *mValue;
2256
0
    }
2257
0
  }
2258
0
}
2259
2260
bool
2261
nsTextEditorState::SetValue(const nsAString& aValue, const nsAString* aOldValue,
2262
                            uint32_t aFlags)
2263
0
{
2264
0
  nsAutoString newValue(aValue);
2265
0
2266
0
  // While mIsCommittingComposition is true (that means that some event
2267
0
  // handlers which are fired during committing composition are the caller of
2268
0
  // this method), GetValue() uses mValueBeingSet for its result because the
2269
0
  // first calls of this methods hasn't set the value yet.  So, when it's true,
2270
0
  // we need to modify mValueBeingSet.  In this case, we will back to the first
2271
0
  // call of this method, then, mValueBeingSet will be truncated when
2272
0
  // mIsCommittingComposition is set false.  See below.
2273
0
  if (mIsCommittingComposition) {
2274
0
    mValueBeingSet = aValue;
2275
0
    // GetValue doesn't return current text frame's content during committing.
2276
0
    // So we cannot trust this old value
2277
0
    aOldValue = nullptr;
2278
0
  }
2279
0
2280
0
  // Note that if this may be called during reframe of the editor.  In such
2281
0
  // case, we shouldn't commit composition.  Therefore, when this is called
2282
0
  // for internal processing, we shouldn't commit the composition.
2283
0
  if (aFlags & (eSetValue_BySetUserInput | eSetValue_ByContent)) {
2284
0
    if (EditorHasComposition()) {
2285
0
      // When this is called recursively, there shouldn't be composition.
2286
0
      if (NS_WARN_IF(mIsCommittingComposition)) {
2287
0
        // Don't request to commit composition again.  But if it occurs,
2288
0
        // we should skip to set the new value to the editor here.  It should
2289
0
        // be set later with the updated mValueBeingSet.
2290
0
        return true;
2291
0
      }
2292
0
      if (NS_WARN_IF(!mBoundFrame)) {
2293
0
        // We're not sure if this case is possible.
2294
0
      } else {
2295
0
        // If setting value won't change current value, we shouldn't commit
2296
0
        // composition for compatibility with the other browsers.
2297
0
        nsAutoString currentValue;
2298
0
        if (aOldValue) {
2299
#ifdef DEBUG
2300
          mBoundFrame->GetText(currentValue);
2301
          MOZ_ASSERT(currentValue.Equals(*aOldValue));
2302
#endif
2303
          currentValue.Assign(*aOldValue);
2304
0
        } else {
2305
0
          mBoundFrame->GetText(currentValue);
2306
0
        }
2307
0
        if (newValue == currentValue) {
2308
0
          // Note that in this case, we shouldn't fire any events with setting
2309
0
          // value because event handlers may try to set value recursively but
2310
0
          // we cannot commit composition at that time due to unsafe to run
2311
0
          // script (see below).
2312
0
          return true;
2313
0
        }
2314
0
        // IME might commit composition, then change value, so we cannot
2315
0
        // trust old value from parameter.
2316
0
        aOldValue = nullptr;
2317
0
      }
2318
0
      // If there is composition, need to commit composition first because
2319
0
      // other browsers do that.
2320
0
      // NOTE: We don't need to block nested calls of this because input nor
2321
0
      //       other events won't be fired by setting values and script blocker
2322
0
      //       is used during setting the value to the editor.  IE also allows
2323
0
      //       to set the editor value on the input event which is caused by
2324
0
      //       forcibly committing composition.
2325
0
      if (nsContentUtils::IsSafeToRunScript()) {
2326
0
        WeakPtr<nsTextEditorState> self(this);
2327
0
        // WARNING: During this call, compositionupdate, compositionend, input
2328
0
        // events will be fired.  Therefore, everything can occur.  E.g., the
2329
0
        // document may be unloaded.
2330
0
        mValueBeingSet = aValue;
2331
0
        mIsCommittingComposition = true;
2332
0
        RefPtr<TextEditor> textEditor = mTextEditor;
2333
0
        nsresult rv = textEditor->CommitComposition();
2334
0
        if (!self.get()) {
2335
0
          return true;
2336
0
        }
2337
0
        mIsCommittingComposition = false;
2338
0
        // If this is called recursively during committing composition and
2339
0
        // some of them may be skipped above.  Therefore, we need to set
2340
0
        // value to the editor with the aValue of the latest call.
2341
0
        newValue = mValueBeingSet;
2342
0
        // When mIsCommittingComposition is false, mValueBeingSet won't be
2343
0
        // used.  Therefore, let's clear it.
2344
0
        mValueBeingSet.Truncate();
2345
0
        if (NS_FAILED(rv)) {
2346
0
          NS_WARNING("nsTextEditorState failed to commit composition");
2347
0
          return true;
2348
0
        }
2349
0
      } else {
2350
0
        NS_WARNING("SetValue() is called when there is composition but "
2351
0
                   "it's not safe to request to commit the composition");
2352
0
      }
2353
0
    }
2354
0
  }
2355
0
2356
0
  // \r is an illegal character in the dom, but people use them,
2357
0
  // so convert windows and mac platform linebreaks to \n:
2358
0
  if (!nsContentUtils::PlatformToDOMLineBreaks(newValue, fallible)) {
2359
0
    return false;
2360
0
  }
2361
0
2362
0
  if (mTextEditor && mBoundFrame) {
2363
0
    // The InsertText call below might flush pending notifications, which
2364
0
    // could lead into a scheduled PrepareEditor to be called.  That will
2365
0
    // lead to crashes (or worse) because we'd be initializing the editor
2366
0
    // before InsertText returns.  This script blocker makes sure that
2367
0
    // PrepareEditor cannot be called prematurely.
2368
0
    nsAutoScriptBlocker scriptBlocker;
2369
0
2370
#ifdef DEBUG
2371
    if (IsSingleLineTextControl()) {
2372
      NS_ASSERTION(mEditorInitialized || mInitializing,
2373
                   "We should never try to use the editor if we're not initialized unless we're being initialized");
2374
    }
2375
#endif
2376
2377
0
    nsAutoString currentValue;
2378
0
    if (aOldValue) {
2379
#ifdef DEBUG
2380
      mBoundFrame->GetText(currentValue);
2381
      MOZ_ASSERT(currentValue.Equals(*aOldValue));
2382
#endif
2383
      currentValue.Assign(*aOldValue);
2384
0
    } else {
2385
0
      mBoundFrame->GetText(currentValue);
2386
0
    }
2387
0
2388
0
    AutoWeakFrame weakFrame(mBoundFrame);
2389
0
2390
0
    // this is necessary to avoid infinite recursion
2391
0
    if (!currentValue.Equals(newValue)) {
2392
0
      RefPtr<TextEditor> textEditor = mTextEditor;
2393
0
      ValueSetter valueSetter(textEditor);
2394
0
2395
0
      nsCOMPtr<nsIDocument> document = textEditor->GetDocument();
2396
0
      if (NS_WARN_IF(!document)) {
2397
0
        return true;
2398
0
      }
2399
0
2400
0
      // Time to mess with our security context... See comments in GetValue()
2401
0
      // for why this is needed.  Note that we have to do this up here, because
2402
0
      // otherwise SelectAll() will fail.
2403
0
      {
2404
0
        AutoNoJSAPI nojsapi;
2405
0
2406
0
        // FYI: It's safe to use raw pointer for selection here because
2407
0
        //      SelectionBatcher will grab it with RefPtr.
2408
0
        Selection* selection =
2409
0
          mSelCon->GetSelection(SelectionType::eNormal);
2410
0
        SelectionBatcher selectionBatcher(selection);
2411
0
2412
0
        if (NS_WARN_IF(!weakFrame.IsAlive())) {
2413
0
          return true;
2414
0
        }
2415
0
2416
0
        valueSetter.Init();
2417
0
2418
0
        // get the flags, remove readonly, disabled and max-length,
2419
0
        // set the value, restore flags
2420
0
        {
2421
0
          AutoRestoreEditorState restoreState(textEditor);
2422
0
2423
0
          mTextListener->SettingValue(true);
2424
0
          bool notifyValueChanged = !!(aFlags & eSetValue_Notify);
2425
0
          mTextListener->SetValueChanged(notifyValueChanged);
2426
0
2427
0
          if (aFlags & eSetValue_BySetUserInput) {
2428
0
            // If the caller inserts text as part of user input, for example,
2429
0
            // autocomplete, we need to replace the text as "insert string"
2430
0
            // because undo should cancel only this operation (i.e., previous
2431
0
            // transactions typed by user shouldn't be merged with this).
2432
0
            DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(newValue);
2433
0
            NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2434
0
              "Failed to set the new value");
2435
0
          } else if (aFlags & eSetValue_ForXUL) {
2436
0
            // On XUL <textbox> element, we need to preserve existing undo
2437
0
            // transactions.
2438
0
            // XXX Do we really need to do such complicated optimization?
2439
0
            //     This was landed for web pages which set <textarea> value
2440
0
            //     per line (bug 518122).  For example:
2441
0
            //       for (;;) {
2442
0
            //         textarea.value += oneLineText + "\n";
2443
0
            //       }
2444
0
            //     However, this path won't be used in web content anymore.
2445
0
            nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon.get();
2446
0
            uint32_t currentLength = currentValue.Length();
2447
0
            uint32_t newlength = newValue.Length();
2448
0
            if (!currentLength ||
2449
0
                !StringBeginsWith(newValue, currentValue)) {
2450
0
              // Replace the whole text.
2451
0
              currentLength = 0;
2452
0
              kungFuDeathGrip->SelectAll();
2453
0
            } else {
2454
0
              // Collapse selection to the end so that we can append data.
2455
0
              mBoundFrame->SelectAllOrCollapseToEndOfText(false);
2456
0
            }
2457
0
            const nsAString& insertValue =
2458
0
              StringTail(newValue, newlength - currentLength);
2459
0
2460
0
            if (insertValue.IsEmpty()) {
2461
0
              DebugOnly<nsresult> rv =
2462
0
                textEditor->DeleteSelectionAsAction(nsIEditor::eNone,
2463
0
                                                    nsIEditor::eStrip);
2464
0
              NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2465
0
                "Failed to remove the text");
2466
0
            } else {
2467
0
              DebugOnly<nsresult> rv =
2468
0
                textEditor->InsertTextAsAction(insertValue);
2469
0
              NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2470
0
                "Failed to insert the new value");
2471
0
            }
2472
0
          } else {
2473
0
            // On <input> or <textarea>, we shouldn't preserve existing undo
2474
0
            // transactions because other browsers do not preserve them too
2475
0
            // and not preserving transactions makes setting value faster.
2476
0
            AutoDisableUndo disableUndo(textEditor);
2477
0
            if (selection) {
2478
0
              // Since we don't use undo transaction, we don't need to store
2479
0
              // selection state.  SetText will set selection to tail.
2480
0
              // Note that textEditor will collapse selection to the end.
2481
0
              // Therefore, it's safe to use RemoveAllRangesTemporarily() here.
2482
0
              selection->RemoveAllRangesTemporarily();
2483
0
            }
2484
0
2485
0
            textEditor->SetText(newValue);
2486
0
2487
0
            // Call the listener's HandleValueChanged() callback manually, since
2488
0
            // we don't use the transaction manager in this path and it could be
2489
0
            // that the editor would bypass calling the listener for that reason.
2490
0
            mTextListener->HandleValueChanged();
2491
0
          }
2492
0
2493
0
          mTextListener->SetValueChanged(true);
2494
0
          mTextListener->SettingValue(false);
2495
0
2496
0
          if (!notifyValueChanged) {
2497
0
            // Listener doesn't update frame, but it is required for placeholder
2498
0
            ValueWasChanged(true);
2499
0
          }
2500
0
        }
2501
0
2502
0
        if (!weakFrame.IsAlive()) {
2503
0
          // If the frame was destroyed because of a flush somewhere inside
2504
0
          // InsertText, mBoundFrame here will be false.  But it's also possible
2505
0
          // for the frame to go away because of another reason (such as deleting
2506
0
          // the existing selection -- see bug 574558), in which case we don't
2507
0
          // need to reset the value here.
2508
0
          if (!mBoundFrame) {
2509
0
            return SetValue(newValue, aFlags & eSetValue_Notify);
2510
0
          }
2511
0
          return true;
2512
0
        }
2513
0
2514
0
        // The new value never includes line breaks caused by hard-wrap.
2515
0
        // So, mCachedValue can always cache the new value.
2516
0
        if (!mBoundFrame->CacheValue(newValue, fallible)) {
2517
0
          return false;
2518
0
        }
2519
0
      }
2520
0
    }
2521
0
  } else {
2522
0
    if (!mValue) {
2523
0
      mValue.emplace();
2524
0
    }
2525
0
2526
0
    // We can't just early-return here if mValue->Equals(newValue), because
2527
0
    // ValueWasChanged and OnValueChanged below still need to be called.
2528
0
    if (!mValue->Equals(newValue) ||
2529
0
        !nsContentUtils::SkipCursorMoveForSameValueSet()) {
2530
0
      if (!mValue->Assign(newValue, fallible)) {
2531
0
        return false;
2532
0
      }
2533
0
2534
0
      // Since we have no editor we presumably have cached selection state.
2535
0
      if (IsSelectionCached()) {
2536
0
        SelectionProperties& props = GetSelectionProperties();
2537
0
        if (aFlags & eSetValue_MoveCursorToEndIfValueChanged) {
2538
0
          props.SetStart(newValue.Length());
2539
0
          props.SetEnd(newValue.Length());
2540
0
          props.SetDirection(nsITextControlFrame::eForward);
2541
0
        } else {
2542
0
          // Make sure our cached selection position is not outside the new value.
2543
0
          props.SetStart(std::min(props.GetStart(), newValue.Length()));
2544
0
          props.SetEnd(std::min(props.GetEnd(), newValue.Length()));
2545
0
        }
2546
0
      }
2547
0
2548
0
      // Update the frame display if needed
2549
0
      if (mBoundFrame) {
2550
0
        mBoundFrame->UpdateValueDisplay(true);
2551
0
      }
2552
0
    } else {
2553
0
      // Even if our value is not actually changing, apparently we need to mark
2554
0
      // our SelectionProperties dirty to make accessibility tests happy.
2555
0
      // Probably because they depend on the SetSelectionRange() call we make on
2556
0
      // our frame in RestoreSelectionState, but I have no idea why they do.
2557
0
      if (IsSelectionCached()) {
2558
0
        SelectionProperties& props = GetSelectionProperties();
2559
0
        props.SetIsDirty();
2560
0
      }
2561
0
    }
2562
0
2563
0
    // If we've reached the point where the root node has been created, we
2564
0
    // can assume that it's safe to notify.
2565
0
    ValueWasChanged(!!mBoundFrame);
2566
0
  }
2567
0
2568
0
  mTextCtrlElement->OnValueChanged(/* aNotify = */ !!mBoundFrame,
2569
0
                                   /* aWasInteractiveUserChange = */ false);
2570
0
2571
0
  return true;
2572
0
}
2573
2574
bool
2575
nsTextEditorState::HasNonEmptyValue()
2576
0
{
2577
0
  if (mTextEditor && mBoundFrame && mEditorInitialized &&
2578
0
      !mIsCommittingComposition) {
2579
0
    bool empty;
2580
0
    nsresult rv = mTextEditor->IsEmpty(&empty);
2581
0
    if (NS_SUCCEEDED(rv)) {
2582
0
      return !empty;
2583
0
    }
2584
0
  }
2585
0
2586
0
  nsAutoString value;
2587
0
  GetValue(value, true);
2588
0
  return !value.IsEmpty();
2589
0
}
2590
2591
void
2592
nsTextEditorState::InitializeKeyboardEventListeners()
2593
0
{
2594
0
  //register key listeners
2595
0
  nsCOMPtr<EventTarget> target = do_QueryInterface(mTextCtrlElement);
2596
0
  EventListenerManager* manager = target->GetOrCreateListenerManager();
2597
0
  if (manager) {
2598
0
    manager->AddEventListenerByType(mTextListener,
2599
0
                                    NS_LITERAL_STRING("keydown"),
2600
0
                                    TrustedEventsAtSystemGroupBubble());
2601
0
    manager->AddEventListenerByType(mTextListener,
2602
0
                                    NS_LITERAL_STRING("keypress"),
2603
0
                                    TrustedEventsAtSystemGroupBubble());
2604
0
    manager->AddEventListenerByType(mTextListener,
2605
0
                                    NS_LITERAL_STRING("keyup"),
2606
0
                                    TrustedEventsAtSystemGroupBubble());
2607
0
  }
2608
0
2609
0
  mSelCon->SetScrollableFrame(do_QueryFrame(mBoundFrame->PrincipalChildList().FirstChild()));
2610
0
}
2611
2612
void
2613
nsTextEditorState::ValueWasChanged(bool aNotify)
2614
0
{
2615
0
  UpdateOverlayTextVisibility(aNotify);
2616
0
}
2617
2618
void
2619
nsTextEditorState::SetPreviewText(const nsAString& aValue, bool aNotify)
2620
0
{
2621
0
  // If we don't have a preview div, there's nothing to do.
2622
0
  Element* previewDiv = GetPreviewNode();
2623
0
  if (!previewDiv)
2624
0
    return;
2625
0
2626
0
  nsAutoString previewValue(aValue);
2627
0
2628
0
  nsContentUtils::RemoveNewlines(previewValue);
2629
0
  MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
2630
0
  previewDiv->GetFirstChild()->AsText()->SetText(previewValue, aNotify);
2631
0
2632
0
  UpdateOverlayTextVisibility(aNotify);
2633
0
}
2634
2635
void
2636
nsTextEditorState::GetPreviewText(nsAString& aValue)
2637
0
{
2638
0
  // If we don't have a preview div, there's nothing to do.
2639
0
  Element* previewDiv = GetPreviewNode();
2640
0
  if (!previewDiv)
2641
0
    return;
2642
0
2643
0
  MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
2644
0
  const nsTextFragment *text = previewDiv->GetFirstChild()->GetText();
2645
0
2646
0
  aValue.Truncate();
2647
0
  text->AppendTo(aValue);
2648
0
}
2649
2650
void
2651
nsTextEditorState::UpdateOverlayTextVisibility(bool aNotify)
2652
0
{
2653
0
  nsAutoString value, previewValue;
2654
0
  bool valueIsEmpty = !HasNonEmptyValue();
2655
0
  GetPreviewText(previewValue);
2656
0
2657
0
  mPreviewVisibility = valueIsEmpty && !previewValue.IsEmpty();
2658
0
  mPlaceholderVisibility = valueIsEmpty && previewValue.IsEmpty();
2659
0
2660
0
  if (mPlaceholderVisibility &&
2661
0
      !nsContentUtils::ShowInputPlaceholderOnFocus()) {
2662
0
    nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2663
0
    mPlaceholderVisibility = !nsContentUtils::IsFocusedContent(content);
2664
0
  }
2665
0
2666
0
  if (mBoundFrame && aNotify) {
2667
0
    mBoundFrame->InvalidateFrame();
2668
0
  }
2669
0
}
2670
2671
void
2672
nsTextEditorState::HideSelectionIfBlurred()
2673
0
{
2674
0
  MOZ_ASSERT(mSelCon, "Should have a selection controller if we have a frame!");
2675
0
  nsCOMPtr<nsIContent> content = do_QueryInterface(mTextCtrlElement);
2676
0
  if (!nsContentUtils::IsFocusedContent(content)) {
2677
0
    mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
2678
0
  }
2679
0
}
2680
2681
bool
2682
nsTextEditorState::EditorHasComposition()
2683
0
{
2684
0
  return mTextEditor && mTextEditor->IsIMEComposing();
2685
0
}