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 "InternetCiter.h"
9 #include "TextEditUtils.h"
10 #include "gfxFontUtils.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/EditAction.h"
13 #include "mozilla/EditorDOMPoint.h"
14 #include "mozilla/EditorUtils.h"  // AutoPlaceholderBatch, AutoRules
15 #include "mozilla/HTMLEditor.h"
16 #include "mozilla/mozalloc.h"
17 #include "mozilla/Preferences.h"
18 #include "mozilla/TextEditRules.h"
19 #include "mozilla/TextComposition.h"
20 #include "mozilla/TextEvents.h"
21 #include "mozilla/dom/Selection.h"
22 #include "mozilla/dom/Event.h"
23 #include "mozilla/dom/Element.h"
24 #include "nsAString.h"
25 #include "nsCRT.h"
26 #include "nsCaret.h"
27 #include "nsCharTraits.h"
28 #include "nsComponentManagerUtils.h"
29 #include "nsContentCID.h"
30 #include "nsContentList.h"
31 #include "nsCopySupport.h"
32 #include "nsDebug.h"
33 #include "nsDependentSubstring.h"
34 #include "nsError.h"
35 #include "nsGkAtoms.h"
36 #include "nsIClipboard.h"
37 #include "nsIContent.h"
38 #include "nsIContentIterator.h"
39 #include "nsIDOMDocument.h"
40 #include "nsIDOMElement.h"
41 #include "nsIDOMEventTarget.h"
42 #include "nsIDOMNode.h"
43 #include "nsIDocumentEncoder.h"
44 #include "nsINode.h"
45 #include "nsIPresShell.h"
46 #include "nsISelectionController.h"
47 #include "nsISupportsPrimitives.h"
48 #include "nsITransferable.h"
49 #include "nsIWeakReferenceUtils.h"
50 #include "nsNameSpaceManager.h"
51 #include "nsLiteralString.h"
52 #include "nsReadableUtils.h"
53 #include "nsServiceManagerUtils.h"
54 #include "nsString.h"
55 #include "nsStringFwd.h"
56 #include "nsTextNode.h"
57 #include "nsUnicharUtils.h"
58 #include "nsXPCOM.h"
59 
60 class nsIOutputStream;
61 class nsISupports;
62 
63 namespace mozilla {
64 
65 using namespace dom;
66 
TextEditor()67 TextEditor::TextEditor()
68     : mWrapColumn(0),
69       mMaxTextLength(-1),
70       mInitTriggerCounter(0),
71       mNewlineHandling(nsIPlaintextEditor::eNewlinesPasteToFirst)
72 #ifdef XP_WIN
73       ,
74       mCaretStyle(1)
75 #else
76       ,
77       mCaretStyle(0)
78 #endif
79 {
80   // check the "single line editor newline handling"
81   // and "caret behaviour in selection" prefs
82   GetDefaultEditorPrefs(mNewlineHandling, mCaretStyle);
83 }
84 
~TextEditor()85 TextEditor::~TextEditor() {
86   // Remove event listeners. Note that if we had an HTML editor,
87   //  it installed its own instead of these
88   RemoveEventListeners();
89 
90   if (mRules) mRules->DetachEditor();
91 }
92 
93 NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor)
94 
95 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TextEditor, EditorBase)
96   if (tmp->mRules) tmp->mRules->DetachEditor();
97   NS_IMPL_CYCLE_COLLECTION_UNLINK(mRules)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedDocumentEncoder)98   NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedDocumentEncoder)
99 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
100 
101 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextEditor, EditorBase)
102   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRules)
103   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedDocumentEncoder)
104 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
105 
106 NS_IMPL_ADDREF_INHERITED(TextEditor, EditorBase)
107 NS_IMPL_RELEASE_INHERITED(TextEditor, EditorBase)
108 
109 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEditor)
110   NS_INTERFACE_MAP_ENTRY(nsIPlaintextEditor)
111   NS_INTERFACE_MAP_ENTRY(nsIEditorMailSupport)
112 NS_INTERFACE_MAP_END_INHERITING(EditorBase)
113 
114 nsresult TextEditor::Init(nsIDocument& aDoc, Element* aRoot,
115                           nsISelectionController* aSelCon, uint32_t aFlags,
116                           const nsAString& aInitialValue) {
117   if (mRules) {
118     mRules->DetachEditor();
119   }
120 
121   nsresult rulesRv = NS_OK;
122   {
123     // block to scope AutoEditInitRulesTrigger
124     AutoEditInitRulesTrigger rulesTrigger(this, rulesRv);
125 
126     // Init the base editor
127     nsresult rv = EditorBase::Init(aDoc, aRoot, aSelCon, aFlags, aInitialValue);
128     if (NS_WARN_IF(NS_FAILED(rv))) {
129       return rv;
130     }
131   }
132   NS_ENSURE_SUCCESS(rulesRv, rulesRv);
133 
134   // mRules may not have been initialized yet, when this is called via
135   // HTMLEditor::Init.
136   if (mRules) {
137     mRules->SetInitialValue(aInitialValue);
138   }
139 
140   return NS_OK;
141 }
142 
143 static int32_t sNewlineHandlingPref = -1, sCaretStylePref = -1;
144 
EditorPrefsChangedCallback(const char * aPrefName,void *)145 static void EditorPrefsChangedCallback(const char* aPrefName, void*) {
146   if (!nsCRT::strcmp(aPrefName, "editor.singleLine.pasteNewlines")) {
147     sNewlineHandlingPref =
148         Preferences::GetInt("editor.singleLine.pasteNewlines",
149                             nsIPlaintextEditor::eNewlinesPasteToFirst);
150   } else if (!nsCRT::strcmp(aPrefName, "layout.selection.caret_style")) {
151     sCaretStylePref = Preferences::GetInt("layout.selection.caret_style",
152 #ifdef XP_WIN
153                                           1);
154     if (!sCaretStylePref) {
155       sCaretStylePref = 1;
156     }
157 #else
158                                           0);
159 #endif
160   }
161 }
162 
163 // static
GetDefaultEditorPrefs(int32_t & aNewlineHandling,int32_t & aCaretStyle)164 void TextEditor::GetDefaultEditorPrefs(int32_t& aNewlineHandling,
165                                        int32_t& aCaretStyle) {
166   if (sNewlineHandlingPref == -1) {
167     Preferences::RegisterCallbackAndCall(EditorPrefsChangedCallback,
168                                          "editor.singleLine.pasteNewlines");
169     Preferences::RegisterCallbackAndCall(EditorPrefsChangedCallback,
170                                          "layout.selection.caret_style");
171   }
172 
173   aNewlineHandling = sNewlineHandlingPref;
174   aCaretStyle = sCaretStylePref;
175 }
176 
BeginEditorInit()177 void TextEditor::BeginEditorInit() { mInitTriggerCounter++; }
178 
EndEditorInit()179 nsresult TextEditor::EndEditorInit() {
180   NS_PRECONDITION(mInitTriggerCounter > 0,
181                   "ended editor init before we began?");
182   mInitTriggerCounter--;
183   if (mInitTriggerCounter) {
184     return NS_OK;
185   }
186 
187   nsresult rv = InitRules();
188   if (NS_FAILED(rv)) {
189     return rv;
190   }
191   // Throw away the old transaction manager if this is not the first time that
192   // we're initializing the editor.
193   EnableUndo(false);
194   EnableUndo(true);
195   return NS_OK;
196 }
197 
198 NS_IMETHODIMP
SetDocumentCharacterSet(const nsACString & characterSet)199 TextEditor::SetDocumentCharacterSet(const nsACString& characterSet) {
200   nsresult rv = EditorBase::SetDocumentCharacterSet(characterSet);
201   NS_ENSURE_SUCCESS(rv, rv);
202 
203   // Update META charset element.
204   nsCOMPtr<nsIDocument> doc = GetDocument();
205   if (NS_WARN_IF(!doc)) {
206     return NS_ERROR_NOT_INITIALIZED;
207   }
208 
209   if (UpdateMetaCharset(*doc, characterSet)) {
210     return NS_OK;
211   }
212 
213   RefPtr<nsContentList> headList =
214       doc->GetElementsByTagName(NS_LITERAL_STRING("head"));
215   if (NS_WARN_IF(!headList)) {
216     return NS_OK;
217   }
218 
219   nsCOMPtr<nsIContent> headNode = headList->Item(0);
220   if (NS_WARN_IF(!headNode)) {
221     return NS_OK;
222   }
223 
224   // Create a new meta charset tag
225   EditorRawDOMPoint atStartOfHeadNode(headNode, 0);
226   RefPtr<Element> metaElement = CreateNode(nsGkAtoms::meta, atStartOfHeadNode);
227   if (NS_WARN_IF(!metaElement)) {
228     return NS_OK;
229   }
230 
231   // Set attributes to the created element
232   if (characterSet.IsEmpty()) {
233     return NS_OK;
234   }
235 
236   // not undoable, undo should undo CreateNode
237   metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
238                        NS_LITERAL_STRING("Content-Type"), true);
239   metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::content,
240                        NS_LITERAL_STRING("text/html;charset=") +
241                            NS_ConvertASCIItoUTF16(characterSet),
242                        true);
243   return NS_OK;
244 }
245 
UpdateMetaCharset(nsIDocument & aDocument,const nsACString & aCharacterSet)246 bool TextEditor::UpdateMetaCharset(nsIDocument& aDocument,
247                                    const nsACString& aCharacterSet) {
248   // get a list of META tags
249   RefPtr<nsContentList> metaList =
250       aDocument.GetElementsByTagName(NS_LITERAL_STRING("meta"));
251   if (NS_WARN_IF(!metaList)) {
252     return false;
253   }
254 
255   for (uint32_t i = 0; i < metaList->Length(true); ++i) {
256     nsCOMPtr<nsIContent> metaNode = metaList->Item(i);
257     MOZ_ASSERT(metaNode);
258 
259     if (!metaNode->IsElement()) {
260       continue;
261     }
262 
263     nsAutoString currentValue;
264     metaNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv,
265                                    currentValue);
266 
267     if (!FindInReadable(NS_LITERAL_STRING("content-type"), currentValue,
268                         nsCaseInsensitiveStringComparator())) {
269       continue;
270     }
271 
272     metaNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::content,
273                                    currentValue);
274 
275     NS_NAMED_LITERAL_STRING(charsetEquals, "charset=");
276     nsAString::const_iterator originalStart, start, end;
277     originalStart = currentValue.BeginReading(start);
278     currentValue.EndReading(end);
279     if (!FindInReadable(charsetEquals, start, end,
280                         nsCaseInsensitiveStringComparator())) {
281       continue;
282     }
283 
284     // set attribute to <original prefix> charset=text/html
285     RefPtr<Element> metaElement = metaNode->AsElement();
286     MOZ_ASSERT(metaElement);
287     nsresult rv = EditorBase::SetAttribute(
288         metaElement, nsGkAtoms::content,
289         Substring(originalStart, start) + charsetEquals +
290             NS_ConvertASCIItoUTF16(aCharacterSet));
291     return NS_SUCCEEDED(rv);
292   }
293   return false;
294 }
295 
296 NS_IMETHODIMP
InitRules()297 TextEditor::InitRules() {
298   if (!mRules) {
299     // instantiate the rules for this text editor
300     mRules = new TextEditRules();
301   }
302   return mRules->Init(this);
303 }
304 
HandleKeyPressEvent(WidgetKeyboardEvent * aKeyboardEvent)305 nsresult TextEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) {
306   // NOTE: When you change this method, you should also change:
307   //   * editor/libeditor/tests/test_texteditor_keyevent_handling.html
308   //   * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
309   //
310   // And also when you add new key handling, you need to change the subclass's
311   // HandleKeyPressEvent()'s switch statement.
312 
313   if (IsReadonly() || IsDisabled()) {
314     // When we're not editable, the events handled on EditorBase.
315     return EditorBase::HandleKeyPressEvent(aKeyboardEvent);
316   }
317 
318   if (NS_WARN_IF(!aKeyboardEvent)) {
319     return NS_ERROR_UNEXPECTED;
320   }
321   MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress,
322              "HandleKeyPressEvent gets non-keypress event");
323 
324   switch (aKeyboardEvent->mKeyCode) {
325     case NS_VK_META:
326     case NS_VK_WIN:
327     case NS_VK_SHIFT:
328     case NS_VK_CONTROL:
329     case NS_VK_ALT:
330     case NS_VK_BACK:
331     case NS_VK_DELETE:
332       // These keys are handled on EditorBase
333       return EditorBase::HandleKeyPressEvent(aKeyboardEvent);
334     case NS_VK_TAB: {
335       if (IsTabbable()) {
336         return NS_OK;  // let it be used for focus switching
337       }
338 
339       if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() ||
340           aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() ||
341           aKeyboardEvent->IsOS()) {
342         return NS_OK;
343       }
344 
345       // else we insert the tab straight through
346       aKeyboardEvent->PreventDefault();
347       return TypedText(NS_LITERAL_STRING("\t"), eTypedText);
348     }
349     case NS_VK_RETURN:
350       if (IsSingleLineEditor() || !aKeyboardEvent->IsInputtingLineBreak()) {
351         return NS_OK;
352       }
353       aKeyboardEvent->PreventDefault();
354       return TypedText(EmptyString(), eTypedBreak);
355   }
356 
357   if (!aKeyboardEvent->IsInputtingText()) {
358     // we don't PreventDefault() here or keybindings like control-x won't work
359     return NS_OK;
360   }
361   aKeyboardEvent->PreventDefault();
362   nsAutoString str(aKeyboardEvent->mCharCode);
363   return TypedText(str, eTypedText);
364 }
365 
366 /* This routine is needed to provide a bottleneck for typing for logging
367    purposes.  Can't use HandleKeyPress() (above) for that since it takes
368    a WidgetKeyboardEvent* parameter.  So instead we pass enough info through
369    to TypedText() to determine what action to take, but without passing
370    an event.
371    */
372 NS_IMETHODIMP
TypedText(const nsAString & aString,ETypingAction aAction)373 TextEditor::TypedText(const nsAString& aString, ETypingAction aAction) {
374   AutoPlaceholderBatch batch(this, nsGkAtoms::TypingTxnName);
375 
376   switch (aAction) {
377     case eTypedText:
378       return InsertText(aString);
379     case eTypedBreak:
380       return InsertLineBreak();
381     default:
382       // eTypedBR is only for HTML
383       return NS_ERROR_FAILURE;
384   }
385 }
386 
CreateBR(const EditorRawDOMPoint & aPointToInsert,EDirection aSelect)387 already_AddRefed<Element> TextEditor::CreateBR(
388     const EditorRawDOMPoint& aPointToInsert, EDirection aSelect /* = eNone */) {
389   RefPtr<Selection> selection = GetSelection();
390   if (NS_WARN_IF(!selection)) {
391     return nullptr;
392   }
393   // We assume everything is fine if newBRElement is not null.
394   return CreateBRImpl(*selection, aPointToInsert, aSelect);
395 }
396 
CreateBRImpl(Selection & aSelection,const EditorRawDOMPoint & aPointToInsert,EDirection aSelect)397 already_AddRefed<Element> TextEditor::CreateBRImpl(
398     Selection& aSelection, const EditorRawDOMPoint& aPointToInsert,
399     EDirection aSelect) {
400   if (NS_WARN_IF(!aPointToInsert.IsSet())) {
401     return nullptr;
402   }
403 
404   // We need to insert a <br> node.
405   RefPtr<Element> newBRElement;
406   if (aPointToInsert.IsInTextNode()) {
407     EditorDOMPoint pointInContainer;
408     if (aPointToInsert.IsStartOfContainer()) {
409       // Insert before the text node.
410       pointInContainer.Set(aPointToInsert.GetContainer());
411       if (NS_WARN_IF(!pointInContainer.IsSet())) {
412         return nullptr;
413       }
414     } else if (aPointToInsert.IsEndOfContainer()) {
415       // Insert after the text node.
416       pointInContainer.Set(aPointToInsert.GetContainer());
417       if (NS_WARN_IF(!pointInContainer.IsSet())) {
418         return nullptr;
419       }
420       DebugOnly<bool> advanced = pointInContainer.AdvanceOffset();
421       NS_WARNING_ASSERTION(advanced,
422                            "Failed to advance offset to after the text node");
423     } else {
424       MOZ_DIAGNOSTIC_ASSERT(aPointToInsert.IsSetAndValid());
425       // Unfortunately, we need to split the text node at the offset.
426       ErrorResult error;
427       nsCOMPtr<nsIContent> newLeftNode = SplitNode(aPointToInsert, error);
428       if (NS_WARN_IF(error.Failed())) {
429         error.SuppressException();
430         return nullptr;
431       }
432       Unused << newLeftNode;
433       // Insert new <br> before the right node.
434       pointInContainer.Set(aPointToInsert.GetContainer());
435     }
436     // Create a <br> node.
437     newBRElement = CreateNode(nsGkAtoms::br, pointInContainer.AsRaw());
438     if (NS_WARN_IF(!newBRElement)) {
439       return nullptr;
440     }
441   } else {
442     newBRElement = CreateNode(nsGkAtoms::br, aPointToInsert);
443     if (NS_WARN_IF(!newBRElement)) {
444       return nullptr;
445     }
446   }
447 
448   switch (aSelect) {
449     case eNone:
450       break;
451     case eNext: {
452       aSelection.SetInterlinePosition(true);
453       // Collapse selection after the <br> node.
454       EditorRawDOMPoint afterBRElement(newBRElement);
455       if (afterBRElement.IsSet()) {
456         DebugOnly<bool> advanced = afterBRElement.AdvanceOffset();
457         NS_WARNING_ASSERTION(advanced,
458                              "Failed to advance offset after the <br> element");
459         ErrorResult error;
460         aSelection.Collapse(afterBRElement, error);
461         NS_WARNING_ASSERTION(
462             !error.Failed(),
463             "Failed to collapse selection after the <br> element");
464       } else {
465         NS_WARNING("The <br> node is not in the DOM tree?");
466       }
467       break;
468     }
469     case ePrevious: {
470       aSelection.SetInterlinePosition(true);
471       // Collapse selection at the <br> node.
472       EditorRawDOMPoint atBRElement(newBRElement);
473       if (atBRElement.IsSet()) {
474         ErrorResult error;
475         aSelection.Collapse(atBRElement, error);
476         NS_WARNING_ASSERTION(
477             !error.Failed(),
478             "Failed to collapse selection at the <br> element");
479       } else {
480         NS_WARNING("The <br> node is not in the DOM tree?");
481       }
482       break;
483     }
484     default:
485       NS_WARNING(
486           "aSelect has invalid value, the caller need to set selection "
487           "by itself");
488       break;
489   }
490 
491   return newBRElement.forget();
492 }
493 
ExtendSelectionForDelete(Selection * aSelection,nsIEditor::EDirection * aAction)494 nsresult TextEditor::ExtendSelectionForDelete(Selection* aSelection,
495                                               nsIEditor::EDirection* aAction) {
496   bool bCollapsed = aSelection->Collapsed();
497 
498   if (*aAction == eNextWord || *aAction == ePreviousWord ||
499       (*aAction == eNext && bCollapsed) ||
500       (*aAction == ePrevious && bCollapsed) || *aAction == eToBeginningOfLine ||
501       *aAction == eToEndOfLine) {
502     nsCOMPtr<nsISelectionController> selCont;
503     GetSelectionController(getter_AddRefs(selCont));
504     NS_ENSURE_TRUE(selCont, NS_ERROR_NO_INTERFACE);
505 
506     switch (*aAction) {
507       case eNextWord: {
508         nsresult rv = selCont->WordExtendForDelete(true);
509         // DeleteSelectionImpl doesn't handle these actions
510         // because it's inside batching, so don't confuse it:
511         *aAction = eNone;
512         if (NS_WARN_IF(NS_FAILED(rv))) {
513           return rv;
514         }
515         return NS_OK;
516       }
517       case ePreviousWord: {
518         nsresult rv = selCont->WordExtendForDelete(false);
519         *aAction = eNone;
520         if (NS_WARN_IF(NS_FAILED(rv))) {
521           return rv;
522         }
523         return NS_OK;
524       }
525       case eNext: {
526         nsresult rv = selCont->CharacterExtendForDelete();
527         // Don't set aAction to eNone (see Bug 502259)
528         if (NS_WARN_IF(NS_FAILED(rv))) {
529           return rv;
530         }
531         return NS_OK;
532       }
533       case ePrevious: {
534         // Only extend the selection where the selection is after a UTF-16
535         // surrogate pair or a variation selector.
536         // For other cases we don't want to do that, in order
537         // to make sure that pressing backspace will only delete the last
538         // typed character.
539         EditorRawDOMPoint atStartOfSelection =
540             EditorBase::GetStartPoint(aSelection);
541         if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
542           return NS_ERROR_FAILURE;
543         }
544 
545         // node might be anonymous DIV, so we find better text node
546         EditorRawDOMPoint insertionPoint =
547             FindBetterInsertionPoint(atStartOfSelection);
548 
549         if (insertionPoint.IsInTextNode()) {
550           const nsTextFragment* data =
551               insertionPoint.GetContainerAsText()->GetText();
552           uint32_t offset = insertionPoint.Offset();
553           if ((offset > 1 && NS_IS_LOW_SURROGATE(data->CharAt(offset - 1)) &&
554                NS_IS_HIGH_SURROGATE(data->CharAt(offset - 2))) ||
555               (offset > 0 &&
556                gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) {
557             nsresult rv = selCont->CharacterExtendForBackspace();
558             if (NS_WARN_IF(NS_FAILED(rv))) {
559               return rv;
560             }
561           }
562         }
563         return NS_OK;
564       }
565       case eToBeginningOfLine: {
566         // Try to move to end
567         selCont->IntraLineMove(true, false);
568         // Select to beginning
569         nsresult rv = selCont->IntraLineMove(false, true);
570         *aAction = eNone;
571         if (NS_WARN_IF(NS_FAILED(rv))) {
572           return rv;
573         }
574         return NS_OK;
575       }
576       case eToEndOfLine: {
577         nsresult rv = selCont->IntraLineMove(true, true);
578         *aAction = eNext;
579         if (NS_WARN_IF(NS_FAILED(rv))) {
580           return rv;
581         }
582         return NS_OK;
583       }
584       // For avoiding several compiler warnings
585       default:
586         return NS_OK;
587     }
588   }
589   return NS_OK;
590 }
591 
DeleteSelection(EDirection aAction,EStripWrappers aStripWrappers)592 nsresult TextEditor::DeleteSelection(EDirection aAction,
593                                      EStripWrappers aStripWrappers) {
594   MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
595 
596   if (!mRules) {
597     return NS_ERROR_NOT_INITIALIZED;
598   }
599 
600   // Protect the edit rules object from dying
601   RefPtr<TextEditRules> rules(mRules);
602 
603   // delete placeholder txns merge.
604   AutoPlaceholderBatch batch(this, nsGkAtoms::DeleteTxnName);
605   AutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction);
606 
607   // pre-process
608   RefPtr<Selection> selection = GetSelection();
609   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
610 
611   // If there is an existing selection when an extended delete is requested,
612   //  platforms that use "caret-style" caret positioning collapse the
613   //  selection to the  start and then create a new selection.
614   //  Platforms that use "selection-style" caret positioning just delete the
615   //  existing selection without extending it.
616   if (!selection->Collapsed() &&
617       (aAction == eNextWord || aAction == ePreviousWord ||
618        aAction == eToBeginningOfLine || aAction == eToEndOfLine)) {
619     if (mCaretStyle == 1) {
620       nsresult rv = selection->CollapseToStart();
621       NS_ENSURE_SUCCESS(rv, rv);
622     } else {
623       aAction = eNone;
624     }
625   }
626 
627   RulesInfo ruleInfo(EditAction::deleteSelection);
628   ruleInfo.collapsedAction = aAction;
629   ruleInfo.stripWrappers = aStripWrappers;
630   bool cancel, handled;
631   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
632   NS_ENSURE_SUCCESS(rv, rv);
633   if (!cancel && !handled) {
634     rv = DeleteSelectionImpl(aAction, aStripWrappers);
635   }
636   if (!cancel) {
637     // post-process
638     rv = rules->DidDoAction(selection, &ruleInfo, rv);
639   }
640   return rv;
641 }
642 
643 NS_IMETHODIMP
InsertText(const nsAString & aStringToInsert)644 TextEditor::InsertText(const nsAString& aStringToInsert) {
645   if (!mRules) {
646     return NS_ERROR_NOT_INITIALIZED;
647   }
648 
649   // Protect the edit rules object from dying
650   RefPtr<TextEditRules> rules(mRules);
651 
652   EditAction opID = EditAction::insertText;
653   if (ShouldHandleIMEComposition()) {
654     opID = EditAction::insertIMEText;
655   }
656   AutoPlaceholderBatch batch(this, nullptr);
657   AutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
658 
659   // pre-process
660   RefPtr<Selection> selection = GetSelection();
661   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
662   nsAutoString resultString;
663   // XXX can we trust instring to outlive ruleInfo,
664   // XXX and ruleInfo not to refer to instring in its dtor?
665   // nsAutoString instring(aStringToInsert);
666   RulesInfo ruleInfo(opID);
667   ruleInfo.inString = &aStringToInsert;
668   ruleInfo.outString = &resultString;
669   ruleInfo.maxLength = mMaxTextLength;
670 
671   bool cancel, handled;
672   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
673   NS_ENSURE_SUCCESS(rv, rv);
674   if (!cancel && !handled) {
675     // we rely on rules code for now - no default implementation
676   }
677   if (cancel) {
678     return NS_OK;
679   }
680   // post-process
681   return rules->DidDoAction(selection, &ruleInfo, rv);
682 }
683 
684 NS_IMETHODIMP
InsertLineBreak()685 TextEditor::InsertLineBreak() {
686   if (!mRules) {
687     return NS_ERROR_NOT_INITIALIZED;
688   }
689 
690   // Protect the edit rules object from dying
691   RefPtr<TextEditRules> rules(mRules);
692 
693   AutoPlaceholderBatch beginBatching(this);
694   AutoRules beginRulesSniffing(this, EditAction::insertBreak, nsIEditor::eNext);
695 
696   // pre-process
697   RefPtr<Selection> selection = GetSelection();
698   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
699 
700   RulesInfo ruleInfo(EditAction::insertBreak);
701   ruleInfo.maxLength = mMaxTextLength;
702   bool cancel, handled;
703   // XXX DidDoAction() won't be called when this returns error.  Perhaps,
704   //     we should move the code between WillDoAction() and DidDoAction()
705   //     to a new method and guarantee that DidDoAction() is always called
706   //     after WillDoAction().
707   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
708   NS_ENSURE_SUCCESS(rv, rv);
709   if (!cancel && !handled) {
710     // get the (collapsed) selection location
711     nsRange* firstRange = selection->GetRangeAt(0);
712     if (NS_WARN_IF(!firstRange)) {
713       return NS_ERROR_FAILURE;
714     }
715 
716     EditorRawDOMPoint pointToInsert(firstRange->StartRef());
717     if (NS_WARN_IF(!pointToInsert.IsSet())) {
718       return NS_ERROR_FAILURE;
719     }
720     MOZ_ASSERT(pointToInsert.IsSetAndValid());
721 
722     // don't put text in places that can't have it
723     if (!pointToInsert.IsInTextNode() &&
724         !CanContainTag(*pointToInsert.GetContainer(),
725                        *nsGkAtoms::textTagName)) {
726       return NS_ERROR_FAILURE;
727     }
728 
729     // we need to get the doc
730     nsCOMPtr<nsIDocument> doc = GetDocument();
731     NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
732 
733     // don't change my selection in subtransactions
734     AutoTransactionsConserveSelection dontChangeMySelection(this);
735 
736     // insert a linefeed character
737     EditorRawDOMPoint pointAfterInsertedLineBreak;
738     rv = InsertTextImpl(*doc, NS_LITERAL_STRING("\n"), pointToInsert,
739                         &pointAfterInsertedLineBreak);
740     if (NS_WARN_IF(!pointAfterInsertedLineBreak.IsSet())) {
741       rv =
742           NS_ERROR_NULL_POINTER;  // don't return here, so DidDoAction is called
743     }
744     if (NS_SUCCEEDED(rv)) {
745       // set the selection to the correct location
746       MOZ_ASSERT(
747           !pointAfterInsertedLineBreak.GetChild(),
748           "After inserting text into a text node, pointAfterInsertedLineBreak."
749           "GetChild() should be nullptr");
750       rv = selection->Collapse(pointAfterInsertedLineBreak);
751       if (NS_SUCCEEDED(rv)) {
752         // see if we're at the end of the editor range
753         EditorRawDOMPoint endPoint = GetEndPoint(selection);
754         if (endPoint == pointAfterInsertedLineBreak) {
755           // SetInterlinePosition(true) means we want the caret to stick to the
756           // content on the "right".  We want the caret to stick to whatever is
757           // past the break.  This is because the break is on the same line we
758           // were on, but the next content will be on the following line.
759           selection->SetInterlinePosition(true);
760         }
761       }
762     }
763   }
764 
765   if (!cancel) {
766     // post-process, always called if WillInsertBreak didn't return cancel==true
767     rv = rules->DidDoAction(selection, &ruleInfo, rv);
768   }
769   return rv;
770 }
771 
SetText(const nsAString & aString)772 nsresult TextEditor::SetText(const nsAString& aString) {
773   if (NS_WARN_IF(!mRules)) {
774     return NS_ERROR_NOT_INITIALIZED;
775   }
776 
777   // Protect the edit rules object from dying
778   RefPtr<TextEditRules> rules(mRules);
779 
780   // delete placeholder txns merge.
781   AutoPlaceholderBatch batch(this, nullptr);
782   AutoRules beginRulesSniffing(this, EditAction::setText, nsIEditor::eNext);
783 
784   // pre-process
785   RefPtr<Selection> selection = GetSelection();
786   if (NS_WARN_IF(!selection)) {
787     return NS_ERROR_NULL_POINTER;
788   }
789   RulesInfo ruleInfo(EditAction::setText);
790   ruleInfo.inString = &aString;
791   ruleInfo.maxLength = mMaxTextLength;
792 
793   bool cancel;
794   bool handled;
795   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
796   if (NS_WARN_IF(NS_FAILED(rv))) {
797     return rv;
798   }
799   if (cancel) {
800     return NS_OK;
801   }
802   if (!handled) {
803     // We want to select trailing BR node to remove all nodes to replace all,
804     // but TextEditor::SelectEntireDocument doesn't select that BR node.
805     if (rules->DocumentIsEmpty()) {
806       // if it's empty, don't select entire doc - that would select
807       // the bogus node
808       Element* rootElement = GetRoot();
809       if (NS_WARN_IF(!rootElement)) {
810         return NS_ERROR_FAILURE;
811       }
812       rv = selection->Collapse(rootElement, 0);
813     } else {
814       rv = EditorBase::SelectEntireDocument(selection);
815     }
816     if (NS_SUCCEEDED(rv)) {
817       if (aString.IsEmpty()) {
818         rv = DeleteSelection(eNone, eStrip);
819       } else {
820         rv = InsertText(aString);
821       }
822     }
823   }
824   // post-process
825   return rules->DidDoAction(selection, &ruleInfo, rv);
826 }
827 
BeginIMEComposition(WidgetCompositionEvent * aEvent)828 nsresult TextEditor::BeginIMEComposition(WidgetCompositionEvent* aEvent) {
829   NS_ENSURE_TRUE(!mComposition, NS_OK);
830 
831   if (IsPasswordEditor()) {
832     NS_ENSURE_TRUE(mRules, NS_ERROR_NULL_POINTER);
833     // Protect the edit rules object from dying
834     RefPtr<TextEditRules> rules(mRules);
835     rules->ResetIMETextPWBuf();
836   }
837 
838   return EditorBase::BeginIMEComposition(aEvent);
839 }
840 
UpdateIMEComposition(WidgetCompositionEvent * aCompsitionChangeEvent)841 nsresult TextEditor::UpdateIMEComposition(
842     WidgetCompositionEvent* aCompsitionChangeEvent) {
843   MOZ_ASSERT(aCompsitionChangeEvent,
844              "aCompsitionChangeEvent must not be nullptr");
845 
846   if (NS_WARN_IF(!aCompsitionChangeEvent)) {
847     return NS_ERROR_INVALID_ARG;
848   }
849 
850   MOZ_ASSERT(aCompsitionChangeEvent->mMessage == eCompositionChange,
851              "The event should be eCompositionChange");
852 
853   if (!EnsureComposition(aCompsitionChangeEvent)) {
854     return NS_OK;
855   }
856 
857   nsCOMPtr<nsIPresShell> ps = GetPresShell();
858   NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
859 
860   RefPtr<Selection> selection = GetSelection();
861   NS_ENSURE_STATE(selection);
862 
863   // NOTE: TextComposition should receive selection change notification before
864   //       CompositionChangeEventHandlingMarker notifies TextComposition of the
865   //       end of handling compositionchange event because TextComposition may
866   //       need to ignore selection changes caused by composition.  Therefore,
867   //       CompositionChangeEventHandlingMarker must be destroyed after a call
868   //       of NotifiyEditorObservers(eNotifyEditorObserversOfEnd) or
869   //       NotifiyEditorObservers(eNotifyEditorObserversOfCancel) which notifies
870   //       TextComposition of a selection change.
871   MOZ_ASSERT(
872       !mPlaceholderBatch,
873       "UpdateIMEComposition() must be called without place holder batch");
874   TextComposition::CompositionChangeEventHandlingMarker
875       compositionChangeEventHandlingMarker(mComposition,
876                                            aCompsitionChangeEvent);
877 
878   RefPtr<nsCaret> caretP = ps->GetCaret();
879 
880   nsresult rv;
881   {
882     AutoPlaceholderBatch batch(this, nsGkAtoms::IMETxnName);
883 
884     MOZ_ASSERT(
885         mIsInEditAction,
886         "AutoPlaceholderBatch should've notified the observes of before-edit");
887     rv = InsertText(aCompsitionChangeEvent->mData);
888 
889     if (caretP) {
890       caretP->SetSelection(selection);
891     }
892   }
893 
894   // If still composing, we should fire input event via observer.
895   // Note that if the composition will be committed by the following
896   // compositionend event, we don't need to notify editor observes of this
897   // change.
898   // NOTE: We must notify after the auto batch will be gone.
899   if (!aCompsitionChangeEvent->IsFollowedByCompositionEnd()) {
900     NotifyEditorObservers(eNotifyEditorObserversOfEnd);
901   }
902 
903   return rv;
904 }
905 
GetInputEventTargetContent()906 already_AddRefed<nsIContent> TextEditor::GetInputEventTargetContent() {
907   nsCOMPtr<nsIContent> target = do_QueryInterface(mEventTarget);
908   return target.forget();
909 }
910 
DocumentIsEmpty(bool * aIsEmpty)911 nsresult TextEditor::DocumentIsEmpty(bool* aIsEmpty) {
912   NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
913 
914   if (mRules->HasBogusNode()) {
915     *aIsEmpty = true;
916     return NS_OK;
917   }
918 
919   // Even if there is no bogus node, we should be detected as empty document
920   // if all the children are text nodes and these have no content.
921   Element* rootElement = GetRoot();
922   if (!rootElement) {
923     *aIsEmpty = true;
924     return NS_OK;
925   }
926 
927   for (nsIContent* child = rootElement->GetFirstChild(); child;
928        child = child->GetNextSibling()) {
929     if (!EditorBase::IsTextNode(child) ||
930         static_cast<nsTextNode*>(child)->TextDataLength()) {
931       *aIsEmpty = false;
932       return NS_OK;
933     }
934   }
935 
936   *aIsEmpty = true;
937   return NS_OK;
938 }
939 
940 NS_IMETHODIMP
GetDocumentIsEmpty(bool * aDocumentIsEmpty)941 TextEditor::GetDocumentIsEmpty(bool* aDocumentIsEmpty) {
942   return DocumentIsEmpty(aDocumentIsEmpty);
943 }
944 
945 NS_IMETHODIMP
GetTextLength(int32_t * aCount)946 TextEditor::GetTextLength(int32_t* aCount) {
947   NS_ASSERTION(aCount, "null pointer");
948 
949   // initialize out params
950   *aCount = 0;
951 
952   // special-case for empty document, to account for the bogus node
953   bool docEmpty;
954   nsresult rv = GetDocumentIsEmpty(&docEmpty);
955   NS_ENSURE_SUCCESS(rv, rv);
956   if (docEmpty) {
957     return NS_OK;
958   }
959 
960   dom::Element* rootElement = GetRoot();
961   NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER);
962 
963   nsCOMPtr<nsIContentIterator> iter =
964       do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv);
965   NS_ENSURE_SUCCESS(rv, rv);
966 
967   uint32_t totalLength = 0;
968   iter->Init(rootElement);
969   for (; !iter->IsDone(); iter->Next()) {
970     nsCOMPtr<nsINode> currentNode = iter->GetCurrentNode();
971     if (IsTextNode(currentNode) && IsEditable(currentNode)) {
972       totalLength += currentNode->Length();
973     }
974   }
975 
976   *aCount = totalLength;
977   return NS_OK;
978 }
979 
980 NS_IMETHODIMP
GetWrapWidth(int32_t * aWrapColumn)981 TextEditor::GetWrapWidth(int32_t* aWrapColumn) {
982   NS_ENSURE_TRUE(aWrapColumn, NS_ERROR_NULL_POINTER);
983 
984   *aWrapColumn = mWrapColumn;
985   return NS_OK;
986 }
987 
988 //
989 // See if the style value includes this attribute, and if it does,
990 // cut out everything from the attribute to the next semicolon.
991 //
CutStyle(const char * stylename,nsString & styleValue)992 static void CutStyle(const char* stylename, nsString& styleValue) {
993   // Find the current wrapping type:
994   int32_t styleStart = styleValue.Find(stylename, true);
995   if (styleStart >= 0) {
996     int32_t styleEnd = styleValue.Find(";", false, styleStart);
997     if (styleEnd > styleStart) {
998       styleValue.Cut(styleStart, styleEnd - styleStart + 1);
999     } else {
1000       styleValue.Cut(styleStart, styleValue.Length() - styleStart);
1001     }
1002   }
1003 }
1004 
1005 NS_IMETHODIMP
SetWrapWidth(int32_t aWrapColumn)1006 TextEditor::SetWrapWidth(int32_t aWrapColumn) {
1007   SetWrapColumn(aWrapColumn);
1008 
1009   // Make sure we're a plaintext editor, otherwise we shouldn't
1010   // do the rest of this.
1011   if (!IsPlaintextEditor()) {
1012     return NS_OK;
1013   }
1014 
1015   // Ought to set a style sheet here ...
1016   // Probably should keep around an mPlaintextStyleSheet for this purpose.
1017   dom::Element* rootElement = GetRoot();
1018   NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER);
1019 
1020   // Get the current style for this root element:
1021   nsAutoString styleValue;
1022   rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue);
1023 
1024   // We'll replace styles for these values:
1025   CutStyle("white-space", styleValue);
1026   CutStyle("width", styleValue);
1027   CutStyle("font-family", styleValue);
1028 
1029   // If we have other style left, trim off any existing semicolons
1030   // or whitespace, then add a known semicolon-space:
1031   if (!styleValue.IsEmpty()) {
1032     styleValue.Trim("; \t", false, true);
1033     styleValue.AppendLiteral("; ");
1034   }
1035 
1036   // Make sure we have fixed-width font.  This should be done for us,
1037   // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;".
1038   // Only do this if we're wrapping.
1039   if (IsWrapHackEnabled() && aWrapColumn >= 0) {
1040     styleValue.AppendLiteral("font-family: -moz-fixed; ");
1041   }
1042 
1043   // and now we're ready to set the new whitespace/wrapping style.
1044   if (aWrapColumn > 0) {
1045     // Wrap to a fixed column.
1046     styleValue.AppendLiteral("white-space: pre-wrap; width: ");
1047     styleValue.AppendInt(aWrapColumn);
1048     styleValue.AppendLiteral("ch;");
1049   } else if (!aWrapColumn) {
1050     styleValue.AppendLiteral("white-space: pre-wrap;");
1051   } else {
1052     styleValue.AppendLiteral("white-space: pre;");
1053   }
1054 
1055   return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue,
1056                               true);
1057 }
1058 
1059 NS_IMETHODIMP
SetWrapColumn(int32_t aWrapColumn)1060 TextEditor::SetWrapColumn(int32_t aWrapColumn) {
1061   mWrapColumn = aWrapColumn;
1062   return NS_OK;
1063 }
1064 
1065 NS_IMETHODIMP
GetNewlineHandling(int32_t * aNewlineHandling)1066 TextEditor::GetNewlineHandling(int32_t* aNewlineHandling) {
1067   NS_ENSURE_ARG_POINTER(aNewlineHandling);
1068 
1069   *aNewlineHandling = mNewlineHandling;
1070   return NS_OK;
1071 }
1072 
1073 NS_IMETHODIMP
SetNewlineHandling(int32_t aNewlineHandling)1074 TextEditor::SetNewlineHandling(int32_t aNewlineHandling) {
1075   mNewlineHandling = aNewlineHandling;
1076 
1077   return NS_OK;
1078 }
1079 
1080 NS_IMETHODIMP
Undo(uint32_t aCount)1081 TextEditor::Undo(uint32_t aCount) {
1082   // Protect the edit rules object from dying
1083   RefPtr<TextEditRules> rules(mRules);
1084 
1085   AutoUpdateViewBatch beginViewBatching(this);
1086 
1087   CommitComposition();
1088 
1089   NotifyEditorObservers(eNotifyEditorObserversOfBefore);
1090 
1091   AutoRules beginRulesSniffing(this, EditAction::undo, nsIEditor::eNone);
1092 
1093   RulesInfo ruleInfo(EditAction::undo);
1094   RefPtr<Selection> selection = GetSelection();
1095   bool cancel, handled;
1096   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1097 
1098   if (!cancel && NS_SUCCEEDED(rv)) {
1099     rv = EditorBase::Undo(aCount);
1100     rv = rules->DidDoAction(selection, &ruleInfo, rv);
1101   }
1102 
1103   NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1104   return rv;
1105 }
1106 
1107 NS_IMETHODIMP
Redo(uint32_t aCount)1108 TextEditor::Redo(uint32_t aCount) {
1109   // Protect the edit rules object from dying
1110   RefPtr<TextEditRules> rules(mRules);
1111 
1112   AutoUpdateViewBatch beginViewBatching(this);
1113 
1114   CommitComposition();
1115 
1116   NotifyEditorObservers(eNotifyEditorObserversOfBefore);
1117 
1118   AutoRules beginRulesSniffing(this, EditAction::redo, nsIEditor::eNone);
1119 
1120   RulesInfo ruleInfo(EditAction::redo);
1121   RefPtr<Selection> selection = GetSelection();
1122   bool cancel, handled;
1123   nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1124 
1125   if (!cancel && NS_SUCCEEDED(rv)) {
1126     rv = EditorBase::Redo(aCount);
1127     rv = rules->DidDoAction(selection, &ruleInfo, rv);
1128   }
1129 
1130   NotifyEditorObservers(eNotifyEditorObserversOfEnd);
1131   return rv;
1132 }
1133 
CanCutOrCopy(PasswordFieldAllowed aPasswordFieldAllowed)1134 bool TextEditor::CanCutOrCopy(PasswordFieldAllowed aPasswordFieldAllowed) {
1135   RefPtr<Selection> selection = GetSelection();
1136   if (!selection) {
1137     return false;
1138   }
1139 
1140   if (aPasswordFieldAllowed == ePasswordFieldNotAllowed && IsPasswordEditor()) {
1141     return false;
1142   }
1143 
1144   return !selection->Collapsed();
1145 }
1146 
FireClipboardEvent(EventMessage aEventMessage,int32_t aSelectionType,bool * aActionTaken)1147 bool TextEditor::FireClipboardEvent(EventMessage aEventMessage,
1148                                     int32_t aSelectionType,
1149                                     bool* aActionTaken) {
1150   if (aEventMessage == ePaste) {
1151     CommitComposition();
1152   }
1153 
1154   nsCOMPtr<nsIPresShell> presShell = GetPresShell();
1155   NS_ENSURE_TRUE(presShell, false);
1156 
1157   RefPtr<Selection> selection = GetSelection();
1158   if (!selection) {
1159     return false;
1160   }
1161 
1162   if (!nsCopySupport::FireClipboardEvent(aEventMessage, aSelectionType,
1163                                          presShell, selection, aActionTaken)) {
1164     return false;
1165   }
1166 
1167   // If the event handler caused the editor to be destroyed, return false.
1168   // Otherwise return true to indicate that the event was not cancelled.
1169   return !mDidPreDestroy;
1170 }
1171 
1172 NS_IMETHODIMP
Cut()1173 TextEditor::Cut() {
1174   bool actionTaken = false;
1175   if (FireClipboardEvent(eCut, nsIClipboard::kGlobalClipboard, &actionTaken)) {
1176     DeleteSelection(eNone, eStrip);
1177   }
1178   return actionTaken ? NS_OK : NS_ERROR_FAILURE;
1179 }
1180 
1181 NS_IMETHODIMP
CanCut(bool * aCanCut)1182 TextEditor::CanCut(bool* aCanCut) {
1183   NS_ENSURE_ARG_POINTER(aCanCut);
1184   // Cut is always enabled in HTML documents
1185   nsCOMPtr<nsIDocument> doc = GetDocument();
1186   *aCanCut = (doc && doc->IsHTMLOrXHTML()) ||
1187              (IsModifiable() && CanCutOrCopy(ePasswordFieldNotAllowed));
1188   return NS_OK;
1189 }
1190 
1191 NS_IMETHODIMP
Copy()1192 TextEditor::Copy() {
1193   bool actionTaken = false;
1194   FireClipboardEvent(eCopy, nsIClipboard::kGlobalClipboard, &actionTaken);
1195 
1196   return actionTaken ? NS_OK : NS_ERROR_FAILURE;
1197 }
1198 
1199 NS_IMETHODIMP
CanCopy(bool * aCanCopy)1200 TextEditor::CanCopy(bool* aCanCopy) {
1201   NS_ENSURE_ARG_POINTER(aCanCopy);
1202   // Copy is always enabled in HTML documents
1203   nsCOMPtr<nsIDocument> doc = GetDocument();
1204   *aCanCopy =
1205       (doc && doc->IsHTMLOrXHTML()) || CanCutOrCopy(ePasswordFieldNotAllowed);
1206   return NS_OK;
1207 }
1208 
1209 NS_IMETHODIMP
CanDelete(bool * aCanDelete)1210 TextEditor::CanDelete(bool* aCanDelete) {
1211   NS_ENSURE_ARG_POINTER(aCanDelete);
1212   *aCanDelete = IsModifiable() && CanCutOrCopy(ePasswordFieldAllowed);
1213   return NS_OK;
1214 }
1215 
1216 // Shared between OutputToString and OutputToStream
GetAndInitDocEncoder(const nsAString & aFormatType,uint32_t aFlags,const nsACString & aCharset)1217 already_AddRefed<nsIDocumentEncoder> TextEditor::GetAndInitDocEncoder(
1218     const nsAString& aFormatType, uint32_t aFlags, const nsACString& aCharset) {
1219   nsCOMPtr<nsIDocumentEncoder> docEncoder;
1220   if (!mCachedDocumentEncoder ||
1221       !mCachedDocumentEncoderType.Equals(aFormatType)) {
1222     nsAutoCString formatType(NS_DOC_ENCODER_CONTRACTID_BASE);
1223     LossyAppendUTF16toASCII(aFormatType, formatType);
1224     docEncoder = do_CreateInstance(formatType.get());
1225     if (NS_WARN_IF(!docEncoder)) {
1226       return nullptr;
1227     }
1228     mCachedDocumentEncoder = docEncoder;
1229     mCachedDocumentEncoderType = aFormatType;
1230   } else {
1231     docEncoder = mCachedDocumentEncoder;
1232   }
1233 
1234   nsCOMPtr<nsIDocument> doc = GetDocument();
1235   NS_ASSERTION(doc, "Need a document");
1236 
1237   nsresult rv = docEncoder->NativeInit(
1238       doc, aFormatType, aFlags | nsIDocumentEncoder::RequiresReinitAfterOutput);
1239   if (NS_WARN_IF(NS_FAILED(rv))) {
1240     return nullptr;
1241   }
1242 
1243   if (!aCharset.IsEmpty() && !aCharset.EqualsLiteral("null")) {
1244     docEncoder->SetCharset(aCharset);
1245   }
1246 
1247   int32_t wc;
1248   (void)GetWrapWidth(&wc);
1249   if (wc >= 0) {
1250     (void)docEncoder->SetWrapColumn(wc);
1251   }
1252 
1253   // Set the selection, if appropriate.
1254   // We do this either if the OutputSelectionOnly flag is set,
1255   // in which case we use our existing selection ...
1256   if (aFlags & nsIDocumentEncoder::OutputSelectionOnly) {
1257     RefPtr<Selection> selection = GetSelection();
1258     if (NS_WARN_IF(!selection)) {
1259       return nullptr;
1260     }
1261     rv = docEncoder->SetSelection(selection);
1262     if (NS_WARN_IF(NS_FAILED(rv))) {
1263       return nullptr;
1264     }
1265   }
1266   // ... or if the root element is not a body,
1267   // in which case we set the selection to encompass the root.
1268   else {
1269     dom::Element* rootElement = GetRoot();
1270     if (NS_WARN_IF(!rootElement)) {
1271       return nullptr;
1272     }
1273     if (!rootElement->IsHTMLElement(nsGkAtoms::body)) {
1274       rv = docEncoder->SetNativeContainerNode(rootElement);
1275       if (NS_WARN_IF(NS_FAILED(rv))) {
1276         return nullptr;
1277       }
1278     }
1279   }
1280 
1281   return docEncoder.forget();
1282 }
1283 
1284 NS_IMETHODIMP
OutputToString(const nsAString & aFormatType,uint32_t aFlags,nsAString & aOutputString)1285 TextEditor::OutputToString(const nsAString& aFormatType, uint32_t aFlags,
1286                            nsAString& aOutputString) {
1287   // Protect the edit rules object from dying
1288   RefPtr<TextEditRules> rules(mRules);
1289 
1290   nsString resultString;
1291   RulesInfo ruleInfo(EditAction::outputText);
1292   ruleInfo.outString = &resultString;
1293   ruleInfo.flags = aFlags;
1294   // XXX Struct should store a nsAReadable*
1295   nsAutoString str(aFormatType);
1296   ruleInfo.outputFormat = &str;
1297   bool cancel, handled;
1298   nsresult rv = rules->WillDoAction(nullptr, &ruleInfo, &cancel, &handled);
1299   if (cancel || NS_FAILED(rv)) {
1300     return rv;
1301   }
1302   if (handled) {
1303     // This case will get triggered by password fields.
1304     aOutputString.Assign(*(ruleInfo.outString));
1305     return rv;
1306   }
1307 
1308   nsAutoCString charsetStr;
1309   rv = GetDocumentCharacterSet(charsetStr);
1310   if (NS_FAILED(rv) || charsetStr.IsEmpty()) {
1311     charsetStr.AssignLiteral("windows-1252");
1312   }
1313 
1314   nsCOMPtr<nsIDocumentEncoder> encoder =
1315       GetAndInitDocEncoder(aFormatType, aFlags, charsetStr);
1316   if (NS_WARN_IF(!encoder)) {
1317     return NS_ERROR_FAILURE;
1318   }
1319 
1320   return encoder->EncodeToString(aOutputString);
1321 }
1322 
1323 NS_IMETHODIMP
OutputToStream(nsIOutputStream * aOutputStream,const nsAString & aFormatType,const nsACString & aCharset,uint32_t aFlags)1324 TextEditor::OutputToStream(nsIOutputStream* aOutputStream,
1325                            const nsAString& aFormatType,
1326                            const nsACString& aCharset, uint32_t aFlags) {
1327   nsresult rv;
1328 
1329   // special-case for empty document when requesting plain text,
1330   // to account for the bogus text node.
1331   // XXX Should there be a similar test in OutputToString?
1332   if (aFormatType.EqualsLiteral("text/plain")) {
1333     bool docEmpty;
1334     rv = GetDocumentIsEmpty(&docEmpty);
1335     NS_ENSURE_SUCCESS(rv, rv);
1336 
1337     if (docEmpty) {
1338       return NS_OK;  // Output nothing.
1339     }
1340   }
1341 
1342   nsCOMPtr<nsIDocumentEncoder> encoder =
1343       GetAndInitDocEncoder(aFormatType, aFlags, aCharset);
1344   if (NS_WARN_IF(!encoder)) {
1345     return NS_ERROR_FAILURE;
1346   }
1347 
1348   return encoder->EncodeToStream(aOutputStream);
1349 }
1350 
1351 NS_IMETHODIMP
InsertTextWithQuotations(const nsAString & aStringToInsert)1352 TextEditor::InsertTextWithQuotations(const nsAString& aStringToInsert) {
1353   return InsertText(aStringToInsert);
1354 }
1355 
1356 NS_IMETHODIMP
PasteAsQuotation(int32_t aSelectionType)1357 TextEditor::PasteAsQuotation(int32_t aSelectionType) {
1358   // Get Clipboard Service
1359   nsresult rv;
1360   nsCOMPtr<nsIClipboard> clipboard(
1361       do_GetService("@mozilla.org/widget/clipboard;1", &rv));
1362   NS_ENSURE_SUCCESS(rv, rv);
1363 
1364   // Get the nsITransferable interface for getting the data from the clipboard
1365   nsCOMPtr<nsITransferable> trans;
1366   rv = PrepareTransferable(getter_AddRefs(trans));
1367   if (NS_SUCCEEDED(rv) && trans) {
1368     // Get the Data from the clipboard
1369     clipboard->GetData(trans, aSelectionType);
1370 
1371     // Now we ask the transferable for the data
1372     // it still owns the data, we just have a pointer to it.
1373     // If it can't support a "text" output of the data the call will fail
1374     nsCOMPtr<nsISupports> genericDataObj;
1375     uint32_t len;
1376     nsAutoCString flav;
1377     rv = trans->GetAnyTransferData(flav, getter_AddRefs(genericDataObj), &len);
1378     if (NS_FAILED(rv)) {
1379       return rv;
1380     }
1381 
1382     if (flav.EqualsLiteral(kUnicodeMime) ||
1383         flav.EqualsLiteral(kMozTextInternal)) {
1384       nsCOMPtr<nsISupportsString> textDataObj(
1385           do_QueryInterface(genericDataObj));
1386       if (textDataObj && len > 0) {
1387         nsAutoString stuffToPaste;
1388         textDataObj->GetData(stuffToPaste);
1389         AutoPlaceholderBatch beginBatching(this);
1390         rv = InsertAsQuotation(stuffToPaste, 0);
1391       }
1392     }
1393   }
1394 
1395   return rv;
1396 }
1397 
1398 NS_IMETHODIMP
InsertAsQuotation(const nsAString & aQuotedText,nsIDOMNode ** aNodeInserted)1399 TextEditor::InsertAsQuotation(const nsAString& aQuotedText,
1400                               nsIDOMNode** aNodeInserted) {
1401   // Protect the edit rules object from dying
1402   RefPtr<TextEditRules> rules(mRules);
1403 
1404   // Let the citer quote it for us:
1405   nsString quotedStuff;
1406   nsresult rv = InternetCiter::GetCiteString(aQuotedText, quotedStuff);
1407   NS_ENSURE_SUCCESS(rv, rv);
1408 
1409   // It's best to put a blank line after the quoted text so that mails
1410   // written without thinking won't be so ugly.
1411   if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != char16_t('\n'))) {
1412     quotedStuff.Append(char16_t('\n'));
1413   }
1414 
1415   // get selection
1416   RefPtr<Selection> selection = GetSelection();
1417   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1418 
1419   AutoPlaceholderBatch beginBatching(this);
1420   AutoRules beginRulesSniffing(this, EditAction::insertText, nsIEditor::eNext);
1421 
1422   // give rules a chance to handle or cancel
1423   RulesInfo ruleInfo(EditAction::insertElement);
1424   bool cancel, handled;
1425   rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
1426   NS_ENSURE_SUCCESS(rv, rv);
1427   if (cancel) {
1428     return NS_OK;  // Rules canceled the operation.
1429   }
1430   if (!handled) {
1431     rv = InsertText(quotedStuff);
1432 
1433     // XXX Should set *aNodeInserted to the first node inserted
1434     if (aNodeInserted && NS_SUCCEEDED(rv)) {
1435       *aNodeInserted = nullptr;
1436     }
1437   }
1438   return rv;
1439 }
1440 
1441 NS_IMETHODIMP
PasteAsCitedQuotation(const nsAString & aCitation,int32_t aSelectionType)1442 TextEditor::PasteAsCitedQuotation(const nsAString& aCitation,
1443                                   int32_t aSelectionType) {
1444   return NS_ERROR_NOT_IMPLEMENTED;
1445 }
1446 
1447 NS_IMETHODIMP
InsertAsCitedQuotation(const nsAString & aQuotedText,const nsAString & aCitation,bool aInsertHTML,nsIDOMNode ** aNodeInserted)1448 TextEditor::InsertAsCitedQuotation(const nsAString& aQuotedText,
1449                                    const nsAString& aCitation, bool aInsertHTML,
1450                                    nsIDOMNode** aNodeInserted) {
1451   return InsertAsQuotation(aQuotedText, aNodeInserted);
1452 }
1453 
SharedOutputString(uint32_t aFlags,bool * aIsCollapsed,nsAString & aResult)1454 nsresult TextEditor::SharedOutputString(uint32_t aFlags, bool* aIsCollapsed,
1455                                         nsAString& aResult) {
1456   RefPtr<Selection> selection = GetSelection();
1457   NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
1458 
1459   *aIsCollapsed = selection->Collapsed();
1460 
1461   if (!*aIsCollapsed) {
1462     aFlags |= nsIDocumentEncoder::OutputSelectionOnly;
1463   }
1464   // If the selection isn't collapsed, we'll use the whole document.
1465 
1466   return OutputToString(NS_LITERAL_STRING("text/plain"), aFlags, aResult);
1467 }
1468 
1469 NS_IMETHODIMP
Rewrap(bool aRespectNewlines)1470 TextEditor::Rewrap(bool aRespectNewlines) {
1471   int32_t wrapCol;
1472   nsresult rv = GetWrapWidth(&wrapCol);
1473   NS_ENSURE_SUCCESS(rv, NS_OK);
1474 
1475   // Rewrap makes no sense if there's no wrap column; default to 72.
1476   if (wrapCol <= 0) {
1477     wrapCol = 72;
1478   }
1479 
1480   nsAutoString current;
1481   bool isCollapsed;
1482   rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted |
1483                               nsIDocumentEncoder::OutputLFLineBreak,
1484                           &isCollapsed, current);
1485   NS_ENSURE_SUCCESS(rv, rv);
1486 
1487   nsString wrapped;
1488   uint32_t firstLineOffset =
1489       0;  // XXX need to reset this if there is a selection
1490   rv = InternetCiter::Rewrap(current, wrapCol, firstLineOffset,
1491                              aRespectNewlines, wrapped);
1492   NS_ENSURE_SUCCESS(rv, rv);
1493 
1494   if (isCollapsed) {
1495     SelectAll();
1496   }
1497 
1498   return InsertTextWithQuotations(wrapped);
1499 }
1500 
1501 NS_IMETHODIMP
StripCites()1502 TextEditor::StripCites() {
1503   nsAutoString current;
1504   bool isCollapsed;
1505   nsresult rv = SharedOutputString(nsIDocumentEncoder::OutputFormatted,
1506                                    &isCollapsed, current);
1507   NS_ENSURE_SUCCESS(rv, rv);
1508 
1509   nsString stripped;
1510   rv = InternetCiter::StripCites(current, stripped);
1511   NS_ENSURE_SUCCESS(rv, rv);
1512 
1513   if (isCollapsed) {
1514     rv = SelectAll();
1515     NS_ENSURE_SUCCESS(rv, rv);
1516   }
1517 
1518   return InsertText(stripped);
1519 }
1520 
1521 NS_IMETHODIMP
GetEmbeddedObjects(nsIArray ** aNodeList)1522 TextEditor::GetEmbeddedObjects(nsIArray** aNodeList) {
1523   if (NS_WARN_IF(!aNodeList)) {
1524     return NS_ERROR_INVALID_ARG;
1525   }
1526 
1527   *aNodeList = nullptr;
1528   return NS_OK;
1529 }
1530 
1531 /**
1532  * All editor operations which alter the doc should be prefaced
1533  * with a call to StartOperation, naming the action and direction.
1534  */
1535 NS_IMETHODIMP
StartOperation(EditAction opID,nsIEditor::EDirection aDirection)1536 TextEditor::StartOperation(EditAction opID, nsIEditor::EDirection aDirection) {
1537   // Protect the edit rules object from dying
1538   RefPtr<TextEditRules> rules(mRules);
1539 
1540   EditorBase::StartOperation(opID, aDirection);  // will set mAction, mDirection
1541   if (rules) {
1542     return rules->BeforeEdit(mAction, mDirection);
1543   }
1544   return NS_OK;
1545 }
1546 
1547 /**
1548  * All editor operations which alter the doc should be followed
1549  * with a call to EndOperation.
1550  */
1551 NS_IMETHODIMP
EndOperation()1552 TextEditor::EndOperation() {
1553   // Protect the edit rules object from dying
1554   RefPtr<TextEditRules> rules(mRules);
1555 
1556   // post processing
1557   nsresult rv = rules ? rules->AfterEdit(mAction, mDirection) : NS_OK;
1558   EditorBase::EndOperation();  // will clear mAction, mDirection
1559   return rv;
1560 }
1561 
SelectEntireDocument(Selection * aSelection)1562 nsresult TextEditor::SelectEntireDocument(Selection* aSelection) {
1563   if (!aSelection || !mRules) {
1564     return NS_ERROR_NULL_POINTER;
1565   }
1566 
1567   // Protect the edit rules object from dying
1568   RefPtr<TextEditRules> rules(mRules);
1569 
1570   // is doc empty?
1571   if (rules->DocumentIsEmpty()) {
1572     // get root node
1573     nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(GetRoot());
1574     NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
1575 
1576     // if it's empty don't select entire doc - that would select the bogus node
1577     return aSelection->Collapse(rootElement, 0);
1578   }
1579 
1580   SelectionBatcher selectionBatcher(aSelection);
1581   nsresult rv = EditorBase::SelectEntireDocument(aSelection);
1582   NS_ENSURE_SUCCESS(rv, rv);
1583 
1584   // Don't select the trailing BR node if we have one
1585   nsCOMPtr<nsIContent> childNode;
1586   rv = GetEndChildNode(aSelection, getter_AddRefs(childNode));
1587   if (NS_WARN_IF(NS_FAILED(rv))) {
1588     return rv;
1589   }
1590   if (childNode) {
1591     childNode = childNode->GetPreviousSibling();
1592   }
1593 
1594   if (childNode && TextEditUtils::IsMozBR(childNode)) {
1595     int32_t parentOffset;
1596     nsINode* parentNode = GetNodeLocation(childNode, &parentOffset);
1597 
1598     return aSelection->Extend(parentNode, parentOffset);
1599   }
1600 
1601   return NS_OK;
1602 }
1603 
GetDOMEventTarget()1604 EventTarget* TextEditor::GetDOMEventTarget() { return mEventTarget; }
1605 
SetAttributeOrEquivalent(Element * aElement,nsAtom * aAttribute,const nsAString & aValue,bool aSuppressTransaction)1606 nsresult TextEditor::SetAttributeOrEquivalent(Element* aElement,
1607                                               nsAtom* aAttribute,
1608                                               const nsAString& aValue,
1609                                               bool aSuppressTransaction) {
1610   return EditorBase::SetAttribute(aElement, aAttribute, aValue);
1611 }
1612 
RemoveAttributeOrEquivalent(Element * aElement,nsAtom * aAttribute,bool aSuppressTransaction)1613 nsresult TextEditor::RemoveAttributeOrEquivalent(Element* aElement,
1614                                                  nsAtom* aAttribute,
1615                                                  bool aSuppressTransaction) {
1616   return EditorBase::RemoveAttribute(aElement, aAttribute);
1617 }
1618 
1619 }  // namespace mozilla
1620