Line data Source code
1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* This Source Code Form is subject to the terms of the Mozilla Public
3 : * License, v. 2.0. If a copy of the MPL was not distributed with this
4 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 :
6 : #include "mozilla/TextEditRules.h"
7 :
8 : #include "TextEditUtils.h"
9 : #include "mozilla/Assertions.h"
10 : #include "mozilla/EditorUtils.h"
11 : #include "mozilla/LookAndFeel.h"
12 : #include "mozilla/Preferences.h"
13 : #include "mozilla/TextComposition.h"
14 : #include "mozilla/TextEditor.h"
15 : #include "mozilla/dom/Element.h"
16 : #include "mozilla/dom/NodeIterator.h"
17 : #include "mozilla/dom/Selection.h"
18 : #include "nsAString.h"
19 : #include "nsCOMPtr.h"
20 : #include "nsCRT.h"
21 : #include "nsCRTGlue.h"
22 : #include "nsComponentManagerUtils.h"
23 : #include "nsContentUtils.h"
24 : #include "nsDebug.h"
25 : #include "nsError.h"
26 : #include "nsGkAtoms.h"
27 : #include "nsIContent.h"
28 : #include "nsIDocumentEncoder.h"
29 : #include "nsIDOMDocument.h"
30 : #include "nsIDOMNode.h"
31 : #include "nsIDOMNodeFilter.h"
32 : #include "nsNameSpaceManager.h"
33 : #include "nsINode.h"
34 : #include "nsIPlaintextEditor.h"
35 : #include "nsISupportsBase.h"
36 : #include "nsLiteralString.h"
37 : #include "nsTextNode.h"
38 : #include "nsUnicharUtils.h"
39 : #include "nsIHTMLCollection.h"
40 : #include "nsPrintfCString.h"
41 :
42 : namespace mozilla {
43 :
44 : using namespace dom;
45 :
46 : #define CANCEL_OPERATION_IF_READONLY_OR_DISABLED \
47 : if (IsReadonly() || IsDisabled()) \
48 : { \
49 : *aCancel = true; \
50 : return NS_OK; \
51 : };
52 :
53 : /********************************************************
54 : * mozilla::TextEditRules
55 : ********************************************************/
56 :
57 1 : TextEditRules::TextEditRules()
58 : : mTextEditor(nullptr)
59 : , mPasswordIMEIndex(0)
60 : , mCachedSelectionOffset(0)
61 : , mActionNesting(0)
62 : , mLockRulesSniffing(false)
63 : , mDidExplicitlySetInterline(false)
64 : , mDeleteBidiImmediately(false)
65 : , mTheAction(EditAction::none)
66 : , mLastStart(0)
67 1 : , mLastLength(0)
68 : {
69 1 : InitFields();
70 1 : }
71 :
72 : void
73 3 : TextEditRules::InitFields()
74 : {
75 3 : mTextEditor = nullptr;
76 3 : mPasswordText.Truncate();
77 3 : mPasswordIMEText.Truncate();
78 3 : mPasswordIMEIndex = 0;
79 3 : mBogusNode = nullptr;
80 3 : mCachedSelectionNode = nullptr;
81 3 : mCachedSelectionOffset = 0;
82 3 : mActionNesting = 0;
83 3 : mLockRulesSniffing = false;
84 3 : mDidExplicitlySetInterline = false;
85 3 : mDeleteBidiImmediately = false;
86 3 : mTheAction = EditAction::none;
87 3 : mTimer = nullptr;
88 3 : mLastStart = 0;
89 3 : mLastLength = 0;
90 3 : }
91 :
92 0 : TextEditRules::~TextEditRules()
93 : {
94 : // do NOT delete mTextEditor here. We do not hold a ref count to
95 : // mTextEditor. mTextEditor owns our lifespan.
96 :
97 0 : if (mTimer) {
98 0 : mTimer->Cancel();
99 : }
100 0 : }
101 :
102 0 : NS_IMPL_CYCLE_COLLECTION(TextEditRules, mBogusNode, mCachedSelectionNode)
103 :
104 2 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEditRules)
105 1 : NS_INTERFACE_MAP_ENTRY(nsIEditRules)
106 0 : NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
107 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditRules)
108 0 : NS_INTERFACE_MAP_END
109 :
110 17 : NS_IMPL_CYCLE_COLLECTING_ADDREF(TextEditRules)
111 16 : NS_IMPL_CYCLE_COLLECTING_RELEASE(TextEditRules)
112 :
113 : NS_IMETHODIMP
114 2 : TextEditRules::Init(TextEditor* aTextEditor)
115 : {
116 2 : if (!aTextEditor) {
117 0 : return NS_ERROR_NULL_POINTER;
118 : }
119 :
120 2 : InitFields();
121 :
122 : // We hold a non-refcounted reference back to our editor.
123 2 : mTextEditor = aTextEditor;
124 4 : RefPtr<Selection> selection = mTextEditor->GetSelection();
125 2 : NS_WARNING_ASSERTION(selection, "editor cannot get selection");
126 :
127 : // Put in a magic br if needed. This method handles null selection,
128 : // which should never happen anyway
129 2 : nsresult rv = CreateBogusNodeIfNeeded(selection);
130 2 : NS_ENSURE_SUCCESS(rv, rv);
131 :
132 : // If the selection hasn't been set up yet, set it up collapsed to the end of
133 : // our editable content.
134 : int32_t rangeCount;
135 2 : rv = selection->GetRangeCount(&rangeCount);
136 2 : NS_ENSURE_SUCCESS(rv, rv);
137 2 : if (!rangeCount) {
138 0 : rv = mTextEditor->EndOfDocument();
139 0 : NS_ENSURE_SUCCESS(rv, rv);
140 : }
141 :
142 2 : if (IsPlaintextEditor()) {
143 : // ensure trailing br node
144 2 : rv = CreateTrailingBRIfNeeded();
145 2 : NS_ENSURE_SUCCESS(rv, rv);
146 : }
147 :
148 2 : mDeleteBidiImmediately =
149 2 : Preferences::GetBool("bidi.edit.delete_immediately", false);
150 :
151 2 : return NS_OK;
152 : }
153 :
154 : NS_IMETHODIMP
155 2 : TextEditRules::SetInitialValue(const nsAString& aValue)
156 : {
157 2 : if (IsPasswordEditor()) {
158 0 : mPasswordText = aValue;
159 : }
160 2 : return NS_OK;
161 : }
162 :
163 : NS_IMETHODIMP
164 1 : TextEditRules::DetachEditor()
165 : {
166 1 : if (mTimer) {
167 0 : mTimer->Cancel();
168 : }
169 1 : mTextEditor = nullptr;
170 1 : return NS_OK;
171 : }
172 :
173 : NS_IMETHODIMP
174 3 : TextEditRules::BeforeEdit(EditAction action,
175 : nsIEditor::EDirection aDirection)
176 : {
177 3 : if (mLockRulesSniffing) {
178 0 : return NS_OK;
179 : }
180 :
181 6 : AutoLockRulesSniffing lockIt(this);
182 3 : mDidExplicitlySetInterline = false;
183 3 : if (!mActionNesting) {
184 : // let rules remember the top level action
185 3 : mTheAction = action;
186 : }
187 3 : mActionNesting++;
188 :
189 : // get the selection and cache the position before editing
190 3 : if (NS_WARN_IF(!mTextEditor)) {
191 0 : return NS_ERROR_FAILURE;
192 : }
193 6 : RefPtr<TextEditor> textEditor = mTextEditor;
194 6 : RefPtr<Selection> selection = textEditor->GetSelection();
195 3 : NS_ENSURE_STATE(selection);
196 :
197 3 : if (action == EditAction::setText) {
198 : // setText replaces all text, so mCachedSelectionNode might be invalid on
199 : // AfterEdit.
200 : // Since this will be used as start position of spellchecker, we should
201 : // use root instead.
202 1 : mCachedSelectionNode = textEditor->GetRoot();
203 1 : mCachedSelectionOffset = 0;
204 : } else {
205 2 : mCachedSelectionNode = selection->GetAnchorNode();
206 2 : selection->GetAnchorOffset(&mCachedSelectionOffset);
207 : }
208 :
209 3 : return NS_OK;
210 : }
211 :
212 : NS_IMETHODIMP
213 3 : TextEditRules::AfterEdit(EditAction action,
214 : nsIEditor::EDirection aDirection)
215 : {
216 3 : if (mLockRulesSniffing) {
217 0 : return NS_OK;
218 : }
219 :
220 6 : AutoLockRulesSniffing lockIt(this);
221 :
222 3 : NS_PRECONDITION(mActionNesting>0, "bad action nesting!");
223 3 : if (!--mActionNesting) {
224 3 : NS_ENSURE_STATE(mTextEditor);
225 6 : RefPtr<Selection> selection = mTextEditor->GetSelection();
226 3 : NS_ENSURE_STATE(selection);
227 :
228 3 : NS_ENSURE_STATE(mTextEditor);
229 : nsresult rv =
230 3 : mTextEditor->HandleInlineSpellCheck(action, selection,
231 : GetAsDOMNode(mCachedSelectionNode),
232 : mCachedSelectionOffset,
233 3 : nullptr, 0, nullptr, 0);
234 3 : NS_ENSURE_SUCCESS(rv, rv);
235 :
236 : // no longer uses mCachedSelectionNode, so release it.
237 3 : mCachedSelectionNode = nullptr;
238 :
239 : // if only trailing <br> remaining remove it
240 3 : rv = RemoveRedundantTrailingBR();
241 3 : if (NS_FAILED(rv)) {
242 0 : return rv;
243 : }
244 :
245 : // detect empty doc
246 3 : rv = CreateBogusNodeIfNeeded(selection);
247 3 : NS_ENSURE_SUCCESS(rv, rv);
248 :
249 : // ensure trailing br node
250 3 : rv = CreateTrailingBRIfNeeded();
251 3 : NS_ENSURE_SUCCESS(rv, rv);
252 :
253 : // collapse the selection to the trailing BR if it's at the end of our text node
254 3 : CollapseSelectionToTrailingBRIfNeeded(selection);
255 : }
256 3 : return NS_OK;
257 : }
258 :
259 : NS_IMETHODIMP
260 5 : TextEditRules::WillDoAction(Selection* aSelection,
261 : RulesInfo* aInfo,
262 : bool* aCancel,
263 : bool* aHandled)
264 : {
265 : // null selection is legal
266 5 : MOZ_ASSERT(aInfo && aCancel && aHandled);
267 :
268 5 : *aCancel = false;
269 5 : *aHandled = false;
270 :
271 : // my kingdom for dynamic cast
272 5 : TextRulesInfo* info = static_cast<TextRulesInfo*>(aInfo);
273 :
274 5 : switch (info->action) {
275 : case EditAction::insertBreak:
276 0 : UndefineCaretBidiLevel(aSelection);
277 0 : return WillInsertBreak(aSelection, aCancel, aHandled, info->maxLength);
278 : case EditAction::insertText:
279 : case EditAction::insertIMEText:
280 0 : UndefineCaretBidiLevel(aSelection);
281 0 : return WillInsertText(info->action, aSelection, aCancel, aHandled,
282 0 : info->inString, info->outString, info->maxLength);
283 : case EditAction::setText:
284 1 : UndefineCaretBidiLevel(aSelection);
285 1 : return WillSetText(*aSelection, aCancel, aHandled, info->inString,
286 1 : info->maxLength);
287 : case EditAction::deleteSelection:
288 0 : return WillDeleteSelection(aSelection, info->collapsedAction,
289 0 : aCancel, aHandled);
290 : case EditAction::undo:
291 0 : return WillUndo(aSelection, aCancel, aHandled);
292 : case EditAction::redo:
293 0 : return WillRedo(aSelection, aCancel, aHandled);
294 : case EditAction::setTextProperty:
295 0 : return WillSetTextProperty(aSelection, aCancel, aHandled);
296 : case EditAction::removeTextProperty:
297 0 : return WillRemoveTextProperty(aSelection, aCancel, aHandled);
298 : case EditAction::outputText:
299 4 : return WillOutputText(aSelection, info->outputFormat, info->outString,
300 4 : info->flags, aCancel, aHandled);
301 : case EditAction::insertElement:
302 : // i had thought this would be html rules only. but we put pre elements
303 : // into plaintext mail when doing quoting for reply! doh!
304 0 : WillInsert(*aSelection, aCancel);
305 0 : return NS_OK;
306 : default:
307 0 : return NS_ERROR_FAILURE;
308 : }
309 : }
310 :
311 : NS_IMETHODIMP
312 1 : TextEditRules::DidDoAction(Selection* aSelection,
313 : RulesInfo* aInfo,
314 : nsresult aResult)
315 : {
316 1 : NS_ENSURE_STATE(mTextEditor);
317 : // don't let any txns in here move the selection around behind our back.
318 : // Note that this won't prevent explicit selection setting from working.
319 2 : AutoTransactionsConserveSelection dontSpazMySelection(mTextEditor);
320 :
321 1 : NS_ENSURE_TRUE(aSelection && aInfo, NS_ERROR_NULL_POINTER);
322 :
323 : // my kingdom for dynamic cast
324 1 : TextRulesInfo* info = static_cast<TextRulesInfo*>(aInfo);
325 :
326 1 : switch (info->action) {
327 : case EditAction::insertBreak:
328 0 : return DidInsertBreak(aSelection, aResult);
329 : case EditAction::insertText:
330 : case EditAction::insertIMEText:
331 0 : return DidInsertText(aSelection, aResult);
332 : case EditAction::setText:
333 1 : return DidSetText(*aSelection, aResult);
334 : case EditAction::deleteSelection:
335 0 : return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
336 : case EditAction::undo:
337 0 : return DidUndo(aSelection, aResult);
338 : case EditAction::redo:
339 0 : return DidRedo(aSelection, aResult);
340 : case EditAction::setTextProperty:
341 0 : return DidSetTextProperty(aSelection, aResult);
342 : case EditAction::removeTextProperty:
343 0 : return DidRemoveTextProperty(aSelection, aResult);
344 : case EditAction::outputText:
345 0 : return DidOutputText(aSelection, aResult);
346 : default:
347 : // Don't fail on transactions we don't handle here!
348 0 : return NS_OK;
349 : }
350 : }
351 :
352 : /**
353 : * Return false if the editor has non-empty text nodes or non-text
354 : * nodes. Otherwise, i.e., there is no meaningful content,
355 : * return true.
356 : */
357 : NS_IMETHODIMP_(bool)
358 4 : TextEditRules::DocumentIsEmpty()
359 : {
360 4 : if (mBogusNode) {
361 1 : return true;
362 : }
363 :
364 : // Even if there is no bogus node, we should detect as empty document
365 : // if all children are text node and these are no content.
366 3 : if (NS_WARN_IF(!mTextEditor)) {
367 0 : return true;
368 : }
369 3 : Element* rootElement = mTextEditor->GetRoot();
370 3 : if (!rootElement) {
371 0 : return true;
372 : }
373 :
374 3 : uint32_t childCount = rootElement->GetChildCount();
375 3 : for (uint32_t i = 0; i < childCount; i++) {
376 3 : nsINode* node = rootElement->GetChildAt(i);
377 6 : if (!EditorBase::IsTextNode(node) ||
378 3 : node->Length()) {
379 3 : return false;
380 : }
381 : }
382 0 : return true;
383 : }
384 :
385 : void
386 1 : TextEditRules::WillInsert(Selection& aSelection, bool* aCancel)
387 : {
388 1 : MOZ_ASSERT(aCancel);
389 :
390 1 : if (IsReadonly() || IsDisabled()) {
391 0 : *aCancel = true;
392 0 : return;
393 : }
394 :
395 : // initialize out param
396 1 : *aCancel = false;
397 :
398 : // check for the magic content node and delete it if it exists
399 1 : if (mBogusNode) {
400 1 : NS_ENSURE_TRUE_VOID(mTextEditor);
401 1 : mTextEditor->DeleteNode(mBogusNode);
402 1 : mBogusNode = nullptr;
403 : }
404 : }
405 :
406 : nsresult
407 0 : TextEditRules::DidInsert(Selection* aSelection,
408 : nsresult aResult)
409 : {
410 0 : return NS_OK;
411 : }
412 :
413 : nsresult
414 0 : TextEditRules::WillInsertBreak(Selection* aSelection,
415 : bool* aCancel,
416 : bool* aHandled,
417 : int32_t aMaxLength)
418 : {
419 0 : if (!aSelection || !aCancel || !aHandled) {
420 0 : return NS_ERROR_NULL_POINTER;
421 : }
422 0 : CANCEL_OPERATION_IF_READONLY_OR_DISABLED
423 0 : *aHandled = false;
424 0 : if (IsSingleLineEditor()) {
425 0 : *aCancel = true;
426 : } else {
427 : // handle docs with a max length
428 : // NOTE, this function copies inString into outString for us.
429 0 : NS_NAMED_LITERAL_STRING(inString, "\n");
430 0 : nsAutoString outString;
431 : bool didTruncate;
432 0 : nsresult rv = TruncateInsertionIfNeeded(aSelection, &inString.AsString(),
433 : &outString, aMaxLength,
434 0 : &didTruncate);
435 0 : NS_ENSURE_SUCCESS(rv, rv);
436 0 : if (didTruncate) {
437 0 : *aCancel = true;
438 0 : return NS_OK;
439 : }
440 :
441 0 : *aCancel = false;
442 :
443 : // if the selection isn't collapsed, delete it.
444 : bool bCollapsed;
445 0 : rv = aSelection->GetIsCollapsed(&bCollapsed);
446 0 : NS_ENSURE_SUCCESS(rv, rv);
447 0 : if (!bCollapsed) {
448 0 : NS_ENSURE_STATE(mTextEditor);
449 0 : rv = mTextEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
450 0 : NS_ENSURE_SUCCESS(rv, rv);
451 : }
452 :
453 0 : WillInsert(*aSelection, aCancel);
454 : // initialize out param
455 : // we want to ignore result of WillInsert()
456 0 : *aCancel = false;
457 : }
458 0 : return NS_OK;
459 : }
460 :
461 : nsresult
462 0 : TextEditRules::DidInsertBreak(Selection* aSelection,
463 : nsresult aResult)
464 : {
465 0 : return NS_OK;
466 : }
467 :
468 : nsresult
469 3 : TextEditRules::CollapseSelectionToTrailingBRIfNeeded(Selection* aSelection)
470 : {
471 : // we only need to execute the stuff below if we are a plaintext editor.
472 : // html editors have a different mechanism for putting in mozBR's
473 : // (because there are a bunch more places you have to worry about it in html)
474 3 : if (!IsPlaintextEditor()) {
475 0 : return NS_OK;
476 : }
477 :
478 3 : NS_ENSURE_STATE(mTextEditor);
479 :
480 : // If there is no selection ranges, we should set to the end of the editor.
481 : // This is usually performed in TextEditRules::Init(), however, if the
482 : // editor is reframed, this may be called by AfterEdit().
483 3 : if (!aSelection->RangeCount()) {
484 0 : mTextEditor->EndOfDocument();
485 : }
486 :
487 : // if we are at the end of the textarea, we need to set the
488 : // selection to stick to the mozBR at the end of the textarea.
489 : int32_t selOffset;
490 6 : nsCOMPtr<nsINode> selNode;
491 : nsresult rv =
492 3 : EditorBase::GetStartNodeAndOffset(aSelection,
493 6 : getter_AddRefs(selNode), &selOffset);
494 3 : if (NS_WARN_IF(NS_FAILED(rv))) {
495 0 : return rv;
496 : }
497 :
498 3 : if (!EditorBase::IsTextNode(selNode)) {
499 3 : return NS_OK; // Nothing to do if we're not at a text node.
500 : }
501 :
502 : // nothing to do if we're not at the end of the text node
503 0 : if (selOffset != static_cast<int32_t>(selNode->Length())) {
504 0 : return NS_OK;
505 : }
506 :
507 : int32_t parentOffset;
508 : nsINode* parentNode =
509 0 : EditorBase::GetNodeLocation(selNode, &parentOffset);
510 :
511 0 : NS_ENSURE_STATE(mTextEditor);
512 0 : nsINode* root = mTextEditor->GetRoot();
513 0 : if (NS_WARN_IF(!root)) {
514 0 : return NS_ERROR_NULL_POINTER;
515 : }
516 0 : if (parentNode != root) {
517 0 : return NS_OK;
518 : }
519 :
520 0 : nsINode* nextNode = parentNode->GetChildAt(parentOffset + 1);
521 0 : if (nextNode && TextEditUtils::IsMozBR(nextNode)) {
522 0 : rv = aSelection->Collapse(parentNode, parentOffset + 1);
523 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
524 0 : return rv;
525 : }
526 : }
527 0 : return NS_OK;
528 : }
529 :
530 : static inline already_AddRefed<nsINode>
531 0 : GetTextNode(Selection* selection)
532 : {
533 : int32_t selOffset;
534 0 : nsCOMPtr<nsINode> selNode;
535 : nsresult rv =
536 0 : EditorBase::GetStartNodeAndOffset(selection,
537 0 : getter_AddRefs(selNode), &selOffset);
538 0 : NS_ENSURE_SUCCESS(rv, nullptr);
539 0 : if (!EditorBase::IsTextNode(selNode)) {
540 : // This should be the root node, walk the tree looking for text nodes
541 : RefPtr<NodeIterator> iter =
542 : new NodeIterator(selNode, nsIDOMNodeFilter::SHOW_TEXT,
543 0 : NodeFilterHolder());
544 0 : while (!EditorBase::IsTextNode(selNode)) {
545 0 : IgnoredErrorResult rv;
546 0 : selNode = iter->NextNode(rv);
547 0 : if (!selNode) {
548 0 : return nullptr;
549 : }
550 : }
551 : }
552 0 : return selNode.forget();
553 : }
554 : #ifdef DEBUG
555 : #define ASSERT_PASSWORD_LENGTHS_EQUAL() \
556 : if (IsPasswordEditor() && mTextEditor->GetRoot()) { \
557 : int32_t txtLen; \
558 : mTextEditor->GetTextLength(&txtLen); \
559 : NS_ASSERTION(mPasswordText.Length() == uint32_t(txtLen), \
560 : "password length not equal to number of asterisks"); \
561 : }
562 : #else
563 : #define ASSERT_PASSWORD_LENGTHS_EQUAL()
564 : #endif
565 :
566 : // static
567 : void
568 1 : TextEditRules::HandleNewLines(nsString& aString,
569 : int32_t aNewlineHandling)
570 : {
571 1 : if (aNewlineHandling < 0) {
572 : int32_t caretStyle;
573 0 : TextEditor::GetDefaultEditorPrefs(aNewlineHandling, caretStyle);
574 : }
575 :
576 1 : switch(aNewlineHandling) {
577 : case nsIPlaintextEditor::eNewlinesReplaceWithSpaces:
578 : // Strip trailing newlines first so we don't wind up with trailing spaces
579 0 : aString.Trim(CRLF, false, true);
580 0 : aString.ReplaceChar(CRLF, ' ');
581 0 : break;
582 : case nsIPlaintextEditor::eNewlinesStrip:
583 0 : aString.StripCRLF();
584 0 : break;
585 : case nsIPlaintextEditor::eNewlinesPasteToFirst:
586 : default: {
587 0 : int32_t firstCRLF = aString.FindCharInSet(CRLF);
588 :
589 : // we get first *non-empty* line.
590 0 : int32_t offset = 0;
591 0 : while (firstCRLF == offset) {
592 0 : offset++;
593 0 : firstCRLF = aString.FindCharInSet(CRLF, offset);
594 : }
595 0 : if (firstCRLF > 0) {
596 0 : aString.Truncate(firstCRLF);
597 : }
598 0 : if (offset > 0) {
599 0 : aString.Cut(0, offset);
600 : }
601 0 : break;
602 : }
603 : case nsIPlaintextEditor::eNewlinesReplaceWithCommas:
604 0 : aString.Trim(CRLF, true, true);
605 0 : aString.ReplaceChar(CRLF, ',');
606 0 : break;
607 : case nsIPlaintextEditor::eNewlinesStripSurroundingWhitespace: {
608 2 : nsAutoString result;
609 1 : uint32_t offset = 0;
610 1 : while (offset < aString.Length()) {
611 1 : int32_t nextCRLF = aString.FindCharInSet(CRLF, offset);
612 1 : if (nextCRLF < 0) {
613 1 : result.Append(nsDependentSubstring(aString, offset));
614 1 : break;
615 : }
616 0 : uint32_t wsBegin = nextCRLF;
617 : // look backwards for the first non-whitespace char
618 0 : while (wsBegin > offset && NS_IS_SPACE(aString[wsBegin - 1])) {
619 0 : --wsBegin;
620 : }
621 0 : result.Append(nsDependentSubstring(aString, offset, wsBegin - offset));
622 0 : offset = nextCRLF + 1;
623 0 : while (offset < aString.Length() && NS_IS_SPACE(aString[offset])) {
624 0 : ++offset;
625 : }
626 : }
627 1 : aString = result;
628 1 : break;
629 : }
630 : case nsIPlaintextEditor::eNewlinesPasteIntact:
631 : // even if we're pasting newlines, don't paste leading/trailing ones
632 0 : aString.Trim(CRLF, true, true);
633 0 : break;
634 : }
635 1 : }
636 :
637 : nsresult
638 0 : TextEditRules::WillInsertText(EditAction aAction,
639 : Selection* aSelection,
640 : bool* aCancel,
641 : bool* aHandled,
642 : const nsAString* inString,
643 : nsAString* outString,
644 : int32_t aMaxLength)
645 : {
646 0 : if (!aSelection || !aCancel || !aHandled) {
647 0 : return NS_ERROR_NULL_POINTER;
648 : }
649 :
650 0 : if (inString->IsEmpty() && aAction != EditAction::insertIMEText) {
651 : // HACK: this is a fix for bug 19395
652 : // I can't outlaw all empty insertions
653 : // because IME transaction depend on them
654 : // There is more work to do to make the
655 : // world safe for IME.
656 0 : *aCancel = true;
657 0 : *aHandled = false;
658 0 : return NS_OK;
659 : }
660 :
661 : // initialize out param
662 0 : *aCancel = false;
663 0 : *aHandled = true;
664 :
665 : // handle docs with a max length
666 : // NOTE, this function copies inString into outString for us.
667 0 : bool truncated = false;
668 0 : nsresult rv = TruncateInsertionIfNeeded(aSelection, inString, outString,
669 0 : aMaxLength, &truncated);
670 0 : NS_ENSURE_SUCCESS(rv, rv);
671 : // If we're exceeding the maxlength when composing IME, we need to clean up
672 : // the composing text, so we shouldn't return early.
673 0 : if (truncated && outString->IsEmpty() &&
674 : aAction != EditAction::insertIMEText) {
675 0 : *aCancel = true;
676 0 : return NS_OK;
677 : }
678 :
679 0 : uint32_t start = 0;
680 0 : uint32_t end = 0;
681 :
682 : // handle password field docs
683 0 : if (IsPasswordEditor()) {
684 0 : NS_ENSURE_STATE(mTextEditor);
685 0 : nsContentUtils::GetSelectionInTextControl(aSelection,
686 0 : mTextEditor->GetRoot(),
687 0 : start, end);
688 : }
689 :
690 : // if the selection isn't collapsed, delete it.
691 : bool bCollapsed;
692 0 : rv = aSelection->GetIsCollapsed(&bCollapsed);
693 0 : NS_ENSURE_SUCCESS(rv, rv);
694 0 : if (!bCollapsed) {
695 0 : NS_ENSURE_STATE(mTextEditor);
696 0 : rv = mTextEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
697 0 : NS_ENSURE_SUCCESS(rv, rv);
698 : }
699 :
700 0 : WillInsert(*aSelection, aCancel);
701 : // initialize out param
702 : // we want to ignore result of WillInsert()
703 0 : *aCancel = false;
704 :
705 : // handle password field data
706 : // this has the side effect of changing all the characters in aOutString
707 : // to the replacement character
708 0 : if (IsPasswordEditor() &&
709 : aAction == EditAction::insertIMEText) {
710 0 : RemoveIMETextFromPWBuf(start, outString);
711 : }
712 :
713 : // People have lots of different ideas about what text fields
714 : // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935.
715 : // The six possible options are:
716 : // 0. paste newlines intact
717 : // 1. paste up to the first newline (default)
718 : // 2. replace newlines with spaces
719 : // 3. strip newlines
720 : // 4. replace with commas
721 : // 5. strip newlines and surrounding whitespace
722 : // So find out what we're expected to do:
723 0 : if (IsSingleLineEditor()) {
724 0 : nsAutoString tString(*outString);
725 :
726 0 : NS_ENSURE_STATE(mTextEditor);
727 0 : HandleNewLines(tString, mTextEditor->mNewlineHandling);
728 :
729 0 : outString->Assign(tString);
730 : }
731 :
732 0 : if (IsPasswordEditor()) {
733 : // manage the password buffer
734 0 : mPasswordText.Insert(*outString, start);
735 :
736 0 : if (LookAndFeel::GetEchoPassword() && !DontEchoPassword()) {
737 0 : HideLastPWInput();
738 0 : mLastStart = start;
739 0 : mLastLength = outString->Length();
740 0 : if (mTimer) {
741 0 : mTimer->Cancel();
742 : } else {
743 0 : mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
744 0 : NS_ENSURE_SUCCESS(rv, rv);
745 : }
746 0 : mTimer->InitWithCallback(this, LookAndFeel::GetPasswordMaskDelay(),
747 0 : nsITimer::TYPE_ONE_SHOT);
748 : } else {
749 0 : FillBufWithPWChars(outString, outString->Length());
750 : }
751 : }
752 :
753 : // get the (collapsed) selection location
754 0 : NS_ENSURE_STATE(aSelection->GetRangeAt(0));
755 0 : nsCOMPtr<nsINode> selNode = aSelection->GetRangeAt(0)->GetStartContainer();
756 0 : int32_t selOffset = aSelection->GetRangeAt(0)->StartOffset();
757 0 : NS_ENSURE_STATE(selNode);
758 :
759 : // don't put text in places that can't have it
760 0 : NS_ENSURE_STATE(mTextEditor);
761 0 : if (!EditorBase::IsTextNode(selNode) &&
762 0 : !mTextEditor->CanContainTag(*selNode, *nsGkAtoms::textTagName)) {
763 0 : return NS_ERROR_FAILURE;
764 : }
765 :
766 : // we need to get the doc
767 0 : NS_ENSURE_STATE(mTextEditor);
768 0 : nsCOMPtr<nsIDocument> doc = mTextEditor->GetDocument();
769 0 : NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
770 :
771 0 : if (aAction == EditAction::insertIMEText) {
772 0 : NS_ENSURE_STATE(mTextEditor);
773 : // Find better insertion point to insert text.
774 0 : mTextEditor->FindBetterInsertionPoint(selNode, selOffset);
775 : // If there is one or more IME selections, its minimum offset should be
776 : // the insertion point.
777 : int32_t IMESelectionOffset =
778 0 : mTextEditor->GetIMESelectionStartOffsetIn(selNode);
779 0 : if (IMESelectionOffset >= 0) {
780 0 : selOffset = IMESelectionOffset;
781 : }
782 0 : rv = mTextEditor->InsertTextImpl(*outString, address_of(selNode),
783 0 : &selOffset, doc);
784 0 : NS_ENSURE_SUCCESS(rv, rv);
785 : } else {
786 : // aAction == EditAction::insertText; find where we are
787 0 : nsCOMPtr<nsINode> curNode = selNode;
788 0 : int32_t curOffset = selOffset;
789 :
790 : // don't spaz my selection in subtransactions
791 0 : NS_ENSURE_STATE(mTextEditor);
792 0 : AutoTransactionsConserveSelection dontSpazMySelection(mTextEditor);
793 :
794 0 : rv = mTextEditor->InsertTextImpl(*outString, address_of(curNode),
795 0 : &curOffset, doc);
796 0 : NS_ENSURE_SUCCESS(rv, rv);
797 :
798 0 : if (curNode) {
799 : // Make the caret attach to the inserted text, unless this text ends with a LF,
800 : // in which case make the caret attach to the next line.
801 : bool endsWithLF =
802 0 : !outString->IsEmpty() && outString->Last() == nsCRT::LF;
803 0 : aSelection->SetInterlinePosition(endsWithLF);
804 :
805 0 : aSelection->Collapse(curNode, curOffset);
806 : }
807 : }
808 0 : ASSERT_PASSWORD_LENGTHS_EQUAL()
809 0 : return NS_OK;
810 : }
811 :
812 : nsresult
813 0 : TextEditRules::DidInsertText(Selection* aSelection,
814 : nsresult aResult)
815 : {
816 0 : return DidInsert(aSelection, aResult);
817 : }
818 :
819 : nsresult
820 1 : TextEditRules::WillSetText(Selection& aSelection,
821 : bool* aCancel,
822 : bool* aHandled,
823 : const nsAString* aString,
824 : int32_t aMaxLength)
825 : {
826 1 : MOZ_ASSERT(aCancel);
827 1 : MOZ_ASSERT(aHandled);
828 1 : MOZ_ASSERT(aString);
829 :
830 1 : CANCEL_OPERATION_IF_READONLY_OR_DISABLED
831 :
832 1 : *aHandled = false;
833 1 : *aCancel = false;
834 :
835 1 : if (NS_WARN_IF(!mTextEditor)) {
836 0 : return NS_ERROR_FAILURE;
837 : }
838 2 : RefPtr<TextEditor> textEditor = mTextEditor;
839 :
840 1 : if (!IsPlaintextEditor() || textEditor->IsIMEComposing() ||
841 : aMaxLength != -1) {
842 : // SetTextTransaction only supports plain text editor without IME.
843 0 : return NS_OK;
844 : }
845 :
846 1 : if (IsPasswordEditor() && LookAndFeel::GetEchoPassword() &&
847 0 : !DontEchoPassword()) {
848 : // Echo password timer will implement on InsertText.
849 0 : return NS_OK;
850 : }
851 :
852 1 : WillInsert(aSelection, aCancel);
853 : // we want to ignore result of WillInsert()
854 1 : *aCancel = false;
855 :
856 2 : RefPtr<Element> rootElement = textEditor->GetRoot();
857 1 : uint32_t count = rootElement->GetChildCount();
858 :
859 : // handles only when there is only one node and it's a text node, or empty.
860 :
861 1 : if (count > 1) {
862 0 : return NS_OK;
863 : }
864 :
865 2 : nsAutoString tString(*aString);
866 :
867 1 : if (IsPasswordEditor()) {
868 0 : mPasswordText.Assign(tString);
869 0 : FillBufWithPWChars(&tString, tString.Length());
870 1 : } else if (IsSingleLineEditor()) {
871 1 : HandleNewLines(tString, textEditor->mNewlineHandling);
872 : }
873 :
874 1 : if (!count) {
875 1 : if (tString.IsEmpty()) {
876 0 : *aHandled = true;
877 0 : return NS_OK;
878 : }
879 2 : RefPtr<nsIDocument> doc = textEditor->GetDocument();
880 1 : if (NS_WARN_IF(!doc)) {
881 0 : return NS_OK;
882 : }
883 2 : RefPtr<nsTextNode> newNode = doc->CreateTextNode(tString);
884 1 : if (NS_WARN_IF(!newNode)) {
885 0 : return NS_OK;
886 : }
887 1 : nsresult rv = textEditor->InsertNode(*newNode, *rootElement, 0);
888 1 : if (NS_WARN_IF(NS_FAILED(rv))) {
889 0 : return rv;
890 : }
891 1 : *aHandled = true;
892 :
893 1 : ASSERT_PASSWORD_LENGTHS_EQUAL();
894 :
895 1 : return NS_OK;
896 : }
897 :
898 0 : nsINode* curNode = rootElement->GetFirstChild();
899 0 : if (NS_WARN_IF(!EditorBase::IsTextNode(curNode))) {
900 0 : return NS_OK;
901 : }
902 :
903 : // Even if empty text, we don't remove text node and set empty text
904 : // for performance
905 0 : nsresult rv = textEditor->SetTextImpl(tString, *curNode->GetAsText());
906 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
907 0 : return rv;
908 : }
909 :
910 0 : *aHandled = true;
911 :
912 0 : ASSERT_PASSWORD_LENGTHS_EQUAL();
913 :
914 0 : return NS_OK;
915 : }
916 :
917 : nsresult
918 1 : TextEditRules::DidSetText(Selection& aSelection,
919 : nsresult aResult)
920 : {
921 1 : return NS_OK;
922 : }
923 :
924 : nsresult
925 0 : TextEditRules::WillSetTextProperty(Selection* aSelection,
926 : bool* aCancel,
927 : bool* aHandled)
928 : {
929 0 : if (!aSelection || !aCancel || !aHandled) {
930 0 : return NS_ERROR_NULL_POINTER;
931 : }
932 :
933 : // XXX: should probably return a success value other than NS_OK that means "not allowed"
934 0 : if (IsPlaintextEditor()) {
935 0 : *aCancel = true;
936 : }
937 0 : return NS_OK;
938 : }
939 :
940 : nsresult
941 0 : TextEditRules::DidSetTextProperty(Selection* aSelection,
942 : nsresult aResult)
943 : {
944 0 : return NS_OK;
945 : }
946 :
947 : nsresult
948 0 : TextEditRules::WillRemoveTextProperty(Selection* aSelection,
949 : bool* aCancel,
950 : bool* aHandled)
951 : {
952 0 : if (!aSelection || !aCancel || !aHandled) {
953 0 : return NS_ERROR_NULL_POINTER;
954 : }
955 :
956 : // XXX: should probably return a success value other than NS_OK that means "not allowed"
957 0 : if (IsPlaintextEditor()) {
958 0 : *aCancel = true;
959 : }
960 0 : return NS_OK;
961 : }
962 :
963 : nsresult
964 0 : TextEditRules::DidRemoveTextProperty(Selection* aSelection,
965 : nsresult aResult)
966 : {
967 0 : return NS_OK;
968 : }
969 :
970 : nsresult
971 0 : TextEditRules::WillDeleteSelection(Selection* aSelection,
972 : nsIEditor::EDirection aCollapsedAction,
973 : bool* aCancel,
974 : bool* aHandled)
975 : {
976 0 : if (!aSelection || !aCancel || !aHandled) {
977 0 : return NS_ERROR_NULL_POINTER;
978 : }
979 0 : CANCEL_OPERATION_IF_READONLY_OR_DISABLED
980 :
981 : // initialize out param
982 0 : *aCancel = false;
983 0 : *aHandled = false;
984 :
985 : // if there is only bogus content, cancel the operation
986 0 : if (mBogusNode) {
987 0 : *aCancel = true;
988 0 : return NS_OK;
989 : }
990 :
991 : // If the current selection is empty (e.g the user presses backspace with
992 : // a collapsed selection), then we want to avoid sending the selectstart
993 : // event to the user, so we hide selection changes. However, we still
994 : // want to send a single selectionchange event to the document, so we
995 : // batch the selectionchange events, such that a single event fires after
996 : // the AutoHideSelectionChanges destructor has been run.
997 0 : SelectionBatcher selectionBatcher(aSelection);
998 0 : AutoHideSelectionChanges hideSelection(aSelection);
999 0 : nsAutoScriptBlocker scriptBlocker;
1000 :
1001 0 : if (IsPasswordEditor()) {
1002 0 : NS_ENSURE_STATE(mTextEditor);
1003 : nsresult rv =
1004 0 : mTextEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction);
1005 0 : NS_ENSURE_SUCCESS(rv, rv);
1006 :
1007 : // manage the password buffer
1008 : uint32_t start, end;
1009 0 : nsContentUtils::GetSelectionInTextControl(aSelection,
1010 0 : mTextEditor->GetRoot(),
1011 0 : start, end);
1012 :
1013 0 : if (LookAndFeel::GetEchoPassword()) {
1014 0 : HideLastPWInput();
1015 0 : mLastStart = start;
1016 0 : mLastLength = 0;
1017 0 : if (mTimer) {
1018 0 : mTimer->Cancel();
1019 : }
1020 : }
1021 :
1022 : // Collapsed selection.
1023 0 : if (end == start) {
1024 : // Deleting back.
1025 0 : if (nsIEditor::ePrevious == aCollapsedAction && start > 0) {
1026 0 : mPasswordText.Cut(start-1, 1);
1027 : }
1028 : // Deleting forward.
1029 0 : else if (nsIEditor::eNext == aCollapsedAction) {
1030 0 : mPasswordText.Cut(start, 1);
1031 : }
1032 : // Otherwise nothing to do for this collapsed selection.
1033 : }
1034 : // Extended selection.
1035 : else {
1036 0 : mPasswordText.Cut(start, end-start);
1037 : }
1038 : } else {
1039 0 : nsCOMPtr<nsIDOMNode> startNode;
1040 : int32_t startOffset;
1041 : nsresult rv =
1042 0 : EditorBase::GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode),
1043 0 : &startOffset);
1044 0 : NS_ENSURE_SUCCESS(rv, rv);
1045 0 : NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
1046 :
1047 : bool bCollapsed;
1048 0 : rv = aSelection->GetIsCollapsed(&bCollapsed);
1049 0 : NS_ENSURE_SUCCESS(rv, rv);
1050 :
1051 0 : if (!bCollapsed) {
1052 0 : return NS_OK;
1053 : }
1054 :
1055 : // Test for distance between caret and text that will be deleted
1056 0 : rv = CheckBidiLevelForDeletion(aSelection, startNode, startOffset,
1057 0 : aCollapsedAction, aCancel);
1058 0 : NS_ENSURE_SUCCESS(rv, rv);
1059 0 : if (*aCancel) {
1060 0 : return NS_OK;
1061 : }
1062 :
1063 0 : NS_ENSURE_STATE(mTextEditor);
1064 0 : rv = mTextEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction);
1065 0 : NS_ENSURE_SUCCESS(rv, rv);
1066 : }
1067 :
1068 0 : NS_ENSURE_STATE(mTextEditor);
1069 : nsresult rv =
1070 0 : mTextEditor->DeleteSelectionImpl(aCollapsedAction, nsIEditor::eStrip);
1071 0 : NS_ENSURE_SUCCESS(rv, rv);
1072 :
1073 0 : *aHandled = true;
1074 0 : ASSERT_PASSWORD_LENGTHS_EQUAL()
1075 0 : return NS_OK;
1076 : }
1077 :
1078 : nsresult
1079 0 : TextEditRules::DidDeleteSelection(Selection* aSelection,
1080 : nsIEditor::EDirection aCollapsedAction,
1081 : nsresult aResult)
1082 : {
1083 0 : nsCOMPtr<nsINode> startNode;
1084 : int32_t startOffset;
1085 : nsresult rv =
1086 0 : EditorBase::GetStartNodeAndOffset(aSelection,
1087 0 : getter_AddRefs(startNode), &startOffset);
1088 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1089 0 : return rv;
1090 : }
1091 0 : if (NS_WARN_IF(!startNode)) {
1092 0 : return NS_ERROR_FAILURE;
1093 : }
1094 :
1095 : // delete empty text nodes at selection
1096 0 : if (EditorBase::IsTextNode(startNode)) {
1097 : // are we in an empty text node?
1098 0 : if (!startNode->Length()) {
1099 0 : NS_ENSURE_STATE(mTextEditor);
1100 0 : rv = mTextEditor->DeleteNode(startNode);
1101 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
1102 0 : return rv;
1103 : }
1104 : }
1105 : }
1106 0 : if (mDidExplicitlySetInterline) {
1107 0 : return NS_OK;
1108 : }
1109 : // We prevent the caret from sticking on the left of prior BR
1110 : // (i.e. the end of previous line) after this deletion. Bug 92124
1111 0 : return aSelection->SetInterlinePosition(true);
1112 : }
1113 :
1114 : nsresult
1115 0 : TextEditRules::WillUndo(Selection* aSelection,
1116 : bool* aCancel,
1117 : bool* aHandled)
1118 : {
1119 0 : if (!aSelection || !aCancel || !aHandled) {
1120 0 : return NS_ERROR_NULL_POINTER;
1121 : }
1122 0 : CANCEL_OPERATION_IF_READONLY_OR_DISABLED
1123 : // initialize out param
1124 0 : *aCancel = false;
1125 0 : *aHandled = false;
1126 0 : return NS_OK;
1127 : }
1128 :
1129 : /**
1130 : * The idea here is to see if the magic empty node has suddenly reappeared as
1131 : * the result of the undo. If it has, set our state so we remember it.
1132 : * There is a tradeoff between doing here and at redo, or doing it everywhere
1133 : * else that might care. Since undo and redo are relatively rare, it makes
1134 : * sense to take the (small) performance hit here.
1135 : */
1136 : nsresult
1137 0 : TextEditRules::DidUndo(Selection* aSelection,
1138 : nsresult aResult)
1139 : {
1140 0 : NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
1141 : // If aResult is an error, we return it.
1142 0 : NS_ENSURE_SUCCESS(aResult, aResult);
1143 :
1144 0 : NS_ENSURE_STATE(mTextEditor);
1145 0 : dom::Element* theRoot = mTextEditor->GetRoot();
1146 0 : NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
1147 0 : nsIContent* node = mTextEditor->GetLeftmostChild(theRoot);
1148 0 : if (node && mTextEditor->IsMozEditorBogusNode(node)) {
1149 0 : mBogusNode = node;
1150 : } else {
1151 0 : mBogusNode = nullptr;
1152 : }
1153 0 : return aResult;
1154 : }
1155 :
1156 : nsresult
1157 0 : TextEditRules::WillRedo(Selection* aSelection,
1158 : bool* aCancel,
1159 : bool* aHandled)
1160 : {
1161 0 : if (!aSelection || !aCancel || !aHandled) {
1162 0 : return NS_ERROR_NULL_POINTER;
1163 : }
1164 0 : CANCEL_OPERATION_IF_READONLY_OR_DISABLED
1165 : // initialize out param
1166 0 : *aCancel = false;
1167 0 : *aHandled = false;
1168 0 : return NS_OK;
1169 : }
1170 :
1171 : nsresult
1172 0 : TextEditRules::DidRedo(Selection* aSelection,
1173 : nsresult aResult)
1174 : {
1175 0 : if (!aSelection) {
1176 0 : return NS_ERROR_NULL_POINTER;
1177 : }
1178 0 : if (NS_FAILED(aResult)) {
1179 0 : return aResult; // if aResult is an error, we return it.
1180 : }
1181 :
1182 0 : NS_ENSURE_STATE(mTextEditor);
1183 0 : RefPtr<Element> theRoot = mTextEditor->GetRoot();
1184 0 : NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
1185 :
1186 : nsCOMPtr<nsIHTMLCollection> nodeList =
1187 0 : theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"));
1188 0 : MOZ_ASSERT(nodeList);
1189 0 : uint32_t len = nodeList->Length();
1190 :
1191 0 : if (len != 1) {
1192 : // only in the case of one br could there be the bogus node
1193 0 : mBogusNode = nullptr;
1194 0 : return NS_OK;
1195 : }
1196 :
1197 0 : RefPtr<Element> node = nodeList->Item(0);
1198 0 : if (mTextEditor->IsMozEditorBogusNode(node)) {
1199 0 : mBogusNode = node;
1200 : } else {
1201 0 : mBogusNode = nullptr;
1202 : }
1203 0 : return NS_OK;
1204 : }
1205 :
1206 : nsresult
1207 4 : TextEditRules::WillOutputText(Selection* aSelection,
1208 : const nsAString* aOutputFormat,
1209 : nsAString* aOutString,
1210 : uint32_t aFlags,
1211 : bool* aCancel,
1212 : bool* aHandled)
1213 : {
1214 : // null selection ok
1215 16 : if (NS_WARN_IF(!aOutString) || NS_WARN_IF(!aOutputFormat) ||
1216 12 : NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) {
1217 0 : return NS_ERROR_NULL_POINTER;
1218 : }
1219 :
1220 : // initialize out param
1221 4 : *aCancel = false;
1222 4 : *aHandled = false;
1223 :
1224 4 : if (!aOutputFormat->LowerCaseEqualsLiteral("text/plain")) {
1225 0 : return NS_OK;
1226 : }
1227 :
1228 : // XXX Looks like that even if it's password field, we need to use the
1229 : // expensive path if the caller requests some complicated handling.
1230 : // However, changing the behavior for password field might cause
1231 : // security issue. So, be careful when you touch here.
1232 4 : if (IsPasswordEditor()) {
1233 0 : *aOutString = mPasswordText;
1234 0 : *aHandled = true;
1235 0 : return NS_OK;
1236 : }
1237 :
1238 : // If there is a bogus node, there's no content. So output empty string.
1239 4 : if (mBogusNode) {
1240 3 : aOutString->Truncate();
1241 3 : *aHandled = true;
1242 3 : return NS_OK;
1243 : }
1244 :
1245 : // If it's necessary to check selection range or the editor wraps hard,
1246 : // we need some complicated handling. In such case, we need to use the
1247 : // expensive path.
1248 : // XXX Anything else what we cannot return plain text simply?
1249 2 : if (aFlags & nsIDocumentEncoder::OutputSelectionOnly ||
1250 1 : aFlags & nsIDocumentEncoder::OutputWrap) {
1251 0 : return NS_OK;
1252 : }
1253 :
1254 : // If it's neither <input type="text"> nor <textarea>, e.g., an HTML editor
1255 : // which is in plaintext mode (e.g., plaintext email composer on Thunderbird),
1256 : // it should be handled by the expensive path.
1257 1 : if (NS_WARN_IF(!mTextEditor) || mTextEditor->AsHTMLEditor()) {
1258 0 : return NS_OK;
1259 : }
1260 :
1261 2 : RefPtr<Element> root = mTextEditor->GetRoot();
1262 1 : if (!root) { // Don't warn it, this is possible, e.g., 997805.html
1263 0 : aOutString->Truncate();
1264 0 : *aHandled = true;
1265 0 : return NS_OK;
1266 : }
1267 :
1268 1 : nsIContent* firstChild = root->GetFirstChild();
1269 1 : if (!firstChild) {
1270 0 : aOutString->Truncate();
1271 0 : *aHandled = true;
1272 0 : return NS_OK;
1273 : }
1274 :
1275 : // If it's an <input type="text"> element, the DOM tree should be:
1276 : // <div class="anonymous-div">
1277 : // #text
1278 : // </div>
1279 : //
1280 : // If it's a <textarea> element, the DOM tree should be:
1281 : // <div class="anonymous-div">
1282 : // #text (if there is)
1283 : // <br type="_moz">
1284 : // <scrollbar orient="horizontal">
1285 : // ...
1286 : // </div>
1287 :
1288 1 : Text* text = firstChild->GetAsText();
1289 : nsIContent* firstChildExceptText =
1290 1 : text ? firstChild->GetNextSibling() : firstChild;
1291 : // If the DOM tree is unexpected, fall back to the expensive path.
1292 1 : bool isInput = IsSingleLineEditor();
1293 1 : bool isTextarea = !isInput;
1294 3 : if (NS_WARN_IF(isInput && firstChildExceptText) ||
1295 2 : NS_WARN_IF(isTextarea && !firstChildExceptText) ||
1296 1 : NS_WARN_IF(isTextarea &&
1297 : !TextEditUtils::IsMozBR(firstChildExceptText) &&
1298 : !firstChildExceptText->IsXULElement(nsGkAtoms::scrollbar))) {
1299 0 : return NS_OK;
1300 : }
1301 :
1302 : // If there is no text node in the expected DOM tree, we can say that it's
1303 : // just empty.
1304 1 : if (!text) {
1305 0 : aOutString->Truncate();
1306 0 : *aHandled = true;
1307 0 : return NS_OK;
1308 : }
1309 :
1310 : // Otherwise, the text is the value.
1311 1 : nsresult rv = text->GetData(*aOutString);
1312 1 : if (NS_WARN_IF(NS_FAILED(rv))) {
1313 : // Fall back to the expensive path if it fails.
1314 0 : return NS_OK;
1315 : }
1316 :
1317 1 : *aHandled = true;
1318 1 : return NS_OK;
1319 : }
1320 :
1321 : nsresult
1322 0 : TextEditRules::DidOutputText(Selection* aSelection,
1323 : nsresult aResult)
1324 : {
1325 0 : return NS_OK;
1326 : }
1327 :
1328 : nsresult
1329 3 : TextEditRules::RemoveRedundantTrailingBR()
1330 : {
1331 : // If the bogus node exists, we have no work to do
1332 3 : if (mBogusNode) {
1333 2 : return NS_OK;
1334 : }
1335 :
1336 : // Likewise, nothing to be done if we could never have inserted a trailing br
1337 1 : if (IsSingleLineEditor()) {
1338 1 : return NS_OK;
1339 : }
1340 :
1341 0 : NS_ENSURE_STATE(mTextEditor);
1342 0 : RefPtr<dom::Element> body = mTextEditor->GetRoot();
1343 0 : if (!body) {
1344 0 : return NS_ERROR_NULL_POINTER;
1345 : }
1346 :
1347 0 : uint32_t childCount = body->GetChildCount();
1348 0 : if (childCount > 1) {
1349 : // The trailing br is redundant if it is the only remaining child node
1350 0 : return NS_OK;
1351 : }
1352 :
1353 0 : RefPtr<nsIContent> child = body->GetFirstChild();
1354 0 : if (!child || !child->IsElement()) {
1355 0 : return NS_OK;
1356 : }
1357 :
1358 0 : dom::Element* elem = child->AsElement();
1359 0 : if (!TextEditUtils::IsMozBR(elem)) {
1360 0 : return NS_OK;
1361 : }
1362 :
1363 : // Rather than deleting this node from the DOM tree we should instead
1364 : // morph this br into the bogus node
1365 0 : elem->UnsetAttr(kNameSpaceID_None, nsGkAtoms::type, true);
1366 :
1367 : // set mBogusNode to be this <br>
1368 0 : mBogusNode = elem;
1369 :
1370 : // give it the bogus node attribute
1371 0 : elem->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom,
1372 0 : kMOZEditorBogusNodeValue, false);
1373 0 : return NS_OK;
1374 : }
1375 :
1376 : nsresult
1377 5 : TextEditRules::CreateTrailingBRIfNeeded()
1378 : {
1379 : // but only if we aren't a single line edit field
1380 5 : if (IsSingleLineEditor()) {
1381 5 : return NS_OK;
1382 : }
1383 :
1384 0 : NS_ENSURE_STATE(mTextEditor);
1385 0 : dom::Element* body = mTextEditor->GetRoot();
1386 0 : NS_ENSURE_TRUE(body, NS_ERROR_NULL_POINTER);
1387 :
1388 0 : nsIContent* lastChild = body->GetLastChild();
1389 : // assuming CreateBogusNodeIfNeeded() has been called first
1390 0 : NS_ENSURE_TRUE(lastChild, NS_ERROR_NULL_POINTER);
1391 :
1392 0 : if (!lastChild->IsHTMLElement(nsGkAtoms::br)) {
1393 0 : AutoTransactionsConserveSelection dontSpazMySelection(mTextEditor);
1394 0 : nsCOMPtr<nsIDOMNode> domBody = do_QueryInterface(body);
1395 0 : return CreateMozBR(domBody, body->Length());
1396 : }
1397 :
1398 : // Check to see if the trailing BR is a former bogus node - this will have
1399 : // stuck around if we previously morphed a trailing node into a bogus node.
1400 0 : if (!mTextEditor->IsMozEditorBogusNode(lastChild)) {
1401 0 : return NS_OK;
1402 : }
1403 :
1404 : // Morph it back to a mozBR
1405 0 : lastChild->UnsetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, false);
1406 0 : lastChild->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
1407 0 : NS_LITERAL_STRING("_moz"), true);
1408 0 : return NS_OK;
1409 : }
1410 :
1411 : nsresult
1412 5 : TextEditRules::CreateBogusNodeIfNeeded(Selection* aSelection)
1413 : {
1414 5 : NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
1415 5 : NS_ENSURE_TRUE(mTextEditor, NS_ERROR_NULL_POINTER);
1416 :
1417 5 : if (mBogusNode) {
1418 : // Let's not create more than one, ok?
1419 2 : return NS_OK;
1420 : }
1421 :
1422 : // tell rules system to not do any post-processing
1423 3 : AutoRules beginRulesSniffing(mTextEditor, EditAction::ignore,
1424 9 : nsIEditor::eNone);
1425 :
1426 6 : nsCOMPtr<dom::Element> body = mTextEditor->GetRoot();
1427 3 : if (!body) {
1428 : // We don't even have a body yet, don't insert any bogus nodes at
1429 : // this point.
1430 0 : return NS_OK;
1431 : }
1432 :
1433 : // Now we've got the body element. Iterate over the body element's children,
1434 : // looking for editable content. If no editable content is found, insert the
1435 : // bogus node.
1436 3 : for (nsCOMPtr<nsIContent> bodyChild = body->GetFirstChild();
1437 : bodyChild;
1438 0 : bodyChild = bodyChild->GetNextSibling()) {
1439 3 : if (mTextEditor->IsMozEditorBogusNode(bodyChild) ||
1440 2 : !mTextEditor->IsEditable(body) || // XXX hoist out of the loop?
1441 2 : mTextEditor->IsEditable(bodyChild) ||
1442 0 : mTextEditor->IsBlockNode(bodyChild)) {
1443 1 : return NS_OK;
1444 : }
1445 : }
1446 :
1447 : // Skip adding the bogus node if body is read-only.
1448 2 : if (!mTextEditor->IsModifiableNode(body)) {
1449 0 : return NS_OK;
1450 : }
1451 :
1452 : // Create a br.
1453 4 : nsCOMPtr<Element> newContent = mTextEditor->CreateHTMLContent(nsGkAtoms::br);
1454 2 : NS_ENSURE_STATE(newContent);
1455 :
1456 : // set mBogusNode to be the newly created <br>
1457 2 : mBogusNode = newContent;
1458 :
1459 : // Give it a special attribute.
1460 4 : newContent->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom,
1461 6 : kMOZEditorBogusNodeValue, false);
1462 :
1463 : // Put the node in the document.
1464 2 : nsresult rv = mTextEditor->InsertNode(*mBogusNode, *body, 0);
1465 2 : NS_ENSURE_SUCCESS(rv, rv);
1466 :
1467 : // Set selection.
1468 2 : aSelection->Collapse(body, 0);
1469 2 : return NS_OK;
1470 : }
1471 :
1472 :
1473 : nsresult
1474 0 : TextEditRules::TruncateInsertionIfNeeded(Selection* aSelection,
1475 : const nsAString* aInString,
1476 : nsAString* aOutString,
1477 : int32_t aMaxLength,
1478 : bool* aTruncated)
1479 : {
1480 0 : if (!aSelection || !aInString || !aOutString) {
1481 0 : return NS_ERROR_NULL_POINTER;
1482 : }
1483 :
1484 0 : if (!aOutString->Assign(*aInString, mozilla::fallible)) {
1485 0 : return NS_ERROR_OUT_OF_MEMORY;
1486 : }
1487 0 : if (aTruncated) {
1488 0 : *aTruncated = false;
1489 : }
1490 :
1491 0 : NS_ENSURE_STATE(mTextEditor);
1492 0 : if (-1 != aMaxLength && IsPlaintextEditor() &&
1493 0 : !mTextEditor->IsIMEComposing()) {
1494 : // Get the current text length.
1495 : // Get the length of inString.
1496 : // Get the length of the selection.
1497 : // If selection is collapsed, it is length 0.
1498 : // Subtract the length of the selection from the len(doc)
1499 : // since we'll delete the selection on insert.
1500 : // This is resultingDocLength.
1501 : // Get old length of IME composing string
1502 : // which will be replaced by new one.
1503 : // If (resultingDocLength) is at or over max, cancel the insert
1504 : // If (resultingDocLength) + (length of input) > max,
1505 : // set aOutString to subset of inString so length = max
1506 : int32_t docLength;
1507 0 : nsresult rv = mTextEditor->GetTextLength(&docLength);
1508 0 : if (NS_FAILED(rv)) {
1509 0 : return rv;
1510 : }
1511 :
1512 : uint32_t start, end;
1513 0 : nsContentUtils::GetSelectionInTextControl(aSelection,
1514 0 : mTextEditor->GetRoot(),
1515 0 : start, end);
1516 :
1517 0 : TextComposition* composition = mTextEditor->GetComposition();
1518 0 : uint32_t oldCompStrLength = composition ? composition->String().Length() : 0;
1519 :
1520 0 : const uint32_t selectionLength = end - start;
1521 0 : const int32_t resultingDocLength = docLength - selectionLength - oldCompStrLength;
1522 0 : if (resultingDocLength >= aMaxLength) {
1523 : // This call is guaranteed to reduce the capacity of the string, so it
1524 : // cannot cause an OOM.
1525 0 : aOutString->Truncate();
1526 0 : if (aTruncated) {
1527 0 : *aTruncated = true;
1528 : }
1529 : } else {
1530 0 : int32_t oldLength = aOutString->Length();
1531 0 : if (oldLength + resultingDocLength > aMaxLength) {
1532 0 : int32_t newLength = aMaxLength - resultingDocLength;
1533 0 : MOZ_ASSERT(newLength > 0);
1534 0 : char16_t newLastChar = aOutString->CharAt(newLength - 1);
1535 0 : char16_t removingFirstChar = aOutString->CharAt(newLength);
1536 : // Don't separate the string between a surrogate pair.
1537 0 : if (NS_IS_HIGH_SURROGATE(newLastChar) &&
1538 0 : NS_IS_LOW_SURROGATE(removingFirstChar)) {
1539 0 : newLength--;
1540 : }
1541 : // XXX What should we do if we're removing IVS and its preceding
1542 : // character won't be removed?
1543 : // This call is guaranteed to reduce the capacity of the string, so it
1544 : // cannot cause an OOM.
1545 0 : aOutString->Truncate(newLength);
1546 0 : if (aTruncated) {
1547 0 : *aTruncated = true;
1548 : }
1549 : }
1550 : }
1551 : }
1552 0 : return NS_OK;
1553 : }
1554 :
1555 : void
1556 0 : TextEditRules::ResetIMETextPWBuf()
1557 : {
1558 0 : mPasswordIMEText.Truncate();
1559 0 : }
1560 :
1561 : void
1562 0 : TextEditRules::RemoveIMETextFromPWBuf(uint32_t& aStart,
1563 : nsAString* aIMEString)
1564 : {
1565 0 : MOZ_ASSERT(aIMEString);
1566 :
1567 : // initialize PasswordIME
1568 0 : if (mPasswordIMEText.IsEmpty()) {
1569 0 : mPasswordIMEIndex = aStart;
1570 : } else {
1571 : // manage the password buffer
1572 0 : mPasswordText.Cut(mPasswordIMEIndex, mPasswordIMEText.Length());
1573 0 : aStart = mPasswordIMEIndex;
1574 : }
1575 :
1576 0 : mPasswordIMEText.Assign(*aIMEString);
1577 0 : }
1578 :
1579 : NS_IMETHODIMP
1580 0 : TextEditRules::Notify(nsITimer* aTimer)
1581 : {
1582 0 : MOZ_ASSERT(mTimer);
1583 :
1584 : // Check whether our text editor's password flag was changed before this
1585 : // "hide password character" timer actually fires.
1586 0 : nsresult rv = IsPasswordEditor() ? HideLastPWInput() : NS_OK;
1587 0 : ASSERT_PASSWORD_LENGTHS_EQUAL();
1588 0 : mLastLength = 0;
1589 0 : return rv;
1590 : }
1591 :
1592 : nsresult
1593 0 : TextEditRules::HideLastPWInput()
1594 : {
1595 0 : if (!mLastLength) {
1596 : // Special case, we're trying to replace a range that no longer exists
1597 0 : return NS_OK;
1598 : }
1599 :
1600 0 : nsAutoString hiddenText;
1601 0 : FillBufWithPWChars(&hiddenText, mLastLength);
1602 :
1603 0 : NS_ENSURE_STATE(mTextEditor);
1604 0 : RefPtr<Selection> selection = mTextEditor->GetSelection();
1605 0 : NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1606 : uint32_t start, end;
1607 0 : nsContentUtils::GetSelectionInTextControl(selection, mTextEditor->GetRoot(),
1608 0 : start, end);
1609 :
1610 0 : nsCOMPtr<nsINode> selNode = GetTextNode(selection);
1611 0 : NS_ENSURE_TRUE(selNode, NS_OK);
1612 :
1613 0 : selNode->GetAsText()->ReplaceData(mLastStart, mLastLength, hiddenText);
1614 : // XXXbz Selection::Collapse/Extend take int32_t, but there are tons of
1615 : // callsites... Converting all that is a battle for another day.
1616 0 : selection->Collapse(selNode, start);
1617 0 : if (start != end) {
1618 0 : selection->Extend(selNode, end);
1619 : }
1620 0 : return NS_OK;
1621 : }
1622 :
1623 : // static
1624 : void
1625 0 : TextEditRules::FillBufWithPWChars(nsAString* aOutString,
1626 : int32_t aLength)
1627 : {
1628 0 : MOZ_ASSERT(aOutString);
1629 :
1630 : // change the output to the platform password character
1631 0 : char16_t passwordChar = LookAndFeel::GetPasswordCharacter();
1632 :
1633 0 : aOutString->Truncate();
1634 0 : for (int32_t i = 0; i < aLength; i++) {
1635 0 : aOutString->Append(passwordChar);
1636 : }
1637 0 : }
1638 :
1639 : /**
1640 : * CreateMozBR() puts a BR node with moz attribute at {inParent, inOffset}.
1641 : */
1642 : nsresult
1643 0 : TextEditRules::CreateMozBR(nsIDOMNode* inParent,
1644 : int32_t inOffset,
1645 : nsIDOMNode** outBRNode)
1646 : {
1647 0 : NS_ENSURE_TRUE(inParent, NS_ERROR_NULL_POINTER);
1648 :
1649 0 : nsCOMPtr<nsIDOMNode> brNode;
1650 0 : NS_ENSURE_STATE(mTextEditor);
1651 0 : nsresult rv = mTextEditor->CreateBR(inParent, inOffset, address_of(brNode));
1652 0 : NS_ENSURE_SUCCESS(rv, rv);
1653 :
1654 : // give it special moz attr
1655 0 : nsCOMPtr<Element> brElem = do_QueryInterface(brNode);
1656 0 : if (brElem) {
1657 0 : rv = mTextEditor->SetAttribute(brElem, nsGkAtoms::type,
1658 0 : NS_LITERAL_STRING("_moz"));
1659 0 : NS_ENSURE_SUCCESS(rv, rv);
1660 : }
1661 :
1662 0 : if (outBRNode) {
1663 0 : brNode.forget(outBRNode);
1664 : }
1665 0 : return NS_OK;
1666 : }
1667 :
1668 : NS_IMETHODIMP
1669 0 : TextEditRules::DocumentModified()
1670 : {
1671 0 : return NS_ERROR_NOT_IMPLEMENTED;
1672 : }
1673 :
1674 : bool
1675 9 : TextEditRules::IsPasswordEditor() const
1676 : {
1677 9 : return mTextEditor ? mTextEditor->IsPasswordEditor() : false;
1678 : }
1679 :
1680 : bool
1681 8 : TextEditRules::IsSingleLineEditor() const
1682 : {
1683 8 : return mTextEditor ? mTextEditor->IsSingleLineEditor() : false;
1684 : }
1685 :
1686 : bool
1687 6 : TextEditRules::IsPlaintextEditor() const
1688 : {
1689 6 : return mTextEditor ? mTextEditor->IsPlaintextEditor() : false;
1690 : }
1691 :
1692 : bool
1693 2 : TextEditRules::IsReadonly() const
1694 : {
1695 2 : return mTextEditor ? mTextEditor->IsReadonly() : false;
1696 : }
1697 :
1698 : bool
1699 2 : TextEditRules::IsDisabled() const
1700 : {
1701 2 : return mTextEditor ? mTextEditor->IsDisabled() : false;
1702 : }
1703 : bool
1704 0 : TextEditRules::IsMailEditor() const
1705 : {
1706 0 : return mTextEditor ? mTextEditor->IsMailEditor() : false;
1707 : }
1708 :
1709 : bool
1710 0 : TextEditRules::DontEchoPassword() const
1711 : {
1712 0 : return mTextEditor ? mTextEditor->DontEchoPassword() : false;
1713 : }
1714 :
1715 : } // namespace mozilla
|