editor/libeditor/text/nsPlaintextEditor.cpp

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

mercurial