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