Bumping manifests a=b2g-bump
[gecko.git] / editor / libeditor / nsTextEditRules.cpp
blob97cdfbba93e14d6c913a96571d21514b42b35677
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/. */
6 #include "mozilla/Assertions.h"
7 #include "mozilla/LookAndFeel.h"
8 #include "mozilla/Preferences.h"
9 #include "mozilla/dom/Selection.h"
10 #include "mozilla/TextComposition.h"
11 #include "mozilla/dom/Element.h"
12 #include "nsAString.h"
13 #include "nsAutoPtr.h"
14 #include "nsCOMPtr.h"
15 #include "nsCRT.h"
16 #include "nsCRTGlue.h"
17 #include "nsComponentManagerUtils.h"
18 #include "nsContentUtils.h"
19 #include "nsDebug.h"
20 #include "nsEditor.h"
21 #include "nsEditorUtils.h"
22 #include "nsError.h"
23 #include "nsGkAtoms.h"
24 #include "nsIContent.h"
25 #include "nsIDOMCharacterData.h"
26 #include "nsIDOMDocument.h"
27 #include "nsIDOMElement.h"
28 #include "nsIDOMNode.h"
29 #include "nsIDOMNodeFilter.h"
30 #include "nsIDOMNodeIterator.h"
31 #include "nsIDOMNodeList.h"
32 #include "nsIDOMText.h"
33 #include "nsNameSpaceManager.h"
34 #include "nsINode.h"
35 #include "nsIPlaintextEditor.h"
36 #include "nsISelection.h"
37 #include "nsISelectionPrivate.h"
38 #include "nsISupportsBase.h"
39 #include "nsLiteralString.h"
40 #include "mozilla/dom/NodeIterator.h"
41 #include "nsTextEditRules.h"
42 #include "nsTextEditUtils.h"
43 #include "nsUnicharUtils.h"
45 using namespace mozilla;
46 using namespace mozilla::dom;
48 #define CANCEL_OPERATION_IF_READONLY_OR_DISABLED \
49 if (IsReadonly() || IsDisabled()) \
50 { \
51 *aCancel = true; \
52 return NS_OK; \
56 /********************************************************
57 * Constructor/Destructor
58 ********************************************************/
60 nsTextEditRules::nsTextEditRules()
62 InitFields();
65 void
66 nsTextEditRules::InitFields()
68 mEditor = nullptr;
69 mPasswordText.Truncate();
70 mPasswordIMEText.Truncate();
71 mPasswordIMEIndex = 0;
72 mBogusNode = nullptr;
73 mCachedSelectionNode = nullptr;
74 mCachedSelectionOffset = 0;
75 mActionNesting = 0;
76 mLockRulesSniffing = false;
77 mDidExplicitlySetInterline = false;
78 mDeleteBidiImmediately = false;
79 mTheAction = EditAction::none;
80 mTimer = nullptr;
81 mLastStart = 0;
82 mLastLength = 0;
85 nsTextEditRules::~nsTextEditRules()
87 // do NOT delete mEditor here. We do not hold a ref count to mEditor. mEditor owns our lifespan.
89 if (mTimer)
90 mTimer->Cancel();
93 /********************************************************
94 * XPCOM Cruft
95 ********************************************************/
97 NS_IMPL_CYCLE_COLLECTION(nsTextEditRules, mBogusNode, mCachedSelectionNode)
99 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTextEditRules)
100 NS_INTERFACE_MAP_ENTRY(nsIEditRules)
101 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
102 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditRules)
103 NS_INTERFACE_MAP_END
105 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextEditRules)
106 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextEditRules)
108 /********************************************************
109 * Public methods
110 ********************************************************/
112 NS_IMETHODIMP
113 nsTextEditRules::Init(nsPlaintextEditor *aEditor)
115 if (!aEditor) { return NS_ERROR_NULL_POINTER; }
117 InitFields();
119 mEditor = aEditor; // we hold a non-refcounted reference back to our editor
120 nsCOMPtr<nsISelection> selection;
121 mEditor->GetSelection(getter_AddRefs(selection));
122 NS_WARN_IF_FALSE(selection, "editor cannot get selection");
124 // Put in a magic br if needed. This method handles null selection,
125 // which should never happen anyway
126 nsresult res = CreateBogusNodeIfNeeded(selection);
127 NS_ENSURE_SUCCESS(res, res);
129 // If the selection hasn't been set up yet, set it up collapsed to the end of
130 // our editable content.
131 int32_t rangeCount;
132 res = selection->GetRangeCount(&rangeCount);
133 NS_ENSURE_SUCCESS(res, res);
134 if (!rangeCount) {
135 res = mEditor->EndOfDocument();
136 NS_ENSURE_SUCCESS(res, res);
139 if (IsPlaintextEditor())
141 // ensure trailing br node
142 res = CreateTrailingBRIfNeeded();
143 NS_ENSURE_SUCCESS(res, res);
146 mDeleteBidiImmediately =
147 Preferences::GetBool("bidi.edit.delete_immediately", false);
149 return res;
152 NS_IMETHODIMP
153 nsTextEditRules::SetInitialValue(const nsAString& aValue)
155 if (IsPasswordEditor()) {
156 mPasswordText = aValue;
158 return NS_OK;
161 NS_IMETHODIMP
162 nsTextEditRules::DetachEditor()
164 if (mTimer)
165 mTimer->Cancel();
167 mEditor = nullptr;
168 return NS_OK;
171 NS_IMETHODIMP
172 nsTextEditRules::BeforeEdit(EditAction action,
173 nsIEditor::EDirection aDirection)
175 if (mLockRulesSniffing) return NS_OK;
177 nsAutoLockRulesSniffing lockIt(this);
178 mDidExplicitlySetInterline = false;
179 if (!mActionNesting)
181 // let rules remember the top level action
182 mTheAction = action;
184 mActionNesting++;
186 // get the selection and cache the position before editing
187 nsCOMPtr<nsISelection> selection;
188 NS_ENSURE_STATE(mEditor);
189 nsresult res = mEditor->GetSelection(getter_AddRefs(selection));
190 NS_ENSURE_SUCCESS(res, res);
192 selection->GetAnchorNode(getter_AddRefs(mCachedSelectionNode));
193 selection->GetAnchorOffset(&mCachedSelectionOffset);
195 return NS_OK;
199 NS_IMETHODIMP
200 nsTextEditRules::AfterEdit(EditAction action,
201 nsIEditor::EDirection aDirection)
203 if (mLockRulesSniffing) return NS_OK;
205 nsAutoLockRulesSniffing lockIt(this);
207 NS_PRECONDITION(mActionNesting>0, "bad action nesting!");
208 nsresult res = NS_OK;
209 if (!--mActionNesting)
211 nsCOMPtr<nsISelection>selection;
212 NS_ENSURE_STATE(mEditor);
213 res = mEditor->GetSelection(getter_AddRefs(selection));
214 NS_ENSURE_SUCCESS(res, res);
216 NS_ENSURE_STATE(mEditor);
217 res = mEditor->HandleInlineSpellCheck(action, selection,
218 mCachedSelectionNode, mCachedSelectionOffset,
219 nullptr, 0, nullptr, 0);
220 NS_ENSURE_SUCCESS(res, res);
222 // if only trailing <br> remaining remove it
223 res = RemoveRedundantTrailingBR();
224 if (NS_FAILED(res))
225 return res;
227 // detect empty doc
228 res = CreateBogusNodeIfNeeded(selection);
229 NS_ENSURE_SUCCESS(res, res);
231 // ensure trailing br node
232 res = CreateTrailingBRIfNeeded();
233 NS_ENSURE_SUCCESS(res, res);
235 // collapse the selection to the trailing BR if it's at the end of our text node
236 CollapseSelectionToTrailingBRIfNeeded(selection);
238 return res;
242 NS_IMETHODIMP
243 nsTextEditRules::WillDoAction(Selection* aSelection,
244 nsRulesInfo* aInfo,
245 bool* aCancel,
246 bool* aHandled)
248 // null selection is legal
249 MOZ_ASSERT(aInfo && aCancel && aHandled);
251 *aCancel = false;
252 *aHandled = false;
254 // my kingdom for dynamic cast
255 nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
257 switch (info->action) {
258 case EditAction::insertBreak:
259 return WillInsertBreak(aSelection, aCancel, aHandled, info->maxLength);
260 case EditAction::insertText:
261 case EditAction::insertIMEText:
262 return WillInsertText(info->action, aSelection, aCancel, aHandled,
263 info->inString, info->outString, info->maxLength);
264 case EditAction::deleteSelection:
265 return WillDeleteSelection(aSelection, info->collapsedAction,
266 aCancel, aHandled);
267 case EditAction::undo:
268 return WillUndo(aSelection, aCancel, aHandled);
269 case EditAction::redo:
270 return WillRedo(aSelection, aCancel, aHandled);
271 case EditAction::setTextProperty:
272 return WillSetTextProperty(aSelection, aCancel, aHandled);
273 case EditAction::removeTextProperty:
274 return WillRemoveTextProperty(aSelection, aCancel, aHandled);
275 case EditAction::outputText:
276 return WillOutputText(aSelection, info->outputFormat, info->outString,
277 aCancel, aHandled);
278 case EditAction::insertElement:
279 // i had thought this would be html rules only. but we put pre elements
280 // into plaintext mail when doing quoting for reply! doh!
281 return WillInsert(aSelection, aCancel);
282 default:
283 return NS_ERROR_FAILURE;
287 NS_IMETHODIMP
288 nsTextEditRules::DidDoAction(nsISelection *aSelection,
289 nsRulesInfo *aInfo, nsresult aResult)
291 NS_ENSURE_STATE(mEditor);
292 // don't let any txns in here move the selection around behind our back.
293 // Note that this won't prevent explicit selection setting from working.
294 nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
296 NS_ENSURE_TRUE(aSelection && aInfo, NS_ERROR_NULL_POINTER);
298 // my kingdom for dynamic cast
299 nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
301 switch (info->action)
303 case EditAction::insertBreak:
304 return DidInsertBreak(aSelection, aResult);
305 case EditAction::insertText:
306 case EditAction::insertIMEText:
307 return DidInsertText(aSelection, aResult);
308 case EditAction::deleteSelection:
309 return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
310 case EditAction::undo:
311 return DidUndo(aSelection, aResult);
312 case EditAction::redo:
313 return DidRedo(aSelection, aResult);
314 case EditAction::setTextProperty:
315 return DidSetTextProperty(aSelection, aResult);
316 case EditAction::removeTextProperty:
317 return DidRemoveTextProperty(aSelection, aResult);
318 case EditAction::outputText:
319 return DidOutputText(aSelection, aResult);
320 default:
321 // Don't fail on transactions we don't handle here!
322 return NS_OK;
327 NS_IMETHODIMP
328 nsTextEditRules::DocumentIsEmpty(bool *aDocumentIsEmpty)
330 NS_ENSURE_TRUE(aDocumentIsEmpty, NS_ERROR_NULL_POINTER);
332 *aDocumentIsEmpty = (mBogusNode != nullptr);
333 return NS_OK;
336 /********************************************************
337 * Protected methods
338 ********************************************************/
341 nsresult
342 nsTextEditRules::WillInsert(nsISelection *aSelection, bool *aCancel)
344 NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER);
346 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
348 // initialize out param
349 *aCancel = false;
351 // check for the magic content node and delete it if it exists
352 if (mBogusNode)
354 NS_ENSURE_STATE(mEditor);
355 mEditor->DeleteNode(mBogusNode);
356 mBogusNode = nullptr;
359 return NS_OK;
362 nsresult
363 nsTextEditRules::DidInsert(nsISelection *aSelection, nsresult aResult)
365 return NS_OK;
368 nsresult
369 nsTextEditRules::WillInsertBreak(Selection* aSelection,
370 bool *aCancel,
371 bool *aHandled,
372 int32_t aMaxLength)
374 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
375 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
376 *aHandled = false;
377 if (IsSingleLineEditor()) {
378 *aCancel = true;
380 else
382 // handle docs with a max length
383 // NOTE, this function copies inString into outString for us.
384 NS_NAMED_LITERAL_STRING(inString, "\n");
385 nsAutoString outString;
386 bool didTruncate;
387 nsresult res = TruncateInsertionIfNeeded(aSelection, &inString, &outString,
388 aMaxLength, &didTruncate);
389 NS_ENSURE_SUCCESS(res, res);
390 if (didTruncate) {
391 *aCancel = true;
392 return NS_OK;
395 *aCancel = false;
397 // if the selection isn't collapsed, delete it.
398 bool bCollapsed;
399 res = aSelection->GetIsCollapsed(&bCollapsed);
400 NS_ENSURE_SUCCESS(res, res);
401 if (!bCollapsed)
403 NS_ENSURE_STATE(mEditor);
404 res = mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
405 NS_ENSURE_SUCCESS(res, res);
408 res = WillInsert(aSelection, aCancel);
409 NS_ENSURE_SUCCESS(res, res);
410 // initialize out param
411 // we want to ignore result of WillInsert()
412 *aCancel = false;
415 return NS_OK;
418 nsresult
419 nsTextEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult)
421 return NS_OK;
424 nsresult
425 nsTextEditRules::CollapseSelectionToTrailingBRIfNeeded(nsISelection* aSelection)
427 // we only need to execute the stuff below if we are a plaintext editor.
428 // html editors have a different mechanism for putting in mozBR's
429 // (because there are a bunch more places you have to worry about it in html)
430 if (!IsPlaintextEditor()) {
431 return NS_OK;
434 // if we are at the end of the textarea, we need to set the
435 // selection to stick to the mozBR at the end of the textarea.
436 int32_t selOffset;
437 nsCOMPtr<nsIDOMNode> selNode;
438 nsresult res;
439 NS_ENSURE_STATE(mEditor);
440 res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
441 NS_ENSURE_SUCCESS(res, res);
443 nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(selNode);
444 if (!nodeAsText) return NS_OK; // nothing to do if we're not at a text node
446 uint32_t length;
447 res = nodeAsText->GetLength(&length);
448 NS_ENSURE_SUCCESS(res, res);
450 // nothing to do if we're not at the end of the text node
451 if (selOffset != int32_t(length))
452 return NS_OK;
454 int32_t parentOffset;
455 nsCOMPtr<nsIDOMNode> parentNode = nsEditor::GetNodeLocation(selNode, &parentOffset);
457 NS_ENSURE_STATE(mEditor);
458 nsCOMPtr<nsIDOMNode> root = do_QueryInterface(mEditor->GetRoot());
459 NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER);
460 if (parentNode != root) return NS_OK;
462 nsCOMPtr<nsIDOMNode> nextNode = mEditor->GetChildAt(parentNode,
463 parentOffset + 1);
464 if (nextNode && nsTextEditUtils::IsMozBR(nextNode))
466 res = aSelection->Collapse(parentNode, parentOffset + 1);
467 NS_ENSURE_SUCCESS(res, res);
469 return res;
472 static inline already_AddRefed<nsIDOMNode>
473 GetTextNode(nsISelection *selection, nsEditor *editor) {
474 int32_t selOffset;
475 nsCOMPtr<nsIDOMNode> selNode;
476 nsresult res = editor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
477 NS_ENSURE_SUCCESS(res, nullptr);
478 if (!editor->IsTextNode(selNode)) {
479 // Get an nsINode from the nsIDOMNode
480 nsCOMPtr<nsINode> node = do_QueryInterface(selNode);
481 // if node is null, return it to indicate there's no text
482 NS_ENSURE_TRUE(node, nullptr);
483 // This should be the root node, walk the tree looking for text nodes
484 NodeFilterHolder filter;
485 nsRefPtr<NodeIterator> iter = new NodeIterator(node, nsIDOMNodeFilter::SHOW_TEXT, filter);
486 while (!editor->IsTextNode(selNode)) {
487 if (NS_FAILED(res = iter->NextNode(getter_AddRefs(selNode))) || !selNode) {
488 return nullptr;
492 return selNode.forget();
494 #ifdef DEBUG
495 #define ASSERT_PASSWORD_LENGTHS_EQUAL() \
496 if (IsPasswordEditor() && mEditor->GetRoot()) { \
497 int32_t txtLen; \
498 mEditor->GetTextLength(&txtLen); \
499 NS_ASSERTION(mPasswordText.Length() == uint32_t(txtLen), \
500 "password length not equal to number of asterisks"); \
502 #else
503 #define ASSERT_PASSWORD_LENGTHS_EQUAL()
504 #endif
506 // static
507 void
508 nsTextEditRules::HandleNewLines(nsString &aString,
509 int32_t aNewlineHandling)
511 if (aNewlineHandling < 0) {
512 int32_t caretStyle;
513 nsPlaintextEditor::GetDefaultEditorPrefs(aNewlineHandling, caretStyle);
516 switch(aNewlineHandling)
518 case nsIPlaintextEditor::eNewlinesReplaceWithSpaces:
519 // Strip trailing newlines first so we don't wind up with trailing spaces
520 aString.Trim(CRLF, false, true);
521 aString.ReplaceChar(CRLF, ' ');
522 break;
523 case nsIPlaintextEditor::eNewlinesStrip:
524 aString.StripChars(CRLF);
525 break;
526 case nsIPlaintextEditor::eNewlinesPasteToFirst:
527 default:
529 int32_t firstCRLF = aString.FindCharInSet(CRLF);
531 // we get first *non-empty* line.
532 int32_t offset = 0;
533 while (firstCRLF == offset)
535 offset++;
536 firstCRLF = aString.FindCharInSet(CRLF, offset);
538 if (firstCRLF > 0)
539 aString.Truncate(firstCRLF);
540 if (offset > 0)
541 aString.Cut(0, offset);
543 break;
544 case nsIPlaintextEditor::eNewlinesReplaceWithCommas:
545 aString.Trim(CRLF, true, true);
546 aString.ReplaceChar(CRLF, ',');
547 break;
548 case nsIPlaintextEditor::eNewlinesStripSurroundingWhitespace:
550 nsString result;
551 uint32_t offset = 0;
552 while (offset < aString.Length())
554 int32_t nextCRLF = aString.FindCharInSet(CRLF, offset);
555 if (nextCRLF < 0) {
556 result.Append(nsDependentSubstring(aString, offset));
557 break;
559 uint32_t wsBegin = nextCRLF;
560 // look backwards for the first non-whitespace char
561 while (wsBegin > offset && NS_IS_SPACE(aString[wsBegin - 1]))
562 --wsBegin;
563 result.Append(nsDependentSubstring(aString, offset, wsBegin - offset));
564 offset = nextCRLF + 1;
565 while (offset < aString.Length() && NS_IS_SPACE(aString[offset]))
566 ++offset;
568 aString = result;
570 break;
571 case nsIPlaintextEditor::eNewlinesPasteIntact:
572 // even if we're pasting newlines, don't paste leading/trailing ones
573 aString.Trim(CRLF, true, true);
574 break;
578 nsresult
579 nsTextEditRules::WillInsertText(EditAction aAction,
580 Selection* aSelection,
581 bool *aCancel,
582 bool *aHandled,
583 const nsAString *inString,
584 nsAString *outString,
585 int32_t aMaxLength)
587 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
589 if (inString->IsEmpty() && aAction != EditAction::insertIMEText) {
590 // HACK: this is a fix for bug 19395
591 // I can't outlaw all empty insertions
592 // because IME transaction depend on them
593 // There is more work to do to make the
594 // world safe for IME.
595 *aCancel = true;
596 *aHandled = false;
597 return NS_OK;
600 // initialize out param
601 *aCancel = false;
602 *aHandled = true;
604 // handle docs with a max length
605 // NOTE, this function copies inString into outString for us.
606 bool truncated = false;
607 nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString,
608 aMaxLength, &truncated);
609 NS_ENSURE_SUCCESS(res, res);
610 // If we're exceeding the maxlength when composing IME, we need to clean up
611 // the composing text, so we shouldn't return early.
612 if (truncated && outString->IsEmpty() &&
613 aAction != EditAction::insertIMEText) {
614 *aCancel = true;
615 return NS_OK;
618 int32_t start = 0;
619 int32_t end = 0;
621 // handle password field docs
622 if (IsPasswordEditor()) {
623 NS_ENSURE_STATE(mEditor);
624 nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(),
625 start, end);
628 // if the selection isn't collapsed, delete it.
629 bool bCollapsed;
630 res = aSelection->GetIsCollapsed(&bCollapsed);
631 NS_ENSURE_SUCCESS(res, res);
632 if (!bCollapsed)
634 NS_ENSURE_STATE(mEditor);
635 res = mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
636 NS_ENSURE_SUCCESS(res, res);
639 res = WillInsert(aSelection, aCancel);
640 NS_ENSURE_SUCCESS(res, res);
641 // initialize out param
642 // we want to ignore result of WillInsert()
643 *aCancel = false;
645 // handle password field data
646 // this has the side effect of changing all the characters in aOutString
647 // to the replacement character
648 if (IsPasswordEditor())
650 if (aAction == EditAction::insertIMEText) {
651 RemoveIMETextFromPWBuf(start, outString);
655 // People have lots of different ideas about what text fields
656 // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935.
657 // The six possible options are:
658 // 0. paste newlines intact
659 // 1. paste up to the first newline (default)
660 // 2. replace newlines with spaces
661 // 3. strip newlines
662 // 4. replace with commas
663 // 5. strip newlines and surrounding whitespace
664 // So find out what we're expected to do:
665 if (IsSingleLineEditor())
667 nsAutoString tString(*outString);
669 NS_ENSURE_STATE(mEditor);
670 HandleNewLines(tString, mEditor->mNewlineHandling);
672 outString->Assign(tString);
675 if (IsPasswordEditor())
677 // manage the password buffer
678 mPasswordText.Insert(*outString, start);
680 if (LookAndFeel::GetEchoPassword() && !DontEchoPassword()) {
681 HideLastPWInput();
682 mLastStart = start;
683 mLastLength = outString->Length();
684 if (mTimer)
686 mTimer->Cancel();
688 else
690 mTimer = do_CreateInstance("@mozilla.org/timer;1", &res);
691 NS_ENSURE_SUCCESS(res, res);
693 mTimer->InitWithCallback(this, LookAndFeel::GetPasswordMaskDelay(),
694 nsITimer::TYPE_ONE_SHOT);
696 else
698 FillBufWithPWChars(outString, outString->Length());
702 // get the (collapsed) selection location
703 nsCOMPtr<nsIDOMNode> selNode;
704 int32_t selOffset;
705 NS_ENSURE_STATE(mEditor);
706 res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
707 NS_ENSURE_SUCCESS(res, res);
709 // don't put text in places that can't have it
710 NS_ENSURE_STATE(mEditor);
711 if (!mEditor->IsTextNode(selNode) &&
712 !mEditor->CanContainTag(selNode, nsGkAtoms::textTagName)) {
713 return NS_ERROR_FAILURE;
716 // we need to get the doc
717 NS_ENSURE_STATE(mEditor);
718 nsCOMPtr<nsIDOMDocument> doc = mEditor->GetDOMDocument();
719 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
721 if (aAction == EditAction::insertIMEText) {
722 NS_ENSURE_STATE(mEditor);
723 res = mEditor->InsertTextImpl(*outString, address_of(selNode), &selOffset, doc);
724 NS_ENSURE_SUCCESS(res, res);
725 } else {
726 // aAction == EditAction::insertText; find where we are
727 nsCOMPtr<nsIDOMNode> curNode = selNode;
728 int32_t curOffset = selOffset;
730 // don't spaz my selection in subtransactions
731 NS_ENSURE_STATE(mEditor);
732 nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
734 res = mEditor->InsertTextImpl(*outString, address_of(curNode),
735 &curOffset, doc);
736 NS_ENSURE_SUCCESS(res, res);
738 if (curNode)
740 // Make the caret attach to the inserted text, unless this text ends with a LF,
741 // in which case make the caret attach to the next line.
742 bool endsWithLF =
743 !outString->IsEmpty() && outString->Last() == nsCRT::LF;
744 aSelection->SetInterlinePosition(endsWithLF);
746 aSelection->Collapse(curNode, curOffset);
749 ASSERT_PASSWORD_LENGTHS_EQUAL()
750 return res;
753 nsresult
754 nsTextEditRules::DidInsertText(nsISelection *aSelection,
755 nsresult aResult)
757 return DidInsert(aSelection, aResult);
762 nsresult
763 nsTextEditRules::WillSetTextProperty(nsISelection *aSelection, bool *aCancel, bool *aHandled)
765 if (!aSelection || !aCancel || !aHandled)
766 { return NS_ERROR_NULL_POINTER; }
768 // XXX: should probably return a success value other than NS_OK that means "not allowed"
769 if (IsPlaintextEditor()) {
770 *aCancel = true;
772 return NS_OK;
775 nsresult
776 nsTextEditRules::DidSetTextProperty(nsISelection *aSelection, nsresult aResult)
778 return NS_OK;
781 nsresult
782 nsTextEditRules::WillRemoveTextProperty(nsISelection *aSelection, bool *aCancel, bool *aHandled)
784 if (!aSelection || !aCancel || !aHandled)
785 { return NS_ERROR_NULL_POINTER; }
787 // XXX: should probably return a success value other than NS_OK that means "not allowed"
788 if (IsPlaintextEditor()) {
789 *aCancel = true;
791 return NS_OK;
794 nsresult
795 nsTextEditRules::DidRemoveTextProperty(nsISelection *aSelection, nsresult aResult)
797 return NS_OK;
800 nsresult
801 nsTextEditRules::WillDeleteSelection(Selection* aSelection,
802 nsIEditor::EDirection aCollapsedAction,
803 bool *aCancel,
804 bool *aHandled)
806 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
807 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
809 // initialize out param
810 *aCancel = false;
811 *aHandled = false;
813 // if there is only bogus content, cancel the operation
814 if (mBogusNode) {
815 *aCancel = true;
816 return NS_OK;
819 nsresult res = NS_OK;
820 nsAutoScriptBlocker scriptBlocker;
822 if (IsPasswordEditor())
824 NS_ENSURE_STATE(mEditor);
825 res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction);
826 NS_ENSURE_SUCCESS(res, res);
828 // manage the password buffer
829 int32_t start, end;
830 nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(),
831 start, end);
833 if (LookAndFeel::GetEchoPassword()) {
834 HideLastPWInput();
835 mLastStart = start;
836 mLastLength = 0;
837 if (mTimer)
839 mTimer->Cancel();
843 if (end == start)
844 { // collapsed selection
845 if (nsIEditor::ePrevious==aCollapsedAction && 0<start) { // del back
846 mPasswordText.Cut(start-1, 1);
848 else if (nsIEditor::eNext==aCollapsedAction) { // del forward
849 mPasswordText.Cut(start, 1);
851 // otherwise nothing to do for this collapsed selection
853 else { // extended selection
854 mPasswordText.Cut(start, end-start);
857 else
859 nsCOMPtr<nsIDOMNode> startNode;
860 int32_t startOffset;
861 NS_ENSURE_STATE(mEditor);
862 res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
863 NS_ENSURE_SUCCESS(res, res);
864 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
866 bool bCollapsed;
867 res = aSelection->GetIsCollapsed(&bCollapsed);
868 NS_ENSURE_SUCCESS(res, res);
870 if (!bCollapsed)
871 return NS_OK;
873 // Test for distance between caret and text that will be deleted
874 res = CheckBidiLevelForDeletion(aSelection, startNode, startOffset, aCollapsedAction, aCancel);
875 NS_ENSURE_SUCCESS(res, res);
876 if (*aCancel) return NS_OK;
878 NS_ENSURE_STATE(mEditor);
879 res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction);
880 NS_ENSURE_SUCCESS(res, res);
883 NS_ENSURE_STATE(mEditor);
884 res = mEditor->DeleteSelectionImpl(aCollapsedAction, nsIEditor::eStrip);
885 NS_ENSURE_SUCCESS(res, res);
887 *aHandled = true;
888 ASSERT_PASSWORD_LENGTHS_EQUAL()
889 return NS_OK;
892 nsresult
893 nsTextEditRules::DidDeleteSelection(nsISelection *aSelection,
894 nsIEditor::EDirection aCollapsedAction,
895 nsresult aResult)
897 nsCOMPtr<nsIDOMNode> startNode;
898 int32_t startOffset;
899 NS_ENSURE_STATE(mEditor);
900 nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
901 NS_ENSURE_SUCCESS(res, res);
902 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
904 // delete empty text nodes at selection
905 if (mEditor->IsTextNode(startNode))
907 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(startNode);
908 uint32_t strLength;
909 res = textNode->GetLength(&strLength);
910 NS_ENSURE_SUCCESS(res, res);
912 // are we in an empty text node?
913 if (!strLength)
915 res = mEditor->DeleteNode(startNode);
916 NS_ENSURE_SUCCESS(res, res);
919 if (!mDidExplicitlySetInterline)
921 // We prevent the caret from sticking on the left of prior BR
922 // (i.e. the end of previous line) after this deletion. Bug 92124
923 nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(aSelection);
924 if (selPriv) res = selPriv->SetInterlinePosition(true);
926 return res;
929 nsresult
930 nsTextEditRules::WillUndo(nsISelection *aSelection, bool *aCancel, bool *aHandled)
932 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
933 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
934 // initialize out param
935 *aCancel = false;
936 *aHandled = false;
937 return NS_OK;
940 /* the idea here is to see if the magic empty node has suddenly reappeared as the result of the undo.
941 * if it has, set our state so we remember it.
942 * There is a tradeoff between doing here and at redo, or doing it everywhere else that might care.
943 * Since undo and redo are relatively rare, it makes sense to take the (small) performance hit here.
945 nsresult
946 nsTextEditRules::DidUndo(nsISelection *aSelection, nsresult aResult)
948 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
949 // If aResult is an error, we return it.
950 NS_ENSURE_SUCCESS(aResult, aResult);
952 NS_ENSURE_STATE(mEditor);
953 dom::Element* theRoot = mEditor->GetRoot();
954 NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
955 nsIContent* node = mEditor->GetLeftmostChild(theRoot);
956 if (node && mEditor->IsMozEditorBogusNode(node)) {
957 mBogusNode = do_QueryInterface(node);
958 } else {
959 mBogusNode = nullptr;
961 return aResult;
964 nsresult
965 nsTextEditRules::WillRedo(nsISelection *aSelection, bool *aCancel, bool *aHandled)
967 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
968 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
969 // initialize out param
970 *aCancel = false;
971 *aHandled = false;
972 return NS_OK;
975 nsresult
976 nsTextEditRules::DidRedo(nsISelection *aSelection, nsresult aResult)
978 nsresult res = aResult; // if aResult is an error, we return it.
979 if (!aSelection) { return NS_ERROR_NULL_POINTER; }
980 if (NS_SUCCEEDED(res))
982 NS_ENSURE_STATE(mEditor);
983 nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot());
984 NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
986 nsCOMPtr<nsIDOMHTMLCollection> nodeList;
987 res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"),
988 getter_AddRefs(nodeList));
989 NS_ENSURE_SUCCESS(res, res);
990 if (nodeList)
992 uint32_t len;
993 nodeList->GetLength(&len);
995 if (len != 1) {
996 // only in the case of one br could there be the bogus node
997 mBogusNode = nullptr;
998 return NS_OK;
1001 nsCOMPtr<nsIDOMNode> node;
1002 nodeList->Item(0, getter_AddRefs(node));
1003 nsCOMPtr<nsIContent> content = do_QueryInterface(node);
1004 MOZ_ASSERT(content);
1005 if (mEditor->IsMozEditorBogusNode(content)) {
1006 mBogusNode = node;
1007 } else {
1008 mBogusNode = nullptr;
1012 return res;
1015 nsresult
1016 nsTextEditRules::WillOutputText(nsISelection *aSelection,
1017 const nsAString *aOutputFormat,
1018 nsAString *aOutString,
1019 bool *aCancel,
1020 bool *aHandled)
1022 // null selection ok
1023 if (!aOutString || !aOutputFormat || !aCancel || !aHandled)
1024 { return NS_ERROR_NULL_POINTER; }
1026 // initialize out param
1027 *aCancel = false;
1028 *aHandled = false;
1030 nsAutoString outputFormat(*aOutputFormat);
1031 ToLowerCase(outputFormat);
1032 if (outputFormat.EqualsLiteral("text/plain"))
1033 { // only use these rules for plain text output
1034 if (IsPasswordEditor())
1036 *aOutString = mPasswordText;
1037 *aHandled = true;
1039 else if (mBogusNode)
1040 { // this means there's no content, so output null string
1041 aOutString->Truncate();
1042 *aHandled = true;
1045 return NS_OK;
1048 nsresult
1049 nsTextEditRules::DidOutputText(nsISelection *aSelection, nsresult aResult)
1051 return NS_OK;
1054 nsresult
1055 nsTextEditRules::RemoveRedundantTrailingBR()
1057 // If the bogus node exists, we have no work to do
1058 if (mBogusNode)
1059 return NS_OK;
1061 // Likewise, nothing to be done if we could never have inserted a trailing br
1062 if (IsSingleLineEditor())
1063 return NS_OK;
1065 NS_ENSURE_STATE(mEditor);
1066 nsRefPtr<dom::Element> body = mEditor->GetRoot();
1067 if (!body)
1068 return NS_ERROR_NULL_POINTER;
1070 uint32_t childCount = body->GetChildCount();
1071 if (childCount > 1) {
1072 // The trailing br is redundant if it is the only remaining child node
1073 return NS_OK;
1076 nsRefPtr<nsIContent> child = body->GetFirstChild();
1077 if (!child || !child->IsElement()) {
1078 return NS_OK;
1081 dom::Element* elem = child->AsElement();
1082 if (!nsTextEditUtils::IsMozBR(elem)) {
1083 return NS_OK;
1086 // Rather than deleting this node from the DOM tree we should instead
1087 // morph this br into the bogus node
1088 elem->UnsetAttr(kNameSpaceID_None, nsGkAtoms::type, true);
1090 // set mBogusNode to be this <br>
1091 mBogusNode = do_QueryInterface(elem);
1093 // give it the bogus node attribute
1094 elem->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom,
1095 kMOZEditorBogusNodeValue, false);
1096 return NS_OK;
1099 nsresult
1100 nsTextEditRules::CreateTrailingBRIfNeeded()
1102 // but only if we aren't a single line edit field
1103 if (IsSingleLineEditor()) {
1104 return NS_OK;
1107 NS_ENSURE_STATE(mEditor);
1108 dom::Element* body = mEditor->GetRoot();
1109 NS_ENSURE_TRUE(body, NS_ERROR_NULL_POINTER);
1111 nsIContent* lastChild = body->GetLastChild();
1112 // assuming CreateBogusNodeIfNeeded() has been called first
1113 NS_ENSURE_TRUE(lastChild, NS_ERROR_NULL_POINTER);
1115 if (!lastChild->IsHTML(nsGkAtoms::br)) {
1116 nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
1117 nsCOMPtr<nsIDOMNode> domBody = do_QueryInterface(body);
1118 return CreateMozBR(domBody, body->Length());
1121 // Check to see if the trailing BR is a former bogus node - this will have
1122 // stuck around if we previously morphed a trailing node into a bogus node.
1123 if (!mEditor->IsMozEditorBogusNode(lastChild)) {
1124 return NS_OK;
1127 // Morph it back to a mozBR
1128 lastChild->UnsetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, false);
1129 lastChild->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1130 NS_LITERAL_STRING("_moz"), true);
1131 return NS_OK;
1134 nsresult
1135 nsTextEditRules::CreateBogusNodeIfNeeded(nsISelection *aSelection)
1137 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
1138 NS_ENSURE_TRUE(mEditor, NS_ERROR_NULL_POINTER);
1140 if (mBogusNode) {
1141 // Let's not create more than one, ok?
1142 return NS_OK;
1145 // tell rules system to not do any post-processing
1146 nsAutoRules beginRulesSniffing(mEditor, EditAction::ignore, nsIEditor::eNone);
1148 nsCOMPtr<dom::Element> body = mEditor->GetRoot();
1149 if (!body) {
1150 // We don't even have a body yet, don't insert any bogus nodes at
1151 // this point.
1152 return NS_OK;
1155 // Now we've got the body element. Iterate over the body element's children,
1156 // looking for editable content. If no editable content is found, insert the
1157 // bogus node.
1158 for (nsCOMPtr<nsIContent> bodyChild = body->GetFirstChild();
1159 bodyChild;
1160 bodyChild = bodyChild->GetNextSibling()) {
1161 if (mEditor->IsMozEditorBogusNode(bodyChild) ||
1162 !mEditor->IsEditable(body) || // XXX hoist out of the loop?
1163 mEditor->IsEditable(bodyChild)) {
1164 return NS_OK;
1168 // Skip adding the bogus node if body is read-only.
1169 if (!mEditor->IsModifiableNode(body)) {
1170 return NS_OK;
1173 // Create a br.
1174 nsCOMPtr<Element> newContent = mEditor->CreateHTMLContent(nsGkAtoms::br);
1175 NS_ENSURE_STATE(newContent);
1177 // set mBogusNode to be the newly created <br>
1178 mBogusNode = do_QueryInterface(newContent);
1179 NS_ENSURE_TRUE(mBogusNode, NS_ERROR_NULL_POINTER);
1181 // Give it a special attribute.
1182 newContent->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom,
1183 kMOZEditorBogusNodeValue, false);
1185 // Put the node in the document.
1186 nsCOMPtr<nsIDOMNode> bodyNode = do_QueryInterface(body);
1187 nsresult rv = mEditor->InsertNode(mBogusNode, bodyNode, 0);
1188 NS_ENSURE_SUCCESS(rv, rv);
1190 // Set selection.
1191 aSelection->CollapseNative(body, 0);
1192 return NS_OK;
1196 nsresult
1197 nsTextEditRules::TruncateInsertionIfNeeded(Selection* aSelection,
1198 const nsAString *aInString,
1199 nsAString *aOutString,
1200 int32_t aMaxLength,
1201 bool *aTruncated)
1203 if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;}
1205 nsresult res = NS_OK;
1206 *aOutString = *aInString;
1207 if (aTruncated) {
1208 *aTruncated = false;
1211 NS_ENSURE_STATE(mEditor);
1212 if ((-1 != aMaxLength) && IsPlaintextEditor() && !mEditor->IsIMEComposing() )
1214 // Get the current text length.
1215 // Get the length of inString.
1216 // Get the length of the selection.
1217 // If selection is collapsed, it is length 0.
1218 // Subtract the length of the selection from the len(doc)
1219 // since we'll delete the selection on insert.
1220 // This is resultingDocLength.
1221 // Get old length of IME composing string
1222 // which will be replaced by new one.
1223 // If (resultingDocLength) is at or over max, cancel the insert
1224 // If (resultingDocLength) + (length of input) > max,
1225 // set aOutString to subset of inString so length = max
1226 int32_t docLength;
1227 res = mEditor->GetTextLength(&docLength);
1228 if (NS_FAILED(res)) { return res; }
1230 int32_t start, end;
1231 nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(),
1232 start, end);
1234 TextComposition* composition = mEditor->GetComposition();
1235 int32_t oldCompStrLength = composition ? composition->String().Length() : 0;
1237 const int32_t selectionLength = end - start;
1238 const int32_t resultingDocLength = docLength - selectionLength - oldCompStrLength;
1239 if (resultingDocLength >= aMaxLength)
1241 aOutString->Truncate();
1242 if (aTruncated) {
1243 *aTruncated = true;
1246 else
1248 int32_t oldLength = aOutString->Length();
1249 if (oldLength + resultingDocLength > aMaxLength) {
1250 int32_t newLength = aMaxLength - resultingDocLength;
1251 MOZ_ASSERT(newLength > 0);
1252 char16_t newLastChar = aOutString->CharAt(newLength - 1);
1253 char16_t removingFirstChar = aOutString->CharAt(newLength);
1254 // Don't separate the string between a surrogate pair.
1255 if (NS_IS_HIGH_SURROGATE(newLastChar) &&
1256 NS_IS_LOW_SURROGATE(removingFirstChar)) {
1257 newLength--;
1259 // XXX What should we do if we're removing IVS and its preceding
1260 // character won't be removed?
1261 aOutString->Truncate(newLength);
1262 if (aTruncated) {
1263 *aTruncated = true;
1268 return res;
1271 void
1272 nsTextEditRules::ResetIMETextPWBuf()
1274 mPasswordIMEText.Truncate();
1277 void
1278 nsTextEditRules::RemoveIMETextFromPWBuf(int32_t &aStart, nsAString *aIMEString)
1280 MOZ_ASSERT(aIMEString);
1282 // initialize PasswordIME
1283 if (mPasswordIMEText.IsEmpty()) {
1284 mPasswordIMEIndex = aStart;
1286 else {
1287 // manage the password buffer
1288 mPasswordText.Cut(mPasswordIMEIndex, mPasswordIMEText.Length());
1289 aStart = mPasswordIMEIndex;
1292 mPasswordIMEText.Assign(*aIMEString);
1295 NS_IMETHODIMP nsTextEditRules::Notify(nsITimer *)
1297 MOZ_ASSERT(mTimer);
1299 // Check whether our text editor's password flag was changed before this
1300 // "hide password character" timer actually fires.
1301 nsresult res = IsPasswordEditor() ? HideLastPWInput() : NS_OK;
1302 ASSERT_PASSWORD_LENGTHS_EQUAL();
1303 mLastLength = 0;
1304 return res;
1307 nsresult nsTextEditRules::HideLastPWInput() {
1308 if (!mLastLength) {
1309 // Special case, we're trying to replace a range that no longer exists
1310 return NS_OK;
1313 nsAutoString hiddenText;
1314 FillBufWithPWChars(&hiddenText, mLastLength);
1316 NS_ENSURE_STATE(mEditor);
1317 nsRefPtr<Selection> selection = mEditor->GetSelection();
1318 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1319 int32_t start, end;
1320 nsContentUtils::GetSelectionInTextControl(selection, mEditor->GetRoot(),
1321 start, end);
1323 nsCOMPtr<nsIDOMNode> selNode = GetTextNode(selection, mEditor);
1324 NS_ENSURE_TRUE(selNode, NS_OK);
1326 nsCOMPtr<nsIDOMCharacterData> nodeAsText(do_QueryInterface(selNode));
1327 NS_ENSURE_TRUE(nodeAsText, NS_OK);
1329 nodeAsText->ReplaceData(mLastStart, mLastLength, hiddenText);
1330 selection->Collapse(selNode, start);
1331 if (start != end)
1332 selection->Extend(selNode, end);
1333 return NS_OK;
1336 // static
1337 void
1338 nsTextEditRules::FillBufWithPWChars(nsAString *aOutString, int32_t aLength)
1340 MOZ_ASSERT(aOutString);
1342 // change the output to the platform password character
1343 char16_t passwordChar = LookAndFeel::GetPasswordCharacter();
1345 int32_t i;
1346 aOutString->Truncate();
1347 for (i=0; i < aLength; i++)
1348 aOutString->Append(passwordChar);
1352 ///////////////////////////////////////////////////////////////////////////
1353 // CreateMozBR: put a BR node with moz attribute at {aNode, aOffset}
1355 nsresult
1356 nsTextEditRules::CreateMozBR(nsIDOMNode* inParent, int32_t inOffset,
1357 nsIDOMNode** outBRNode)
1359 NS_ENSURE_TRUE(inParent, NS_ERROR_NULL_POINTER);
1361 nsCOMPtr<nsIDOMNode> brNode;
1362 NS_ENSURE_STATE(mEditor);
1363 nsresult res = mEditor->CreateBR(inParent, inOffset, address_of(brNode));
1364 NS_ENSURE_SUCCESS(res, res);
1366 // give it special moz attr
1367 nsCOMPtr<nsIDOMElement> brElem = do_QueryInterface(brNode);
1368 if (brElem) {
1369 res = mEditor->SetAttribute(brElem, NS_LITERAL_STRING("type"), NS_LITERAL_STRING("_moz"));
1370 NS_ENSURE_SUCCESS(res, res);
1373 if (outBRNode) {
1374 brNode.forget(outBRNode);
1376 return NS_OK;
1379 NS_IMETHODIMP
1380 nsTextEditRules::DocumentModified()
1382 return NS_ERROR_NOT_IMPLEMENTED;