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