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