Bug 630001, part2 - fix nsAccUtils::TextLength to not use nsIFrame::GetRenderedText...
[mozilla-central.git] / editor / libeditor / text / nsTextEditRules.cpp
blob7f6200f46540093f7e0607bd122cd5c6cb3673f1
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
24 * Alternatively, the contents of this file may be used under the terms of
25 * either of the GNU General Public License Version 2 or later (the "GPL"),
26 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 #include "nsTextEditRules.h"
40 #include "nsEditor.h"
41 #include "nsTextEditUtils.h"
42 #include "nsCRT.h"
44 #include "nsCOMPtr.h"
45 #include "nsIServiceManager.h"
46 #include "nsIDOMNode.h"
47 #include "nsIDOMElement.h"
48 #include "nsIDOMText.h"
49 #include "nsIDOMNodeList.h"
50 #include "nsISelection.h"
51 #include "nsISelectionPrivate.h"
52 #include "nsISelectionController.h"
53 #include "nsIDOMRange.h"
54 #include "nsIDOMNSRange.h"
55 #include "nsIDOMCharacterData.h"
56 #include "nsIContent.h"
57 #include "nsIContentIterator.h"
58 #include "nsEditorUtils.h"
59 #include "EditTxn.h"
60 #include "nsEditProperty.h"
61 #include "nsIPrefBranch.h"
62 #include "nsIPrefService.h"
63 #include "nsUnicharUtils.h"
64 #include "nsILookAndFeel.h"
65 #include "nsWidgetsCID.h"
66 #include "DeleteTextTxn.h"
67 #include "nsNodeIterator.h"
68 #include "nsIDOMNodeFilter.h"
70 // for IBMBIDI
71 #include "nsFrameSelection.h"
73 static NS_DEFINE_CID(kLookAndFeelCID, NS_LOOKANDFEEL_CID);
75 #define CANCEL_OPERATION_IF_READONLY_OR_DISABLED \
76 if (IsReadonly() || IsDisabled()) \
77 { \
78 *aCancel = PR_TRUE; \
79 return NS_OK; \
83 nsresult
84 NS_NewTextEditRules(nsIEditRules** aInstancePtrResult)
86 nsTextEditRules * rules = new nsTextEditRules();
87 if (rules)
88 return rules->QueryInterface(NS_GET_IID(nsIEditRules), (void**) aInstancePtrResult);
89 return NS_ERROR_OUT_OF_MEMORY;
93 /********************************************************
94 * Constructor/Destructor
95 ********************************************************/
97 nsTextEditRules::nsTextEditRules()
98 : mEditor(nsnull)
99 , mPasswordText()
100 , mPasswordIMEText()
101 , mPasswordIMEIndex(0)
102 , mActionNesting(0)
103 , mLockRulesSniffing(PR_FALSE)
104 , mDidExplicitlySetInterline(PR_FALSE)
105 , mTheAction(0)
106 , mLastStart(0)
107 , mLastLength(0)
111 nsTextEditRules::~nsTextEditRules()
113 // do NOT delete mEditor here. We do not hold a ref count to mEditor. mEditor owns our lifespan.
115 if (mTimer)
116 mTimer->Cancel();
119 /********************************************************
120 * XPCOM Cruft
121 ********************************************************/
123 NS_IMPL_CYCLE_COLLECTION_2(nsTextEditRules, mBogusNode, mCachedSelectionNode)
125 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTextEditRules)
126 NS_INTERFACE_MAP_ENTRY(nsIEditRules)
127 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
128 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditRules)
129 NS_INTERFACE_MAP_END
131 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextEditRules)
132 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextEditRules)
134 /********************************************************
135 * Public methods
136 ********************************************************/
138 NS_IMETHODIMP
139 nsTextEditRules::Init(nsPlaintextEditor *aEditor)
141 if (!aEditor) { return NS_ERROR_NULL_POINTER; }
143 mEditor = aEditor; // we hold a non-refcounted reference back to our editor
144 nsCOMPtr<nsISelection> selection;
145 mEditor->GetSelection(getter_AddRefs(selection));
146 NS_ASSERTION(selection, "editor cannot get selection");
148 // Put in a magic br if needed. This method handles null selection,
149 // which should never happen anyway
150 nsresult res = CreateBogusNodeIfNeeded(selection);
151 NS_ENSURE_SUCCESS(res, res);
153 // If the selection hasn't been set up yet, set it up collapsed to the end of
154 // our editable content.
155 PRInt32 rangeCount;
156 res = selection->GetRangeCount(&rangeCount);
157 NS_ENSURE_SUCCESS(res, res);
158 if (!rangeCount) {
159 res = mEditor->EndOfDocument();
160 NS_ENSURE_SUCCESS(res, res);
163 if (IsPlaintextEditor())
165 // ensure trailing br node
166 res = CreateTrailingBRIfNeeded();
167 NS_ENSURE_SUCCESS(res, res);
170 PRBool deleteBidiImmediately = PR_FALSE;
171 nsCOMPtr<nsIPrefBranch> prefBranch =
172 do_GetService(NS_PREFSERVICE_CONTRACTID, &res);
173 if (NS_SUCCEEDED(res))
174 prefBranch->GetBoolPref("bidi.edit.delete_immediately",
175 &deleteBidiImmediately);
176 mDeleteBidiImmediately = deleteBidiImmediately;
178 return res;
181 NS_IMETHODIMP
182 nsTextEditRules::DetachEditor()
184 if (mTimer)
185 mTimer->Cancel();
187 mEditor = nsnull;
188 return NS_OK;
191 NS_IMETHODIMP
192 nsTextEditRules::BeforeEdit(PRInt32 action, nsIEditor::EDirection aDirection)
194 if (mLockRulesSniffing) return NS_OK;
196 nsAutoLockRulesSniffing lockIt(this);
197 mDidExplicitlySetInterline = PR_FALSE;
198 if (!mActionNesting)
200 // let rules remember the top level action
201 mTheAction = action;
203 mActionNesting++;
205 // get the selection and cache the position before editing
206 nsCOMPtr<nsISelection> selection;
207 nsresult res = mEditor->GetSelection(getter_AddRefs(selection));
208 NS_ENSURE_SUCCESS(res, res);
210 selection->GetAnchorNode(getter_AddRefs(mCachedSelectionNode));
211 selection->GetAnchorOffset(&mCachedSelectionOffset);
213 return NS_OK;
217 NS_IMETHODIMP
218 nsTextEditRules::AfterEdit(PRInt32 action, nsIEditor::EDirection aDirection)
220 if (mLockRulesSniffing) return NS_OK;
222 nsAutoLockRulesSniffing lockIt(this);
224 NS_PRECONDITION(mActionNesting>0, "bad action nesting!");
225 nsresult res = NS_OK;
226 if (!--mActionNesting)
228 nsCOMPtr<nsISelection>selection;
229 res = mEditor->GetSelection(getter_AddRefs(selection));
230 NS_ENSURE_SUCCESS(res, res);
232 res = mEditor->HandleInlineSpellCheck(action, selection,
233 mCachedSelectionNode, mCachedSelectionOffset,
234 nsnull, 0, nsnull, 0);
235 NS_ENSURE_SUCCESS(res, res);
237 // detect empty doc
238 res = CreateBogusNodeIfNeeded(selection);
239 NS_ENSURE_SUCCESS(res, res);
241 // insure trailing br node
242 res = CreateTrailingBRIfNeeded();
243 NS_ENSURE_SUCCESS(res, res);
245 // collapse the selection to the trailing BR if it's at the end of our text node
246 CollapseSelectionToTrailingBRIfNeeded(selection);
248 /* After inserting text the cursor Bidi level must be set to the level of the inserted text.
249 * This is difficult, because we cannot know what the level is until after the Bidi algorithm
250 * is applied to the whole paragraph.
252 * So we set the cursor Bidi level to UNDEFINED here, and the caret code will set it correctly later
254 if (action == nsEditor::kOpInsertText
255 || action == nsEditor::kOpInsertIMEText) {
256 nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(selection));
257 nsCOMPtr<nsFrameSelection> frameSelection;
258 privateSelection->GetFrameSelection(getter_AddRefs(frameSelection));
259 if (frameSelection) {
260 frameSelection->UndefineCaretBidiLevel();
264 return res;
268 NS_IMETHODIMP
269 nsTextEditRules::WillDoAction(nsISelection *aSelection,
270 nsRulesInfo *aInfo,
271 PRBool *aCancel,
272 PRBool *aHandled)
274 // null selection is legal
275 if (!aInfo || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
276 #if defined(DEBUG_ftang)
277 printf("nsTextEditRules::WillDoAction action= %d", aInfo->action);
278 #endif
280 *aCancel = PR_FALSE;
281 *aHandled = PR_FALSE;
283 // my kingdom for dynamic cast
284 nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
286 switch (info->action)
288 case kInsertBreak:
289 return WillInsertBreak(aSelection, aCancel, aHandled, info->maxLength);
290 case kInsertText:
291 case kInsertTextIME:
292 return WillInsertText(info->action,
293 aSelection,
294 aCancel,
295 aHandled,
296 info->inString,
297 info->outString,
298 info->maxLength);
299 case kDeleteSelection:
300 return WillDeleteSelection(aSelection, info->collapsedAction, aCancel, aHandled);
301 case kUndo:
302 return WillUndo(aSelection, aCancel, aHandled);
303 case kRedo:
304 return WillRedo(aSelection, aCancel, aHandled);
305 case kSetTextProperty:
306 return WillSetTextProperty(aSelection, aCancel, aHandled);
307 case kRemoveTextProperty:
308 return WillRemoveTextProperty(aSelection, aCancel, aHandled);
309 case kOutputText:
310 return WillOutputText(aSelection,
311 info->outputFormat,
312 info->outString,
313 aCancel,
314 aHandled);
315 case kInsertElement: // i had thought this would be html rules only. but we put pre elements
316 // into plaintext mail when doing quoting for reply! doh!
317 return WillInsert(aSelection, aCancel);
319 return NS_ERROR_FAILURE;
322 NS_IMETHODIMP
323 nsTextEditRules::DidDoAction(nsISelection *aSelection,
324 nsRulesInfo *aInfo, nsresult aResult)
326 // don't let any txns in here move the selection around behind our back.
327 // Note that this won't prevent explicit selection setting from working.
328 nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
330 NS_ENSURE_TRUE(aSelection && aInfo, NS_ERROR_NULL_POINTER);
332 // my kingdom for dynamic cast
333 nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
335 switch (info->action)
337 case kInsertBreak:
338 return DidInsertBreak(aSelection, aResult);
339 case kInsertText:
340 case kInsertTextIME:
341 return DidInsertText(aSelection, aResult);
342 case kDeleteSelection:
343 return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
344 case kUndo:
345 return DidUndo(aSelection, aResult);
346 case kRedo:
347 return DidRedo(aSelection, aResult);
348 case kSetTextProperty:
349 return DidSetTextProperty(aSelection, aResult);
350 case kRemoveTextProperty:
351 return DidRemoveTextProperty(aSelection, aResult);
352 case kOutputText:
353 return DidOutputText(aSelection, aResult);
355 // Don't fail on transactions we don't handle here!
356 return NS_OK;
360 NS_IMETHODIMP
361 nsTextEditRules::DocumentIsEmpty(PRBool *aDocumentIsEmpty)
363 NS_ENSURE_TRUE(aDocumentIsEmpty, NS_ERROR_NULL_POINTER);
365 *aDocumentIsEmpty = (mBogusNode != nsnull);
366 return NS_OK;
369 /********************************************************
370 * Protected methods
371 ********************************************************/
374 nsresult
375 nsTextEditRules::WillInsert(nsISelection *aSelection, PRBool *aCancel)
377 NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER);
379 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
381 // initialize out param
382 *aCancel = PR_FALSE;
384 // check for the magic content node and delete it if it exists
385 if (mBogusNode)
387 mEditor->DeleteNode(mBogusNode);
388 mBogusNode = nsnull;
391 return NS_OK;
394 nsresult
395 nsTextEditRules::DidInsert(nsISelection *aSelection, nsresult aResult)
397 return NS_OK;
400 nsresult
401 nsTextEditRules::WillInsertBreak(nsISelection *aSelection,
402 PRBool *aCancel,
403 PRBool *aHandled,
404 PRInt32 aMaxLength)
406 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
407 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
408 *aHandled = PR_FALSE;
409 if (IsSingleLineEditor()) {
410 *aCancel = PR_TRUE;
412 else
414 // handle docs with a max length
415 // NOTE, this function copies inString into outString for us.
416 NS_NAMED_LITERAL_STRING(inString, "\n");
417 nsAutoString outString;
418 PRBool didTruncate;
419 nsresult res = TruncateInsertionIfNeeded(aSelection, &inString, &outString,
420 aMaxLength, &didTruncate);
421 NS_ENSURE_SUCCESS(res, res);
422 if (didTruncate) {
423 *aCancel = PR_TRUE;
424 return NS_OK;
427 *aCancel = PR_FALSE;
429 // if the selection isn't collapsed, delete it.
430 PRBool bCollapsed;
431 res = aSelection->GetIsCollapsed(&bCollapsed);
432 NS_ENSURE_SUCCESS(res, res);
433 if (!bCollapsed)
435 res = mEditor->DeleteSelection(nsIEditor::eNone);
436 NS_ENSURE_SUCCESS(res, res);
439 res = WillInsert(aSelection, aCancel);
440 NS_ENSURE_SUCCESS(res, res);
441 // initialize out param
442 // we want to ignore result of WillInsert()
443 *aCancel = PR_FALSE;
446 return NS_OK;
449 nsresult
450 nsTextEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult)
452 return NS_OK;
455 nsresult
456 nsTextEditRules::CollapseSelectionToTrailingBRIfNeeded(nsISelection* aSelection)
458 // we only need to execute the stuff below if we are a plaintext editor.
459 // html editors have a different mechanism for putting in mozBR's
460 // (because there are a bunch more places you have to worry about it in html)
461 if (!IsPlaintextEditor()) {
462 return NS_OK;
465 // if we are at the end of the textarea, we need to set the
466 // selection to stick to the mozBR at the end of the textarea.
467 PRInt32 selOffset;
468 nsCOMPtr<nsIDOMNode> selNode;
469 nsresult res;
470 res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
471 NS_ENSURE_SUCCESS(res, res);
473 nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(selNode);
474 if (!nodeAsText) return NS_OK; // nothing to do if we're not at a text node
476 PRUint32 length;
477 res = nodeAsText->GetLength(&length);
478 NS_ENSURE_SUCCESS(res, res);
480 // nothing to do if we're not at the end of the text node
481 if (selOffset != PRInt32(length))
482 return NS_OK;
484 nsCOMPtr<nsIDOMNode> parentNode;
485 PRInt32 parentOffset;
486 res = nsEditor::GetNodeLocation(selNode, address_of(parentNode),
487 &parentOffset);
488 NS_ENSURE_SUCCESS(res, res);
490 nsIDOMElement *rootElem = mEditor->GetRoot();
491 nsCOMPtr<nsIDOMNode> root = do_QueryInterface(rootElem);
492 NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER);
493 if (parentNode != root) return NS_OK;
495 nsCOMPtr<nsIDOMNode> nextNode = mEditor->GetChildAt(parentNode,
496 parentOffset + 1);
497 if (nextNode && nsTextEditUtils::IsMozBR(nextNode))
499 res = aSelection->Collapse(parentNode, parentOffset + 1);
500 NS_ENSURE_SUCCESS(res, res);
502 return res;
505 static inline already_AddRefed<nsIDOMNode>
506 GetTextNode(nsISelection *selection, nsEditor *editor) {
507 PRInt32 selOffset;
508 nsCOMPtr<nsIDOMNode> selNode;
509 nsresult res = editor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
510 NS_ENSURE_SUCCESS(res, nsnull);
511 if (!editor->IsTextNode(selNode)) {
512 // Get an nsINode from the nsIDOMNode
513 nsCOMPtr<nsINode> node = do_QueryInterface(selNode);
514 // if node is null, return it to indicate there's no text
515 NS_ENSURE_TRUE(node, nsnull);
516 // This should be the root node, walk the tree looking for text nodes
517 nsNodeIterator iter(node, nsIDOMNodeFilter::SHOW_TEXT, nsnull, PR_TRUE);
518 while (!editor->IsTextNode(selNode)) {
519 if (NS_FAILED(res = iter.NextNode(getter_AddRefs(selNode))) || !selNode) {
520 return nsnull;
524 return selNode.forget();
526 #ifdef DEBUG
527 #define ASSERT_PASSWORD_LENGTHS_EQUAL() \
528 if (IsPasswordEditor()) { \
529 PRInt32 txtLen; \
530 mEditor->GetTextLength(&txtLen); \
531 NS_ASSERTION(mPasswordText.Length() == PRUint32(txtLen), \
532 "password length not equal to number of asterisks"); \
534 #else
535 #define ASSERT_PASSWORD_LENGTHS_EQUAL()
536 #endif
538 // static
539 void
540 nsTextEditRules::HandleNewLines(nsString &aString,
541 PRInt32 aNewlineHandling)
543 if (aNewlineHandling < 0) {
544 PRInt32 caretStyle;
545 nsPlaintextEditor::GetDefaultEditorPrefs(aNewlineHandling, caretStyle);
548 switch(aNewlineHandling)
550 case nsIPlaintextEditor::eNewlinesReplaceWithSpaces:
551 // Strip trailing newlines first so we don't wind up with trailing spaces
552 aString.Trim(CRLF, PR_FALSE, PR_TRUE);
553 aString.ReplaceChar(CRLF, ' ');
554 break;
555 case nsIPlaintextEditor::eNewlinesStrip:
556 aString.StripChars(CRLF);
557 break;
558 case nsIPlaintextEditor::eNewlinesPasteToFirst:
559 default:
561 PRInt32 firstCRLF = aString.FindCharInSet(CRLF);
563 // we get first *non-empty* line.
564 PRInt32 offset = 0;
565 while (firstCRLF == offset)
567 offset++;
568 firstCRLF = aString.FindCharInSet(CRLF, offset);
570 if (firstCRLF > 0)
571 aString.Truncate(firstCRLF);
572 if (offset > 0)
573 aString.Cut(0, offset);
575 break;
576 case nsIPlaintextEditor::eNewlinesReplaceWithCommas:
577 aString.Trim(CRLF, PR_TRUE, PR_TRUE);
578 aString.ReplaceChar(CRLF, ',');
579 break;
580 case nsIPlaintextEditor::eNewlinesStripSurroundingWhitespace:
582 // find each newline, and strip all the whitespace before
583 // and after it
584 PRInt32 firstCRLF = aString.FindCharInSet(CRLF);
585 while (firstCRLF >= 0)
587 PRUint32 wsBegin = firstCRLF, wsEnd = firstCRLF + 1;
588 // look backwards for the first non-whitespace char
589 while (wsBegin > 0 && NS_IS_SPACE(aString[wsBegin - 1]))
590 --wsBegin;
591 while (wsEnd < aString.Length() && NS_IS_SPACE(aString[wsEnd]))
592 ++wsEnd;
593 // now cut this range out of the string
594 aString.Cut(wsBegin, wsEnd - wsBegin);
595 // look for another CR or LF
596 firstCRLF = aString.FindCharInSet(CRLF);
599 break;
600 case nsIPlaintextEditor::eNewlinesPasteIntact:
601 // even if we're pasting newlines, don't paste leading/trailing ones
602 aString.Trim(CRLF, PR_TRUE, PR_TRUE);
603 break;
607 nsresult
608 nsTextEditRules::WillInsertText(PRInt32 aAction,
609 nsISelection *aSelection,
610 PRBool *aCancel,
611 PRBool *aHandled,
612 const nsAString *inString,
613 nsAString *outString,
614 PRInt32 aMaxLength)
616 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
618 if (inString->IsEmpty() && (aAction != kInsertTextIME))
620 // HACK: this is a fix for bug 19395
621 // I can't outlaw all empty insertions
622 // because IME transaction depend on them
623 // There is more work to do to make the
624 // world safe for IME.
625 *aCancel = PR_TRUE;
626 *aHandled = PR_FALSE;
627 return NS_OK;
630 // initialize out param
631 *aCancel = PR_FALSE;
632 *aHandled = PR_TRUE;
634 // handle docs with a max length
635 // NOTE, this function copies inString into outString for us.
636 PRBool truncated = PR_FALSE;
637 nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString,
638 aMaxLength, &truncated);
639 NS_ENSURE_SUCCESS(res, res);
640 if (truncated && outString->IsEmpty()) {
641 *aCancel = PR_TRUE;
642 return NS_OK;
645 PRUint32 start = 0;
646 PRUint32 end = 0;
648 // handle password field docs
649 if (IsPasswordEditor())
651 res = mEditor->GetTextSelectionOffsets(aSelection, start, end);
652 NS_ASSERTION((NS_SUCCEEDED(res)), "getTextSelectionOffsets failed!");
653 NS_ENSURE_SUCCESS(res, res);
656 // if the selection isn't collapsed, delete it.
657 PRBool bCollapsed;
658 res = aSelection->GetIsCollapsed(&bCollapsed);
659 NS_ENSURE_SUCCESS(res, res);
660 if (!bCollapsed)
662 res = mEditor->DeleteSelection(nsIEditor::eNone);
663 NS_ENSURE_SUCCESS(res, res);
666 res = WillInsert(aSelection, aCancel);
667 NS_ENSURE_SUCCESS(res, res);
668 // initialize out param
669 // we want to ignore result of WillInsert()
670 *aCancel = PR_FALSE;
672 // handle password field data
673 // this has the side effect of changing all the characters in aOutString
674 // to the replacement character
675 if (IsPasswordEditor())
677 if (aAction == kInsertTextIME) {
678 res = RemoveIMETextFromPWBuf(start, outString);
679 NS_ENSURE_SUCCESS(res, res);
683 // People have lots of different ideas about what text fields
684 // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935.
685 // The six possible options are:
686 // 0. paste newlines intact
687 // 1. paste up to the first newline (default)
688 // 2. replace newlines with spaces
689 // 3. strip newlines
690 // 4. replace with commas
691 // 5. strip newlines and surrounding whitespace
692 // So find out what we're expected to do:
693 if (IsSingleLineEditor())
695 nsAutoString tString(*outString);
697 HandleNewLines(tString, mEditor->mNewlineHandling);
699 outString->Assign(tString);
702 if (IsPasswordEditor())
704 // manage the password buffer
705 mPasswordText.Insert(*outString, start);
707 nsCOMPtr<nsILookAndFeel> lookAndFeel = do_GetService(kLookAndFeelCID);
708 if (lookAndFeel->GetEchoPassword() && !DontEchoPassword()) {
709 HideLastPWInput();
710 mLastStart = start;
711 mLastLength = outString->Length();
712 if (mTimer)
714 mTimer->Cancel();
716 else
718 mTimer = do_CreateInstance("@mozilla.org/timer;1", &res);
719 NS_ENSURE_SUCCESS(res, res);
721 mTimer->InitWithCallback(this, 600, nsITimer::TYPE_ONE_SHOT);
723 else
725 res = FillBufWithPWChars(outString, outString->Length());
726 NS_ENSURE_SUCCESS(res, res);
730 // get the (collapsed) selection location
731 nsCOMPtr<nsIDOMNode> selNode;
732 PRInt32 selOffset;
733 res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
734 NS_ENSURE_SUCCESS(res, res);
736 // don't put text in places that can't have it
737 if (!mEditor->IsTextNode(selNode) && !mEditor->CanContainTag(selNode, NS_LITERAL_STRING("#text")))
738 return NS_ERROR_FAILURE;
740 // we need to get the doc
741 nsCOMPtr<nsIDOMDocument>doc;
742 res = mEditor->GetDocument(getter_AddRefs(doc));
743 NS_ENSURE_SUCCESS(res, res);
744 NS_ENSURE_TRUE(doc, NS_ERROR_NULL_POINTER);
746 if (aAction == kInsertTextIME)
748 res = mEditor->InsertTextImpl(*outString, address_of(selNode), &selOffset, doc);
749 NS_ENSURE_SUCCESS(res, res);
751 else // aAction == kInsertText
753 // find where we are
754 nsCOMPtr<nsIDOMNode> curNode = selNode;
755 PRInt32 curOffset = selOffset;
757 // don't spaz my selection in subtransactions
758 nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
760 res = mEditor->InsertTextImpl(*outString, address_of(curNode),
761 &curOffset, doc);
762 NS_ENSURE_SUCCESS(res, res);
764 if (curNode)
766 // Make the caret attach to the inserted text, unless this text ends with a LF,
767 // in which case make the caret attach to the next line.
768 PRBool endsWithLF =
769 !outString->IsEmpty() && outString->Last() == nsCRT::LF;
770 nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(aSelection));
771 selPrivate->SetInterlinePosition(endsWithLF);
773 aSelection->Collapse(curNode, curOffset);
776 ASSERT_PASSWORD_LENGTHS_EQUAL()
777 return res;
780 nsresult
781 nsTextEditRules::DidInsertText(nsISelection *aSelection,
782 nsresult aResult)
784 return DidInsert(aSelection, aResult);
789 nsresult
790 nsTextEditRules::WillSetTextProperty(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
792 if (!aSelection || !aCancel || !aHandled)
793 { return NS_ERROR_NULL_POINTER; }
795 // XXX: should probably return a success value other than NS_OK that means "not allowed"
796 if (IsPlaintextEditor()) {
797 *aCancel = PR_TRUE;
799 return NS_OK;
802 nsresult
803 nsTextEditRules::DidSetTextProperty(nsISelection *aSelection, nsresult aResult)
805 return NS_OK;
808 nsresult
809 nsTextEditRules::WillRemoveTextProperty(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
811 if (!aSelection || !aCancel || !aHandled)
812 { return NS_ERROR_NULL_POINTER; }
814 // XXX: should probably return a success value other than NS_OK that means "not allowed"
815 if (IsPlaintextEditor()) {
816 *aCancel = PR_TRUE;
818 return NS_OK;
821 nsresult
822 nsTextEditRules::DidRemoveTextProperty(nsISelection *aSelection, nsresult aResult)
824 return NS_OK;
827 nsresult
828 nsTextEditRules::WillDeleteSelection(nsISelection *aSelection,
829 nsIEditor::EDirection aCollapsedAction,
830 PRBool *aCancel,
831 PRBool *aHandled)
833 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
834 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
836 // initialize out param
837 *aCancel = PR_FALSE;
838 *aHandled = PR_FALSE;
840 // if there is only bogus content, cancel the operation
841 if (mBogusNode) {
842 *aCancel = PR_TRUE;
843 return NS_OK;
846 nsresult res = NS_OK;
848 if (IsPasswordEditor())
850 res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction);
851 NS_ENSURE_SUCCESS(res, res);
853 // manage the password buffer
854 PRUint32 start, end;
855 mEditor->GetTextSelectionOffsets(aSelection, start, end);
856 NS_ENSURE_SUCCESS(res, res);
857 nsCOMPtr<nsILookAndFeel> lookAndFeel = do_GetService(kLookAndFeelCID);
859 if (lookAndFeel->GetEchoPassword()) {
860 HideLastPWInput();
861 mLastStart = start;
862 mLastLength = 0;
863 if (mTimer)
865 mTimer->Cancel();
869 if (end == start)
870 { // collapsed selection
871 if (nsIEditor::ePrevious==aCollapsedAction && 0<start) { // del back
872 mPasswordText.Cut(start-1, 1);
874 else if (nsIEditor::eNext==aCollapsedAction) { // del forward
875 mPasswordText.Cut(start, 1);
877 // otherwise nothing to do for this collapsed selection
879 else { // extended selection
880 mPasswordText.Cut(start, end-start);
883 else
885 nsCOMPtr<nsIDOMNode> startNode;
886 PRInt32 startOffset;
887 res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
888 NS_ENSURE_SUCCESS(res, res);
889 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
891 PRBool bCollapsed;
892 res = aSelection->GetIsCollapsed(&bCollapsed);
893 NS_ENSURE_SUCCESS(res, res);
895 if (!bCollapsed)
896 return NS_OK;
898 // Test for distance between caret and text that will be deleted
899 res = CheckBidiLevelForDeletion(aSelection, startNode, startOffset, aCollapsedAction, aCancel);
900 NS_ENSURE_SUCCESS(res, res);
901 if (*aCancel) return NS_OK;
903 res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction);
904 NS_ENSURE_SUCCESS(res, res);
907 res = mEditor->DeleteSelectionImpl(aCollapsedAction);
908 NS_ENSURE_SUCCESS(res, res);
910 *aHandled = PR_TRUE;
911 ASSERT_PASSWORD_LENGTHS_EQUAL()
912 return NS_OK;
915 nsresult
916 nsTextEditRules::DidDeleteSelection(nsISelection *aSelection,
917 nsIEditor::EDirection aCollapsedAction,
918 nsresult aResult)
920 nsCOMPtr<nsIDOMNode> startNode;
921 PRInt32 startOffset;
922 nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
923 NS_ENSURE_SUCCESS(res, res);
924 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
926 // delete empty text nodes at selection
927 if (mEditor->IsTextNode(startNode))
929 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(startNode);
930 PRUint32 strLength;
931 res = textNode->GetLength(&strLength);
932 NS_ENSURE_SUCCESS(res, res);
934 // are we in an empty text node?
935 if (!strLength)
937 res = mEditor->DeleteNode(startNode);
938 NS_ENSURE_SUCCESS(res, res);
941 if (!mDidExplicitlySetInterline)
943 // We prevent the caret from sticking on the left of prior BR
944 // (i.e. the end of previous line) after this deletion. Bug 92124
945 nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(aSelection);
946 if (selPriv) res = selPriv->SetInterlinePosition(PR_TRUE);
948 return res;
951 nsresult
952 nsTextEditRules::WillUndo(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
954 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
955 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
956 // initialize out param
957 *aCancel = PR_FALSE;
958 *aHandled = PR_FALSE;
959 return NS_OK;
962 /* the idea here is to see if the magic empty node has suddenly reappeared as the result of the undo.
963 * if it has, set our state so we remember it.
964 * There is a tradeoff between doing here and at redo, or doing it everywhere else that might care.
965 * Since undo and redo are relatively rare, it makes sense to take the (small) performance hit here.
967 nsresult
968 nsTextEditRules:: DidUndo(nsISelection *aSelection, nsresult aResult)
970 nsresult res = aResult; // if aResult is an error, we return it.
971 if (!aSelection) { return NS_ERROR_NULL_POINTER; }
972 if (NS_SUCCEEDED(res))
974 if (mBogusNode) {
975 mBogusNode = nsnull;
977 else
979 nsIDOMElement *theRoot = mEditor->GetRoot();
980 NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
981 nsCOMPtr<nsIDOMNode> node = mEditor->GetLeftmostChild(theRoot);
982 if (node && mEditor->IsMozEditorBogusNode(node))
983 mBogusNode = node;
986 return res;
989 nsresult
990 nsTextEditRules::WillRedo(nsISelection *aSelection, PRBool *aCancel, PRBool *aHandled)
992 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
993 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
994 // initialize out param
995 *aCancel = PR_FALSE;
996 *aHandled = PR_FALSE;
997 return NS_OK;
1000 nsresult
1001 nsTextEditRules::DidRedo(nsISelection *aSelection, nsresult aResult)
1003 nsresult res = aResult; // if aResult is an error, we return it.
1004 if (!aSelection) { return NS_ERROR_NULL_POINTER; }
1005 if (NS_SUCCEEDED(res))
1007 if (mBogusNode) {
1008 mBogusNode = nsnull;
1010 else
1012 nsIDOMElement *theRoot = mEditor->GetRoot();
1013 NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
1015 nsCOMPtr<nsIDOMNodeList> nodeList;
1016 res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"),
1017 getter_AddRefs(nodeList));
1018 NS_ENSURE_SUCCESS(res, res);
1019 if (nodeList)
1021 PRUint32 len;
1022 nodeList->GetLength(&len);
1024 if (len != 1) return NS_OK; // only in the case of one br could there be the bogus node
1025 nsCOMPtr<nsIDOMNode> node;
1026 nodeList->Item(0, getter_AddRefs(node));
1027 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
1028 if (mEditor->IsMozEditorBogusNode(node))
1029 mBogusNode = node;
1033 return res;
1036 nsresult
1037 nsTextEditRules::WillOutputText(nsISelection *aSelection,
1038 const nsAString *aOutputFormat,
1039 nsAString *aOutString,
1040 PRBool *aCancel,
1041 PRBool *aHandled)
1043 // null selection ok
1044 if (!aOutString || !aOutputFormat || !aCancel || !aHandled)
1045 { return NS_ERROR_NULL_POINTER; }
1047 // initialize out param
1048 *aCancel = PR_FALSE;
1049 *aHandled = PR_FALSE;
1051 nsAutoString outputFormat(*aOutputFormat);
1052 ToLowerCase(outputFormat);
1053 if (outputFormat.EqualsLiteral("text/plain"))
1054 { // only use these rules for plain text output
1055 if (IsPasswordEditor())
1057 *aOutString = mPasswordText;
1058 *aHandled = PR_TRUE;
1060 else if (mBogusNode)
1061 { // this means there's no content, so output null string
1062 aOutString->Truncate();
1063 *aHandled = PR_TRUE;
1066 return NS_OK;
1069 nsresult
1070 nsTextEditRules::DidOutputText(nsISelection *aSelection, nsresult aResult)
1072 return NS_OK;
1075 nsresult
1076 nsTextEditRules::CreateTrailingBRIfNeeded()
1078 // but only if we aren't a single line edit field
1079 if (IsSingleLineEditor())
1080 return NS_OK;
1081 nsIDOMNode *body = mEditor->GetRoot();
1082 NS_ENSURE_TRUE(body, NS_ERROR_NULL_POINTER);
1083 nsCOMPtr<nsIDOMNode> lastChild;
1084 nsresult res = body->GetLastChild(getter_AddRefs(lastChild));
1085 // assuming CreateBogusNodeIfNeeded() has been called first
1086 NS_ENSURE_SUCCESS(res, res);
1087 NS_ENSURE_TRUE(lastChild, NS_ERROR_NULL_POINTER);
1089 if (!nsTextEditUtils::IsBreak(lastChild))
1091 nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
1092 PRUint32 rootLen;
1093 res = mEditor->GetLengthOfDOMNode(body, rootLen);
1094 NS_ENSURE_SUCCESS(res, res);
1095 nsCOMPtr<nsIDOMNode> unused;
1096 res = CreateMozBR(body, rootLen, address_of(unused));
1098 return res;
1101 nsresult
1102 nsTextEditRules::CreateBogusNodeIfNeeded(nsISelection *aSelection)
1104 if (!aSelection) { return NS_ERROR_NULL_POINTER; }
1105 if (!mEditor) { return NS_ERROR_NULL_POINTER; }
1106 if (mBogusNode) return NS_OK; // let's not create more than one, ok?
1108 // tell rules system to not do any post-processing
1109 nsAutoRules beginRulesSniffing(mEditor, nsEditor::kOpIgnore, nsIEditor::eNone);
1111 nsIDOMNode* body = mEditor->GetRoot();
1112 if (!body)
1114 // we don't even have a body yet, don't insert any bogus nodes at
1115 // this point.
1117 return NS_OK;
1120 // now we've got the body tag.
1121 // iterate the body tag, looking for editable content
1122 // if no editable content is found, insert the bogus node
1123 PRBool needsBogusContent=PR_TRUE;
1124 nsCOMPtr<nsIDOMNode> bodyChild;
1125 nsresult res = body->GetFirstChild(getter_AddRefs(bodyChild));
1126 while ((NS_SUCCEEDED(res)) && bodyChild)
1128 if (mEditor->IsMozEditorBogusNode(bodyChild) ||
1129 !mEditor->IsEditable(body) ||
1130 mEditor->IsEditable(bodyChild))
1132 needsBogusContent = PR_FALSE;
1133 break;
1135 nsCOMPtr<nsIDOMNode>temp;
1136 bodyChild->GetNextSibling(getter_AddRefs(temp));
1137 bodyChild = do_QueryInterface(temp);
1139 // Skip adding the bogus node if body is read-only
1140 if (needsBogusContent && mEditor->IsModifiableNode(body))
1142 // create a br
1143 nsCOMPtr<nsIContent> newContent;
1144 res = mEditor->CreateHTMLContent(NS_LITERAL_STRING("br"), getter_AddRefs(newContent));
1145 NS_ENSURE_SUCCESS(res, res);
1146 nsCOMPtr<nsIDOMElement>brElement = do_QueryInterface(newContent);
1148 // set mBogusNode to be the newly created <br>
1149 mBogusNode = brElement;
1150 NS_ENSURE_TRUE(mBogusNode, NS_ERROR_NULL_POINTER);
1152 // give it a special attribute
1153 newContent->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom,
1154 kMOZEditorBogusNodeValue, PR_FALSE);
1156 // put the node in the document
1157 res = mEditor->InsertNode(mBogusNode, body, 0);
1158 NS_ENSURE_SUCCESS(res, res);
1160 // set selection
1161 aSelection->Collapse(body, 0);
1163 return res;
1167 nsresult
1168 nsTextEditRules::TruncateInsertionIfNeeded(nsISelection *aSelection,
1169 const nsAString *aInString,
1170 nsAString *aOutString,
1171 PRInt32 aMaxLength,
1172 PRBool *aTruncated)
1174 if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;}
1176 nsresult res = NS_OK;
1177 *aOutString = *aInString;
1178 if (aTruncated) {
1179 *aTruncated = PR_FALSE;
1182 if ((-1 != aMaxLength) && IsPlaintextEditor() && !mEditor->IsIMEComposing() )
1184 // Get the current text length.
1185 // Get the length of inString.
1186 // Get the length of the selection.
1187 // If selection is collapsed, it is length 0.
1188 // Subtract the length of the selection from the len(doc)
1189 // since we'll delete the selection on insert.
1190 // This is resultingDocLength.
1191 // Get old length of IME composing string
1192 // which will be replaced by new one.
1193 // If (resultingDocLength) is at or over max, cancel the insert
1194 // If (resultingDocLength) + (length of input) > max,
1195 // set aOutString to subset of inString so length = max
1196 PRInt32 docLength;
1197 res = mEditor->GetTextLength(&docLength);
1198 if (NS_FAILED(res)) { return res; }
1200 PRUint32 start, end;
1201 res = mEditor->GetTextSelectionOffsets(aSelection, start, end);
1202 if (NS_FAILED(res)) { return res; }
1204 PRInt32 oldCompStrLength;
1205 res = mEditor->GetIMEBufferLength(&oldCompStrLength);
1206 if (NS_FAILED(res)) { return res; }
1208 const PRInt32 selectionLength = end - start;
1209 const PRInt32 resultingDocLength = docLength - selectionLength - oldCompStrLength;
1210 if (resultingDocLength >= aMaxLength)
1212 aOutString->Truncate();
1213 if (aTruncated) {
1214 *aTruncated = PR_TRUE;
1217 else
1219 PRInt32 inCount = aOutString->Length();
1220 if (inCount + resultingDocLength > aMaxLength)
1222 aOutString->Truncate(aMaxLength - resultingDocLength);
1223 if (aTruncated) {
1224 *aTruncated = PR_TRUE;
1229 return res;
1232 nsresult
1233 nsTextEditRules::ResetIMETextPWBuf()
1235 mPasswordIMEText.Truncate();
1236 return NS_OK;
1239 nsresult
1240 nsTextEditRules::RemoveIMETextFromPWBuf(PRUint32 &aStart, nsAString *aIMEString)
1242 if (!aIMEString) {
1243 return NS_ERROR_NULL_POINTER;
1246 // initialize PasswordIME
1247 if (mPasswordIMEText.IsEmpty()) {
1248 mPasswordIMEIndex = aStart;
1250 else {
1251 // manage the password buffer
1252 mPasswordText.Cut(mPasswordIMEIndex, mPasswordIMEText.Length());
1253 aStart = mPasswordIMEIndex;
1256 mPasswordIMEText.Assign(*aIMEString);
1257 return NS_OK;
1260 NS_IMETHODIMP nsTextEditRules::Notify(class nsITimer *) {
1261 nsresult res = HideLastPWInput();
1262 ASSERT_PASSWORD_LENGTHS_EQUAL();
1263 mLastLength = 0;
1264 return res;
1267 nsresult nsTextEditRules::HideLastPWInput() {
1268 if (!mLastLength) {
1269 // Special case, we're trying to replace a range that no longer exists
1270 return NS_OK;
1273 nsAutoString hiddenText;
1274 FillBufWithPWChars(&hiddenText, mLastLength);
1276 nsCOMPtr<nsISelection> selection;
1277 PRUint32 start, end;
1278 nsresult res = mEditor->GetSelection(getter_AddRefs(selection));
1279 NS_ENSURE_SUCCESS(res, res);
1280 res = mEditor->GetTextSelectionOffsets(selection, start, end);
1281 NS_ENSURE_SUCCESS(res, res);
1283 nsCOMPtr<nsIDOMNode> selNode = GetTextNode(selection, mEditor);
1284 NS_ENSURE_TRUE(selNode, NS_OK);
1286 nsCOMPtr<nsIDOMCharacterData> nodeAsText(do_QueryInterface(selNode));
1287 NS_ENSURE_TRUE(nodeAsText, NS_OK);
1289 nodeAsText->ReplaceData(mLastStart, mLastLength, hiddenText);
1290 selection->Collapse(selNode, start);
1291 if (start != end)
1292 selection->Extend(selNode, end);
1293 return NS_OK;
1296 // static
1297 nsresult
1298 nsTextEditRules::FillBufWithPWChars(nsAString *aOutString, PRInt32 aLength)
1300 if (!aOutString) {return NS_ERROR_NULL_POINTER;}
1302 // change the output to the platform password character
1303 PRUnichar passwordChar = PRUnichar('*');
1304 nsCOMPtr<nsILookAndFeel> lookAndFeel = do_GetService(kLookAndFeelCID);
1305 if (lookAndFeel)
1307 passwordChar = lookAndFeel->GetPasswordCharacter();
1310 PRInt32 i;
1311 aOutString->Truncate();
1312 for (i=0; i < aLength; i++)
1313 aOutString->Append(passwordChar);
1315 return NS_OK;
1319 ///////////////////////////////////////////////////////////////////////////
1320 // CreateMozBR: put a BR node with moz attribute at {aNode, aOffset}
1322 nsresult
1323 nsTextEditRules::CreateMozBR(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outBRNode)
1325 NS_ENSURE_TRUE(inParent && outBRNode, NS_ERROR_NULL_POINTER);
1327 nsresult res = mEditor->CreateBR(inParent, inOffset, outBRNode);
1328 NS_ENSURE_SUCCESS(res, res);
1330 // give it special moz attr
1331 nsCOMPtr<nsIDOMElement> brElem = do_QueryInterface(*outBRNode);
1332 if (brElem)
1334 res = mEditor->SetAttribute(brElem, NS_LITERAL_STRING("type"), NS_LITERAL_STRING("_moz"));
1335 NS_ENSURE_SUCCESS(res, res);
1337 return res;
1340 NS_IMETHODIMP
1341 nsTextEditRules::DocumentModified()
1343 return NS_ERROR_NOT_IMPLEMENTED;