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"
16 #include "nsCRTGlue.h"
17 #include "nsComponentManagerUtils.h"
18 #include "nsContentUtils.h"
21 #include "nsEditorUtils.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"
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()) \
56 /********************************************************
57 * Constructor/Destructor
58 ********************************************************/
60 nsTextEditRules::nsTextEditRules()
66 nsTextEditRules::InitFields()
69 mPasswordText
.Truncate();
70 mPasswordIMEText
.Truncate();
71 mPasswordIMEIndex
= 0;
73 mCachedSelectionNode
= nullptr;
74 mCachedSelectionOffset
= 0;
76 mLockRulesSniffing
= false;
77 mDidExplicitlySetInterline
= false;
78 mDeleteBidiImmediately
= false;
79 mTheAction
= EditAction::none
;
85 nsTextEditRules::~nsTextEditRules()
87 // do NOT delete mEditor here. We do not hold a ref count to mEditor. mEditor owns our lifespan.
93 /********************************************************
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
)
105 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextEditRules
)
106 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextEditRules
)
108 /********************************************************
110 ********************************************************/
113 nsTextEditRules::Init(nsPlaintextEditor
*aEditor
)
115 if (!aEditor
) { return NS_ERROR_NULL_POINTER
; }
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.
132 res
= selection
->GetRangeCount(&rangeCount
);
133 NS_ENSURE_SUCCESS(res
, res
);
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);
153 nsTextEditRules::SetInitialValue(const nsAString
& aValue
)
155 if (IsPasswordEditor()) {
156 mPasswordText
= aValue
;
162 nsTextEditRules::DetachEditor()
172 nsTextEditRules::BeforeEdit(EditAction action
,
173 nsIEditor::EDirection aDirection
)
175 if (mLockRulesSniffing
) return NS_OK
;
177 nsAutoLockRulesSniffing
lockIt(this);
178 mDidExplicitlySetInterline
= false;
181 // let rules remember the top level action
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
);
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();
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
);
243 nsTextEditRules::WillDoAction(Selection
* aSelection
,
248 // null selection is legal
249 MOZ_ASSERT(aInfo
&& aCancel
&& aHandled
);
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
,
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
,
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
);
283 return NS_ERROR_FAILURE
;
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
);
321 // Don't fail on transactions we don't handle here!
328 nsTextEditRules::DocumentIsEmpty(bool *aDocumentIsEmpty
)
330 NS_ENSURE_TRUE(aDocumentIsEmpty
, NS_ERROR_NULL_POINTER
);
332 *aDocumentIsEmpty
= (mBogusNode
!= nullptr);
336 /********************************************************
338 ********************************************************/
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
351 // check for the magic content node and delete it if it exists
354 NS_ENSURE_STATE(mEditor
);
355 mEditor
->DeleteNode(mBogusNode
);
356 mBogusNode
= nullptr;
363 nsTextEditRules::DidInsert(nsISelection
*aSelection
, nsresult aResult
)
369 nsTextEditRules::WillInsertBreak(Selection
* aSelection
,
374 if (!aSelection
|| !aCancel
|| !aHandled
) { return NS_ERROR_NULL_POINTER
; }
375 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
377 if (IsSingleLineEditor()) {
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
;
387 nsresult res
= TruncateInsertionIfNeeded(aSelection
, &inString
, &outString
,
388 aMaxLength
, &didTruncate
);
389 NS_ENSURE_SUCCESS(res
, res
);
397 // if the selection isn't collapsed, delete it.
399 res
= aSelection
->GetIsCollapsed(&bCollapsed
);
400 NS_ENSURE_SUCCESS(res
, res
);
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()
419 nsTextEditRules::DidInsertBreak(nsISelection
*aSelection
, nsresult aResult
)
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()) {
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.
437 nsCOMPtr
<nsIDOMNode
> selNode
;
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
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
))
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
,
464 if (nextNode
&& nsTextEditUtils::IsMozBR(nextNode
))
466 res
= aSelection
->Collapse(parentNode
, parentOffset
+ 1);
467 NS_ENSURE_SUCCESS(res
, res
);
472 static inline already_AddRefed
<nsIDOMNode
>
473 GetTextNode(nsISelection
*selection
, nsEditor
*editor
) {
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
) {
492 return selNode
.forget();
495 #define ASSERT_PASSWORD_LENGTHS_EQUAL() \
496 if (IsPasswordEditor() && mEditor->GetRoot()) { \
498 mEditor->GetTextLength(&txtLen); \
499 NS_ASSERTION(mPasswordText.Length() == uint32_t(txtLen), \
500 "password length not equal to number of asterisks"); \
503 #define ASSERT_PASSWORD_LENGTHS_EQUAL()
508 nsTextEditRules::HandleNewLines(nsString
&aString
,
509 int32_t aNewlineHandling
)
511 if (aNewlineHandling
< 0) {
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
, ' ');
523 case nsIPlaintextEditor::eNewlinesStrip
:
524 aString
.StripChars(CRLF
);
526 case nsIPlaintextEditor::eNewlinesPasteToFirst
:
529 int32_t firstCRLF
= aString
.FindCharInSet(CRLF
);
531 // we get first *non-empty* line.
533 while (firstCRLF
== offset
)
536 firstCRLF
= aString
.FindCharInSet(CRLF
, offset
);
539 aString
.Truncate(firstCRLF
);
541 aString
.Cut(0, offset
);
544 case nsIPlaintextEditor::eNewlinesReplaceWithCommas
:
545 aString
.Trim(CRLF
, true, true);
546 aString
.ReplaceChar(CRLF
, ',');
548 case nsIPlaintextEditor::eNewlinesStripSurroundingWhitespace
:
552 while (offset
< aString
.Length())
554 int32_t nextCRLF
= aString
.FindCharInSet(CRLF
, offset
);
556 result
.Append(nsDependentSubstring(aString
, offset
));
559 uint32_t wsBegin
= nextCRLF
;
560 // look backwards for the first non-whitespace char
561 while (wsBegin
> offset
&& NS_IS_SPACE(aString
[wsBegin
- 1]))
563 result
.Append(nsDependentSubstring(aString
, offset
, wsBegin
- offset
));
564 offset
= nextCRLF
+ 1;
565 while (offset
< aString
.Length() && NS_IS_SPACE(aString
[offset
]))
571 case nsIPlaintextEditor::eNewlinesPasteIntact
:
572 // even if we're pasting newlines, don't paste leading/trailing ones
573 aString
.Trim(CRLF
, true, true);
579 nsTextEditRules::WillInsertText(EditAction aAction
,
580 Selection
* aSelection
,
583 const nsAString
*inString
,
584 nsAString
*outString
,
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.
600 // initialize out param
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
) {
621 // handle password field docs
622 if (IsPasswordEditor()) {
623 NS_ENSURE_STATE(mEditor
);
624 nsContentUtils::GetSelectionInTextControl(aSelection
, mEditor
->GetRoot(),
628 // if the selection isn't collapsed, delete it.
630 res
= aSelection
->GetIsCollapsed(&bCollapsed
);
631 NS_ENSURE_SUCCESS(res
, res
);
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()
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
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()) {
683 mLastLength
= outString
->Length();
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
);
698 FillBufWithPWChars(outString
, outString
->Length());
702 // get the (collapsed) selection location
703 nsCOMPtr
<nsIDOMNode
> selNode
;
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
);
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
),
736 NS_ENSURE_SUCCESS(res
, res
);
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.
743 !outString
->IsEmpty() && outString
->Last() == nsCRT::LF
;
744 aSelection
->SetInterlinePosition(endsWithLF
);
746 aSelection
->Collapse(curNode
, curOffset
);
749 ASSERT_PASSWORD_LENGTHS_EQUAL()
754 nsTextEditRules::DidInsertText(nsISelection
*aSelection
,
757 return DidInsert(aSelection
, aResult
);
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()) {
776 nsTextEditRules::DidSetTextProperty(nsISelection
*aSelection
, nsresult aResult
)
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()) {
795 nsTextEditRules::DidRemoveTextProperty(nsISelection
*aSelection
, nsresult aResult
)
801 nsTextEditRules::WillDeleteSelection(Selection
* aSelection
,
802 nsIEditor::EDirection aCollapsedAction
,
806 if (!aSelection
|| !aCancel
|| !aHandled
) { return NS_ERROR_NULL_POINTER
; }
807 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
809 // initialize out param
813 // if there is only bogus content, cancel the operation
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
830 nsContentUtils::GetSelectionInTextControl(aSelection
, mEditor
->GetRoot(),
833 if (LookAndFeel::GetEchoPassword()) {
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
);
859 nsCOMPtr
<nsIDOMNode
> startNode
;
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
);
867 res
= aSelection
->GetIsCollapsed(&bCollapsed
);
868 NS_ENSURE_SUCCESS(res
, res
);
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
);
888 ASSERT_PASSWORD_LENGTHS_EQUAL()
893 nsTextEditRules::DidDeleteSelection(nsISelection
*aSelection
,
894 nsIEditor::EDirection aCollapsedAction
,
897 nsCOMPtr
<nsIDOMNode
> startNode
;
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
);
909 res
= textNode
->GetLength(&strLength
);
910 NS_ENSURE_SUCCESS(res
, res
);
912 // are we in an empty text node?
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);
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
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.
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
);
959 mBogusNode
= nullptr;
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
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
);
993 nodeList
->GetLength(&len
);
996 // only in the case of one br could there be the bogus node
997 mBogusNode
= nullptr;
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
)) {
1008 mBogusNode
= nullptr;
1016 nsTextEditRules::WillOutputText(nsISelection
*aSelection
,
1017 const nsAString
*aOutputFormat
,
1018 nsAString
*aOutString
,
1022 // null selection ok
1023 if (!aOutString
|| !aOutputFormat
|| !aCancel
|| !aHandled
)
1024 { return NS_ERROR_NULL_POINTER
; }
1026 // initialize out param
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
;
1039 else if (mBogusNode
)
1040 { // this means there's no content, so output null string
1041 aOutString
->Truncate();
1049 nsTextEditRules::DidOutputText(nsISelection
*aSelection
, nsresult aResult
)
1055 nsTextEditRules::RemoveRedundantTrailingBR()
1057 // If the bogus node exists, we have no work to do
1061 // Likewise, nothing to be done if we could never have inserted a trailing br
1062 if (IsSingleLineEditor())
1065 NS_ENSURE_STATE(mEditor
);
1066 nsRefPtr
<dom::Element
> body
= mEditor
->GetRoot();
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
1076 nsRefPtr
<nsIContent
> child
= body
->GetFirstChild();
1077 if (!child
|| !child
->IsElement()) {
1081 dom::Element
* elem
= child
->AsElement();
1082 if (!nsTextEditUtils::IsMozBR(elem
)) {
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);
1100 nsTextEditRules::CreateTrailingBRIfNeeded()
1102 // but only if we aren't a single line edit field
1103 if (IsSingleLineEditor()) {
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
)) {
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);
1135 nsTextEditRules::CreateBogusNodeIfNeeded(nsISelection
*aSelection
)
1137 NS_ENSURE_TRUE(aSelection
, NS_ERROR_NULL_POINTER
);
1138 NS_ENSURE_TRUE(mEditor
, NS_ERROR_NULL_POINTER
);
1141 // Let's not create more than one, 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();
1150 // We don't even have a body yet, don't insert any bogus nodes at
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
1158 for (nsCOMPtr
<nsIContent
> bodyChild
= body
->GetFirstChild();
1160 bodyChild
= bodyChild
->GetNextSibling()) {
1161 if (mEditor
->IsMozEditorBogusNode(bodyChild
) ||
1162 !mEditor
->IsEditable(body
) || // XXX hoist out of the loop?
1163 mEditor
->IsEditable(bodyChild
)) {
1168 // Skip adding the bogus node if body is read-only.
1169 if (!mEditor
->IsModifiableNode(body
)) {
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
);
1191 aSelection
->CollapseNative(body
, 0);
1197 nsTextEditRules::TruncateInsertionIfNeeded(Selection
* aSelection
,
1198 const nsAString
*aInString
,
1199 nsAString
*aOutString
,
1203 if (!aSelection
|| !aInString
|| !aOutString
) {return NS_ERROR_NULL_POINTER
;}
1205 nsresult res
= NS_OK
;
1206 *aOutString
= *aInString
;
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
1227 res
= mEditor
->GetTextLength(&docLength
);
1228 if (NS_FAILED(res
)) { return res
; }
1231 nsContentUtils::GetSelectionInTextControl(aSelection
, mEditor
->GetRoot(),
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();
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
)) {
1259 // XXX What should we do if we're removing IVS and its preceding
1260 // character won't be removed?
1261 aOutString
->Truncate(newLength
);
1272 nsTextEditRules::ResetIMETextPWBuf()
1274 mPasswordIMEText
.Truncate();
1278 nsTextEditRules::RemoveIMETextFromPWBuf(int32_t &aStart
, nsAString
*aIMEString
)
1280 MOZ_ASSERT(aIMEString
);
1282 // initialize PasswordIME
1283 if (mPasswordIMEText
.IsEmpty()) {
1284 mPasswordIMEIndex
= aStart
;
1287 // manage the password buffer
1288 mPasswordText
.Cut(mPasswordIMEIndex
, mPasswordIMEText
.Length());
1289 aStart
= mPasswordIMEIndex
;
1292 mPasswordIMEText
.Assign(*aIMEString
);
1295 NS_IMETHODIMP
nsTextEditRules::Notify(nsITimer
*)
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();
1307 nsresult
nsTextEditRules::HideLastPWInput() {
1309 // Special case, we're trying to replace a range that no longer exists
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
);
1320 nsContentUtils::GetSelectionInTextControl(selection
, mEditor
->GetRoot(),
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
);
1332 selection
->Extend(selNode
, end
);
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();
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}
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
);
1369 res
= mEditor
->SetAttribute(brElem
, NS_LITERAL_STRING("type"), NS_LITERAL_STRING("_moz"));
1370 NS_ENSURE_SUCCESS(res
, res
);
1374 brNode
.forget(outBRNode
);
1380 nsTextEditRules::DocumentModified()
1382 return NS_ERROR_NOT_IMPLEMENTED
;