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