Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/editor/libeditor/TextEditor.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* This Source Code Form is subject to the terms of the Mozilla Public
3
 * License, v. 2.0. If a copy of the MPL was not distributed with this
4
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6
#include "mozilla/TextEditor.h"
7
8
#include "EditAggregateTransaction.h"
9
#include "HTMLEditRules.h"
10
#include "InternetCiter.h"
11
#include "TextEditUtils.h"
12
#include "gfxFontUtils.h"
13
#include "mozilla/Assertions.h"
14
#include "mozilla/EditAction.h"
15
#include "mozilla/EditorDOMPoint.h"
16
#include "mozilla/EditorUtils.h"
17
#include "mozilla/HTMLEditor.h"
18
#include "mozilla/IMEStateManager.h"
19
#include "mozilla/mozalloc.h"
20
#include "mozilla/Preferences.h"
21
#include "mozilla/TextEditRules.h"
22
#include "mozilla/TextComposition.h"
23
#include "mozilla/TextEvents.h"
24
#include "mozilla/TextServicesDocument.h"
25
#include "mozilla/dom/Selection.h"
26
#include "mozilla/dom/Event.h"
27
#include "mozilla/dom/Element.h"
28
#include "nsAString.h"
29
#include "nsCRT.h"
30
#include "nsCaret.h"
31
#include "nsCharTraits.h"
32
#include "nsComponentManagerUtils.h"
33
#include "nsContentCID.h"
34
#include "nsContentList.h"
35
#include "nsCopySupport.h"
36
#include "nsDebug.h"
37
#include "nsDependentSubstring.h"
38
#include "nsError.h"
39
#include "nsGkAtoms.h"
40
#include "nsIAbsorbingTransaction.h"
41
#include "nsIClipboard.h"
42
#include "nsIContent.h"
43
#include "nsIContentIterator.h"
44
#include "nsIDocumentEncoder.h"
45
#include "nsINode.h"
46
#include "nsIPresShell.h"
47
#include "nsISelectionController.h"
48
#include "nsISupportsPrimitives.h"
49
#include "nsITransferable.h"
50
#include "nsIWeakReferenceUtils.h"
51
#include "nsNameSpaceManager.h"
52
#include "nsLiteralString.h"
53
#include "nsReadableUtils.h"
54
#include "nsServiceManagerUtils.h"
55
#include "nsString.h"
56
#include "nsStringFwd.h"
57
#include "nsTextNode.h"
58
#include "nsUnicharUtils.h"
59
#include "nsXPCOM.h"
60
61
class nsIOutputStream;
62
class nsISupports;
63
64
namespace mozilla {
65
66
using namespace dom;
67
68
template already_AddRefed<Element>
69
TextEditor::InsertBrElementWithTransaction(
70
              Selection& aSelection,
71
              const EditorDOMPoint& aPointToInsert,
72
              EDirection aSelect);
73
template already_AddRefed<Element>
74
TextEditor::InsertBrElementWithTransaction(
75
              Selection& aSelection,
76
              const EditorRawDOMPoint& aPointToInsert,
77
              EDirection aSelect);
78
79
TextEditor::TextEditor()
80
  : mWrapColumn(0)
81
  , mMaxTextLength(-1)
82
  , mInitTriggerCounter(0)
83
  , mNewlineHandling(nsIPlaintextEditor::eNewlinesPasteToFirst)
84
#ifdef XP_WIN
85
  , mCaretStyle(1)
86
#else
87
  , mCaretStyle(0)
88
#endif
89
0
{
90
0
  // check the "single line editor newline handling"
91
0
  // and "caret behaviour in selection" prefs
92
0
  GetDefaultEditorPrefs(mNewlineHandling, mCaretStyle);
93
0
}
94
95
TextEditor::~TextEditor()
96
0
{
97
0
  // Remove event listeners. Note that if we had an HTML editor,
98
0
  //  it installed its own instead of these
99
0
  RemoveEventListeners();
100
0
101
0
  if (mRules)
102
0
    mRules->DetachEditor();
103
0
}
104
105
NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor)
106
107
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TextEditor, EditorBase)
108
0
  if (tmp->mRules)
109
0
    tmp->mRules->DetachEditor();
110
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mRules)
111
0
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedDocumentEncoder)
112
0
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
113
114
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextEditor, EditorBase)
115
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRules)
116
0
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedDocumentEncoder)
117
0
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
118
119
NS_IMPL_ADDREF_INHERITED(TextEditor, EditorBase)
120
NS_IMPL_RELEASE_INHERITED(TextEditor, EditorBase)
121
122
0
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEditor)
123
0
  NS_INTERFACE_MAP_ENTRY(nsIPlaintextEditor)
124
0
NS_INTERFACE_MAP_END_INHERITING(EditorBase)
125
126
127
nsresult
128
TextEditor::Init(nsIDocument& aDoc,
129
                 Element* aRoot,
130
                 nsISelectionController* aSelCon,
131
                 uint32_t aFlags,
132
                 const nsAString& aInitialValue)
133
0
{
134
0
  if (mRules) {
135
0
    mRules->DetachEditor();
136
0
  }
137
0
138
0
  nsresult rulesRv = NS_OK;
139
0
  {
140
0
    // block to scope AutoEditInitRulesTrigger
141
0
    AutoEditInitRulesTrigger rulesTrigger(this, rulesRv);
142
0
143
0
    // Init the base editor
144
0
    nsresult rv = EditorBase::Init(aDoc, aRoot, aSelCon, aFlags, aInitialValue);
145
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
146
0
      return rv;
147
0
    }
148
0
  }
149
0
  NS_ENSURE_SUCCESS(rulesRv, rulesRv);
150
0
151
0
  // mRules may not have been initialized yet, when this is called via
152
0
  // HTMLEditor::Init.
153
0
  if (mRules) {
154
0
    mRules->SetInitialValue(aInitialValue);
155
0
  }
156
0
157
0
  return NS_OK;
158
0
}
159
160
static int32_t sNewlineHandlingPref = -1,
161
               sCaretStylePref = -1;
162
163
static void
164
EditorPrefsChangedCallback(const char* aPrefName, void *)
165
0
{
166
0
  if (!nsCRT::strcmp(aPrefName, "editor.singleLine.pasteNewlines")) {
167
0
    sNewlineHandlingPref =
168
0
      Preferences::GetInt("editor.singleLine.pasteNewlines",
169
0
                          nsIPlaintextEditor::eNewlinesPasteToFirst);
170
0
  } else if (!nsCRT::strcmp(aPrefName, "layout.selection.caret_style")) {
171
0
    sCaretStylePref = Preferences::GetInt("layout.selection.caret_style",
172
#ifdef XP_WIN
173
                                                 1);
174
    if (!sCaretStylePref) {
175
      sCaretStylePref = 1;
176
    }
177
#else
178
                                                 0);
179
0
#endif
180
0
  }
181
0
}
182
183
// static
184
void
185
TextEditor::GetDefaultEditorPrefs(int32_t& aNewlineHandling,
186
                                  int32_t& aCaretStyle)
187
0
{
188
0
  if (sNewlineHandlingPref == -1) {
189
0
    Preferences::RegisterCallbackAndCall(EditorPrefsChangedCallback,
190
0
                                         "editor.singleLine.pasteNewlines");
191
0
    Preferences::RegisterCallbackAndCall(EditorPrefsChangedCallback,
192
0
                                         "layout.selection.caret_style");
193
0
  }
194
0
195
0
  aNewlineHandling = sNewlineHandlingPref;
196
0
  aCaretStyle = sCaretStylePref;
197
0
}
198
199
void
200
TextEditor::BeginEditorInit()
201
0
{
202
0
  mInitTriggerCounter++;
203
0
}
204
205
nsresult
206
TextEditor::EndEditorInit()
207
0
{
208
0
  MOZ_ASSERT(mInitTriggerCounter > 0, "ended editor init before we began?");
209
0
  mInitTriggerCounter--;
210
0
  if (mInitTriggerCounter) {
211
0
    return NS_OK;
212
0
  }
213
0
214
0
  nsresult rv = InitRules();
215
0
  if (NS_FAILED(rv)) {
216
0
    return rv;
217
0
  }
218
0
  // Throw away the old transaction manager if this is not the first time that
219
0
  // we're initializing the editor.
220
0
  ClearUndoRedo();
221
0
  EnableUndoRedo();
222
0
  return NS_OK;
223
0
}
224
225
NS_IMETHODIMP
226
TextEditor::SetDocumentCharacterSet(const nsACString& characterSet)
227
0
{
228
0
  nsresult rv = EditorBase::SetDocumentCharacterSet(characterSet);
229
0
  NS_ENSURE_SUCCESS(rv, rv);
230
0
231
0
  // Update META charset element.
232
0
  nsCOMPtr<nsIDocument> doc = GetDocument();
233
0
  if (NS_WARN_IF(!doc)) {
234
0
    return NS_ERROR_NOT_INITIALIZED;
235
0
  }
236
0
237
0
  if (UpdateMetaCharset(*doc, characterSet)) {
238
0
    return NS_OK;
239
0
  }
240
0
241
0
  RefPtr<nsContentList> headList =
242
0
    doc->GetElementsByTagName(NS_LITERAL_STRING("head"));
243
0
  if (NS_WARN_IF(!headList)) {
244
0
    return NS_OK;
245
0
  }
246
0
247
0
  nsCOMPtr<nsIContent> headNode = headList->Item(0);
248
0
  if (NS_WARN_IF(!headNode)) {
249
0
    return NS_OK;
250
0
  }
251
0
252
0
  // Create a new meta charset tag
253
0
  EditorRawDOMPoint atStartOfHeadNode(headNode, 0);
254
0
  RefPtr<Element> metaElement =
255
0
    CreateNodeWithTransaction(*nsGkAtoms::meta, atStartOfHeadNode);
256
0
  if (NS_WARN_IF(!metaElement)) {
257
0
    return NS_OK;
258
0
  }
259
0
260
0
  // Set attributes to the created element
261
0
  if (characterSet.IsEmpty()) {
262
0
    return NS_OK;
263
0
  }
264
0
265
0
  // not undoable, undo should undo CreateNodeWithTransaction().
266
0
  metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
267
0
                       NS_LITERAL_STRING("Content-Type"), true);
268
0
  metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::content,
269
0
                       NS_LITERAL_STRING("text/html;charset=") +
270
0
                         NS_ConvertASCIItoUTF16(characterSet),
271
0
                       true);
272
0
  return NS_OK;
273
0
}
274
275
bool
276
TextEditor::UpdateMetaCharset(nsIDocument& aDocument,
277
                              const nsACString& aCharacterSet)
278
0
{
279
0
  // get a list of META tags
280
0
  RefPtr<nsContentList> metaList =
281
0
    aDocument.GetElementsByTagName(NS_LITERAL_STRING("meta"));
282
0
  if (NS_WARN_IF(!metaList)) {
283
0
    return false;
284
0
  }
285
0
286
0
  for (uint32_t i = 0; i < metaList->Length(true); ++i) {
287
0
    nsCOMPtr<nsIContent> metaNode = metaList->Item(i);
288
0
    MOZ_ASSERT(metaNode);
289
0
290
0
    if (!metaNode->IsElement()) {
291
0
      continue;
292
0
    }
293
0
294
0
    nsAutoString currentValue;
295
0
    metaNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
296
0
                                   currentValue);
297
0
298
0
    if (!FindInReadable(NS_LITERAL_STRING("content-type"),
299
0
                        currentValue,
300
0
                        nsCaseInsensitiveStringComparator())) {
301
0
      continue;
302
0
    }
303
0
304
0
    metaNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::content,
305
0
                                   currentValue);
306
0
307
0
    NS_NAMED_LITERAL_STRING(charsetEquals, "charset=");
308
0
    nsAString::const_iterator originalStart, start, end;
309
0
    originalStart = currentValue.BeginReading(start);
310
0
    currentValue.EndReading(end);
311
0
    if (!FindInReadable(charsetEquals, start, end,
312
0
                        nsCaseInsensitiveStringComparator())) {
313
0
      continue;
314
0
    }
315
0
316
0
    // set attribute to <original prefix> charset=text/html
317
0
    RefPtr<Element> metaElement = metaNode->AsElement();
318
0
    MOZ_ASSERT(metaElement);
319
0
    nsresult rv =
320
0
      SetAttributeWithTransaction(*metaElement, *nsGkAtoms::content,
321
0
                                  Substring(originalStart, start) +
322
0
                                    charsetEquals +
323
0
                                    NS_ConvertASCIItoUTF16(aCharacterSet));
324
0
    return NS_SUCCEEDED(rv);
325
0
  }
326
0
  return false;
327
0
}
328
329
nsresult
330
TextEditor::InitRules()
331
0
{
332
0
  if (!mRules) {
333
0
    // instantiate the rules for this text editor
334
0
    mRules = new TextEditRules();
335
0
  }
336
0
  return mRules->Init(this);
337
0
}
338
339
nsresult
340
TextEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent)
341
0
{
342
0
  // NOTE: When you change this method, you should also change:
343
0
  //   * editor/libeditor/tests/test_texteditor_keyevent_handling.html
344
0
  //   * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
345
0
  //
346
0
  // And also when you add new key handling, you need to change the subclass's
347
0
  // HandleKeyPressEvent()'s switch statement.
348
0
349
0
  if (IsReadonly() || IsDisabled()) {
350
0
    // When we're not editable, the events handled on EditorBase.
351
0
    return EditorBase::HandleKeyPressEvent(aKeyboardEvent);
352
0
  }
353
0
354
0
  if (NS_WARN_IF(!aKeyboardEvent)) {
355
0
    return NS_ERROR_UNEXPECTED;
356
0
  }
357
0
  MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress,
358
0
             "HandleKeyPressEvent gets non-keypress event");
359
0
360
0
  switch (aKeyboardEvent->mKeyCode) {
361
0
    case NS_VK_META:
362
0
    case NS_VK_WIN:
363
0
    case NS_VK_SHIFT:
364
0
    case NS_VK_CONTROL:
365
0
    case NS_VK_ALT:
366
0
      // These keys are handled on EditorBase
367
0
      return EditorBase::HandleKeyPressEvent(aKeyboardEvent);
368
0
    case NS_VK_BACK:
369
0
      if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() ||
370
0
          aKeyboardEvent->IsMeta() || aKeyboardEvent->IsOS()) {
371
0
        return NS_OK;
372
0
      }
373
0
      DeleteSelectionAsAction(nsIEditor::ePrevious, nsIEditor::eStrip);
374
0
      aKeyboardEvent->PreventDefault(); // consumed
375
0
      return NS_OK;
376
0
    case NS_VK_DELETE:
377
0
      // on certain platforms (such as windows) the shift key
378
0
      // modifies what delete does (cmd_cut in this case).
379
0
      // bailing here to allow the keybindings to do the cut.
380
0
      if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() ||
381
0
          aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() ||
382
0
          aKeyboardEvent->IsOS()) {
383
0
        return NS_OK;
384
0
      }
385
0
      DeleteSelectionAsAction(nsIEditor::eNext, nsIEditor::eStrip);
386
0
      aKeyboardEvent->PreventDefault(); // consumed
387
0
      return NS_OK;
388
0
    case NS_VK_TAB: {
389
0
      if (IsTabbable()) {
390
0
        return NS_OK; // let it be used for focus switching
391
0
      }
392
0
393
0
      if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() ||
394
0
          aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() ||
395
0
          aKeyboardEvent->IsOS()) {
396
0
        return NS_OK;
397
0
      }
398
0
399
0
      // else we insert the tab straight through
400
0
      aKeyboardEvent->PreventDefault();
401
0
      return OnInputText(NS_LITERAL_STRING("\t"));
402
0
    }
403
0
    case NS_VK_RETURN:
404
0
      if (IsSingleLineEditor() || !aKeyboardEvent->IsInputtingLineBreak()) {
405
0
        return NS_OK;
406
0
      }
407
0
      aKeyboardEvent->PreventDefault();
408
0
      return OnInputParagraphSeparator();
409
0
  }
410
0
411
0
  if (!aKeyboardEvent->IsInputtingText()) {
412
0
    // we don't PreventDefault() here or keybindings like control-x won't work
413
0
    return NS_OK;
414
0
  }
415
0
  aKeyboardEvent->PreventDefault();
416
0
  nsAutoString str(aKeyboardEvent->mCharCode);
417
0
  return OnInputText(str);
418
0
}
419
420
nsresult
421
TextEditor::OnInputText(const nsAString& aStringToInsert)
422
0
{
423
0
  AutoPlaceholderBatch batch(this, nsGkAtoms::TypingTxnName);
424
0
  nsresult rv = InsertTextAsSubAction(aStringToInsert);
425
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
426
0
    return rv;
427
0
  }
428
0
  return NS_OK;
429
0
}
430
431
nsresult
432
TextEditor::OnInputParagraphSeparator()
433
0
{
434
0
  AutoPlaceholderBatch batch(this, nsGkAtoms::TypingTxnName);
435
0
  nsresult rv = InsertParagraphSeparatorAsAction();
436
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
437
0
    return rv;
438
0
  }
439
0
  return NS_OK;
440
0
}
441
442
template<typename PT, typename CT>
443
already_AddRefed<Element>
444
TextEditor::InsertBrElementWithTransaction(
445
              Selection& aSelection,
446
              const EditorDOMPointBase<PT, CT>& aPointToInsert,
447
              EDirection aSelect /* = eNone */)
448
0
{
449
0
  if (NS_WARN_IF(!aPointToInsert.IsSet())) {
450
0
    return nullptr;
451
0
  }
452
0
453
0
  // We need to insert a <br> node.
454
0
  RefPtr<Element> newBRElement;
455
0
  if (aPointToInsert.IsInTextNode()) {
456
0
    EditorDOMPoint pointInContainer;
457
0
    if (aPointToInsert.IsStartOfContainer()) {
458
0
      // Insert before the text node.
459
0
      pointInContainer.Set(aPointToInsert.GetContainer());
460
0
      if (NS_WARN_IF(!pointInContainer.IsSet())) {
461
0
        return nullptr;
462
0
      }
463
0
    } else if (aPointToInsert.IsEndOfContainer()) {
464
0
      // Insert after the text node.
465
0
      pointInContainer.Set(aPointToInsert.GetContainer());
466
0
      if (NS_WARN_IF(!pointInContainer.IsSet())) {
467
0
        return nullptr;
468
0
      }
469
0
      DebugOnly<bool> advanced = pointInContainer.AdvanceOffset();
470
0
      NS_WARNING_ASSERTION(advanced,
471
0
        "Failed to advance offset to after the text node");
472
0
    } else {
473
0
      MOZ_DIAGNOSTIC_ASSERT(aPointToInsert.IsSetAndValid());
474
0
      // Unfortunately, we need to split the text node at the offset.
475
0
      ErrorResult error;
476
0
      nsCOMPtr<nsIContent> newLeftNode =
477
0
        SplitNodeWithTransaction(aPointToInsert, error);
478
0
      if (NS_WARN_IF(error.Failed())) {
479
0
        error.SuppressException();
480
0
        return nullptr;
481
0
      }
482
0
      Unused << newLeftNode;
483
0
      // Insert new <br> before the right node.
484
0
      pointInContainer.Set(aPointToInsert.GetContainer());
485
0
    }
486
0
    // Create a <br> node.
487
0
    newBRElement = CreateNodeWithTransaction(*nsGkAtoms::br, pointInContainer);
488
0
    if (NS_WARN_IF(!newBRElement)) {
489
0
      return nullptr;
490
0
    }
491
0
  } else {
492
0
    newBRElement = CreateNodeWithTransaction(*nsGkAtoms::br, aPointToInsert);
493
0
    if (NS_WARN_IF(!newBRElement)) {
494
0
      return nullptr;
495
0
    }
496
0
  }
497
0
498
0
  switch (aSelect) {
499
0
    case eNone:
500
0
      break;
501
0
    case eNext: {
502
0
      aSelection.SetInterlinePosition(true, IgnoreErrors());
503
0
      // Collapse selection after the <br> node.
504
0
      EditorRawDOMPoint afterBRElement(newBRElement);
505
0
      if (afterBRElement.IsSet()) {
506
0
        DebugOnly<bool> advanced = afterBRElement.AdvanceOffset();
507
0
        NS_WARNING_ASSERTION(advanced,
508
0
          "Failed to advance offset after the <br> element");
509
0
        ErrorResult error;
510
0
        aSelection.Collapse(afterBRElement, error);
511
0
        NS_WARNING_ASSERTION(!error.Failed(),
512
0
          "Failed to collapse selection after the <br> element");
513
0
      } else {
514
0
        NS_WARNING("The <br> node is not in the DOM tree?");
515
0
      }
516
0
      break;
517
0
    }
518
0
    case ePrevious: {
519
0
      aSelection.SetInterlinePosition(true, IgnoreErrors());
520
0
      // Collapse selection at the <br> node.
521
0
      EditorRawDOMPoint atBRElement(newBRElement);
522
0
      if (atBRElement.IsSet()) {
523
0
        ErrorResult error;
524
0
        aSelection.Collapse(atBRElement, error);
525
0
        NS_WARNING_ASSERTION(!error.Failed(),
526
0
          "Failed to collapse selection at the <br> element");
527
0
      } else {
528
0
        NS_WARNING("The <br> node is not in the DOM tree?");
529
0
      }
530
0
      break;
531
0
    }
532
0
    default:
533
0
      NS_WARNING("aSelect has invalid value, the caller need to set selection "
534
0
                 "by itself");
535
0
      break;
536
0
  }
537
0
538
0
  return newBRElement.forget();
539
0
}
Unexecuted instantiation: already_AddRefed<mozilla::dom::Element> mozilla::TextEditor::InsertBrElementWithTransaction<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent> >(mozilla::dom::Selection&, mozilla::EditorDOMPointBase<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent> > const&, short)
Unexecuted instantiation: already_AddRefed<mozilla::dom::Element> mozilla::TextEditor::InsertBrElementWithTransaction<nsINode*, nsIContent*>(mozilla::dom::Selection&, mozilla::EditorDOMPointBase<nsINode*, nsIContent*> const&, short)
540
541
nsresult
542
TextEditor::ExtendSelectionForDelete(Selection* aSelection,
543
                                     nsIEditor::EDirection* aAction)
544
0
{
545
0
  bool bCollapsed = aSelection->IsCollapsed();
546
0
547
0
  if (*aAction == eNextWord ||
548
0
      *aAction == ePreviousWord ||
549
0
      (*aAction == eNext && bCollapsed) ||
550
0
      (*aAction == ePrevious && bCollapsed) ||
551
0
      *aAction == eToBeginningOfLine ||
552
0
      *aAction == eToEndOfLine) {
553
0
    nsCOMPtr<nsISelectionController> selCont;
554
0
    GetSelectionController(getter_AddRefs(selCont));
555
0
    NS_ENSURE_TRUE(selCont, NS_ERROR_NO_INTERFACE);
556
0
557
0
    switch (*aAction) {
558
0
      case eNextWord: {
559
0
        nsresult rv = selCont->WordExtendForDelete(true);
560
0
        // DeleteSelectionWithTransaction() doesn't handle these actions
561
0
        // because it's inside batching, so don't confuse it:
562
0
        *aAction = eNone;
563
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
564
0
          return rv;
565
0
        }
566
0
        return NS_OK;
567
0
      }
568
0
      case ePreviousWord: {
569
0
        nsresult rv = selCont->WordExtendForDelete(false);
570
0
        *aAction = eNone;
571
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
572
0
          return rv;
573
0
        }
574
0
        return NS_OK;
575
0
      }
576
0
      case eNext: {
577
0
        nsresult rv = selCont->CharacterExtendForDelete();
578
0
        // Don't set aAction to eNone (see Bug 502259)
579
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
580
0
          return rv;
581
0
        }
582
0
        return NS_OK;
583
0
      }
584
0
      case ePrevious: {
585
0
        // Only extend the selection where the selection is after a UTF-16
586
0
        // surrogate pair or a variation selector.
587
0
        // For other cases we don't want to do that, in order
588
0
        // to make sure that pressing backspace will only delete the last
589
0
        // typed character.
590
0
        EditorRawDOMPoint atStartOfSelection =
591
0
          EditorBase::GetStartPoint(aSelection);
592
0
        if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
593
0
          return NS_ERROR_FAILURE;
594
0
        }
595
0
596
0
        // node might be anonymous DIV, so we find better text node
597
0
        EditorRawDOMPoint insertionPoint =
598
0
          FindBetterInsertionPoint(atStartOfSelection);
599
0
600
0
        if (insertionPoint.IsInTextNode()) {
601
0
          const nsTextFragment* data =
602
0
            insertionPoint.GetContainerAsText()->GetText();
603
0
          uint32_t offset = insertionPoint.Offset();
604
0
          if ((offset > 1 &&
605
0
               NS_IS_LOW_SURROGATE(data->CharAt(offset - 1)) &&
606
0
               NS_IS_HIGH_SURROGATE(data->CharAt(offset - 2))) ||
607
0
              (offset > 0 &&
608
0
               gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) {
609
0
            nsresult rv = selCont->CharacterExtendForBackspace();
610
0
            if (NS_WARN_IF(NS_FAILED(rv))) {
611
0
              return rv;
612
0
            }
613
0
          }
614
0
        }
615
0
        return NS_OK;
616
0
      }
617
0
      case eToBeginningOfLine: {
618
0
        // Select to beginning
619
0
        nsresult rv = selCont->IntraLineMove(false, true);
620
0
        *aAction = eNone;
621
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
622
0
          return rv;
623
0
        }
624
0
        return NS_OK;
625
0
      }
626
0
      case eToEndOfLine: {
627
0
        nsresult rv = selCont->IntraLineMove(true, true);
628
0
        *aAction = eNext;
629
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
630
0
          return rv;
631
0
        }
632
0
        return NS_OK;
633
0
      }
634
0
      // For avoiding several compiler warnings
635
0
      default:
636
0
        return NS_OK;
637
0
    }
638
0
  }
639
0
  return NS_OK;
640
0
}
641
642
NS_IMETHODIMP
643
TextEditor::DeleteSelection(EDirection aAction,
644
                            EStripWrappers aStripWrappers)
645
0
{
646
0
  nsresult rv = DeleteSelectionAsAction(aAction, aStripWrappers);
647
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
648
0
    return rv;
649
0
  }
650
0
  return NS_OK;
651
0
}
652
653
nsresult
654
TextEditor::DeleteSelectionAsAction(EDirection aDirection,
655
                                    EStripWrappers aStripWrappers)
656
0
{
657
0
  MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
658
0
  // Showing this assertion is fine if this method is called by outside via
659
0
  // mutation event listener or something.  Otherwise, this is called by
660
0
  // wrong method.
661
0
  NS_ASSERTION(!mPlaceholderBatch,
662
0
    "Should be called only when this is the only edit action of the operation "
663
0
    "unless mutation event listener nests some operations");
664
0
665
0
  // delete placeholder txns merge.
666
0
  AutoPlaceholderBatch batch(this, nsGkAtoms::DeleteTxnName);
667
0
  nsresult rv = DeleteSelectionAsSubAction(aDirection, aStripWrappers);
668
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
669
0
    return rv;
670
0
  }
671
0
  return NS_OK;
672
0
}
673
674
nsresult
675
TextEditor::DeleteSelectionAsSubAction(EDirection aDirection,
676
                                       EStripWrappers aStripWrappers)
677
0
{
678
0
  MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
679
0
  MOZ_ASSERT(mPlaceholderBatch);
680
0
681
0
  if (!mRules) {
682
0
    return NS_ERROR_NOT_INITIALIZED;
683
0
  }
684
0
685
0
  // Protect the edit rules object from dying
686
0
  RefPtr<TextEditRules> rules(mRules);
687
0
688
0
  // pre-process
689
0
  RefPtr<Selection> selection = GetSelection();
690
0
  if (NS_WARN_IF(!selection)) {
691
0
    return NS_ERROR_FAILURE;
692
0
  }
693
0
694
0
  // If there is an existing selection when an extended delete is requested,
695
0
  //  platforms that use "caret-style" caret positioning collapse the
696
0
  //  selection to the  start and then create a new selection.
697
0
  //  Platforms that use "selection-style" caret positioning just delete the
698
0
  //  existing selection without extending it.
699
0
  if (!selection->IsCollapsed()) {
700
0
    switch (aDirection) {
701
0
      case eNextWord:
702
0
      case ePreviousWord:
703
0
      case eToBeginningOfLine:
704
0
      case eToEndOfLine: {
705
0
        if (mCaretStyle != 1) {
706
0
          aDirection = eNone;
707
0
          break;
708
0
        }
709
0
        ErrorResult error;
710
0
        selection->CollapseToStart(error);
711
0
        if (NS_WARN_IF(error.Failed())) {
712
0
          return error.StealNSResult();
713
0
        }
714
0
        break;
715
0
      }
716
0
      default:
717
0
        break;
718
0
    }
719
0
  }
720
0
721
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
722
0
                                      *this,
723
0
                                      EditSubAction::eDeleteSelectedContent,
724
0
                                      aDirection);
725
0
  EditSubActionInfo subActionInfo(EditSubAction::eDeleteSelectedContent);
726
0
  subActionInfo.collapsedAction = aDirection;
727
0
  subActionInfo.stripWrappers = aStripWrappers;
728
0
  bool cancel, handled;
729
0
  nsresult rv =
730
0
    rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
731
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
732
0
    return rv;
733
0
  }
734
0
  if (!cancel && !handled) {
735
0
    rv = DeleteSelectionWithTransaction(aDirection, aStripWrappers);
736
0
  }
737
0
  if (!cancel) {
738
0
    // post-process
739
0
    rv = rules->DidDoAction(selection, subActionInfo, rv);
740
0
  }
741
0
  return rv;
742
0
}
743
744
nsresult
745
TextEditor::DeleteSelectionWithTransaction(EDirection aDirection,
746
                                           EStripWrappers aStripWrappers)
747
0
{
748
0
  MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
749
0
750
0
  RefPtr<Selection> selection = GetSelection();
751
0
  if (NS_WARN_IF(!selection)) {
752
0
    return NS_ERROR_NOT_INITIALIZED;
753
0
  }
754
0
755
0
  RefPtr<EditAggregateTransaction> deleteSelectionTransaction;
756
0
  nsCOMPtr<nsINode> deleteNode;
757
0
  int32_t deleteCharOffset = 0, deleteCharLength = 0;
758
0
  if (!selection->IsCollapsed() || aDirection != eNone) {
759
0
    deleteSelectionTransaction =
760
0
      CreateTxnForDeleteSelection(aDirection,
761
0
                                  getter_AddRefs(deleteNode),
762
0
                                  &deleteCharOffset,
763
0
                                  &deleteCharLength);
764
0
    if (NS_WARN_IF(!deleteSelectionTransaction)) {
765
0
      return NS_ERROR_FAILURE;
766
0
    }
767
0
  }
768
0
769
0
  RefPtr<CharacterData> deleteCharData =
770
0
    CharacterData::FromNodeOrNull(deleteNode);
771
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
772
0
                                      *this,
773
0
                                      EditSubAction::eDeleteSelectedContent,
774
0
                                      aDirection);
775
0
776
0
  if (mRules && mRules->AsHTMLEditRules()) {
777
0
    if (!deleteNode) {
778
0
      RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules();
779
0
      htmlEditRules->WillDeleteSelection(*selection);
780
0
    } else if (!deleteCharData) {
781
0
      RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules();
782
0
      htmlEditRules->WillDeleteNode(*selection, *deleteNode);
783
0
    }
784
0
  }
785
0
786
0
  // Notify nsIEditActionListener::WillDelete[Selection|Text]
787
0
  if (!mActionListeners.IsEmpty()) {
788
0
    if (!deleteNode) {
789
0
      AutoActionListenerArray listeners(mActionListeners);
790
0
      for (auto& listener : listeners) {
791
0
        listener->WillDeleteSelection(selection);
792
0
      }
793
0
    } else if (deleteCharData) {
794
0
      AutoActionListenerArray listeners(mActionListeners);
795
0
      for (auto& listener : listeners) {
796
0
        listener->WillDeleteText(deleteCharData, deleteCharOffset, 1);
797
0
      }
798
0
    }
799
0
  }
800
0
801
0
  // Delete the specified amount
802
0
  nsresult rv = DoTransaction(deleteSelectionTransaction);
803
0
804
0
  if (mRules && mRules->AsHTMLEditRules() && deleteCharData) {
805
0
    MOZ_ASSERT(deleteNode);
806
0
    RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules();
807
0
    htmlEditRules->DidDeleteText(*selection, *deleteNode, deleteCharOffset, 1);
808
0
  }
809
0
810
0
  if (mTextServicesDocument && NS_SUCCEEDED(rv) &&
811
0
      deleteNode && !deleteCharData) {
812
0
    RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
813
0
    textServicesDocument->DidDeleteNode(deleteNode);
814
0
  }
815
0
816
0
  // Notify nsIEditActionListener::DidDelete[Selection|Text|Node]
817
0
  {
818
0
    AutoActionListenerArray listeners(mActionListeners);
819
0
    if (!deleteNode) {
820
0
      for (auto& listener : mActionListeners) {
821
0
        listener->DidDeleteSelection(selection);
822
0
      }
823
0
    } else if (deleteCharData) {
824
0
      for (auto& listener : mActionListeners) {
825
0
        listener->DidDeleteText(deleteCharData, deleteCharOffset, 1, rv);
826
0
      }
827
0
    } else {
828
0
      for (auto& listener : mActionListeners) {
829
0
        listener->DidDeleteNode(deleteNode, rv);
830
0
      }
831
0
    }
832
0
  }
833
0
834
0
  return rv;
835
0
}
836
837
already_AddRefed<Element>
838
TextEditor::DeleteSelectionAndCreateElement(nsAtom& aTag)
839
0
{
840
0
  nsresult rv = DeleteSelectionAndPrepareToCreateNode();
841
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
842
0
    return nullptr;
843
0
  }
844
0
845
0
  RefPtr<Selection> selection = GetSelection();
846
0
  if (NS_WARN_IF(!selection)) {
847
0
    return nullptr;
848
0
  }
849
0
850
0
  EditorRawDOMPoint pointToInsert(selection->AnchorRef());
851
0
  if (!pointToInsert.IsSet()) {
852
0
    return nullptr;
853
0
  }
854
0
  RefPtr<Element> newElement = CreateNodeWithTransaction(aTag, pointToInsert);
855
0
856
0
  // We want the selection to be just after the new node
857
0
  EditorRawDOMPoint afterNewElement(newElement);
858
0
  MOZ_ASSERT(afterNewElement.IsSetAndValid());
859
0
  DebugOnly<bool> advanced = afterNewElement.AdvanceOffset();
860
0
  NS_WARNING_ASSERTION(advanced,
861
0
                       "Failed to move offset next to the new element");
862
0
  ErrorResult error;
863
0
  selection->Collapse(afterNewElement, error);
864
0
  if (NS_WARN_IF(error.Failed())) {
865
0
    // XXX Even if it succeeded to create new element, this returns error
866
0
    //     when Selection.Collapse() fails something.  This could occur with
867
0
    //     mutation observer or mutation event listener.
868
0
    error.SuppressException();
869
0
    return nullptr;
870
0
  }
871
0
  return newElement.forget();
872
0
}
873
874
nsresult
875
TextEditor::DeleteSelectionAndPrepareToCreateNode()
876
0
{
877
0
  RefPtr<Selection> selection = GetSelection();
878
0
  if (NS_WARN_IF(!selection)) {
879
0
    return NS_ERROR_NOT_INITIALIZED;
880
0
  }
881
0
882
0
  if (NS_WARN_IF(!selection->GetAnchorFocusRange())) {
883
0
    return NS_OK;
884
0
  }
885
0
886
0
  if (!selection->GetAnchorFocusRange()->Collapsed()) {
887
0
    nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
888
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
889
0
      return rv;
890
0
    }
891
0
    MOZ_ASSERT(selection->GetAnchorFocusRange() &&
892
0
               selection->GetAnchorFocusRange()->Collapsed(),
893
0
               "Selection not collapsed after delete");
894
0
  }
895
0
896
0
  // If the selection is a chardata node, split it if necessary and compute
897
0
  // where to put the new node
898
0
  EditorDOMPoint atAnchor(selection->AnchorRef());
899
0
  if (NS_WARN_IF(!atAnchor.IsSet()) || !atAnchor.IsInDataNode()) {
900
0
    return NS_OK;
901
0
  }
902
0
903
0
  if (NS_WARN_IF(!atAnchor.GetContainer()->GetParentNode())) {
904
0
    return NS_ERROR_FAILURE;
905
0
  }
906
0
907
0
  if (atAnchor.IsStartOfContainer()) {
908
0
    EditorRawDOMPoint atAnchorContainer(atAnchor.GetContainer());
909
0
    if (NS_WARN_IF(!atAnchorContainer.IsSetAndValid())) {
910
0
      return NS_ERROR_FAILURE;
911
0
    }
912
0
    ErrorResult error;
913
0
    selection->Collapse(atAnchorContainer, error);
914
0
    if (NS_WARN_IF(error.Failed())) {
915
0
      return error.StealNSResult();
916
0
    }
917
0
    return NS_OK;
918
0
  }
919
0
920
0
  if (atAnchor.IsEndOfContainer()) {
921
0
    EditorRawDOMPoint afterAnchorContainer(atAnchor.GetContainer());
922
0
    if (NS_WARN_IF(!afterAnchorContainer.AdvanceOffset())) {
923
0
      return NS_ERROR_FAILURE;
924
0
    }
925
0
    ErrorResult error;
926
0
    selection->Collapse(afterAnchorContainer, error);
927
0
    if (NS_WARN_IF(error.Failed())) {
928
0
      return error.StealNSResult();
929
0
    }
930
0
    return NS_OK;
931
0
  }
932
0
933
0
  ErrorResult error;
934
0
  nsCOMPtr<nsIContent> newLeftNode = SplitNodeWithTransaction(atAnchor, error);
935
0
  if (NS_WARN_IF(error.Failed())) {
936
0
    return error.StealNSResult();
937
0
  }
938
0
939
0
  EditorRawDOMPoint atRightNode(atAnchor.GetContainer());
940
0
  if (NS_WARN_IF(!atRightNode.IsSet())) {
941
0
    return NS_ERROR_FAILURE;
942
0
  }
943
0
  MOZ_ASSERT(atRightNode.IsSetAndValid());
944
0
  selection->Collapse(atRightNode, error);
945
0
  if (NS_WARN_IF(error.Failed())) {
946
0
    return error.StealNSResult();
947
0
  }
948
0
  return NS_OK;
949
0
}
950
951
NS_IMETHODIMP
952
TextEditor::InsertText(const nsAString& aStringToInsert)
953
0
{
954
0
  nsresult rv = InsertTextAsAction(aStringToInsert);
955
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
956
0
    return rv;
957
0
  }
958
0
  return NS_OK;
959
0
}
960
961
nsresult
962
TextEditor::InsertTextAsAction(const nsAString& aStringToInsert)
963
0
{
964
0
  // Showing this assertion is fine if this method is called by outside via
965
0
  // mutation event listener or something.  Otherwise, this is called by
966
0
  // wrong method.
967
0
  NS_ASSERTION(!mPlaceholderBatch,
968
0
    "Should be called only when this is the only edit action of the operation "
969
0
    "unless mutation event listener nests some operations");
970
0
971
0
  AutoPlaceholderBatch batch(this, nullptr);
972
0
  nsresult rv = InsertTextAsSubAction(aStringToInsert);
973
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
974
0
    return rv;
975
0
  }
976
0
  return NS_OK;
977
0
}
978
979
nsresult
980
TextEditor::InsertTextAsSubAction(const nsAString& aStringToInsert)
981
0
{
982
0
  MOZ_ASSERT(mPlaceholderBatch);
983
0
984
0
  if (!mRules) {
985
0
    return NS_ERROR_NOT_INITIALIZED;
986
0
  }
987
0
988
0
  // Protect the edit rules object from dying.
989
0
  RefPtr<TextEditRules> rules(mRules);
990
0
991
0
  EditSubAction editSubAction =
992
0
    ShouldHandleIMEComposition() ?
993
0
      EditSubAction::eInsertTextComingFromIME : EditSubAction::eInsertText;
994
0
995
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
996
0
                                      *this, editSubAction, nsIEditor::eNext);
997
0
998
0
  RefPtr<Selection> selection = GetSelection();
999
0
  if (NS_WARN_IF(!selection)) {
1000
0
    return NS_ERROR_FAILURE;
1001
0
  }
1002
0
1003
0
  nsAutoString resultString;
1004
0
  // XXX can we trust instring to outlive subActionInfo,
1005
0
  // XXX and subActionInfo not to refer to instring in its dtor?
1006
0
  //nsAutoString instring(aStringToInsert);
1007
0
  EditSubActionInfo subActionInfo(editSubAction);
1008
0
  subActionInfo.inString = &aStringToInsert;
1009
0
  subActionInfo.outString = &resultString;
1010
0
  subActionInfo.maxLength = mMaxTextLength;
1011
0
1012
0
  bool cancel, handled;
1013
0
  nsresult rv =
1014
0
    rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
1015
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1016
0
    return rv;
1017
0
  }
1018
0
  if (!cancel && !handled) {
1019
0
    // we rely on rules code for now - no default implementation
1020
0
  }
1021
0
  if (cancel) {
1022
0
    return NS_OK;
1023
0
  }
1024
0
  // post-process
1025
0
  rv = rules->DidDoAction(selection, subActionInfo, NS_OK);
1026
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1027
0
    return rv;
1028
0
  }
1029
0
  return NS_OK;
1030
0
}
1031
1032
NS_IMETHODIMP
1033
TextEditor::InsertLineBreak()
1034
0
{
1035
0
  return InsertParagraphSeparatorAsAction();
1036
0
}
1037
1038
nsresult
1039
TextEditor::InsertParagraphSeparatorAsAction()
1040
0
{
1041
0
  if (!mRules) {
1042
0
    return NS_ERROR_NOT_INITIALIZED;
1043
0
  }
1044
0
1045
0
  // Protect the edit rules object from dying
1046
0
  RefPtr<TextEditRules> rules(mRules);
1047
0
1048
0
  AutoPlaceholderBatch beginBatching(this);
1049
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1050
0
                                      *this,
1051
0
                                      EditSubAction::eInsertParagraphSeparator,
1052
0
                                      nsIEditor::eNext);
1053
0
1054
0
  RefPtr<Selection> selection = GetSelection();
1055
0
  if (NS_WARN_IF(!selection)) {
1056
0
    return NS_ERROR_FAILURE;
1057
0
  }
1058
0
1059
0
  EditSubActionInfo subActionInfo(EditSubAction::eInsertParagraphSeparator);
1060
0
  subActionInfo.maxLength = mMaxTextLength;
1061
0
  bool cancel, handled;
1062
0
  nsresult rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
1063
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1064
0
    // XXX DidDoAction() won't be called when WillDoAction() returns error.
1065
0
    //     Perhaps, we should move the code between WillDoAction() and
1066
0
    //     DidDoAction() to a new method and guarantee that DidDoAction() is
1067
0
    //     always called after WillDoAction().
1068
0
    return rv;
1069
0
  }
1070
0
1071
0
  if (!cancel && !handled) {
1072
0
    // get the (collapsed) selection location
1073
0
    nsRange* firstRange = selection->GetRangeAt(0);
1074
0
    if (NS_WARN_IF(!firstRange)) {
1075
0
      return NS_ERROR_FAILURE;
1076
0
    }
1077
0
1078
0
    EditorRawDOMPoint pointToInsert(firstRange->StartRef());
1079
0
    if (NS_WARN_IF(!pointToInsert.IsSet())) {
1080
0
      return NS_ERROR_FAILURE;
1081
0
    }
1082
0
    MOZ_ASSERT(pointToInsert.IsSetAndValid());
1083
0
1084
0
    // don't put text in places that can't have it
1085
0
    if (!pointToInsert.IsInTextNode() &&
1086
0
        !CanContainTag(*pointToInsert.GetContainer(),
1087
0
                       *nsGkAtoms::textTagName)) {
1088
0
      return NS_ERROR_FAILURE;
1089
0
    }
1090
0
1091
0
    // we need to get the doc
1092
0
    nsCOMPtr<nsIDocument> doc = GetDocument();
1093
0
    if (NS_WARN_IF(!doc)) {
1094
0
      return NS_ERROR_NOT_INITIALIZED;
1095
0
    }
1096
0
1097
0
    // don't change my selection in subtransactions
1098
0
    AutoTransactionsConserveSelection dontChangeMySelection(*this);
1099
0
1100
0
    // insert a linefeed character
1101
0
    EditorRawDOMPoint pointAfterInsertedLineBreak;
1102
0
    rv = InsertTextWithTransaction(*doc, NS_LITERAL_STRING("\n"), pointToInsert,
1103
0
                                   &pointAfterInsertedLineBreak);
1104
0
    if (NS_WARN_IF(!pointAfterInsertedLineBreak.IsSet())) {
1105
0
      rv = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called
1106
0
    }
1107
0
    if (NS_SUCCEEDED(rv)) {
1108
0
      // set the selection to the correct location
1109
0
      MOZ_ASSERT(!pointAfterInsertedLineBreak.GetChild(),
1110
0
        "After inserting text into a text node, pointAfterInsertedLineBreak."
1111
0
        "GetChild() should be nullptr");
1112
0
      rv = selection->Collapse(pointAfterInsertedLineBreak);
1113
0
      if (NS_SUCCEEDED(rv)) {
1114
0
        // see if we're at the end of the editor range
1115
0
        EditorRawDOMPoint endPoint = GetEndPoint(selection);
1116
0
        if (endPoint == pointAfterInsertedLineBreak) {
1117
0
          // SetInterlinePosition(true) means we want the caret to stick to the
1118
0
          // content on the "right".  We want the caret to stick to whatever is
1119
0
          // past the break.  This is because the break is on the same line we
1120
0
          // were on, but the next content will be on the following line.
1121
0
          selection->SetInterlinePosition(true, IgnoreErrors());
1122
0
        }
1123
0
      }
1124
0
    }
1125
0
  }
1126
0
1127
0
  if (!cancel) {
1128
0
    // post-process, always called if WillInsertBreak didn't return cancel==true
1129
0
    rv = rules->DidDoAction(selection, subActionInfo, rv);
1130
0
  }
1131
0
  return rv;
1132
0
}
1133
1134
nsresult
1135
TextEditor::SetText(const nsAString& aString)
1136
0
{
1137
0
  MOZ_ASSERT(aString.FindChar(static_cast<char16_t>('\r')) == kNotFound);
1138
0
1139
0
  AutoPlaceholderBatch batch(this, nullptr);
1140
0
  nsresult rv = SetTextAsSubAction(aString);
1141
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1142
0
    return rv;
1143
0
  }
1144
0
  return NS_OK;
1145
0
}
1146
1147
nsresult
1148
TextEditor::ReplaceTextAsAction(const nsAString& aString,
1149
                                nsRange* aReplaceRange /* = nullptr */)
1150
0
{
1151
0
  AutoPlaceholderBatch batch(this, nullptr);
1152
0
1153
0
  // This should emulates inserting text for better undo/redo behavior.
1154
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1155
0
                                      *this, EditSubAction::eInsertText,
1156
0
                                      nsIEditor::eNext);
1157
0
1158
0
  if (!aReplaceRange) {
1159
0
    nsresult rv = SetTextAsSubAction(aString);
1160
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1161
0
      return rv;
1162
0
    }
1163
0
    return NS_OK;
1164
0
  }
1165
0
1166
0
  if (NS_WARN_IF(aString.IsEmpty() && aReplaceRange->Collapsed())) {
1167
0
    return NS_OK;
1168
0
  }
1169
0
1170
0
  // Note that do not notify selectionchange caused by selecting all text
1171
0
  // because it's preparation of our delete implementation so web apps
1172
0
  // shouldn't receive such selectionchange before the first mutation.
1173
0
  AutoUpdateViewBatch preventSelectionChangeEvent(this);
1174
0
1175
0
  RefPtr<Selection> selection = GetSelection();
1176
0
  if (NS_WARN_IF(!selection)) {
1177
0
    return NS_ERROR_FAILURE;
1178
0
  }
1179
0
1180
0
  // Select the range but as far as possible, we should not create new range
1181
0
  // even if it's part of special Selection.
1182
0
  nsresult rv = selection->RemoveAllRangesTemporarily();
1183
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1184
0
    return rv;
1185
0
  }
1186
0
  ErrorResult error;
1187
0
  selection->AddRange(*aReplaceRange, error);
1188
0
  if (NS_WARN_IF(error.Failed())) {
1189
0
    return error.StealNSResult();
1190
0
  }
1191
0
1192
0
  rv = ReplaceSelectionAsSubAction(aString);
1193
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1194
0
    return rv;
1195
0
  }
1196
0
  return NS_OK;
1197
0
}
1198
1199
nsresult
1200
TextEditor::SetTextAsSubAction(const nsAString& aString)
1201
0
{
1202
0
  MOZ_ASSERT(mPlaceholderBatch);
1203
0
1204
0
  if (NS_WARN_IF(!mRules)) {
1205
0
    return NS_ERROR_NOT_INITIALIZED;
1206
0
  }
1207
0
1208
0
  // Protect the edit rules object from dying
1209
0
  RefPtr<TextEditRules> rules(mRules);
1210
0
1211
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1212
0
                                      *this, EditSubAction::eSetText,
1213
0
                                      nsIEditor::eNext);
1214
0
1215
0
  // pre-process
1216
0
  RefPtr<Selection> selection = GetSelection();
1217
0
  if (NS_WARN_IF(!selection)) {
1218
0
    return NS_ERROR_NULL_POINTER;
1219
0
  }
1220
0
  EditSubActionInfo subActionInfo(EditSubAction::eSetText);
1221
0
  subActionInfo.inString = &aString;
1222
0
  subActionInfo.maxLength = mMaxTextLength;
1223
0
1224
0
  bool cancel;
1225
0
  bool handled;
1226
0
  nsresult rv =
1227
0
    rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
1228
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1229
0
    return rv;
1230
0
  }
1231
0
  if (cancel) {
1232
0
    return NS_OK;
1233
0
  }
1234
0
  if (!handled) {
1235
0
    // Note that do not notify selectionchange caused by selecting all text
1236
0
    // because it's preparation of our delete implementation so web apps
1237
0
    // shouldn't receive such selectionchange before the first mutation.
1238
0
    AutoUpdateViewBatch preventSelectionChangeEvent(this);
1239
0
1240
0
    // We want to select trailing BR node to remove all nodes to replace all,
1241
0
    // but TextEditor::SelectEntireDocument doesn't select that BR node.
1242
0
    if (rules->DocumentIsEmpty()) {
1243
0
      // if it's empty, don't select entire doc - that would select
1244
0
      // the bogus node
1245
0
      Element* rootElement = GetRoot();
1246
0
      if (NS_WARN_IF(!rootElement)) {
1247
0
        return NS_ERROR_FAILURE;
1248
0
      }
1249
0
      rv = selection->Collapse(rootElement, 0);
1250
0
    } else {
1251
0
      rv = EditorBase::SelectEntireDocument(selection);
1252
0
    }
1253
0
    if (NS_SUCCEEDED(rv)) {
1254
0
      rv = ReplaceSelectionAsSubAction(aString);
1255
0
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1256
0
        "Failed to replace selection with new string");
1257
0
    }
1258
0
  }
1259
0
  // post-process
1260
0
  return rules->DidDoAction(selection, subActionInfo, rv);
1261
0
}
1262
1263
nsresult
1264
TextEditor::ReplaceSelectionAsSubAction(const nsAString& aString)
1265
0
{
1266
0
  if (aString.IsEmpty()) {
1267
0
    nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
1268
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1269
0
      return rv;
1270
0
    }
1271
0
    return NS_OK;
1272
0
  }
1273
0
1274
0
  nsresult rv = InsertTextAsSubAction(aString);
1275
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1276
0
    return rv;
1277
0
  }
1278
0
  return NS_OK;
1279
0
}
1280
1281
bool
1282
TextEditor::EnsureComposition(WidgetCompositionEvent& aCompositionEvent)
1283
0
{
1284
0
  if (mComposition) {
1285
0
    return true;
1286
0
  }
1287
0
  // The compositionstart event must cause creating new TextComposition
1288
0
  // instance at being dispatched by IMEStateManager.
1289
0
  mComposition = IMEStateManager::GetTextCompositionFor(&aCompositionEvent);
1290
0
  if (!mComposition) {
1291
0
    // However, TextComposition may be committed before the composition
1292
0
    // event comes here.
1293
0
    return false;
1294
0
  }
1295
0
  mComposition->StartHandlingComposition(this);
1296
0
  return true;
1297
0
}
1298
1299
nsresult
1300
TextEditor::OnCompositionStart(WidgetCompositionEvent& aCompositionStartEvent)
1301
0
{
1302
0
  if (NS_WARN_IF(mComposition)) {
1303
0
    return NS_OK;
1304
0
  }
1305
0
1306
0
  if (IsPasswordEditor()) {
1307
0
    if (NS_WARN_IF(!mRules)) {
1308
0
      return NS_ERROR_FAILURE;
1309
0
    }
1310
0
    // Protect the edit rules object from dying
1311
0
    RefPtr<TextEditRules> rules(mRules);
1312
0
    rules->ResetIMETextPWBuf();
1313
0
  }
1314
0
1315
0
  EnsureComposition(aCompositionStartEvent);
1316
0
  NS_WARNING_ASSERTION(mComposition, "Failed to get TextComposition instance?");
1317
0
  return NS_OK;
1318
0
}
1319
1320
nsresult
1321
TextEditor::OnCompositionChange(WidgetCompositionEvent& aCompsitionChangeEvent)
1322
0
{
1323
0
  MOZ_ASSERT(aCompsitionChangeEvent.mMessage == eCompositionChange,
1324
0
             "The event should be eCompositionChange");
1325
0
1326
0
  if (!EnsureComposition(aCompsitionChangeEvent)) {
1327
0
    return NS_OK;
1328
0
  }
1329
0
1330
0
  nsIPresShell* presShell = GetPresShell();
1331
0
  if (NS_WARN_IF(!presShell)) {
1332
0
    return NS_ERROR_NOT_INITIALIZED;
1333
0
  }
1334
0
1335
0
  RefPtr<Selection> selection = GetSelection();
1336
0
  if (NS_WARN_IF(!selection)) {
1337
0
    return NS_ERROR_FAILURE;
1338
0
  }
1339
0
1340
0
  // NOTE: TextComposition should receive selection change notification before
1341
0
  //       CompositionChangeEventHandlingMarker notifies TextComposition of the
1342
0
  //       end of handling compositionchange event because TextComposition may
1343
0
  //       need to ignore selection changes caused by composition.  Therefore,
1344
0
  //       CompositionChangeEventHandlingMarker must be destroyed after a call
1345
0
  //       of NotifiyEditorObservers(eNotifyEditorObserversOfEnd) or
1346
0
  //       NotifiyEditorObservers(eNotifyEditorObserversOfCancel) which notifies
1347
0
  //       TextComposition of a selection change.
1348
0
  MOZ_ASSERT(!mPlaceholderBatch,
1349
0
    "UpdateIMEComposition() must be called without place holder batch");
1350
0
  TextComposition::CompositionChangeEventHandlingMarker
1351
0
    compositionChangeEventHandlingMarker(mComposition, &aCompsitionChangeEvent);
1352
0
1353
0
  RefPtr<nsCaret> caretP = presShell->GetCaret();
1354
0
1355
0
  nsresult rv;
1356
0
  {
1357
0
    AutoPlaceholderBatch batch(this, nsGkAtoms::IMETxnName);
1358
0
1359
0
    MOZ_ASSERT(mIsInEditSubAction,
1360
0
      "AutoPlaceholderBatch should've notified the observes of before-edit");
1361
0
    rv = InsertTextAsSubAction(aCompsitionChangeEvent.mData);
1362
0
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1363
0
      "Failed to insert new composition string");
1364
0
1365
0
    if (caretP) {
1366
0
      caretP->SetSelection(selection);
1367
0
    }
1368
0
  }
1369
0
1370
0
  // If still composing, we should fire input event via observer.
1371
0
  // Note that if the composition will be committed by the following
1372
0
  // compositionend event, we don't need to notify editor observes of this
1373
0
  // change.
1374
0
  // NOTE: We must notify after the auto batch will be gone.
1375
0
  if (!aCompsitionChangeEvent.IsFollowedByCompositionEnd()) {
1376
0
    NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1377
0
  }
1378
0
1379
0
  return rv;
1380
0
}
1381
1382
void
1383
TextEditor::OnCompositionEnd(WidgetCompositionEvent& aCompositionEndEvent)
1384
0
{
1385
0
  if (NS_WARN_IF(!mComposition)) {
1386
0
    return;
1387
0
  }
1388
0
1389
0
  // commit the IME transaction..we can get at it via the transaction mgr.
1390
0
  // Note that this means IME won't work without an undo stack!
1391
0
  if (mTransactionManager) {
1392
0
    nsCOMPtr<nsITransaction> txn = mTransactionManager->PeekUndoStack();
1393
0
    nsCOMPtr<nsIAbsorbingTransaction> plcTxn = do_QueryInterface(txn);
1394
0
    if (plcTxn) {
1395
0
      DebugOnly<nsresult> rv = plcTxn->Commit();
1396
0
      NS_ASSERTION(NS_SUCCEEDED(rv),
1397
0
                   "nsIAbsorbingTransaction::Commit() failed");
1398
0
    }
1399
0
  }
1400
0
1401
0
  // Composition string may have hidden the caret.  Therefore, we need to
1402
0
  // cancel it here.
1403
0
  HideCaret(false);
1404
0
1405
0
  // FYI: mComposition still keeps storing container text node of committed
1406
0
  //      string, its offset and length.  However, they will be invalidated
1407
0
  //      soon since its Destroy() will be called by IMEStateManager.
1408
0
  mComposition->EndHandlingComposition(this);
1409
0
  mComposition = nullptr;
1410
0
1411
0
  // notify editor observers of action
1412
0
  NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1413
0
}
1414
1415
already_AddRefed<nsIContent>
1416
TextEditor::GetInputEventTargetContent()
1417
0
{
1418
0
  nsCOMPtr<nsIContent> target = do_QueryInterface(mEventTarget);
1419
0
  return target.forget();
1420
0
}
1421
1422
nsresult
1423
TextEditor::IsEmpty(bool* aIsEmpty) const
1424
0
{
1425
0
  if (NS_WARN_IF(!mRules)) {
1426
0
    return NS_ERROR_NOT_INITIALIZED;
1427
0
  }
1428
0
1429
0
  *aIsEmpty = true;
1430
0
1431
0
  if (mRules->HasBogusNode()) {
1432
0
    return NS_OK;
1433
0
  }
1434
0
1435
0
  // Even if there is no bogus node, we should be detected as empty editor
1436
0
  // if all the children are text nodes and these have no content.
1437
0
  Element* rootElement = GetRoot();
1438
0
  if (!rootElement) {
1439
0
    // XXX Why don't we return an error in such case??
1440
0
    return NS_OK;
1441
0
  }
1442
0
1443
0
  for (nsIContent* child = rootElement->GetFirstChild();
1444
0
       child; child = child->GetNextSibling()) {
1445
0
    if (!EditorBase::IsTextNode(child) ||
1446
0
        static_cast<nsTextNode*>(child)->TextDataLength()) {
1447
0
      *aIsEmpty = false;
1448
0
      return NS_OK;
1449
0
    }
1450
0
  }
1451
0
1452
0
  return NS_OK;
1453
0
}
1454
1455
NS_IMETHODIMP
1456
TextEditor::GetDocumentIsEmpty(bool* aDocumentIsEmpty)
1457
0
{
1458
0
  nsresult rv = IsEmpty(aDocumentIsEmpty);
1459
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1460
0
    return rv;
1461
0
  }
1462
0
  return NS_OK;
1463
0
}
1464
1465
NS_IMETHODIMP
1466
TextEditor::GetTextLength(int32_t* aCount)
1467
0
{
1468
0
  MOZ_ASSERT(aCount);
1469
0
1470
0
  // initialize out params
1471
0
  *aCount = 0;
1472
0
1473
0
  // special-case for empty document, to account for the bogus node
1474
0
  bool isEmpty = false;
1475
0
  nsresult rv = IsEmpty(&isEmpty);
1476
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1477
0
    return rv;
1478
0
  }
1479
0
  if (isEmpty) {
1480
0
    return NS_OK;
1481
0
  }
1482
0
1483
0
  Element* rootElement = GetRoot();
1484
0
  if (NS_WARN_IF(!rootElement)) {
1485
0
    return NS_ERROR_FAILURE;
1486
0
  }
1487
0
1488
0
  nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
1489
0
1490
0
  uint32_t totalLength = 0;
1491
0
  iter->Init(rootElement);
1492
0
  for (; !iter->IsDone(); iter->Next()) {
1493
0
    nsCOMPtr<nsINode> currentNode = iter->GetCurrentNode();
1494
0
    if (IsTextNode(currentNode) && IsEditable(currentNode)) {
1495
0
      totalLength += currentNode->Length();
1496
0
    }
1497
0
  }
1498
0
1499
0
  *aCount = totalLength;
1500
0
  return NS_OK;
1501
0
}
1502
1503
NS_IMETHODIMP
1504
TextEditor::GetWrapWidth(int32_t* aWrapColumn)
1505
0
{
1506
0
  if (NS_WARN_IF(!aWrapColumn)) {
1507
0
    return NS_ERROR_INVALID_ARG;
1508
0
  }
1509
0
  *aWrapColumn = WrapWidth();
1510
0
  return NS_OK;
1511
0
}
1512
1513
//
1514
// See if the style value includes this attribute, and if it does,
1515
// cut out everything from the attribute to the next semicolon.
1516
//
1517
static void CutStyle(const char* stylename, nsString& styleValue)
1518
0
{
1519
0
  // Find the current wrapping type:
1520
0
  int32_t styleStart = styleValue.Find(stylename, true);
1521
0
  if (styleStart >= 0) {
1522
0
    int32_t styleEnd = styleValue.Find(";", false, styleStart);
1523
0
    if (styleEnd > styleStart) {
1524
0
      styleValue.Cut(styleStart, styleEnd - styleStart + 1);
1525
0
    } else {
1526
0
      styleValue.Cut(styleStart, styleValue.Length() - styleStart);
1527
0
    }
1528
0
  }
1529
0
}
1530
1531
NS_IMETHODIMP
1532
TextEditor::SetWrapWidth(int32_t aWrapColumn)
1533
0
{
1534
0
  SetWrapColumn(aWrapColumn);
1535
0
1536
0
  // Make sure we're a plaintext editor, otherwise we shouldn't
1537
0
  // do the rest of this.
1538
0
  if (!IsPlaintextEditor()) {
1539
0
    return NS_OK;
1540
0
  }
1541
0
1542
0
  // Ought to set a style sheet here ...
1543
0
  // Probably should keep around an mPlaintextStyleSheet for this purpose.
1544
0
  dom::Element *rootElement = GetRoot();
1545
0
  NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER);
1546
0
1547
0
  // Get the current style for this root element:
1548
0
  nsAutoString styleValue;
1549
0
  rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue);
1550
0
1551
0
  // We'll replace styles for these values:
1552
0
  CutStyle("white-space", styleValue);
1553
0
  CutStyle("width", styleValue);
1554
0
  CutStyle("font-family", styleValue);
1555
0
1556
0
  // If we have other style left, trim off any existing semicolons
1557
0
  // or whitespace, then add a known semicolon-space:
1558
0
  if (!styleValue.IsEmpty()) {
1559
0
    styleValue.Trim("; \t", false, true);
1560
0
    styleValue.AppendLiteral("; ");
1561
0
  }
1562
0
1563
0
  // Make sure we have fixed-width font.  This should be done for us,
1564
0
  // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;".
1565
0
  // Only do this if we're wrapping.
1566
0
  if (IsWrapHackEnabled() && aWrapColumn >= 0) {
1567
0
    styleValue.AppendLiteral("font-family: -moz-fixed; ");
1568
0
  }
1569
0
1570
0
  // and now we're ready to set the new whitespace/wrapping style.
1571
0
  if (aWrapColumn > 0) {
1572
0
    // Wrap to a fixed column.
1573
0
    styleValue.AppendLiteral("white-space: pre-wrap; width: ");
1574
0
    styleValue.AppendInt(aWrapColumn);
1575
0
    styleValue.AppendLiteral("ch;");
1576
0
  } else if (!aWrapColumn) {
1577
0
    styleValue.AppendLiteral("white-space: pre-wrap;");
1578
0
  } else {
1579
0
    styleValue.AppendLiteral("white-space: pre;");
1580
0
  }
1581
0
1582
0
  return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue, true);
1583
0
}
1584
1585
NS_IMETHODIMP
1586
TextEditor::SetWrapColumn(int32_t aWrapColumn)
1587
0
{
1588
0
  mWrapColumn = aWrapColumn;
1589
0
  return NS_OK;
1590
0
}
1591
1592
NS_IMETHODIMP
1593
TextEditor::GetNewlineHandling(int32_t* aNewlineHandling)
1594
0
{
1595
0
  NS_ENSURE_ARG_POINTER(aNewlineHandling);
1596
0
1597
0
  *aNewlineHandling = mNewlineHandling;
1598
0
  return NS_OK;
1599
0
}
1600
1601
NS_IMETHODIMP
1602
TextEditor::SetNewlineHandling(int32_t aNewlineHandling)
1603
0
{
1604
0
  mNewlineHandling = aNewlineHandling;
1605
0
1606
0
  return NS_OK;
1607
0
}
1608
1609
NS_IMETHODIMP
1610
TextEditor::Undo(uint32_t aCount)
1611
0
{
1612
0
  // If we don't have transaction in the undo stack, we shouldn't notify
1613
0
  // anybody of trying to undo since it's not useful notification but we
1614
0
  // need to pay some runtime cost.
1615
0
  if (!CanUndo()) {
1616
0
    return NS_OK;
1617
0
  }
1618
0
1619
0
  // If there is composition, we shouldn't allow to undo with committing
1620
0
  // composition since Chrome doesn't allow it and it doesn't make sense
1621
0
  // because committing composition causes one transaction and Undo(1)
1622
0
  // undoes the committing composition.
1623
0
  if (GetComposition()) {
1624
0
    return NS_OK;
1625
0
  }
1626
0
1627
0
  // Protect the edit rules object from dying.
1628
0
  RefPtr<TextEditRules> rules(mRules);
1629
0
1630
0
  AutoUpdateViewBatch beginViewBatching(this);
1631
0
1632
0
  NotifyEditorObservers(eNotifyEditorObserversOfBefore);
1633
0
  if (NS_WARN_IF(!CanUndo()) || NS_WARN_IF(Destroyed())) {
1634
0
    return NS_ERROR_FAILURE;
1635
0
  }
1636
0
1637
0
  nsresult rv;
1638
0
  {
1639
0
    AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1640
0
                                        *this, EditSubAction::eUndo,
1641
0
                                        nsIEditor::eNone);
1642
0
1643
0
    EditSubActionInfo subActionInfo(EditSubAction::eUndo);
1644
0
    RefPtr<Selection> selection = GetSelection();
1645
0
    bool cancel, handled;
1646
0
    rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
1647
0
    if (!cancel && NS_SUCCEEDED(rv)) {
1648
0
      RefPtr<TransactionManager> transactionManager(mTransactionManager);
1649
0
      for (uint32_t i = 0; i < aCount; ++i) {
1650
0
        rv = transactionManager->Undo();
1651
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
1652
0
          break;
1653
0
        }
1654
0
        DoAfterUndoTransaction();
1655
0
      }
1656
0
      rv = rules->DidDoAction(selection, subActionInfo, rv);
1657
0
    }
1658
0
  }
1659
0
1660
0
  NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1661
0
  return rv;
1662
0
}
1663
1664
NS_IMETHODIMP
1665
TextEditor::Redo(uint32_t aCount)
1666
0
{
1667
0
  // If we don't have transaction in the redo stack, we shouldn't notify
1668
0
  // anybody of trying to redo since it's not useful notification but we
1669
0
  // need to pay some runtime cost.
1670
0
  if (!CanRedo()) {
1671
0
    return NS_OK;
1672
0
  }
1673
0
1674
0
  // If there is composition, we shouldn't allow to redo with committing
1675
0
  // composition since Chrome doesn't allow it and it doesn't make sense
1676
0
  // because committing composition causes removing all transactions from
1677
0
  // the redo queue.  So, it becomes impossible to redo anything.
1678
0
  if (GetComposition()) {
1679
0
    return NS_OK;
1680
0
  }
1681
0
1682
0
  // Protect the edit rules object from dying.
1683
0
  RefPtr<TextEditRules> rules(mRules);
1684
0
1685
0
  AutoUpdateViewBatch beginViewBatching(this);
1686
0
1687
0
  NotifyEditorObservers(eNotifyEditorObserversOfBefore);
1688
0
  if (NS_WARN_IF(!CanRedo()) || NS_WARN_IF(Destroyed())) {
1689
0
    return NS_ERROR_FAILURE;
1690
0
  }
1691
0
1692
0
  nsresult rv;
1693
0
  {
1694
0
    AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
1695
0
                                        *this, EditSubAction::eRedo,
1696
0
                                        nsIEditor::eNone);
1697
0
1698
0
    EditSubActionInfo subActionInfo(EditSubAction::eRedo);
1699
0
    RefPtr<Selection> selection = GetSelection();
1700
0
    bool cancel, handled;
1701
0
    rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
1702
0
    if (!cancel && NS_SUCCEEDED(rv)) {
1703
0
      RefPtr<TransactionManager> transactionManager(mTransactionManager);
1704
0
      for (uint32_t i = 0; i < aCount; ++i) {
1705
0
        nsresult rv = transactionManager->Redo();
1706
0
        if (NS_WARN_IF(NS_FAILED(rv))) {
1707
0
          break;
1708
0
        }
1709
0
        DoAfterRedoTransaction();
1710
0
      }
1711
0
      rv = rules->DidDoAction(selection, subActionInfo, rv);
1712
0
    }
1713
0
  }
1714
0
1715
0
  NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1716
0
  return rv;
1717
0
}
1718
1719
bool
1720
TextEditor::CanCutOrCopy(PasswordFieldAllowed aPasswordFieldAllowed)
1721
0
{
1722
0
  RefPtr<Selection> selection = GetSelection();
1723
0
  if (!selection) {
1724
0
    return false;
1725
0
  }
1726
0
1727
0
  if (aPasswordFieldAllowed == ePasswordFieldNotAllowed &&
1728
0
      IsPasswordEditor()) {
1729
0
    return false;
1730
0
  }
1731
0
1732
0
  return !selection->IsCollapsed();
1733
0
}
1734
1735
bool
1736
TextEditor::FireClipboardEvent(EventMessage aEventMessage,
1737
                               int32_t aSelectionType,
1738
                               bool* aActionTaken)
1739
0
{
1740
0
  if (aEventMessage == ePaste) {
1741
0
    CommitComposition();
1742
0
  }
1743
0
1744
0
  nsCOMPtr<nsIPresShell> presShell = GetPresShell();
1745
0
  NS_ENSURE_TRUE(presShell, false);
1746
0
1747
0
  RefPtr<Selection> selection = GetSelection();
1748
0
  if (!selection) {
1749
0
    return false;
1750
0
  }
1751
0
1752
0
  if (!nsCopySupport::FireClipboardEvent(aEventMessage, aSelectionType,
1753
0
                                         presShell, selection, aActionTaken)) {
1754
0
    return false;
1755
0
  }
1756
0
1757
0
  // If the event handler caused the editor to be destroyed, return false.
1758
0
  // Otherwise return true to indicate that the event was not cancelled.
1759
0
  return !mDidPreDestroy;
1760
0
}
1761
1762
NS_IMETHODIMP
1763
TextEditor::Cut()
1764
0
{
1765
0
  bool actionTaken = false;
1766
0
  if (FireClipboardEvent(eCut, nsIClipboard::kGlobalClipboard, &actionTaken)) {
1767
0
    // XXX This transaction name is referred by PlaceholderTransaction::Merge()
1768
0
    //     so that we need to keep using it here.
1769
0
    AutoPlaceholderBatch batch(this, nsGkAtoms::DeleteTxnName);
1770
0
    DeleteSelectionAsSubAction(eNone, eStrip);
1771
0
  }
1772
0
  return actionTaken ? NS_OK : NS_ERROR_FAILURE;
1773
0
}
1774
1775
NS_IMETHODIMP
1776
TextEditor::CanCut(bool* aCanCut)
1777
0
{
1778
0
  NS_ENSURE_ARG_POINTER(aCanCut);
1779
0
  // Cut is always enabled in HTML documents
1780
0
  nsCOMPtr<nsIDocument> doc = GetDocument();
1781
0
  *aCanCut = (doc && doc->IsHTMLOrXHTML()) ||
1782
0
    (IsModifiable() && CanCutOrCopy(ePasswordFieldNotAllowed));
1783
0
  return NS_OK;
1784
0
}
1785
1786
NS_IMETHODIMP
1787
TextEditor::Copy()
1788
0
{
1789
0
  bool actionTaken = false;
1790
0
  FireClipboardEvent(eCopy, nsIClipboard::kGlobalClipboard, &actionTaken);
1791
0
1792
0
  return actionTaken ? NS_OK : NS_ERROR_FAILURE;
1793
0
}
1794
1795
NS_IMETHODIMP
1796
TextEditor::CanCopy(bool* aCanCopy)
1797
0
{
1798
0
  NS_ENSURE_ARG_POINTER(aCanCopy);
1799
0
  // Copy is always enabled in HTML documents
1800
0
  nsCOMPtr<nsIDocument> doc = GetDocument();
1801
0
  *aCanCopy = (doc && doc->IsHTMLOrXHTML()) ||
1802
0
    CanCutOrCopy(ePasswordFieldNotAllowed);
1803
0
  return NS_OK;
1804
0
}
1805
1806
NS_IMETHODIMP
1807
TextEditor::CanDelete(bool* aCanDelete)
1808
0
{
1809
0
  NS_ENSURE_ARG_POINTER(aCanDelete);
1810
0
  *aCanDelete = IsModifiable() && CanCutOrCopy(ePasswordFieldAllowed);
1811
0
  return NS_OK;
1812
0
}
1813
1814
already_AddRefed<nsIDocumentEncoder>
1815
TextEditor::GetAndInitDocEncoder(const nsAString& aFormatType,
1816
                                 uint32_t aDocumentEncoderFlags,
1817
                                 const nsACString& aCharset) const
1818
0
{
1819
0
  nsCOMPtr<nsIDocumentEncoder> docEncoder;
1820
0
  if (!mCachedDocumentEncoder ||
1821
0
      !mCachedDocumentEncoderType.Equals(aFormatType)) {
1822
0
    nsAutoCString formatType(NS_DOC_ENCODER_CONTRACTID_BASE);
1823
0
    LossyAppendUTF16toASCII(aFormatType, formatType);
1824
0
    docEncoder = do_CreateInstance(formatType.get());
1825
0
    if (NS_WARN_IF(!docEncoder)) {
1826
0
      return nullptr;
1827
0
    }
1828
0
    mCachedDocumentEncoder = docEncoder;
1829
0
    mCachedDocumentEncoderType = aFormatType;
1830
0
  } else {
1831
0
    docEncoder = mCachedDocumentEncoder;
1832
0
  }
1833
0
1834
0
  nsCOMPtr<nsIDocument> doc = GetDocument();
1835
0
  NS_ASSERTION(doc, "Need a document");
1836
0
1837
0
  nsresult rv =
1838
0
    docEncoder->NativeInit(
1839
0
                  doc, aFormatType,
1840
0
                  aDocumentEncoderFlags |
1841
0
                    nsIDocumentEncoder::RequiresReinitAfterOutput);
1842
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1843
0
    return nullptr;
1844
0
  }
1845
0
1846
0
  if (!aCharset.IsEmpty() && !aCharset.EqualsLiteral("null")) {
1847
0
    docEncoder->SetCharset(aCharset);
1848
0
  }
1849
0
1850
0
  int32_t wrapWidth = WrapWidth();
1851
0
  if (wrapWidth >= 0) {
1852
0
    Unused << docEncoder->SetWrapColumn(wrapWidth);
1853
0
  }
1854
0
1855
0
  // Set the selection, if appropriate.
1856
0
  // We do this either if the OutputSelectionOnly flag is set,
1857
0
  // in which case we use our existing selection ...
1858
0
  if (aDocumentEncoderFlags & nsIDocumentEncoder::OutputSelectionOnly) {
1859
0
    RefPtr<Selection> selection = GetSelection();
1860
0
    if (NS_WARN_IF(!selection)) {
1861
0
      return nullptr;
1862
0
    }
1863
0
    rv = docEncoder->SetSelection(selection);
1864
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1865
0
      return nullptr;
1866
0
    }
1867
0
  }
1868
0
  // ... or if the root element is not a body,
1869
0
  // in which case we set the selection to encompass the root.
1870
0
  else {
1871
0
    dom::Element* rootElement = GetRoot();
1872
0
    if (NS_WARN_IF(!rootElement)) {
1873
0
      return nullptr;
1874
0
    }
1875
0
    if (!rootElement->IsHTMLElement(nsGkAtoms::body)) {
1876
0
      rv = docEncoder->SetContainerNode(rootElement);
1877
0
      if (NS_WARN_IF(NS_FAILED(rv))) {
1878
0
        return nullptr;
1879
0
      }
1880
0
    }
1881
0
  }
1882
0
1883
0
  return docEncoder.forget();
1884
0
}
1885
1886
NS_IMETHODIMP
1887
TextEditor::OutputToString(const nsAString& aFormatType,
1888
                           uint32_t aDocumentEncoderFlags,
1889
                           nsAString& aOutputString)
1890
0
{
1891
0
  return ComputeValueInternal(aFormatType, aDocumentEncoderFlags,
1892
0
                              aOutputString);
1893
0
}
1894
1895
nsresult
1896
TextEditor::ComputeValueInternal(const nsAString& aFormatType,
1897
                                 uint32_t aDocumentEncoderFlags,
1898
                                 nsAString& aOutputString) const
1899
0
{
1900
0
  // Protect the edit rules object from dying
1901
0
  RefPtr<TextEditRules> rules(mRules);
1902
0
1903
0
  EditSubActionInfo subActionInfo(EditSubAction::eComputeTextToOutput);
1904
0
  subActionInfo.outString = &aOutputString;
1905
0
  subActionInfo.flags = aDocumentEncoderFlags;
1906
0
  subActionInfo.outputFormat = &aFormatType;
1907
0
  Selection* selection = GetSelection();
1908
0
  if (NS_WARN_IF(!selection)) {
1909
0
    return NS_ERROR_FAILURE;
1910
0
  }
1911
0
  bool cancel, handled;
1912
0
  nsresult rv =
1913
0
    rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
1914
0
  if (cancel || NS_FAILED(rv)) {
1915
0
    return rv;
1916
0
  }
1917
0
  if (handled) {
1918
0
    // This case will get triggered by password fields or single text node only.
1919
0
    return rv;
1920
0
  }
1921
0
1922
0
  nsAutoCString charset;
1923
0
  rv = GetDocumentCharsetInternal(charset);
1924
0
  if (NS_FAILED(rv) || charset.IsEmpty()) {
1925
0
    charset.AssignLiteral("windows-1252");
1926
0
  }
1927
0
1928
0
  nsCOMPtr<nsIDocumentEncoder> encoder =
1929
0
    GetAndInitDocEncoder(aFormatType, aDocumentEncoderFlags, charset);
1930
0
  if (NS_WARN_IF(!encoder)) {
1931
0
    return NS_ERROR_FAILURE;
1932
0
  }
1933
0
1934
0
  // XXX Why don't we call TextEditRules::DidDoAction() here?
1935
0
  return encoder->EncodeToString(aOutputString);
1936
0
}
1937
1938
nsresult
1939
TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType)
1940
0
{
1941
0
  MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard ||
1942
0
             aClipboardType == nsIClipboard::kSelectionClipboard);
1943
0
1944
0
  // Get Clipboard Service
1945
0
  nsresult rv;
1946
0
  nsCOMPtr<nsIClipboard> clipboard =
1947
0
    do_GetService("@mozilla.org/widget/clipboard;1", &rv);
1948
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1949
0
    return rv;
1950
0
  }
1951
0
1952
0
  // Get the nsITransferable interface for getting the data from the clipboard
1953
0
  nsCOMPtr<nsITransferable> trans;
1954
0
  rv = PrepareTransferable(getter_AddRefs(trans));
1955
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
1956
0
    return rv;
1957
0
  }
1958
0
  if (!trans) {
1959
0
    return NS_OK;
1960
0
  }
1961
0
1962
0
  // Get the Data from the clipboard
1963
0
  clipboard->GetData(trans, aClipboardType);
1964
0
1965
0
  // Now we ask the transferable for the data
1966
0
  // it still owns the data, we just have a pointer to it.
1967
0
  // If it can't support a "text" output of the data the call will fail
1968
0
  nsCOMPtr<nsISupports> genericDataObj;
1969
0
  uint32_t len;
1970
0
  nsAutoCString flav;
1971
0
  rv = trans->GetAnyTransferData(flav, getter_AddRefs(genericDataObj),
1972
0
                                 &len);
1973
0
  if (NS_FAILED(rv)) {
1974
0
    return rv;
1975
0
  }
1976
0
1977
0
  if (!flav.EqualsLiteral(kUnicodeMime) &&
1978
0
      !flav.EqualsLiteral(kMozTextInternal)) {
1979
0
    return NS_OK;
1980
0
  }
1981
0
1982
0
  nsCOMPtr<nsISupportsString> textDataObj = do_QueryInterface(genericDataObj);
1983
0
  if (textDataObj && len > 0) {
1984
0
    nsAutoString stuffToPaste;
1985
0
    textDataObj->GetData ( stuffToPaste );
1986
0
    AutoPlaceholderBatch beginBatching(this);
1987
0
    rv = InsertWithQuotationsAsSubAction(stuffToPaste);
1988
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
1989
0
      return rv;
1990
0
    }
1991
0
  }
1992
0
  return NS_OK;
1993
0
}
1994
1995
nsresult
1996
TextEditor::InsertWithQuotationsAsSubAction(const nsAString& aQuotedText)
1997
0
{
1998
0
  // Protect the edit rules object from dying
1999
0
  RefPtr<TextEditRules> rules(mRules);
2000
0
2001
0
  // Let the citer quote it for us:
2002
0
  nsString quotedStuff;
2003
0
  nsresult rv = InternetCiter::GetCiteString(aQuotedText, quotedStuff);
2004
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2005
0
    return rv;
2006
0
  }
2007
0
2008
0
  // It's best to put a blank line after the quoted text so that mails
2009
0
  // written without thinking won't be so ugly.
2010
0
  if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != char16_t('\n'))) {
2011
0
    quotedStuff.Append(char16_t('\n'));
2012
0
  }
2013
0
2014
0
  RefPtr<Selection> selection = GetSelection();
2015
0
  if (NS_WARN_IF(!selection)) {
2016
0
    return NS_ERROR_FAILURE;
2017
0
  }
2018
0
2019
0
  AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction(
2020
0
                                      *this, EditSubAction::eInsertText,
2021
0
                                      nsIEditor::eNext);
2022
0
2023
0
  // XXX This WillDoAction() usage is hacky.  If it returns as handled,
2024
0
  //     this method cannot work as expected.  So, this should have specific
2025
0
  //     sub-action rather than using eInsertElement.
2026
0
  EditSubActionInfo subActionInfo(EditSubAction::eInsertElement);
2027
0
  bool cancel, handled;
2028
0
  rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled);
2029
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2030
0
    return rv;
2031
0
  }
2032
0
  if (cancel) {
2033
0
    return NS_OK; // Rules canceled the operation.
2034
0
  }
2035
0
  MOZ_ASSERT(!handled, "WillDoAction() shouldn't handle in this case");
2036
0
  if (!handled) {
2037
0
    rv = InsertTextAsSubAction(quotedStuff);
2038
0
    if (NS_WARN_IF(NS_FAILED(rv))) {
2039
0
      return rv;
2040
0
    }
2041
0
  }
2042
0
  // XXX Why don't we call TextEditRules::DidDoAction()?
2043
0
  return NS_OK;
2044
0
}
2045
2046
nsresult
2047
TextEditor::SharedOutputString(uint32_t aFlags,
2048
                               bool* aIsCollapsed,
2049
                               nsAString& aResult)
2050
0
{
2051
0
  RefPtr<Selection> selection = GetSelection();
2052
0
  NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
2053
0
2054
0
  *aIsCollapsed = selection->IsCollapsed();
2055
0
2056
0
  if (!*aIsCollapsed) {
2057
0
    aFlags |= nsIDocumentEncoder::OutputSelectionOnly;
2058
0
  }
2059
0
  // If the selection isn't collapsed, we'll use the whole document.
2060
0
  return ComputeValueInternal(NS_LITERAL_STRING("text/plain"), aFlags, aResult);
2061
0
}
2062
2063
void
2064
TextEditor::OnStartToHandleTopLevelEditSubAction(
2065
              EditSubAction aEditSubAction,
2066
              nsIEditor::EDirection aDirection)
2067
0
{
2068
0
  // Protect the edit rules object from dying
2069
0
  RefPtr<TextEditRules> rules(mRules);
2070
0
2071
0
  EditorBase::OnStartToHandleTopLevelEditSubAction(aEditSubAction, aDirection);
2072
0
  if (!rules) {
2073
0
    return;
2074
0
  }
2075
0
2076
0
  MOZ_ASSERT(mTopLevelEditSubAction == aEditSubAction);
2077
0
  MOZ_ASSERT(mDirection == aDirection);
2078
0
  DebugOnly<nsresult> rv =
2079
0
    rules->BeforeEdit(mTopLevelEditSubAction, mDirection);
2080
0
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2081
0
    "TextEditRules::BeforeEdit() failed to handle something");
2082
0
}
2083
2084
void
2085
TextEditor::OnEndHandlingTopLevelEditSubAction()
2086
0
{
2087
0
  // Protect the edit rules object from dying
2088
0
  RefPtr<TextEditRules> rules(mRules);
2089
0
2090
0
  // post processing
2091
0
  DebugOnly<nsresult> rv =
2092
0
    rules ? rules->AfterEdit(mTopLevelEditSubAction, mDirection) : NS_OK;
2093
0
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2094
0
    "TextEditRules::AfterEdit() failed to handle something");
2095
0
  EditorBase::OnEndHandlingTopLevelEditSubAction();
2096
0
  MOZ_ASSERT(!mTopLevelEditSubAction);
2097
0
  MOZ_ASSERT(mDirection == eNone);
2098
0
}
2099
2100
nsresult
2101
TextEditor::SelectEntireDocument(Selection* aSelection)
2102
0
{
2103
0
  if (!aSelection || !mRules) {
2104
0
    return NS_ERROR_NULL_POINTER;
2105
0
  }
2106
0
2107
0
  // Protect the edit rules object from dying
2108
0
  RefPtr<TextEditRules> rules(mRules);
2109
0
2110
0
  // is doc empty?
2111
0
  if (rules->DocumentIsEmpty()) {
2112
0
    // get root node
2113
0
    Element* rootElement = GetRoot();
2114
0
    if (NS_WARN_IF(!rootElement)) {
2115
0
      return NS_ERROR_FAILURE;
2116
0
    }
2117
0
2118
0
    // if it's empty don't select entire doc - that would select the bogus node
2119
0
    return aSelection->Collapse(rootElement, 0);
2120
0
  }
2121
0
2122
0
  SelectionBatcher selectionBatcher(aSelection);
2123
0
  nsresult rv = EditorBase::SelectEntireDocument(aSelection);
2124
0
  NS_ENSURE_SUCCESS(rv, rv);
2125
0
2126
0
  // Don't select the trailing BR node if we have one
2127
0
  nsCOMPtr<nsIContent> childNode;
2128
0
  rv = GetEndChildNode(aSelection, getter_AddRefs(childNode));
2129
0
  if (NS_WARN_IF(NS_FAILED(rv))) {
2130
0
    return rv;
2131
0
  }
2132
0
  if (childNode) {
2133
0
    childNode = childNode->GetPreviousSibling();
2134
0
  }
2135
0
2136
0
  if (childNode && TextEditUtils::IsMozBR(childNode)) {
2137
0
    int32_t parentOffset;
2138
0
    nsINode* parentNode = GetNodeLocation(childNode, &parentOffset);
2139
0
2140
0
    return aSelection->Extend(parentNode, parentOffset);
2141
0
  }
2142
0
2143
0
  return NS_OK;
2144
0
}
2145
2146
EventTarget*
2147
TextEditor::GetDOMEventTarget()
2148
0
{
2149
0
  return mEventTarget;
2150
0
}
2151
2152
2153
nsresult
2154
TextEditor::SetAttributeOrEquivalent(Element* aElement,
2155
                                     nsAtom* aAttribute,
2156
                                     const nsAString& aValue,
2157
                                     bool aSuppressTransaction)
2158
0
{
2159
0
  if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) {
2160
0
    return NS_ERROR_INVALID_ARG;
2161
0
  }
2162
0
  return SetAttributeWithTransaction(*aElement, *aAttribute, aValue);
2163
0
}
2164
2165
nsresult
2166
TextEditor::RemoveAttributeOrEquivalent(Element* aElement,
2167
                                        nsAtom* aAttribute,
2168
                                        bool aSuppressTransaction)
2169
0
{
2170
0
  if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) {
2171
0
    return NS_ERROR_INVALID_ARG;
2172
0
  }
2173
0
  return RemoveAttributeWithTransaction(*aElement, *aAttribute);
2174
0
}
2175
2176
} // namespace mozilla