/src/mozilla-central/editor/libeditor/TextEditor.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/TextEditor.h" |
7 | | |
8 | | #include "EditAggregateTransaction.h" |
9 | | #include "HTMLEditRules.h" |
10 | | #include "InternetCiter.h" |
11 | | #include "TextEditUtils.h" |
12 | | #include "gfxFontUtils.h" |
13 | | #include "mozilla/Assertions.h" |
14 | | #include "mozilla/EditAction.h" |
15 | | #include "mozilla/EditorDOMPoint.h" |
16 | | #include "mozilla/EditorUtils.h" |
17 | | #include "mozilla/HTMLEditor.h" |
18 | | #include "mozilla/IMEStateManager.h" |
19 | | #include "mozilla/mozalloc.h" |
20 | | #include "mozilla/Preferences.h" |
21 | | #include "mozilla/TextEditRules.h" |
22 | | #include "mozilla/TextComposition.h" |
23 | | #include "mozilla/TextEvents.h" |
24 | | #include "mozilla/TextServicesDocument.h" |
25 | | #include "mozilla/dom/Selection.h" |
26 | | #include "mozilla/dom/Event.h" |
27 | | #include "mozilla/dom/Element.h" |
28 | | #include "nsAString.h" |
29 | | #include "nsCRT.h" |
30 | | #include "nsCaret.h" |
31 | | #include "nsCharTraits.h" |
32 | | #include "nsComponentManagerUtils.h" |
33 | | #include "nsContentCID.h" |
34 | | #include "nsContentList.h" |
35 | | #include "nsCopySupport.h" |
36 | | #include "nsDebug.h" |
37 | | #include "nsDependentSubstring.h" |
38 | | #include "nsError.h" |
39 | | #include "nsGkAtoms.h" |
40 | | #include "nsIAbsorbingTransaction.h" |
41 | | #include "nsIClipboard.h" |
42 | | #include "nsIContent.h" |
43 | | #include "nsIContentIterator.h" |
44 | | #include "nsIDocumentEncoder.h" |
45 | | #include "nsINode.h" |
46 | | #include "nsIPresShell.h" |
47 | | #include "nsISelectionController.h" |
48 | | #include "nsISupportsPrimitives.h" |
49 | | #include "nsITransferable.h" |
50 | | #include "nsIWeakReferenceUtils.h" |
51 | | #include "nsNameSpaceManager.h" |
52 | | #include "nsLiteralString.h" |
53 | | #include "nsReadableUtils.h" |
54 | | #include "nsServiceManagerUtils.h" |
55 | | #include "nsString.h" |
56 | | #include "nsStringFwd.h" |
57 | | #include "nsTextNode.h" |
58 | | #include "nsUnicharUtils.h" |
59 | | #include "nsXPCOM.h" |
60 | | |
61 | | class nsIOutputStream; |
62 | | class nsISupports; |
63 | | |
64 | | namespace mozilla { |
65 | | |
66 | | using namespace dom; |
67 | | |
68 | | template already_AddRefed<Element> |
69 | | TextEditor::InsertBrElementWithTransaction( |
70 | | Selection& aSelection, |
71 | | const EditorDOMPoint& aPointToInsert, |
72 | | EDirection aSelect); |
73 | | template already_AddRefed<Element> |
74 | | TextEditor::InsertBrElementWithTransaction( |
75 | | Selection& aSelection, |
76 | | const EditorRawDOMPoint& aPointToInsert, |
77 | | EDirection aSelect); |
78 | | |
79 | | TextEditor::TextEditor() |
80 | | : mWrapColumn(0) |
81 | | , mMaxTextLength(-1) |
82 | | , mInitTriggerCounter(0) |
83 | | , mNewlineHandling(nsIPlaintextEditor::eNewlinesPasteToFirst) |
84 | | #ifdef XP_WIN |
85 | | , mCaretStyle(1) |
86 | | #else |
87 | | , mCaretStyle(0) |
88 | | #endif |
89 | 0 | { |
90 | 0 | // check the "single line editor newline handling" |
91 | 0 | // and "caret behaviour in selection" prefs |
92 | 0 | GetDefaultEditorPrefs(mNewlineHandling, mCaretStyle); |
93 | 0 | } |
94 | | |
95 | | TextEditor::~TextEditor() |
96 | 0 | { |
97 | 0 | // Remove event listeners. Note that if we had an HTML editor, |
98 | 0 | // it installed its own instead of these |
99 | 0 | RemoveEventListeners(); |
100 | 0 |
|
101 | 0 | if (mRules) |
102 | 0 | mRules->DetachEditor(); |
103 | 0 | } |
104 | | |
105 | | NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor) |
106 | | |
107 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TextEditor, EditorBase) |
108 | 0 | if (tmp->mRules) |
109 | 0 | tmp->mRules->DetachEditor(); |
110 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mRules) |
111 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK(mCachedDocumentEncoder) |
112 | 0 | NS_IMPL_CYCLE_COLLECTION_UNLINK_END |
113 | | |
114 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextEditor, EditorBase) |
115 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRules) |
116 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedDocumentEncoder) |
117 | 0 | NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END |
118 | | |
119 | | NS_IMPL_ADDREF_INHERITED(TextEditor, EditorBase) |
120 | | NS_IMPL_RELEASE_INHERITED(TextEditor, EditorBase) |
121 | | |
122 | 0 | NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TextEditor) |
123 | 0 | NS_INTERFACE_MAP_ENTRY(nsIPlaintextEditor) |
124 | 0 | NS_INTERFACE_MAP_END_INHERITING(EditorBase) |
125 | | |
126 | | |
127 | | nsresult |
128 | | TextEditor::Init(nsIDocument& aDoc, |
129 | | Element* aRoot, |
130 | | nsISelectionController* aSelCon, |
131 | | uint32_t aFlags, |
132 | | const nsAString& aInitialValue) |
133 | 0 | { |
134 | 0 | if (mRules) { |
135 | 0 | mRules->DetachEditor(); |
136 | 0 | } |
137 | 0 |
|
138 | 0 | nsresult rulesRv = NS_OK; |
139 | 0 | { |
140 | 0 | // block to scope AutoEditInitRulesTrigger |
141 | 0 | AutoEditInitRulesTrigger rulesTrigger(this, rulesRv); |
142 | 0 |
|
143 | 0 | // Init the base editor |
144 | 0 | nsresult rv = EditorBase::Init(aDoc, aRoot, aSelCon, aFlags, aInitialValue); |
145 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
146 | 0 | return rv; |
147 | 0 | } |
148 | 0 | } |
149 | 0 | NS_ENSURE_SUCCESS(rulesRv, rulesRv); |
150 | 0 |
|
151 | 0 | // mRules may not have been initialized yet, when this is called via |
152 | 0 | // HTMLEditor::Init. |
153 | 0 | if (mRules) { |
154 | 0 | mRules->SetInitialValue(aInitialValue); |
155 | 0 | } |
156 | 0 |
|
157 | 0 | return NS_OK; |
158 | 0 | } |
159 | | |
160 | | static int32_t sNewlineHandlingPref = -1, |
161 | | sCaretStylePref = -1; |
162 | | |
163 | | static void |
164 | | EditorPrefsChangedCallback(const char* aPrefName, void *) |
165 | 0 | { |
166 | 0 | if (!nsCRT::strcmp(aPrefName, "editor.singleLine.pasteNewlines")) { |
167 | 0 | sNewlineHandlingPref = |
168 | 0 | Preferences::GetInt("editor.singleLine.pasteNewlines", |
169 | 0 | nsIPlaintextEditor::eNewlinesPasteToFirst); |
170 | 0 | } else if (!nsCRT::strcmp(aPrefName, "layout.selection.caret_style")) { |
171 | 0 | sCaretStylePref = Preferences::GetInt("layout.selection.caret_style", |
172 | | #ifdef XP_WIN |
173 | | 1); |
174 | | if (!sCaretStylePref) { |
175 | | sCaretStylePref = 1; |
176 | | } |
177 | | #else |
178 | | 0); |
179 | 0 | #endif |
180 | 0 | } |
181 | 0 | } |
182 | | |
183 | | // static |
184 | | void |
185 | | TextEditor::GetDefaultEditorPrefs(int32_t& aNewlineHandling, |
186 | | int32_t& aCaretStyle) |
187 | 0 | { |
188 | 0 | if (sNewlineHandlingPref == -1) { |
189 | 0 | Preferences::RegisterCallbackAndCall(EditorPrefsChangedCallback, |
190 | 0 | "editor.singleLine.pasteNewlines"); |
191 | 0 | Preferences::RegisterCallbackAndCall(EditorPrefsChangedCallback, |
192 | 0 | "layout.selection.caret_style"); |
193 | 0 | } |
194 | 0 |
|
195 | 0 | aNewlineHandling = sNewlineHandlingPref; |
196 | 0 | aCaretStyle = sCaretStylePref; |
197 | 0 | } |
198 | | |
199 | | void |
200 | | TextEditor::BeginEditorInit() |
201 | 0 | { |
202 | 0 | mInitTriggerCounter++; |
203 | 0 | } |
204 | | |
205 | | nsresult |
206 | | TextEditor::EndEditorInit() |
207 | 0 | { |
208 | 0 | MOZ_ASSERT(mInitTriggerCounter > 0, "ended editor init before we began?"); |
209 | 0 | mInitTriggerCounter--; |
210 | 0 | if (mInitTriggerCounter) { |
211 | 0 | return NS_OK; |
212 | 0 | } |
213 | 0 | |
214 | 0 | nsresult rv = InitRules(); |
215 | 0 | if (NS_FAILED(rv)) { |
216 | 0 | return rv; |
217 | 0 | } |
218 | 0 | // Throw away the old transaction manager if this is not the first time that |
219 | 0 | // we're initializing the editor. |
220 | 0 | ClearUndoRedo(); |
221 | 0 | EnableUndoRedo(); |
222 | 0 | return NS_OK; |
223 | 0 | } |
224 | | |
225 | | NS_IMETHODIMP |
226 | | TextEditor::SetDocumentCharacterSet(const nsACString& characterSet) |
227 | 0 | { |
228 | 0 | nsresult rv = EditorBase::SetDocumentCharacterSet(characterSet); |
229 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
230 | 0 |
|
231 | 0 | // Update META charset element. |
232 | 0 | nsCOMPtr<nsIDocument> doc = GetDocument(); |
233 | 0 | if (NS_WARN_IF(!doc)) { |
234 | 0 | return NS_ERROR_NOT_INITIALIZED; |
235 | 0 | } |
236 | 0 | |
237 | 0 | if (UpdateMetaCharset(*doc, characterSet)) { |
238 | 0 | return NS_OK; |
239 | 0 | } |
240 | 0 | |
241 | 0 | RefPtr<nsContentList> headList = |
242 | 0 | doc->GetElementsByTagName(NS_LITERAL_STRING("head")); |
243 | 0 | if (NS_WARN_IF(!headList)) { |
244 | 0 | return NS_OK; |
245 | 0 | } |
246 | 0 | |
247 | 0 | nsCOMPtr<nsIContent> headNode = headList->Item(0); |
248 | 0 | if (NS_WARN_IF(!headNode)) { |
249 | 0 | return NS_OK; |
250 | 0 | } |
251 | 0 | |
252 | 0 | // Create a new meta charset tag |
253 | 0 | EditorRawDOMPoint atStartOfHeadNode(headNode, 0); |
254 | 0 | RefPtr<Element> metaElement = |
255 | 0 | CreateNodeWithTransaction(*nsGkAtoms::meta, atStartOfHeadNode); |
256 | 0 | if (NS_WARN_IF(!metaElement)) { |
257 | 0 | return NS_OK; |
258 | 0 | } |
259 | 0 | |
260 | 0 | // Set attributes to the created element |
261 | 0 | if (characterSet.IsEmpty()) { |
262 | 0 | return NS_OK; |
263 | 0 | } |
264 | 0 | |
265 | 0 | // not undoable, undo should undo CreateNodeWithTransaction(). |
266 | 0 | metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, |
267 | 0 | NS_LITERAL_STRING("Content-Type"), true); |
268 | 0 | metaElement->SetAttr(kNameSpaceID_None, nsGkAtoms::content, |
269 | 0 | NS_LITERAL_STRING("text/html;charset=") + |
270 | 0 | NS_ConvertASCIItoUTF16(characterSet), |
271 | 0 | true); |
272 | 0 | return NS_OK; |
273 | 0 | } |
274 | | |
275 | | bool |
276 | | TextEditor::UpdateMetaCharset(nsIDocument& aDocument, |
277 | | const nsACString& aCharacterSet) |
278 | 0 | { |
279 | 0 | // get a list of META tags |
280 | 0 | RefPtr<nsContentList> metaList = |
281 | 0 | aDocument.GetElementsByTagName(NS_LITERAL_STRING("meta")); |
282 | 0 | if (NS_WARN_IF(!metaList)) { |
283 | 0 | return false; |
284 | 0 | } |
285 | 0 | |
286 | 0 | for (uint32_t i = 0; i < metaList->Length(true); ++i) { |
287 | 0 | nsCOMPtr<nsIContent> metaNode = metaList->Item(i); |
288 | 0 | MOZ_ASSERT(metaNode); |
289 | 0 |
|
290 | 0 | if (!metaNode->IsElement()) { |
291 | 0 | continue; |
292 | 0 | } |
293 | 0 | |
294 | 0 | nsAutoString currentValue; |
295 | 0 | metaNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, |
296 | 0 | currentValue); |
297 | 0 |
|
298 | 0 | if (!FindInReadable(NS_LITERAL_STRING("content-type"), |
299 | 0 | currentValue, |
300 | 0 | nsCaseInsensitiveStringComparator())) { |
301 | 0 | continue; |
302 | 0 | } |
303 | 0 | |
304 | 0 | metaNode->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::content, |
305 | 0 | currentValue); |
306 | 0 |
|
307 | 0 | NS_NAMED_LITERAL_STRING(charsetEquals, "charset="); |
308 | 0 | nsAString::const_iterator originalStart, start, end; |
309 | 0 | originalStart = currentValue.BeginReading(start); |
310 | 0 | currentValue.EndReading(end); |
311 | 0 | if (!FindInReadable(charsetEquals, start, end, |
312 | 0 | nsCaseInsensitiveStringComparator())) { |
313 | 0 | continue; |
314 | 0 | } |
315 | 0 | |
316 | 0 | // set attribute to <original prefix> charset=text/html |
317 | 0 | RefPtr<Element> metaElement = metaNode->AsElement(); |
318 | 0 | MOZ_ASSERT(metaElement); |
319 | 0 | nsresult rv = |
320 | 0 | SetAttributeWithTransaction(*metaElement, *nsGkAtoms::content, |
321 | 0 | Substring(originalStart, start) + |
322 | 0 | charsetEquals + |
323 | 0 | NS_ConvertASCIItoUTF16(aCharacterSet)); |
324 | 0 | return NS_SUCCEEDED(rv); |
325 | 0 | } |
326 | 0 | return false; |
327 | 0 | } |
328 | | |
329 | | nsresult |
330 | | TextEditor::InitRules() |
331 | 0 | { |
332 | 0 | if (!mRules) { |
333 | 0 | // instantiate the rules for this text editor |
334 | 0 | mRules = new TextEditRules(); |
335 | 0 | } |
336 | 0 | return mRules->Init(this); |
337 | 0 | } |
338 | | |
339 | | nsresult |
340 | | TextEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) |
341 | 0 | { |
342 | 0 | // NOTE: When you change this method, you should also change: |
343 | 0 | // * editor/libeditor/tests/test_texteditor_keyevent_handling.html |
344 | 0 | // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html |
345 | 0 | // |
346 | 0 | // And also when you add new key handling, you need to change the subclass's |
347 | 0 | // HandleKeyPressEvent()'s switch statement. |
348 | 0 |
|
349 | 0 | if (IsReadonly() || IsDisabled()) { |
350 | 0 | // When we're not editable, the events handled on EditorBase. |
351 | 0 | return EditorBase::HandleKeyPressEvent(aKeyboardEvent); |
352 | 0 | } |
353 | 0 | |
354 | 0 | if (NS_WARN_IF(!aKeyboardEvent)) { |
355 | 0 | return NS_ERROR_UNEXPECTED; |
356 | 0 | } |
357 | 0 | MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress, |
358 | 0 | "HandleKeyPressEvent gets non-keypress event"); |
359 | 0 |
|
360 | 0 | switch (aKeyboardEvent->mKeyCode) { |
361 | 0 | case NS_VK_META: |
362 | 0 | case NS_VK_WIN: |
363 | 0 | case NS_VK_SHIFT: |
364 | 0 | case NS_VK_CONTROL: |
365 | 0 | case NS_VK_ALT: |
366 | 0 | // These keys are handled on EditorBase |
367 | 0 | return EditorBase::HandleKeyPressEvent(aKeyboardEvent); |
368 | 0 | case NS_VK_BACK: |
369 | 0 | if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() || |
370 | 0 | aKeyboardEvent->IsMeta() || aKeyboardEvent->IsOS()) { |
371 | 0 | return NS_OK; |
372 | 0 | } |
373 | 0 | DeleteSelectionAsAction(nsIEditor::ePrevious, nsIEditor::eStrip); |
374 | 0 | aKeyboardEvent->PreventDefault(); // consumed |
375 | 0 | return NS_OK; |
376 | 0 | case NS_VK_DELETE: |
377 | 0 | // on certain platforms (such as windows) the shift key |
378 | 0 | // modifies what delete does (cmd_cut in this case). |
379 | 0 | // bailing here to allow the keybindings to do the cut. |
380 | 0 | if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() || |
381 | 0 | aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() || |
382 | 0 | aKeyboardEvent->IsOS()) { |
383 | 0 | return NS_OK; |
384 | 0 | } |
385 | 0 | DeleteSelectionAsAction(nsIEditor::eNext, nsIEditor::eStrip); |
386 | 0 | aKeyboardEvent->PreventDefault(); // consumed |
387 | 0 | return NS_OK; |
388 | 0 | case NS_VK_TAB: { |
389 | 0 | if (IsTabbable()) { |
390 | 0 | return NS_OK; // let it be used for focus switching |
391 | 0 | } |
392 | 0 | |
393 | 0 | if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() || |
394 | 0 | aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta() || |
395 | 0 | aKeyboardEvent->IsOS()) { |
396 | 0 | return NS_OK; |
397 | 0 | } |
398 | 0 | |
399 | 0 | // else we insert the tab straight through |
400 | 0 | aKeyboardEvent->PreventDefault(); |
401 | 0 | return OnInputText(NS_LITERAL_STRING("\t")); |
402 | 0 | } |
403 | 0 | case NS_VK_RETURN: |
404 | 0 | if (IsSingleLineEditor() || !aKeyboardEvent->IsInputtingLineBreak()) { |
405 | 0 | return NS_OK; |
406 | 0 | } |
407 | 0 | aKeyboardEvent->PreventDefault(); |
408 | 0 | return OnInputParagraphSeparator(); |
409 | 0 | } |
410 | 0 | |
411 | 0 | if (!aKeyboardEvent->IsInputtingText()) { |
412 | 0 | // we don't PreventDefault() here or keybindings like control-x won't work |
413 | 0 | return NS_OK; |
414 | 0 | } |
415 | 0 | aKeyboardEvent->PreventDefault(); |
416 | 0 | nsAutoString str(aKeyboardEvent->mCharCode); |
417 | 0 | return OnInputText(str); |
418 | 0 | } |
419 | | |
420 | | nsresult |
421 | | TextEditor::OnInputText(const nsAString& aStringToInsert) |
422 | 0 | { |
423 | 0 | AutoPlaceholderBatch batch(this, nsGkAtoms::TypingTxnName); |
424 | 0 | nsresult rv = InsertTextAsSubAction(aStringToInsert); |
425 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
426 | 0 | return rv; |
427 | 0 | } |
428 | 0 | return NS_OK; |
429 | 0 | } |
430 | | |
431 | | nsresult |
432 | | TextEditor::OnInputParagraphSeparator() |
433 | 0 | { |
434 | 0 | AutoPlaceholderBatch batch(this, nsGkAtoms::TypingTxnName); |
435 | 0 | nsresult rv = InsertParagraphSeparatorAsAction(); |
436 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
437 | 0 | return rv; |
438 | 0 | } |
439 | 0 | return NS_OK; |
440 | 0 | } |
441 | | |
442 | | template<typename PT, typename CT> |
443 | | already_AddRefed<Element> |
444 | | TextEditor::InsertBrElementWithTransaction( |
445 | | Selection& aSelection, |
446 | | const EditorDOMPointBase<PT, CT>& aPointToInsert, |
447 | | EDirection aSelect /* = eNone */) |
448 | 0 | { |
449 | 0 | if (NS_WARN_IF(!aPointToInsert.IsSet())) { |
450 | 0 | return nullptr; |
451 | 0 | } |
452 | 0 | |
453 | 0 | // We need to insert a <br> node. |
454 | 0 | RefPtr<Element> newBRElement; |
455 | 0 | if (aPointToInsert.IsInTextNode()) { |
456 | 0 | EditorDOMPoint pointInContainer; |
457 | 0 | if (aPointToInsert.IsStartOfContainer()) { |
458 | 0 | // Insert before the text node. |
459 | 0 | pointInContainer.Set(aPointToInsert.GetContainer()); |
460 | 0 | if (NS_WARN_IF(!pointInContainer.IsSet())) { |
461 | 0 | return nullptr; |
462 | 0 | } |
463 | 0 | } else if (aPointToInsert.IsEndOfContainer()) { |
464 | 0 | // Insert after the text node. |
465 | 0 | pointInContainer.Set(aPointToInsert.GetContainer()); |
466 | 0 | if (NS_WARN_IF(!pointInContainer.IsSet())) { |
467 | 0 | return nullptr; |
468 | 0 | } |
469 | 0 | DebugOnly<bool> advanced = pointInContainer.AdvanceOffset(); |
470 | 0 | NS_WARNING_ASSERTION(advanced, |
471 | 0 | "Failed to advance offset to after the text node"); |
472 | 0 | } else { |
473 | 0 | MOZ_DIAGNOSTIC_ASSERT(aPointToInsert.IsSetAndValid()); |
474 | 0 | // Unfortunately, we need to split the text node at the offset. |
475 | 0 | ErrorResult error; |
476 | 0 | nsCOMPtr<nsIContent> newLeftNode = |
477 | 0 | SplitNodeWithTransaction(aPointToInsert, error); |
478 | 0 | if (NS_WARN_IF(error.Failed())) { |
479 | 0 | error.SuppressException(); |
480 | 0 | return nullptr; |
481 | 0 | } |
482 | 0 | Unused << newLeftNode; |
483 | 0 | // Insert new <br> before the right node. |
484 | 0 | pointInContainer.Set(aPointToInsert.GetContainer()); |
485 | 0 | } |
486 | 0 | // Create a <br> node. |
487 | 0 | newBRElement = CreateNodeWithTransaction(*nsGkAtoms::br, pointInContainer); |
488 | 0 | if (NS_WARN_IF(!newBRElement)) { |
489 | 0 | return nullptr; |
490 | 0 | } |
491 | 0 | } else { |
492 | 0 | newBRElement = CreateNodeWithTransaction(*nsGkAtoms::br, aPointToInsert); |
493 | 0 | if (NS_WARN_IF(!newBRElement)) { |
494 | 0 | return nullptr; |
495 | 0 | } |
496 | 0 | } |
497 | 0 | |
498 | 0 | switch (aSelect) { |
499 | 0 | case eNone: |
500 | 0 | break; |
501 | 0 | case eNext: { |
502 | 0 | aSelection.SetInterlinePosition(true, IgnoreErrors()); |
503 | 0 | // Collapse selection after the <br> node. |
504 | 0 | EditorRawDOMPoint afterBRElement(newBRElement); |
505 | 0 | if (afterBRElement.IsSet()) { |
506 | 0 | DebugOnly<bool> advanced = afterBRElement.AdvanceOffset(); |
507 | 0 | NS_WARNING_ASSERTION(advanced, |
508 | 0 | "Failed to advance offset after the <br> element"); |
509 | 0 | ErrorResult error; |
510 | 0 | aSelection.Collapse(afterBRElement, error); |
511 | 0 | NS_WARNING_ASSERTION(!error.Failed(), |
512 | 0 | "Failed to collapse selection after the <br> element"); |
513 | 0 | } else { |
514 | 0 | NS_WARNING("The <br> node is not in the DOM tree?"); |
515 | 0 | } |
516 | 0 | break; |
517 | 0 | } |
518 | 0 | case ePrevious: { |
519 | 0 | aSelection.SetInterlinePosition(true, IgnoreErrors()); |
520 | 0 | // Collapse selection at the <br> node. |
521 | 0 | EditorRawDOMPoint atBRElement(newBRElement); |
522 | 0 | if (atBRElement.IsSet()) { |
523 | 0 | ErrorResult error; |
524 | 0 | aSelection.Collapse(atBRElement, error); |
525 | 0 | NS_WARNING_ASSERTION(!error.Failed(), |
526 | 0 | "Failed to collapse selection at the <br> element"); |
527 | 0 | } else { |
528 | 0 | NS_WARNING("The <br> node is not in the DOM tree?"); |
529 | 0 | } |
530 | 0 | break; |
531 | 0 | } |
532 | 0 | default: |
533 | 0 | NS_WARNING("aSelect has invalid value, the caller need to set selection " |
534 | 0 | "by itself"); |
535 | 0 | break; |
536 | 0 | } |
537 | 0 |
|
538 | 0 | return newBRElement.forget(); |
539 | 0 | } Unexecuted instantiation: already_AddRefed<mozilla::dom::Element> mozilla::TextEditor::InsertBrElementWithTransaction<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent> >(mozilla::dom::Selection&, mozilla::EditorDOMPointBase<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent> > const&, short) Unexecuted instantiation: already_AddRefed<mozilla::dom::Element> mozilla::TextEditor::InsertBrElementWithTransaction<nsINode*, nsIContent*>(mozilla::dom::Selection&, mozilla::EditorDOMPointBase<nsINode*, nsIContent*> const&, short) |
540 | | |
541 | | nsresult |
542 | | TextEditor::ExtendSelectionForDelete(Selection* aSelection, |
543 | | nsIEditor::EDirection* aAction) |
544 | 0 | { |
545 | 0 | bool bCollapsed = aSelection->IsCollapsed(); |
546 | 0 |
|
547 | 0 | if (*aAction == eNextWord || |
548 | 0 | *aAction == ePreviousWord || |
549 | 0 | (*aAction == eNext && bCollapsed) || |
550 | 0 | (*aAction == ePrevious && bCollapsed) || |
551 | 0 | *aAction == eToBeginningOfLine || |
552 | 0 | *aAction == eToEndOfLine) { |
553 | 0 | nsCOMPtr<nsISelectionController> selCont; |
554 | 0 | GetSelectionController(getter_AddRefs(selCont)); |
555 | 0 | NS_ENSURE_TRUE(selCont, NS_ERROR_NO_INTERFACE); |
556 | 0 |
|
557 | 0 | switch (*aAction) { |
558 | 0 | case eNextWord: { |
559 | 0 | nsresult rv = selCont->WordExtendForDelete(true); |
560 | 0 | // DeleteSelectionWithTransaction() doesn't handle these actions |
561 | 0 | // because it's inside batching, so don't confuse it: |
562 | 0 | *aAction = eNone; |
563 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
564 | 0 | return rv; |
565 | 0 | } |
566 | 0 | return NS_OK; |
567 | 0 | } |
568 | 0 | case ePreviousWord: { |
569 | 0 | nsresult rv = selCont->WordExtendForDelete(false); |
570 | 0 | *aAction = eNone; |
571 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
572 | 0 | return rv; |
573 | 0 | } |
574 | 0 | return NS_OK; |
575 | 0 | } |
576 | 0 | case eNext: { |
577 | 0 | nsresult rv = selCont->CharacterExtendForDelete(); |
578 | 0 | // Don't set aAction to eNone (see Bug 502259) |
579 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
580 | 0 | return rv; |
581 | 0 | } |
582 | 0 | return NS_OK; |
583 | 0 | } |
584 | 0 | case ePrevious: { |
585 | 0 | // Only extend the selection where the selection is after a UTF-16 |
586 | 0 | // surrogate pair or a variation selector. |
587 | 0 | // For other cases we don't want to do that, in order |
588 | 0 | // to make sure that pressing backspace will only delete the last |
589 | 0 | // typed character. |
590 | 0 | EditorRawDOMPoint atStartOfSelection = |
591 | 0 | EditorBase::GetStartPoint(aSelection); |
592 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
593 | 0 | return NS_ERROR_FAILURE; |
594 | 0 | } |
595 | 0 | |
596 | 0 | // node might be anonymous DIV, so we find better text node |
597 | 0 | EditorRawDOMPoint insertionPoint = |
598 | 0 | FindBetterInsertionPoint(atStartOfSelection); |
599 | 0 |
|
600 | 0 | if (insertionPoint.IsInTextNode()) { |
601 | 0 | const nsTextFragment* data = |
602 | 0 | insertionPoint.GetContainerAsText()->GetText(); |
603 | 0 | uint32_t offset = insertionPoint.Offset(); |
604 | 0 | if ((offset > 1 && |
605 | 0 | NS_IS_LOW_SURROGATE(data->CharAt(offset - 1)) && |
606 | 0 | NS_IS_HIGH_SURROGATE(data->CharAt(offset - 2))) || |
607 | 0 | (offset > 0 && |
608 | 0 | gfxFontUtils::IsVarSelector(data->CharAt(offset - 1)))) { |
609 | 0 | nsresult rv = selCont->CharacterExtendForBackspace(); |
610 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
611 | 0 | return rv; |
612 | 0 | } |
613 | 0 | } |
614 | 0 | } |
615 | 0 | return NS_OK; |
616 | 0 | } |
617 | 0 | case eToBeginningOfLine: { |
618 | 0 | // Select to beginning |
619 | 0 | nsresult rv = selCont->IntraLineMove(false, true); |
620 | 0 | *aAction = eNone; |
621 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
622 | 0 | return rv; |
623 | 0 | } |
624 | 0 | return NS_OK; |
625 | 0 | } |
626 | 0 | case eToEndOfLine: { |
627 | 0 | nsresult rv = selCont->IntraLineMove(true, true); |
628 | 0 | *aAction = eNext; |
629 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
630 | 0 | return rv; |
631 | 0 | } |
632 | 0 | return NS_OK; |
633 | 0 | } |
634 | 0 | // For avoiding several compiler warnings |
635 | 0 | default: |
636 | 0 | return NS_OK; |
637 | 0 | } |
638 | 0 | } |
639 | 0 | return NS_OK; |
640 | 0 | } |
641 | | |
642 | | NS_IMETHODIMP |
643 | | TextEditor::DeleteSelection(EDirection aAction, |
644 | | EStripWrappers aStripWrappers) |
645 | 0 | { |
646 | 0 | nsresult rv = DeleteSelectionAsAction(aAction, aStripWrappers); |
647 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
648 | 0 | return rv; |
649 | 0 | } |
650 | 0 | return NS_OK; |
651 | 0 | } |
652 | | |
653 | | nsresult |
654 | | TextEditor::DeleteSelectionAsAction(EDirection aDirection, |
655 | | EStripWrappers aStripWrappers) |
656 | 0 | { |
657 | 0 | MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); |
658 | 0 | // Showing this assertion is fine if this method is called by outside via |
659 | 0 | // mutation event listener or something. Otherwise, this is called by |
660 | 0 | // wrong method. |
661 | 0 | NS_ASSERTION(!mPlaceholderBatch, |
662 | 0 | "Should be called only when this is the only edit action of the operation " |
663 | 0 | "unless mutation event listener nests some operations"); |
664 | 0 |
|
665 | 0 | // delete placeholder txns merge. |
666 | 0 | AutoPlaceholderBatch batch(this, nsGkAtoms::DeleteTxnName); |
667 | 0 | nsresult rv = DeleteSelectionAsSubAction(aDirection, aStripWrappers); |
668 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
669 | 0 | return rv; |
670 | 0 | } |
671 | 0 | return NS_OK; |
672 | 0 | } |
673 | | |
674 | | nsresult |
675 | | TextEditor::DeleteSelectionAsSubAction(EDirection aDirection, |
676 | | EStripWrappers aStripWrappers) |
677 | 0 | { |
678 | 0 | MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); |
679 | 0 | MOZ_ASSERT(mPlaceholderBatch); |
680 | 0 |
|
681 | 0 | if (!mRules) { |
682 | 0 | return NS_ERROR_NOT_INITIALIZED; |
683 | 0 | } |
684 | 0 | |
685 | 0 | // Protect the edit rules object from dying |
686 | 0 | RefPtr<TextEditRules> rules(mRules); |
687 | 0 |
|
688 | 0 | // pre-process |
689 | 0 | RefPtr<Selection> selection = GetSelection(); |
690 | 0 | if (NS_WARN_IF(!selection)) { |
691 | 0 | return NS_ERROR_FAILURE; |
692 | 0 | } |
693 | 0 | |
694 | 0 | // If there is an existing selection when an extended delete is requested, |
695 | 0 | // platforms that use "caret-style" caret positioning collapse the |
696 | 0 | // selection to the start and then create a new selection. |
697 | 0 | // Platforms that use "selection-style" caret positioning just delete the |
698 | 0 | // existing selection without extending it. |
699 | 0 | if (!selection->IsCollapsed()) { |
700 | 0 | switch (aDirection) { |
701 | 0 | case eNextWord: |
702 | 0 | case ePreviousWord: |
703 | 0 | case eToBeginningOfLine: |
704 | 0 | case eToEndOfLine: { |
705 | 0 | if (mCaretStyle != 1) { |
706 | 0 | aDirection = eNone; |
707 | 0 | break; |
708 | 0 | } |
709 | 0 | ErrorResult error; |
710 | 0 | selection->CollapseToStart(error); |
711 | 0 | if (NS_WARN_IF(error.Failed())) { |
712 | 0 | return error.StealNSResult(); |
713 | 0 | } |
714 | 0 | break; |
715 | 0 | } |
716 | 0 | default: |
717 | 0 | break; |
718 | 0 | } |
719 | 0 | } |
720 | 0 | |
721 | 0 | AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction( |
722 | 0 | *this, |
723 | 0 | EditSubAction::eDeleteSelectedContent, |
724 | 0 | aDirection); |
725 | 0 | EditSubActionInfo subActionInfo(EditSubAction::eDeleteSelectedContent); |
726 | 0 | subActionInfo.collapsedAction = aDirection; |
727 | 0 | subActionInfo.stripWrappers = aStripWrappers; |
728 | 0 | bool cancel, handled; |
729 | 0 | nsresult rv = |
730 | 0 | rules->WillDoAction(selection, subActionInfo, &cancel, &handled); |
731 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
732 | 0 | return rv; |
733 | 0 | } |
734 | 0 | if (!cancel && !handled) { |
735 | 0 | rv = DeleteSelectionWithTransaction(aDirection, aStripWrappers); |
736 | 0 | } |
737 | 0 | if (!cancel) { |
738 | 0 | // post-process |
739 | 0 | rv = rules->DidDoAction(selection, subActionInfo, rv); |
740 | 0 | } |
741 | 0 | return rv; |
742 | 0 | } |
743 | | |
744 | | nsresult |
745 | | TextEditor::DeleteSelectionWithTransaction(EDirection aDirection, |
746 | | EStripWrappers aStripWrappers) |
747 | 0 | { |
748 | 0 | MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip); |
749 | 0 |
|
750 | 0 | RefPtr<Selection> selection = GetSelection(); |
751 | 0 | if (NS_WARN_IF(!selection)) { |
752 | 0 | return NS_ERROR_NOT_INITIALIZED; |
753 | 0 | } |
754 | 0 | |
755 | 0 | RefPtr<EditAggregateTransaction> deleteSelectionTransaction; |
756 | 0 | nsCOMPtr<nsINode> deleteNode; |
757 | 0 | int32_t deleteCharOffset = 0, deleteCharLength = 0; |
758 | 0 | if (!selection->IsCollapsed() || aDirection != eNone) { |
759 | 0 | deleteSelectionTransaction = |
760 | 0 | CreateTxnForDeleteSelection(aDirection, |
761 | 0 | getter_AddRefs(deleteNode), |
762 | 0 | &deleteCharOffset, |
763 | 0 | &deleteCharLength); |
764 | 0 | if (NS_WARN_IF(!deleteSelectionTransaction)) { |
765 | 0 | return NS_ERROR_FAILURE; |
766 | 0 | } |
767 | 0 | } |
768 | 0 | |
769 | 0 | RefPtr<CharacterData> deleteCharData = |
770 | 0 | CharacterData::FromNodeOrNull(deleteNode); |
771 | 0 | AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction( |
772 | 0 | *this, |
773 | 0 | EditSubAction::eDeleteSelectedContent, |
774 | 0 | aDirection); |
775 | 0 |
|
776 | 0 | if (mRules && mRules->AsHTMLEditRules()) { |
777 | 0 | if (!deleteNode) { |
778 | 0 | RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules(); |
779 | 0 | htmlEditRules->WillDeleteSelection(*selection); |
780 | 0 | } else if (!deleteCharData) { |
781 | 0 | RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules(); |
782 | 0 | htmlEditRules->WillDeleteNode(*selection, *deleteNode); |
783 | 0 | } |
784 | 0 | } |
785 | 0 |
|
786 | 0 | // Notify nsIEditActionListener::WillDelete[Selection|Text] |
787 | 0 | if (!mActionListeners.IsEmpty()) { |
788 | 0 | if (!deleteNode) { |
789 | 0 | AutoActionListenerArray listeners(mActionListeners); |
790 | 0 | for (auto& listener : listeners) { |
791 | 0 | listener->WillDeleteSelection(selection); |
792 | 0 | } |
793 | 0 | } else if (deleteCharData) { |
794 | 0 | AutoActionListenerArray listeners(mActionListeners); |
795 | 0 | for (auto& listener : listeners) { |
796 | 0 | listener->WillDeleteText(deleteCharData, deleteCharOffset, 1); |
797 | 0 | } |
798 | 0 | } |
799 | 0 | } |
800 | 0 |
|
801 | 0 | // Delete the specified amount |
802 | 0 | nsresult rv = DoTransaction(deleteSelectionTransaction); |
803 | 0 |
|
804 | 0 | if (mRules && mRules->AsHTMLEditRules() && deleteCharData) { |
805 | 0 | MOZ_ASSERT(deleteNode); |
806 | 0 | RefPtr<HTMLEditRules> htmlEditRules = mRules->AsHTMLEditRules(); |
807 | 0 | htmlEditRules->DidDeleteText(*selection, *deleteNode, deleteCharOffset, 1); |
808 | 0 | } |
809 | 0 |
|
810 | 0 | if (mTextServicesDocument && NS_SUCCEEDED(rv) && |
811 | 0 | deleteNode && !deleteCharData) { |
812 | 0 | RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument; |
813 | 0 | textServicesDocument->DidDeleteNode(deleteNode); |
814 | 0 | } |
815 | 0 |
|
816 | 0 | // Notify nsIEditActionListener::DidDelete[Selection|Text|Node] |
817 | 0 | { |
818 | 0 | AutoActionListenerArray listeners(mActionListeners); |
819 | 0 | if (!deleteNode) { |
820 | 0 | for (auto& listener : mActionListeners) { |
821 | 0 | listener->DidDeleteSelection(selection); |
822 | 0 | } |
823 | 0 | } else if (deleteCharData) { |
824 | 0 | for (auto& listener : mActionListeners) { |
825 | 0 | listener->DidDeleteText(deleteCharData, deleteCharOffset, 1, rv); |
826 | 0 | } |
827 | 0 | } else { |
828 | 0 | for (auto& listener : mActionListeners) { |
829 | 0 | listener->DidDeleteNode(deleteNode, rv); |
830 | 0 | } |
831 | 0 | } |
832 | 0 | } |
833 | 0 |
|
834 | 0 | return rv; |
835 | 0 | } |
836 | | |
837 | | already_AddRefed<Element> |
838 | | TextEditor::DeleteSelectionAndCreateElement(nsAtom& aTag) |
839 | 0 | { |
840 | 0 | nsresult rv = DeleteSelectionAndPrepareToCreateNode(); |
841 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
842 | 0 | return nullptr; |
843 | 0 | } |
844 | 0 | |
845 | 0 | RefPtr<Selection> selection = GetSelection(); |
846 | 0 | if (NS_WARN_IF(!selection)) { |
847 | 0 | return nullptr; |
848 | 0 | } |
849 | 0 | |
850 | 0 | EditorRawDOMPoint pointToInsert(selection->AnchorRef()); |
851 | 0 | if (!pointToInsert.IsSet()) { |
852 | 0 | return nullptr; |
853 | 0 | } |
854 | 0 | RefPtr<Element> newElement = CreateNodeWithTransaction(aTag, pointToInsert); |
855 | 0 |
|
856 | 0 | // We want the selection to be just after the new node |
857 | 0 | EditorRawDOMPoint afterNewElement(newElement); |
858 | 0 | MOZ_ASSERT(afterNewElement.IsSetAndValid()); |
859 | 0 | DebugOnly<bool> advanced = afterNewElement.AdvanceOffset(); |
860 | 0 | NS_WARNING_ASSERTION(advanced, |
861 | 0 | "Failed to move offset next to the new element"); |
862 | 0 | ErrorResult error; |
863 | 0 | selection->Collapse(afterNewElement, error); |
864 | 0 | if (NS_WARN_IF(error.Failed())) { |
865 | 0 | // XXX Even if it succeeded to create new element, this returns error |
866 | 0 | // when Selection.Collapse() fails something. This could occur with |
867 | 0 | // mutation observer or mutation event listener. |
868 | 0 | error.SuppressException(); |
869 | 0 | return nullptr; |
870 | 0 | } |
871 | 0 | return newElement.forget(); |
872 | 0 | } |
873 | | |
874 | | nsresult |
875 | | TextEditor::DeleteSelectionAndPrepareToCreateNode() |
876 | 0 | { |
877 | 0 | RefPtr<Selection> selection = GetSelection(); |
878 | 0 | if (NS_WARN_IF(!selection)) { |
879 | 0 | return NS_ERROR_NOT_INITIALIZED; |
880 | 0 | } |
881 | 0 | |
882 | 0 | if (NS_WARN_IF(!selection->GetAnchorFocusRange())) { |
883 | 0 | return NS_OK; |
884 | 0 | } |
885 | 0 | |
886 | 0 | if (!selection->GetAnchorFocusRange()->Collapsed()) { |
887 | 0 | nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip); |
888 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
889 | 0 | return rv; |
890 | 0 | } |
891 | 0 | MOZ_ASSERT(selection->GetAnchorFocusRange() && |
892 | 0 | selection->GetAnchorFocusRange()->Collapsed(), |
893 | 0 | "Selection not collapsed after delete"); |
894 | 0 | } |
895 | 0 |
|
896 | 0 | // If the selection is a chardata node, split it if necessary and compute |
897 | 0 | // where to put the new node |
898 | 0 | EditorDOMPoint atAnchor(selection->AnchorRef()); |
899 | 0 | if (NS_WARN_IF(!atAnchor.IsSet()) || !atAnchor.IsInDataNode()) { |
900 | 0 | return NS_OK; |
901 | 0 | } |
902 | 0 | |
903 | 0 | if (NS_WARN_IF(!atAnchor.GetContainer()->GetParentNode())) { |
904 | 0 | return NS_ERROR_FAILURE; |
905 | 0 | } |
906 | 0 | |
907 | 0 | if (atAnchor.IsStartOfContainer()) { |
908 | 0 | EditorRawDOMPoint atAnchorContainer(atAnchor.GetContainer()); |
909 | 0 | if (NS_WARN_IF(!atAnchorContainer.IsSetAndValid())) { |
910 | 0 | return NS_ERROR_FAILURE; |
911 | 0 | } |
912 | 0 | ErrorResult error; |
913 | 0 | selection->Collapse(atAnchorContainer, error); |
914 | 0 | if (NS_WARN_IF(error.Failed())) { |
915 | 0 | return error.StealNSResult(); |
916 | 0 | } |
917 | 0 | return NS_OK; |
918 | 0 | } |
919 | 0 | |
920 | 0 | if (atAnchor.IsEndOfContainer()) { |
921 | 0 | EditorRawDOMPoint afterAnchorContainer(atAnchor.GetContainer()); |
922 | 0 | if (NS_WARN_IF(!afterAnchorContainer.AdvanceOffset())) { |
923 | 0 | return NS_ERROR_FAILURE; |
924 | 0 | } |
925 | 0 | ErrorResult error; |
926 | 0 | selection->Collapse(afterAnchorContainer, error); |
927 | 0 | if (NS_WARN_IF(error.Failed())) { |
928 | 0 | return error.StealNSResult(); |
929 | 0 | } |
930 | 0 | return NS_OK; |
931 | 0 | } |
932 | 0 | |
933 | 0 | ErrorResult error; |
934 | 0 | nsCOMPtr<nsIContent> newLeftNode = SplitNodeWithTransaction(atAnchor, error); |
935 | 0 | if (NS_WARN_IF(error.Failed())) { |
936 | 0 | return error.StealNSResult(); |
937 | 0 | } |
938 | 0 | |
939 | 0 | EditorRawDOMPoint atRightNode(atAnchor.GetContainer()); |
940 | 0 | if (NS_WARN_IF(!atRightNode.IsSet())) { |
941 | 0 | return NS_ERROR_FAILURE; |
942 | 0 | } |
943 | 0 | MOZ_ASSERT(atRightNode.IsSetAndValid()); |
944 | 0 | selection->Collapse(atRightNode, error); |
945 | 0 | if (NS_WARN_IF(error.Failed())) { |
946 | 0 | return error.StealNSResult(); |
947 | 0 | } |
948 | 0 | return NS_OK; |
949 | 0 | } |
950 | | |
951 | | NS_IMETHODIMP |
952 | | TextEditor::InsertText(const nsAString& aStringToInsert) |
953 | 0 | { |
954 | 0 | nsresult rv = InsertTextAsAction(aStringToInsert); |
955 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
956 | 0 | return rv; |
957 | 0 | } |
958 | 0 | return NS_OK; |
959 | 0 | } |
960 | | |
961 | | nsresult |
962 | | TextEditor::InsertTextAsAction(const nsAString& aStringToInsert) |
963 | 0 | { |
964 | 0 | // Showing this assertion is fine if this method is called by outside via |
965 | 0 | // mutation event listener or something. Otherwise, this is called by |
966 | 0 | // wrong method. |
967 | 0 | NS_ASSERTION(!mPlaceholderBatch, |
968 | 0 | "Should be called only when this is the only edit action of the operation " |
969 | 0 | "unless mutation event listener nests some operations"); |
970 | 0 |
|
971 | 0 | AutoPlaceholderBatch batch(this, nullptr); |
972 | 0 | nsresult rv = InsertTextAsSubAction(aStringToInsert); |
973 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
974 | 0 | return rv; |
975 | 0 | } |
976 | 0 | return NS_OK; |
977 | 0 | } |
978 | | |
979 | | nsresult |
980 | | TextEditor::InsertTextAsSubAction(const nsAString& aStringToInsert) |
981 | 0 | { |
982 | 0 | MOZ_ASSERT(mPlaceholderBatch); |
983 | 0 |
|
984 | 0 | if (!mRules) { |
985 | 0 | return NS_ERROR_NOT_INITIALIZED; |
986 | 0 | } |
987 | 0 | |
988 | 0 | // Protect the edit rules object from dying. |
989 | 0 | RefPtr<TextEditRules> rules(mRules); |
990 | 0 |
|
991 | 0 | EditSubAction editSubAction = |
992 | 0 | ShouldHandleIMEComposition() ? |
993 | 0 | EditSubAction::eInsertTextComingFromIME : EditSubAction::eInsertText; |
994 | 0 |
|
995 | 0 | AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction( |
996 | 0 | *this, editSubAction, nsIEditor::eNext); |
997 | 0 |
|
998 | 0 | RefPtr<Selection> selection = GetSelection(); |
999 | 0 | if (NS_WARN_IF(!selection)) { |
1000 | 0 | return NS_ERROR_FAILURE; |
1001 | 0 | } |
1002 | 0 | |
1003 | 0 | nsAutoString resultString; |
1004 | 0 | // XXX can we trust instring to outlive subActionInfo, |
1005 | 0 | // XXX and subActionInfo not to refer to instring in its dtor? |
1006 | 0 | //nsAutoString instring(aStringToInsert); |
1007 | 0 | EditSubActionInfo subActionInfo(editSubAction); |
1008 | 0 | subActionInfo.inString = &aStringToInsert; |
1009 | 0 | subActionInfo.outString = &resultString; |
1010 | 0 | subActionInfo.maxLength = mMaxTextLength; |
1011 | 0 |
|
1012 | 0 | bool cancel, handled; |
1013 | 0 | nsresult rv = |
1014 | 0 | rules->WillDoAction(selection, subActionInfo, &cancel, &handled); |
1015 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1016 | 0 | return rv; |
1017 | 0 | } |
1018 | 0 | if (!cancel && !handled) { |
1019 | 0 | // we rely on rules code for now - no default implementation |
1020 | 0 | } |
1021 | 0 | if (cancel) { |
1022 | 0 | return NS_OK; |
1023 | 0 | } |
1024 | 0 | // post-process |
1025 | 0 | rv = rules->DidDoAction(selection, subActionInfo, NS_OK); |
1026 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1027 | 0 | return rv; |
1028 | 0 | } |
1029 | 0 | return NS_OK; |
1030 | 0 | } |
1031 | | |
1032 | | NS_IMETHODIMP |
1033 | | TextEditor::InsertLineBreak() |
1034 | 0 | { |
1035 | 0 | return InsertParagraphSeparatorAsAction(); |
1036 | 0 | } |
1037 | | |
1038 | | nsresult |
1039 | | TextEditor::InsertParagraphSeparatorAsAction() |
1040 | 0 | { |
1041 | 0 | if (!mRules) { |
1042 | 0 | return NS_ERROR_NOT_INITIALIZED; |
1043 | 0 | } |
1044 | 0 | |
1045 | 0 | // Protect the edit rules object from dying |
1046 | 0 | RefPtr<TextEditRules> rules(mRules); |
1047 | 0 |
|
1048 | 0 | AutoPlaceholderBatch beginBatching(this); |
1049 | 0 | AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction( |
1050 | 0 | *this, |
1051 | 0 | EditSubAction::eInsertParagraphSeparator, |
1052 | 0 | nsIEditor::eNext); |
1053 | 0 |
|
1054 | 0 | RefPtr<Selection> selection = GetSelection(); |
1055 | 0 | if (NS_WARN_IF(!selection)) { |
1056 | 0 | return NS_ERROR_FAILURE; |
1057 | 0 | } |
1058 | 0 | |
1059 | 0 | EditSubActionInfo subActionInfo(EditSubAction::eInsertParagraphSeparator); |
1060 | 0 | subActionInfo.maxLength = mMaxTextLength; |
1061 | 0 | bool cancel, handled; |
1062 | 0 | nsresult rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled); |
1063 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1064 | 0 | // XXX DidDoAction() won't be called when WillDoAction() returns error. |
1065 | 0 | // Perhaps, we should move the code between WillDoAction() and |
1066 | 0 | // DidDoAction() to a new method and guarantee that DidDoAction() is |
1067 | 0 | // always called after WillDoAction(). |
1068 | 0 | return rv; |
1069 | 0 | } |
1070 | 0 | |
1071 | 0 | if (!cancel && !handled) { |
1072 | 0 | // get the (collapsed) selection location |
1073 | 0 | nsRange* firstRange = selection->GetRangeAt(0); |
1074 | 0 | if (NS_WARN_IF(!firstRange)) { |
1075 | 0 | return NS_ERROR_FAILURE; |
1076 | 0 | } |
1077 | 0 | |
1078 | 0 | EditorRawDOMPoint pointToInsert(firstRange->StartRef()); |
1079 | 0 | if (NS_WARN_IF(!pointToInsert.IsSet())) { |
1080 | 0 | return NS_ERROR_FAILURE; |
1081 | 0 | } |
1082 | 0 | MOZ_ASSERT(pointToInsert.IsSetAndValid()); |
1083 | 0 |
|
1084 | 0 | // don't put text in places that can't have it |
1085 | 0 | if (!pointToInsert.IsInTextNode() && |
1086 | 0 | !CanContainTag(*pointToInsert.GetContainer(), |
1087 | 0 | *nsGkAtoms::textTagName)) { |
1088 | 0 | return NS_ERROR_FAILURE; |
1089 | 0 | } |
1090 | 0 | |
1091 | 0 | // we need to get the doc |
1092 | 0 | nsCOMPtr<nsIDocument> doc = GetDocument(); |
1093 | 0 | if (NS_WARN_IF(!doc)) { |
1094 | 0 | return NS_ERROR_NOT_INITIALIZED; |
1095 | 0 | } |
1096 | 0 | |
1097 | 0 | // don't change my selection in subtransactions |
1098 | 0 | AutoTransactionsConserveSelection dontChangeMySelection(*this); |
1099 | 0 |
|
1100 | 0 | // insert a linefeed character |
1101 | 0 | EditorRawDOMPoint pointAfterInsertedLineBreak; |
1102 | 0 | rv = InsertTextWithTransaction(*doc, NS_LITERAL_STRING("\n"), pointToInsert, |
1103 | 0 | &pointAfterInsertedLineBreak); |
1104 | 0 | if (NS_WARN_IF(!pointAfterInsertedLineBreak.IsSet())) { |
1105 | 0 | rv = NS_ERROR_NULL_POINTER; // don't return here, so DidDoAction is called |
1106 | 0 | } |
1107 | 0 | if (NS_SUCCEEDED(rv)) { |
1108 | 0 | // set the selection to the correct location |
1109 | 0 | MOZ_ASSERT(!pointAfterInsertedLineBreak.GetChild(), |
1110 | 0 | "After inserting text into a text node, pointAfterInsertedLineBreak." |
1111 | 0 | "GetChild() should be nullptr"); |
1112 | 0 | rv = selection->Collapse(pointAfterInsertedLineBreak); |
1113 | 0 | if (NS_SUCCEEDED(rv)) { |
1114 | 0 | // see if we're at the end of the editor range |
1115 | 0 | EditorRawDOMPoint endPoint = GetEndPoint(selection); |
1116 | 0 | if (endPoint == pointAfterInsertedLineBreak) { |
1117 | 0 | // SetInterlinePosition(true) means we want the caret to stick to the |
1118 | 0 | // content on the "right". We want the caret to stick to whatever is |
1119 | 0 | // past the break. This is because the break is on the same line we |
1120 | 0 | // were on, but the next content will be on the following line. |
1121 | 0 | selection->SetInterlinePosition(true, IgnoreErrors()); |
1122 | 0 | } |
1123 | 0 | } |
1124 | 0 | } |
1125 | 0 | } |
1126 | 0 |
|
1127 | 0 | if (!cancel) { |
1128 | 0 | // post-process, always called if WillInsertBreak didn't return cancel==true |
1129 | 0 | rv = rules->DidDoAction(selection, subActionInfo, rv); |
1130 | 0 | } |
1131 | 0 | return rv; |
1132 | 0 | } |
1133 | | |
1134 | | nsresult |
1135 | | TextEditor::SetText(const nsAString& aString) |
1136 | 0 | { |
1137 | 0 | MOZ_ASSERT(aString.FindChar(static_cast<char16_t>('\r')) == kNotFound); |
1138 | 0 |
|
1139 | 0 | AutoPlaceholderBatch batch(this, nullptr); |
1140 | 0 | nsresult rv = SetTextAsSubAction(aString); |
1141 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1142 | 0 | return rv; |
1143 | 0 | } |
1144 | 0 | return NS_OK; |
1145 | 0 | } |
1146 | | |
1147 | | nsresult |
1148 | | TextEditor::ReplaceTextAsAction(const nsAString& aString, |
1149 | | nsRange* aReplaceRange /* = nullptr */) |
1150 | 0 | { |
1151 | 0 | AutoPlaceholderBatch batch(this, nullptr); |
1152 | 0 |
|
1153 | 0 | // This should emulates inserting text for better undo/redo behavior. |
1154 | 0 | AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction( |
1155 | 0 | *this, EditSubAction::eInsertText, |
1156 | 0 | nsIEditor::eNext); |
1157 | 0 |
|
1158 | 0 | if (!aReplaceRange) { |
1159 | 0 | nsresult rv = SetTextAsSubAction(aString); |
1160 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1161 | 0 | return rv; |
1162 | 0 | } |
1163 | 0 | return NS_OK; |
1164 | 0 | } |
1165 | 0 | |
1166 | 0 | if (NS_WARN_IF(aString.IsEmpty() && aReplaceRange->Collapsed())) { |
1167 | 0 | return NS_OK; |
1168 | 0 | } |
1169 | 0 | |
1170 | 0 | // Note that do not notify selectionchange caused by selecting all text |
1171 | 0 | // because it's preparation of our delete implementation so web apps |
1172 | 0 | // shouldn't receive such selectionchange before the first mutation. |
1173 | 0 | AutoUpdateViewBatch preventSelectionChangeEvent(this); |
1174 | 0 |
|
1175 | 0 | RefPtr<Selection> selection = GetSelection(); |
1176 | 0 | if (NS_WARN_IF(!selection)) { |
1177 | 0 | return NS_ERROR_FAILURE; |
1178 | 0 | } |
1179 | 0 | |
1180 | 0 | // Select the range but as far as possible, we should not create new range |
1181 | 0 | // even if it's part of special Selection. |
1182 | 0 | nsresult rv = selection->RemoveAllRangesTemporarily(); |
1183 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1184 | 0 | return rv; |
1185 | 0 | } |
1186 | 0 | ErrorResult error; |
1187 | 0 | selection->AddRange(*aReplaceRange, error); |
1188 | 0 | if (NS_WARN_IF(error.Failed())) { |
1189 | 0 | return error.StealNSResult(); |
1190 | 0 | } |
1191 | 0 | |
1192 | 0 | rv = ReplaceSelectionAsSubAction(aString); |
1193 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1194 | 0 | return rv; |
1195 | 0 | } |
1196 | 0 | return NS_OK; |
1197 | 0 | } |
1198 | | |
1199 | | nsresult |
1200 | | TextEditor::SetTextAsSubAction(const nsAString& aString) |
1201 | 0 | { |
1202 | 0 | MOZ_ASSERT(mPlaceholderBatch); |
1203 | 0 |
|
1204 | 0 | if (NS_WARN_IF(!mRules)) { |
1205 | 0 | return NS_ERROR_NOT_INITIALIZED; |
1206 | 0 | } |
1207 | 0 | |
1208 | 0 | // Protect the edit rules object from dying |
1209 | 0 | RefPtr<TextEditRules> rules(mRules); |
1210 | 0 |
|
1211 | 0 | AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction( |
1212 | 0 | *this, EditSubAction::eSetText, |
1213 | 0 | nsIEditor::eNext); |
1214 | 0 |
|
1215 | 0 | // pre-process |
1216 | 0 | RefPtr<Selection> selection = GetSelection(); |
1217 | 0 | if (NS_WARN_IF(!selection)) { |
1218 | 0 | return NS_ERROR_NULL_POINTER; |
1219 | 0 | } |
1220 | 0 | EditSubActionInfo subActionInfo(EditSubAction::eSetText); |
1221 | 0 | subActionInfo.inString = &aString; |
1222 | 0 | subActionInfo.maxLength = mMaxTextLength; |
1223 | 0 |
|
1224 | 0 | bool cancel; |
1225 | 0 | bool handled; |
1226 | 0 | nsresult rv = |
1227 | 0 | rules->WillDoAction(selection, subActionInfo, &cancel, &handled); |
1228 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1229 | 0 | return rv; |
1230 | 0 | } |
1231 | 0 | if (cancel) { |
1232 | 0 | return NS_OK; |
1233 | 0 | } |
1234 | 0 | if (!handled) { |
1235 | 0 | // Note that do not notify selectionchange caused by selecting all text |
1236 | 0 | // because it's preparation of our delete implementation so web apps |
1237 | 0 | // shouldn't receive such selectionchange before the first mutation. |
1238 | 0 | AutoUpdateViewBatch preventSelectionChangeEvent(this); |
1239 | 0 |
|
1240 | 0 | // We want to select trailing BR node to remove all nodes to replace all, |
1241 | 0 | // but TextEditor::SelectEntireDocument doesn't select that BR node. |
1242 | 0 | if (rules->DocumentIsEmpty()) { |
1243 | 0 | // if it's empty, don't select entire doc - that would select |
1244 | 0 | // the bogus node |
1245 | 0 | Element* rootElement = GetRoot(); |
1246 | 0 | if (NS_WARN_IF(!rootElement)) { |
1247 | 0 | return NS_ERROR_FAILURE; |
1248 | 0 | } |
1249 | 0 | rv = selection->Collapse(rootElement, 0); |
1250 | 0 | } else { |
1251 | 0 | rv = EditorBase::SelectEntireDocument(selection); |
1252 | 0 | } |
1253 | 0 | if (NS_SUCCEEDED(rv)) { |
1254 | 0 | rv = ReplaceSelectionAsSubAction(aString); |
1255 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
1256 | 0 | "Failed to replace selection with new string"); |
1257 | 0 | } |
1258 | 0 | } |
1259 | 0 | // post-process |
1260 | 0 | return rules->DidDoAction(selection, subActionInfo, rv); |
1261 | 0 | } |
1262 | | |
1263 | | nsresult |
1264 | | TextEditor::ReplaceSelectionAsSubAction(const nsAString& aString) |
1265 | 0 | { |
1266 | 0 | if (aString.IsEmpty()) { |
1267 | 0 | nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip); |
1268 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1269 | 0 | return rv; |
1270 | 0 | } |
1271 | 0 | return NS_OK; |
1272 | 0 | } |
1273 | 0 | |
1274 | 0 | nsresult rv = InsertTextAsSubAction(aString); |
1275 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1276 | 0 | return rv; |
1277 | 0 | } |
1278 | 0 | return NS_OK; |
1279 | 0 | } |
1280 | | |
1281 | | bool |
1282 | | TextEditor::EnsureComposition(WidgetCompositionEvent& aCompositionEvent) |
1283 | 0 | { |
1284 | 0 | if (mComposition) { |
1285 | 0 | return true; |
1286 | 0 | } |
1287 | 0 | // The compositionstart event must cause creating new TextComposition |
1288 | 0 | // instance at being dispatched by IMEStateManager. |
1289 | 0 | mComposition = IMEStateManager::GetTextCompositionFor(&aCompositionEvent); |
1290 | 0 | if (!mComposition) { |
1291 | 0 | // However, TextComposition may be committed before the composition |
1292 | 0 | // event comes here. |
1293 | 0 | return false; |
1294 | 0 | } |
1295 | 0 | mComposition->StartHandlingComposition(this); |
1296 | 0 | return true; |
1297 | 0 | } |
1298 | | |
1299 | | nsresult |
1300 | | TextEditor::OnCompositionStart(WidgetCompositionEvent& aCompositionStartEvent) |
1301 | 0 | { |
1302 | 0 | if (NS_WARN_IF(mComposition)) { |
1303 | 0 | return NS_OK; |
1304 | 0 | } |
1305 | 0 | |
1306 | 0 | if (IsPasswordEditor()) { |
1307 | 0 | if (NS_WARN_IF(!mRules)) { |
1308 | 0 | return NS_ERROR_FAILURE; |
1309 | 0 | } |
1310 | 0 | // Protect the edit rules object from dying |
1311 | 0 | RefPtr<TextEditRules> rules(mRules); |
1312 | 0 | rules->ResetIMETextPWBuf(); |
1313 | 0 | } |
1314 | 0 |
|
1315 | 0 | EnsureComposition(aCompositionStartEvent); |
1316 | 0 | NS_WARNING_ASSERTION(mComposition, "Failed to get TextComposition instance?"); |
1317 | 0 | return NS_OK; |
1318 | 0 | } |
1319 | | |
1320 | | nsresult |
1321 | | TextEditor::OnCompositionChange(WidgetCompositionEvent& aCompsitionChangeEvent) |
1322 | 0 | { |
1323 | 0 | MOZ_ASSERT(aCompsitionChangeEvent.mMessage == eCompositionChange, |
1324 | 0 | "The event should be eCompositionChange"); |
1325 | 0 |
|
1326 | 0 | if (!EnsureComposition(aCompsitionChangeEvent)) { |
1327 | 0 | return NS_OK; |
1328 | 0 | } |
1329 | 0 | |
1330 | 0 | nsIPresShell* presShell = GetPresShell(); |
1331 | 0 | if (NS_WARN_IF(!presShell)) { |
1332 | 0 | return NS_ERROR_NOT_INITIALIZED; |
1333 | 0 | } |
1334 | 0 | |
1335 | 0 | RefPtr<Selection> selection = GetSelection(); |
1336 | 0 | if (NS_WARN_IF(!selection)) { |
1337 | 0 | return NS_ERROR_FAILURE; |
1338 | 0 | } |
1339 | 0 | |
1340 | 0 | // NOTE: TextComposition should receive selection change notification before |
1341 | 0 | // CompositionChangeEventHandlingMarker notifies TextComposition of the |
1342 | 0 | // end of handling compositionchange event because TextComposition may |
1343 | 0 | // need to ignore selection changes caused by composition. Therefore, |
1344 | 0 | // CompositionChangeEventHandlingMarker must be destroyed after a call |
1345 | 0 | // of NotifiyEditorObservers(eNotifyEditorObserversOfEnd) or |
1346 | 0 | // NotifiyEditorObservers(eNotifyEditorObserversOfCancel) which notifies |
1347 | 0 | // TextComposition of a selection change. |
1348 | 0 | MOZ_ASSERT(!mPlaceholderBatch, |
1349 | 0 | "UpdateIMEComposition() must be called without place holder batch"); |
1350 | 0 | TextComposition::CompositionChangeEventHandlingMarker |
1351 | 0 | compositionChangeEventHandlingMarker(mComposition, &aCompsitionChangeEvent); |
1352 | 0 |
|
1353 | 0 | RefPtr<nsCaret> caretP = presShell->GetCaret(); |
1354 | 0 |
|
1355 | 0 | nsresult rv; |
1356 | 0 | { |
1357 | 0 | AutoPlaceholderBatch batch(this, nsGkAtoms::IMETxnName); |
1358 | 0 |
|
1359 | 0 | MOZ_ASSERT(mIsInEditSubAction, |
1360 | 0 | "AutoPlaceholderBatch should've notified the observes of before-edit"); |
1361 | 0 | rv = InsertTextAsSubAction(aCompsitionChangeEvent.mData); |
1362 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
1363 | 0 | "Failed to insert new composition string"); |
1364 | 0 |
|
1365 | 0 | if (caretP) { |
1366 | 0 | caretP->SetSelection(selection); |
1367 | 0 | } |
1368 | 0 | } |
1369 | 0 |
|
1370 | 0 | // If still composing, we should fire input event via observer. |
1371 | 0 | // Note that if the composition will be committed by the following |
1372 | 0 | // compositionend event, we don't need to notify editor observes of this |
1373 | 0 | // change. |
1374 | 0 | // NOTE: We must notify after the auto batch will be gone. |
1375 | 0 | if (!aCompsitionChangeEvent.IsFollowedByCompositionEnd()) { |
1376 | 0 | NotifyEditorObservers(eNotifyEditorObserversOfEnd); |
1377 | 0 | } |
1378 | 0 |
|
1379 | 0 | return rv; |
1380 | 0 | } |
1381 | | |
1382 | | void |
1383 | | TextEditor::OnCompositionEnd(WidgetCompositionEvent& aCompositionEndEvent) |
1384 | 0 | { |
1385 | 0 | if (NS_WARN_IF(!mComposition)) { |
1386 | 0 | return; |
1387 | 0 | } |
1388 | 0 | |
1389 | 0 | // commit the IME transaction..we can get at it via the transaction mgr. |
1390 | 0 | // Note that this means IME won't work without an undo stack! |
1391 | 0 | if (mTransactionManager) { |
1392 | 0 | nsCOMPtr<nsITransaction> txn = mTransactionManager->PeekUndoStack(); |
1393 | 0 | nsCOMPtr<nsIAbsorbingTransaction> plcTxn = do_QueryInterface(txn); |
1394 | 0 | if (plcTxn) { |
1395 | 0 | DebugOnly<nsresult> rv = plcTxn->Commit(); |
1396 | 0 | NS_ASSERTION(NS_SUCCEEDED(rv), |
1397 | 0 | "nsIAbsorbingTransaction::Commit() failed"); |
1398 | 0 | } |
1399 | 0 | } |
1400 | 0 |
|
1401 | 0 | // Composition string may have hidden the caret. Therefore, we need to |
1402 | 0 | // cancel it here. |
1403 | 0 | HideCaret(false); |
1404 | 0 |
|
1405 | 0 | // FYI: mComposition still keeps storing container text node of committed |
1406 | 0 | // string, its offset and length. However, they will be invalidated |
1407 | 0 | // soon since its Destroy() will be called by IMEStateManager. |
1408 | 0 | mComposition->EndHandlingComposition(this); |
1409 | 0 | mComposition = nullptr; |
1410 | 0 |
|
1411 | 0 | // notify editor observers of action |
1412 | 0 | NotifyEditorObservers(eNotifyEditorObserversOfEnd); |
1413 | 0 | } |
1414 | | |
1415 | | already_AddRefed<nsIContent> |
1416 | | TextEditor::GetInputEventTargetContent() |
1417 | 0 | { |
1418 | 0 | nsCOMPtr<nsIContent> target = do_QueryInterface(mEventTarget); |
1419 | 0 | return target.forget(); |
1420 | 0 | } |
1421 | | |
1422 | | nsresult |
1423 | | TextEditor::IsEmpty(bool* aIsEmpty) const |
1424 | 0 | { |
1425 | 0 | if (NS_WARN_IF(!mRules)) { |
1426 | 0 | return NS_ERROR_NOT_INITIALIZED; |
1427 | 0 | } |
1428 | 0 | |
1429 | 0 | *aIsEmpty = true; |
1430 | 0 |
|
1431 | 0 | if (mRules->HasBogusNode()) { |
1432 | 0 | return NS_OK; |
1433 | 0 | } |
1434 | 0 | |
1435 | 0 | // Even if there is no bogus node, we should be detected as empty editor |
1436 | 0 | // if all the children are text nodes and these have no content. |
1437 | 0 | Element* rootElement = GetRoot(); |
1438 | 0 | if (!rootElement) { |
1439 | 0 | // XXX Why don't we return an error in such case?? |
1440 | 0 | return NS_OK; |
1441 | 0 | } |
1442 | 0 | |
1443 | 0 | for (nsIContent* child = rootElement->GetFirstChild(); |
1444 | 0 | child; child = child->GetNextSibling()) { |
1445 | 0 | if (!EditorBase::IsTextNode(child) || |
1446 | 0 | static_cast<nsTextNode*>(child)->TextDataLength()) { |
1447 | 0 | *aIsEmpty = false; |
1448 | 0 | return NS_OK; |
1449 | 0 | } |
1450 | 0 | } |
1451 | 0 |
|
1452 | 0 | return NS_OK; |
1453 | 0 | } |
1454 | | |
1455 | | NS_IMETHODIMP |
1456 | | TextEditor::GetDocumentIsEmpty(bool* aDocumentIsEmpty) |
1457 | 0 | { |
1458 | 0 | nsresult rv = IsEmpty(aDocumentIsEmpty); |
1459 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1460 | 0 | return rv; |
1461 | 0 | } |
1462 | 0 | return NS_OK; |
1463 | 0 | } |
1464 | | |
1465 | | NS_IMETHODIMP |
1466 | | TextEditor::GetTextLength(int32_t* aCount) |
1467 | 0 | { |
1468 | 0 | MOZ_ASSERT(aCount); |
1469 | 0 |
|
1470 | 0 | // initialize out params |
1471 | 0 | *aCount = 0; |
1472 | 0 |
|
1473 | 0 | // special-case for empty document, to account for the bogus node |
1474 | 0 | bool isEmpty = false; |
1475 | 0 | nsresult rv = IsEmpty(&isEmpty); |
1476 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1477 | 0 | return rv; |
1478 | 0 | } |
1479 | 0 | if (isEmpty) { |
1480 | 0 | return NS_OK; |
1481 | 0 | } |
1482 | 0 | |
1483 | 0 | Element* rootElement = GetRoot(); |
1484 | 0 | if (NS_WARN_IF(!rootElement)) { |
1485 | 0 | return NS_ERROR_FAILURE; |
1486 | 0 | } |
1487 | 0 | |
1488 | 0 | nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator(); |
1489 | 0 |
|
1490 | 0 | uint32_t totalLength = 0; |
1491 | 0 | iter->Init(rootElement); |
1492 | 0 | for (; !iter->IsDone(); iter->Next()) { |
1493 | 0 | nsCOMPtr<nsINode> currentNode = iter->GetCurrentNode(); |
1494 | 0 | if (IsTextNode(currentNode) && IsEditable(currentNode)) { |
1495 | 0 | totalLength += currentNode->Length(); |
1496 | 0 | } |
1497 | 0 | } |
1498 | 0 |
|
1499 | 0 | *aCount = totalLength; |
1500 | 0 | return NS_OK; |
1501 | 0 | } |
1502 | | |
1503 | | NS_IMETHODIMP |
1504 | | TextEditor::GetWrapWidth(int32_t* aWrapColumn) |
1505 | 0 | { |
1506 | 0 | if (NS_WARN_IF(!aWrapColumn)) { |
1507 | 0 | return NS_ERROR_INVALID_ARG; |
1508 | 0 | } |
1509 | 0 | *aWrapColumn = WrapWidth(); |
1510 | 0 | return NS_OK; |
1511 | 0 | } |
1512 | | |
1513 | | // |
1514 | | // See if the style value includes this attribute, and if it does, |
1515 | | // cut out everything from the attribute to the next semicolon. |
1516 | | // |
1517 | | static void CutStyle(const char* stylename, nsString& styleValue) |
1518 | 0 | { |
1519 | 0 | // Find the current wrapping type: |
1520 | 0 | int32_t styleStart = styleValue.Find(stylename, true); |
1521 | 0 | if (styleStart >= 0) { |
1522 | 0 | int32_t styleEnd = styleValue.Find(";", false, styleStart); |
1523 | 0 | if (styleEnd > styleStart) { |
1524 | 0 | styleValue.Cut(styleStart, styleEnd - styleStart + 1); |
1525 | 0 | } else { |
1526 | 0 | styleValue.Cut(styleStart, styleValue.Length() - styleStart); |
1527 | 0 | } |
1528 | 0 | } |
1529 | 0 | } |
1530 | | |
1531 | | NS_IMETHODIMP |
1532 | | TextEditor::SetWrapWidth(int32_t aWrapColumn) |
1533 | 0 | { |
1534 | 0 | SetWrapColumn(aWrapColumn); |
1535 | 0 |
|
1536 | 0 | // Make sure we're a plaintext editor, otherwise we shouldn't |
1537 | 0 | // do the rest of this. |
1538 | 0 | if (!IsPlaintextEditor()) { |
1539 | 0 | return NS_OK; |
1540 | 0 | } |
1541 | 0 | |
1542 | 0 | // Ought to set a style sheet here ... |
1543 | 0 | // Probably should keep around an mPlaintextStyleSheet for this purpose. |
1544 | 0 | dom::Element *rootElement = GetRoot(); |
1545 | 0 | NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER); |
1546 | 0 |
|
1547 | 0 | // Get the current style for this root element: |
1548 | 0 | nsAutoString styleValue; |
1549 | 0 | rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue); |
1550 | 0 |
|
1551 | 0 | // We'll replace styles for these values: |
1552 | 0 | CutStyle("white-space", styleValue); |
1553 | 0 | CutStyle("width", styleValue); |
1554 | 0 | CutStyle("font-family", styleValue); |
1555 | 0 |
|
1556 | 0 | // If we have other style left, trim off any existing semicolons |
1557 | 0 | // or whitespace, then add a known semicolon-space: |
1558 | 0 | if (!styleValue.IsEmpty()) { |
1559 | 0 | styleValue.Trim("; \t", false, true); |
1560 | 0 | styleValue.AppendLiteral("; "); |
1561 | 0 | } |
1562 | 0 |
|
1563 | 0 | // Make sure we have fixed-width font. This should be done for us, |
1564 | 0 | // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;". |
1565 | 0 | // Only do this if we're wrapping. |
1566 | 0 | if (IsWrapHackEnabled() && aWrapColumn >= 0) { |
1567 | 0 | styleValue.AppendLiteral("font-family: -moz-fixed; "); |
1568 | 0 | } |
1569 | 0 |
|
1570 | 0 | // and now we're ready to set the new whitespace/wrapping style. |
1571 | 0 | if (aWrapColumn > 0) { |
1572 | 0 | // Wrap to a fixed column. |
1573 | 0 | styleValue.AppendLiteral("white-space: pre-wrap; width: "); |
1574 | 0 | styleValue.AppendInt(aWrapColumn); |
1575 | 0 | styleValue.AppendLiteral("ch;"); |
1576 | 0 | } else if (!aWrapColumn) { |
1577 | 0 | styleValue.AppendLiteral("white-space: pre-wrap;"); |
1578 | 0 | } else { |
1579 | 0 | styleValue.AppendLiteral("white-space: pre;"); |
1580 | 0 | } |
1581 | 0 |
|
1582 | 0 | return rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleValue, true); |
1583 | 0 | } |
1584 | | |
1585 | | NS_IMETHODIMP |
1586 | | TextEditor::SetWrapColumn(int32_t aWrapColumn) |
1587 | 0 | { |
1588 | 0 | mWrapColumn = aWrapColumn; |
1589 | 0 | return NS_OK; |
1590 | 0 | } |
1591 | | |
1592 | | NS_IMETHODIMP |
1593 | | TextEditor::GetNewlineHandling(int32_t* aNewlineHandling) |
1594 | 0 | { |
1595 | 0 | NS_ENSURE_ARG_POINTER(aNewlineHandling); |
1596 | 0 |
|
1597 | 0 | *aNewlineHandling = mNewlineHandling; |
1598 | 0 | return NS_OK; |
1599 | 0 | } |
1600 | | |
1601 | | NS_IMETHODIMP |
1602 | | TextEditor::SetNewlineHandling(int32_t aNewlineHandling) |
1603 | 0 | { |
1604 | 0 | mNewlineHandling = aNewlineHandling; |
1605 | 0 |
|
1606 | 0 | return NS_OK; |
1607 | 0 | } |
1608 | | |
1609 | | NS_IMETHODIMP |
1610 | | TextEditor::Undo(uint32_t aCount) |
1611 | 0 | { |
1612 | 0 | // If we don't have transaction in the undo stack, we shouldn't notify |
1613 | 0 | // anybody of trying to undo since it's not useful notification but we |
1614 | 0 | // need to pay some runtime cost. |
1615 | 0 | if (!CanUndo()) { |
1616 | 0 | return NS_OK; |
1617 | 0 | } |
1618 | 0 | |
1619 | 0 | // If there is composition, we shouldn't allow to undo with committing |
1620 | 0 | // composition since Chrome doesn't allow it and it doesn't make sense |
1621 | 0 | // because committing composition causes one transaction and Undo(1) |
1622 | 0 | // undoes the committing composition. |
1623 | 0 | if (GetComposition()) { |
1624 | 0 | return NS_OK; |
1625 | 0 | } |
1626 | 0 | |
1627 | 0 | // Protect the edit rules object from dying. |
1628 | 0 | RefPtr<TextEditRules> rules(mRules); |
1629 | 0 |
|
1630 | 0 | AutoUpdateViewBatch beginViewBatching(this); |
1631 | 0 |
|
1632 | 0 | NotifyEditorObservers(eNotifyEditorObserversOfBefore); |
1633 | 0 | if (NS_WARN_IF(!CanUndo()) || NS_WARN_IF(Destroyed())) { |
1634 | 0 | return NS_ERROR_FAILURE; |
1635 | 0 | } |
1636 | 0 | |
1637 | 0 | nsresult rv; |
1638 | 0 | { |
1639 | 0 | AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction( |
1640 | 0 | *this, EditSubAction::eUndo, |
1641 | 0 | nsIEditor::eNone); |
1642 | 0 |
|
1643 | 0 | EditSubActionInfo subActionInfo(EditSubAction::eUndo); |
1644 | 0 | RefPtr<Selection> selection = GetSelection(); |
1645 | 0 | bool cancel, handled; |
1646 | 0 | rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled); |
1647 | 0 | if (!cancel && NS_SUCCEEDED(rv)) { |
1648 | 0 | RefPtr<TransactionManager> transactionManager(mTransactionManager); |
1649 | 0 | for (uint32_t i = 0; i < aCount; ++i) { |
1650 | 0 | rv = transactionManager->Undo(); |
1651 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1652 | 0 | break; |
1653 | 0 | } |
1654 | 0 | DoAfterUndoTransaction(); |
1655 | 0 | } |
1656 | 0 | rv = rules->DidDoAction(selection, subActionInfo, rv); |
1657 | 0 | } |
1658 | 0 | } |
1659 | 0 |
|
1660 | 0 | NotifyEditorObservers(eNotifyEditorObserversOfEnd); |
1661 | 0 | return rv; |
1662 | 0 | } |
1663 | | |
1664 | | NS_IMETHODIMP |
1665 | | TextEditor::Redo(uint32_t aCount) |
1666 | 0 | { |
1667 | 0 | // If we don't have transaction in the redo stack, we shouldn't notify |
1668 | 0 | // anybody of trying to redo since it's not useful notification but we |
1669 | 0 | // need to pay some runtime cost. |
1670 | 0 | if (!CanRedo()) { |
1671 | 0 | return NS_OK; |
1672 | 0 | } |
1673 | 0 | |
1674 | 0 | // If there is composition, we shouldn't allow to redo with committing |
1675 | 0 | // composition since Chrome doesn't allow it and it doesn't make sense |
1676 | 0 | // because committing composition causes removing all transactions from |
1677 | 0 | // the redo queue. So, it becomes impossible to redo anything. |
1678 | 0 | if (GetComposition()) { |
1679 | 0 | return NS_OK; |
1680 | 0 | } |
1681 | 0 | |
1682 | 0 | // Protect the edit rules object from dying. |
1683 | 0 | RefPtr<TextEditRules> rules(mRules); |
1684 | 0 |
|
1685 | 0 | AutoUpdateViewBatch beginViewBatching(this); |
1686 | 0 |
|
1687 | 0 | NotifyEditorObservers(eNotifyEditorObserversOfBefore); |
1688 | 0 | if (NS_WARN_IF(!CanRedo()) || NS_WARN_IF(Destroyed())) { |
1689 | 0 | return NS_ERROR_FAILURE; |
1690 | 0 | } |
1691 | 0 | |
1692 | 0 | nsresult rv; |
1693 | 0 | { |
1694 | 0 | AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction( |
1695 | 0 | *this, EditSubAction::eRedo, |
1696 | 0 | nsIEditor::eNone); |
1697 | 0 |
|
1698 | 0 | EditSubActionInfo subActionInfo(EditSubAction::eRedo); |
1699 | 0 | RefPtr<Selection> selection = GetSelection(); |
1700 | 0 | bool cancel, handled; |
1701 | 0 | rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled); |
1702 | 0 | if (!cancel && NS_SUCCEEDED(rv)) { |
1703 | 0 | RefPtr<TransactionManager> transactionManager(mTransactionManager); |
1704 | 0 | for (uint32_t i = 0; i < aCount; ++i) { |
1705 | 0 | nsresult rv = transactionManager->Redo(); |
1706 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1707 | 0 | break; |
1708 | 0 | } |
1709 | 0 | DoAfterRedoTransaction(); |
1710 | 0 | } |
1711 | 0 | rv = rules->DidDoAction(selection, subActionInfo, rv); |
1712 | 0 | } |
1713 | 0 | } |
1714 | 0 |
|
1715 | 0 | NotifyEditorObservers(eNotifyEditorObserversOfEnd); |
1716 | 0 | return rv; |
1717 | 0 | } |
1718 | | |
1719 | | bool |
1720 | | TextEditor::CanCutOrCopy(PasswordFieldAllowed aPasswordFieldAllowed) |
1721 | 0 | { |
1722 | 0 | RefPtr<Selection> selection = GetSelection(); |
1723 | 0 | if (!selection) { |
1724 | 0 | return false; |
1725 | 0 | } |
1726 | 0 | |
1727 | 0 | if (aPasswordFieldAllowed == ePasswordFieldNotAllowed && |
1728 | 0 | IsPasswordEditor()) { |
1729 | 0 | return false; |
1730 | 0 | } |
1731 | 0 | |
1732 | 0 | return !selection->IsCollapsed(); |
1733 | 0 | } |
1734 | | |
1735 | | bool |
1736 | | TextEditor::FireClipboardEvent(EventMessage aEventMessage, |
1737 | | int32_t aSelectionType, |
1738 | | bool* aActionTaken) |
1739 | 0 | { |
1740 | 0 | if (aEventMessage == ePaste) { |
1741 | 0 | CommitComposition(); |
1742 | 0 | } |
1743 | 0 |
|
1744 | 0 | nsCOMPtr<nsIPresShell> presShell = GetPresShell(); |
1745 | 0 | NS_ENSURE_TRUE(presShell, false); |
1746 | 0 |
|
1747 | 0 | RefPtr<Selection> selection = GetSelection(); |
1748 | 0 | if (!selection) { |
1749 | 0 | return false; |
1750 | 0 | } |
1751 | 0 | |
1752 | 0 | if (!nsCopySupport::FireClipboardEvent(aEventMessage, aSelectionType, |
1753 | 0 | presShell, selection, aActionTaken)) { |
1754 | 0 | return false; |
1755 | 0 | } |
1756 | 0 | |
1757 | 0 | // If the event handler caused the editor to be destroyed, return false. |
1758 | 0 | // Otherwise return true to indicate that the event was not cancelled. |
1759 | 0 | return !mDidPreDestroy; |
1760 | 0 | } |
1761 | | |
1762 | | NS_IMETHODIMP |
1763 | | TextEditor::Cut() |
1764 | 0 | { |
1765 | 0 | bool actionTaken = false; |
1766 | 0 | if (FireClipboardEvent(eCut, nsIClipboard::kGlobalClipboard, &actionTaken)) { |
1767 | 0 | // XXX This transaction name is referred by PlaceholderTransaction::Merge() |
1768 | 0 | // so that we need to keep using it here. |
1769 | 0 | AutoPlaceholderBatch batch(this, nsGkAtoms::DeleteTxnName); |
1770 | 0 | DeleteSelectionAsSubAction(eNone, eStrip); |
1771 | 0 | } |
1772 | 0 | return actionTaken ? NS_OK : NS_ERROR_FAILURE; |
1773 | 0 | } |
1774 | | |
1775 | | NS_IMETHODIMP |
1776 | | TextEditor::CanCut(bool* aCanCut) |
1777 | 0 | { |
1778 | 0 | NS_ENSURE_ARG_POINTER(aCanCut); |
1779 | 0 | // Cut is always enabled in HTML documents |
1780 | 0 | nsCOMPtr<nsIDocument> doc = GetDocument(); |
1781 | 0 | *aCanCut = (doc && doc->IsHTMLOrXHTML()) || |
1782 | 0 | (IsModifiable() && CanCutOrCopy(ePasswordFieldNotAllowed)); |
1783 | 0 | return NS_OK; |
1784 | 0 | } |
1785 | | |
1786 | | NS_IMETHODIMP |
1787 | | TextEditor::Copy() |
1788 | 0 | { |
1789 | 0 | bool actionTaken = false; |
1790 | 0 | FireClipboardEvent(eCopy, nsIClipboard::kGlobalClipboard, &actionTaken); |
1791 | 0 |
|
1792 | 0 | return actionTaken ? NS_OK : NS_ERROR_FAILURE; |
1793 | 0 | } |
1794 | | |
1795 | | NS_IMETHODIMP |
1796 | | TextEditor::CanCopy(bool* aCanCopy) |
1797 | 0 | { |
1798 | 0 | NS_ENSURE_ARG_POINTER(aCanCopy); |
1799 | 0 | // Copy is always enabled in HTML documents |
1800 | 0 | nsCOMPtr<nsIDocument> doc = GetDocument(); |
1801 | 0 | *aCanCopy = (doc && doc->IsHTMLOrXHTML()) || |
1802 | 0 | CanCutOrCopy(ePasswordFieldNotAllowed); |
1803 | 0 | return NS_OK; |
1804 | 0 | } |
1805 | | |
1806 | | NS_IMETHODIMP |
1807 | | TextEditor::CanDelete(bool* aCanDelete) |
1808 | 0 | { |
1809 | 0 | NS_ENSURE_ARG_POINTER(aCanDelete); |
1810 | 0 | *aCanDelete = IsModifiable() && CanCutOrCopy(ePasswordFieldAllowed); |
1811 | 0 | return NS_OK; |
1812 | 0 | } |
1813 | | |
1814 | | already_AddRefed<nsIDocumentEncoder> |
1815 | | TextEditor::GetAndInitDocEncoder(const nsAString& aFormatType, |
1816 | | uint32_t aDocumentEncoderFlags, |
1817 | | const nsACString& aCharset) const |
1818 | 0 | { |
1819 | 0 | nsCOMPtr<nsIDocumentEncoder> docEncoder; |
1820 | 0 | if (!mCachedDocumentEncoder || |
1821 | 0 | !mCachedDocumentEncoderType.Equals(aFormatType)) { |
1822 | 0 | nsAutoCString formatType(NS_DOC_ENCODER_CONTRACTID_BASE); |
1823 | 0 | LossyAppendUTF16toASCII(aFormatType, formatType); |
1824 | 0 | docEncoder = do_CreateInstance(formatType.get()); |
1825 | 0 | if (NS_WARN_IF(!docEncoder)) { |
1826 | 0 | return nullptr; |
1827 | 0 | } |
1828 | 0 | mCachedDocumentEncoder = docEncoder; |
1829 | 0 | mCachedDocumentEncoderType = aFormatType; |
1830 | 0 | } else { |
1831 | 0 | docEncoder = mCachedDocumentEncoder; |
1832 | 0 | } |
1833 | 0 |
|
1834 | 0 | nsCOMPtr<nsIDocument> doc = GetDocument(); |
1835 | 0 | NS_ASSERTION(doc, "Need a document"); |
1836 | 0 |
|
1837 | 0 | nsresult rv = |
1838 | 0 | docEncoder->NativeInit( |
1839 | 0 | doc, aFormatType, |
1840 | 0 | aDocumentEncoderFlags | |
1841 | 0 | nsIDocumentEncoder::RequiresReinitAfterOutput); |
1842 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1843 | 0 | return nullptr; |
1844 | 0 | } |
1845 | 0 | |
1846 | 0 | if (!aCharset.IsEmpty() && !aCharset.EqualsLiteral("null")) { |
1847 | 0 | docEncoder->SetCharset(aCharset); |
1848 | 0 | } |
1849 | 0 |
|
1850 | 0 | int32_t wrapWidth = WrapWidth(); |
1851 | 0 | if (wrapWidth >= 0) { |
1852 | 0 | Unused << docEncoder->SetWrapColumn(wrapWidth); |
1853 | 0 | } |
1854 | 0 |
|
1855 | 0 | // Set the selection, if appropriate. |
1856 | 0 | // We do this either if the OutputSelectionOnly flag is set, |
1857 | 0 | // in which case we use our existing selection ... |
1858 | 0 | if (aDocumentEncoderFlags & nsIDocumentEncoder::OutputSelectionOnly) { |
1859 | 0 | RefPtr<Selection> selection = GetSelection(); |
1860 | 0 | if (NS_WARN_IF(!selection)) { |
1861 | 0 | return nullptr; |
1862 | 0 | } |
1863 | 0 | rv = docEncoder->SetSelection(selection); |
1864 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1865 | 0 | return nullptr; |
1866 | 0 | } |
1867 | 0 | } |
1868 | 0 | // ... or if the root element is not a body, |
1869 | 0 | // in which case we set the selection to encompass the root. |
1870 | 0 | else { |
1871 | 0 | dom::Element* rootElement = GetRoot(); |
1872 | 0 | if (NS_WARN_IF(!rootElement)) { |
1873 | 0 | return nullptr; |
1874 | 0 | } |
1875 | 0 | if (!rootElement->IsHTMLElement(nsGkAtoms::body)) { |
1876 | 0 | rv = docEncoder->SetContainerNode(rootElement); |
1877 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1878 | 0 | return nullptr; |
1879 | 0 | } |
1880 | 0 | } |
1881 | 0 | } |
1882 | 0 | |
1883 | 0 | return docEncoder.forget(); |
1884 | 0 | } |
1885 | | |
1886 | | NS_IMETHODIMP |
1887 | | TextEditor::OutputToString(const nsAString& aFormatType, |
1888 | | uint32_t aDocumentEncoderFlags, |
1889 | | nsAString& aOutputString) |
1890 | 0 | { |
1891 | 0 | return ComputeValueInternal(aFormatType, aDocumentEncoderFlags, |
1892 | 0 | aOutputString); |
1893 | 0 | } |
1894 | | |
1895 | | nsresult |
1896 | | TextEditor::ComputeValueInternal(const nsAString& aFormatType, |
1897 | | uint32_t aDocumentEncoderFlags, |
1898 | | nsAString& aOutputString) const |
1899 | 0 | { |
1900 | 0 | // Protect the edit rules object from dying |
1901 | 0 | RefPtr<TextEditRules> rules(mRules); |
1902 | 0 |
|
1903 | 0 | EditSubActionInfo subActionInfo(EditSubAction::eComputeTextToOutput); |
1904 | 0 | subActionInfo.outString = &aOutputString; |
1905 | 0 | subActionInfo.flags = aDocumentEncoderFlags; |
1906 | 0 | subActionInfo.outputFormat = &aFormatType; |
1907 | 0 | Selection* selection = GetSelection(); |
1908 | 0 | if (NS_WARN_IF(!selection)) { |
1909 | 0 | return NS_ERROR_FAILURE; |
1910 | 0 | } |
1911 | 0 | bool cancel, handled; |
1912 | 0 | nsresult rv = |
1913 | 0 | rules->WillDoAction(selection, subActionInfo, &cancel, &handled); |
1914 | 0 | if (cancel || NS_FAILED(rv)) { |
1915 | 0 | return rv; |
1916 | 0 | } |
1917 | 0 | if (handled) { |
1918 | 0 | // This case will get triggered by password fields or single text node only. |
1919 | 0 | return rv; |
1920 | 0 | } |
1921 | 0 | |
1922 | 0 | nsAutoCString charset; |
1923 | 0 | rv = GetDocumentCharsetInternal(charset); |
1924 | 0 | if (NS_FAILED(rv) || charset.IsEmpty()) { |
1925 | 0 | charset.AssignLiteral("windows-1252"); |
1926 | 0 | } |
1927 | 0 |
|
1928 | 0 | nsCOMPtr<nsIDocumentEncoder> encoder = |
1929 | 0 | GetAndInitDocEncoder(aFormatType, aDocumentEncoderFlags, charset); |
1930 | 0 | if (NS_WARN_IF(!encoder)) { |
1931 | 0 | return NS_ERROR_FAILURE; |
1932 | 0 | } |
1933 | 0 | |
1934 | 0 | // XXX Why don't we call TextEditRules::DidDoAction() here? |
1935 | 0 | return encoder->EncodeToString(aOutputString); |
1936 | 0 | } |
1937 | | |
1938 | | nsresult |
1939 | | TextEditor::PasteAsQuotationAsAction(int32_t aClipboardType) |
1940 | 0 | { |
1941 | 0 | MOZ_ASSERT(aClipboardType == nsIClipboard::kGlobalClipboard || |
1942 | 0 | aClipboardType == nsIClipboard::kSelectionClipboard); |
1943 | 0 |
|
1944 | 0 | // Get Clipboard Service |
1945 | 0 | nsresult rv; |
1946 | 0 | nsCOMPtr<nsIClipboard> clipboard = |
1947 | 0 | do_GetService("@mozilla.org/widget/clipboard;1", &rv); |
1948 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1949 | 0 | return rv; |
1950 | 0 | } |
1951 | 0 | |
1952 | 0 | // Get the nsITransferable interface for getting the data from the clipboard |
1953 | 0 | nsCOMPtr<nsITransferable> trans; |
1954 | 0 | rv = PrepareTransferable(getter_AddRefs(trans)); |
1955 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1956 | 0 | return rv; |
1957 | 0 | } |
1958 | 0 | if (!trans) { |
1959 | 0 | return NS_OK; |
1960 | 0 | } |
1961 | 0 | |
1962 | 0 | // Get the Data from the clipboard |
1963 | 0 | clipboard->GetData(trans, aClipboardType); |
1964 | 0 |
|
1965 | 0 | // Now we ask the transferable for the data |
1966 | 0 | // it still owns the data, we just have a pointer to it. |
1967 | 0 | // If it can't support a "text" output of the data the call will fail |
1968 | 0 | nsCOMPtr<nsISupports> genericDataObj; |
1969 | 0 | uint32_t len; |
1970 | 0 | nsAutoCString flav; |
1971 | 0 | rv = trans->GetAnyTransferData(flav, getter_AddRefs(genericDataObj), |
1972 | 0 | &len); |
1973 | 0 | if (NS_FAILED(rv)) { |
1974 | 0 | return rv; |
1975 | 0 | } |
1976 | 0 | |
1977 | 0 | if (!flav.EqualsLiteral(kUnicodeMime) && |
1978 | 0 | !flav.EqualsLiteral(kMozTextInternal)) { |
1979 | 0 | return NS_OK; |
1980 | 0 | } |
1981 | 0 | |
1982 | 0 | nsCOMPtr<nsISupportsString> textDataObj = do_QueryInterface(genericDataObj); |
1983 | 0 | if (textDataObj && len > 0) { |
1984 | 0 | nsAutoString stuffToPaste; |
1985 | 0 | textDataObj->GetData ( stuffToPaste ); |
1986 | 0 | AutoPlaceholderBatch beginBatching(this); |
1987 | 0 | rv = InsertWithQuotationsAsSubAction(stuffToPaste); |
1988 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1989 | 0 | return rv; |
1990 | 0 | } |
1991 | 0 | } |
1992 | 0 | return NS_OK; |
1993 | 0 | } |
1994 | | |
1995 | | nsresult |
1996 | | TextEditor::InsertWithQuotationsAsSubAction(const nsAString& aQuotedText) |
1997 | 0 | { |
1998 | 0 | // Protect the edit rules object from dying |
1999 | 0 | RefPtr<TextEditRules> rules(mRules); |
2000 | 0 |
|
2001 | 0 | // Let the citer quote it for us: |
2002 | 0 | nsString quotedStuff; |
2003 | 0 | nsresult rv = InternetCiter::GetCiteString(aQuotedText, quotedStuff); |
2004 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2005 | 0 | return rv; |
2006 | 0 | } |
2007 | 0 | |
2008 | 0 | // It's best to put a blank line after the quoted text so that mails |
2009 | 0 | // written without thinking won't be so ugly. |
2010 | 0 | if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != char16_t('\n'))) { |
2011 | 0 | quotedStuff.Append(char16_t('\n')); |
2012 | 0 | } |
2013 | 0 |
|
2014 | 0 | RefPtr<Selection> selection = GetSelection(); |
2015 | 0 | if (NS_WARN_IF(!selection)) { |
2016 | 0 | return NS_ERROR_FAILURE; |
2017 | 0 | } |
2018 | 0 | |
2019 | 0 | AutoTopLevelEditSubActionNotifier maybeTopLevelEditSubAction( |
2020 | 0 | *this, EditSubAction::eInsertText, |
2021 | 0 | nsIEditor::eNext); |
2022 | 0 |
|
2023 | 0 | // XXX This WillDoAction() usage is hacky. If it returns as handled, |
2024 | 0 | // this method cannot work as expected. So, this should have specific |
2025 | 0 | // sub-action rather than using eInsertElement. |
2026 | 0 | EditSubActionInfo subActionInfo(EditSubAction::eInsertElement); |
2027 | 0 | bool cancel, handled; |
2028 | 0 | rv = rules->WillDoAction(selection, subActionInfo, &cancel, &handled); |
2029 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2030 | 0 | return rv; |
2031 | 0 | } |
2032 | 0 | if (cancel) { |
2033 | 0 | return NS_OK; // Rules canceled the operation. |
2034 | 0 | } |
2035 | 0 | MOZ_ASSERT(!handled, "WillDoAction() shouldn't handle in this case"); |
2036 | 0 | if (!handled) { |
2037 | 0 | rv = InsertTextAsSubAction(quotedStuff); |
2038 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2039 | 0 | return rv; |
2040 | 0 | } |
2041 | 0 | } |
2042 | 0 | // XXX Why don't we call TextEditRules::DidDoAction()? |
2043 | 0 | return NS_OK; |
2044 | 0 | } |
2045 | | |
2046 | | nsresult |
2047 | | TextEditor::SharedOutputString(uint32_t aFlags, |
2048 | | bool* aIsCollapsed, |
2049 | | nsAString& aResult) |
2050 | 0 | { |
2051 | 0 | RefPtr<Selection> selection = GetSelection(); |
2052 | 0 | NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED); |
2053 | 0 |
|
2054 | 0 | *aIsCollapsed = selection->IsCollapsed(); |
2055 | 0 |
|
2056 | 0 | if (!*aIsCollapsed) { |
2057 | 0 | aFlags |= nsIDocumentEncoder::OutputSelectionOnly; |
2058 | 0 | } |
2059 | 0 | // If the selection isn't collapsed, we'll use the whole document. |
2060 | 0 | return ComputeValueInternal(NS_LITERAL_STRING("text/plain"), aFlags, aResult); |
2061 | 0 | } |
2062 | | |
2063 | | void |
2064 | | TextEditor::OnStartToHandleTopLevelEditSubAction( |
2065 | | EditSubAction aEditSubAction, |
2066 | | nsIEditor::EDirection aDirection) |
2067 | 0 | { |
2068 | 0 | // Protect the edit rules object from dying |
2069 | 0 | RefPtr<TextEditRules> rules(mRules); |
2070 | 0 |
|
2071 | 0 | EditorBase::OnStartToHandleTopLevelEditSubAction(aEditSubAction, aDirection); |
2072 | 0 | if (!rules) { |
2073 | 0 | return; |
2074 | 0 | } |
2075 | 0 | |
2076 | 0 | MOZ_ASSERT(mTopLevelEditSubAction == aEditSubAction); |
2077 | 0 | MOZ_ASSERT(mDirection == aDirection); |
2078 | 0 | DebugOnly<nsresult> rv = |
2079 | 0 | rules->BeforeEdit(mTopLevelEditSubAction, mDirection); |
2080 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
2081 | 0 | "TextEditRules::BeforeEdit() failed to handle something"); |
2082 | 0 | } |
2083 | | |
2084 | | void |
2085 | | TextEditor::OnEndHandlingTopLevelEditSubAction() |
2086 | 0 | { |
2087 | 0 | // Protect the edit rules object from dying |
2088 | 0 | RefPtr<TextEditRules> rules(mRules); |
2089 | 0 |
|
2090 | 0 | // post processing |
2091 | 0 | DebugOnly<nsresult> rv = |
2092 | 0 | rules ? rules->AfterEdit(mTopLevelEditSubAction, mDirection) : NS_OK; |
2093 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
2094 | 0 | "TextEditRules::AfterEdit() failed to handle something"); |
2095 | 0 | EditorBase::OnEndHandlingTopLevelEditSubAction(); |
2096 | 0 | MOZ_ASSERT(!mTopLevelEditSubAction); |
2097 | 0 | MOZ_ASSERT(mDirection == eNone); |
2098 | 0 | } |
2099 | | |
2100 | | nsresult |
2101 | | TextEditor::SelectEntireDocument(Selection* aSelection) |
2102 | 0 | { |
2103 | 0 | if (!aSelection || !mRules) { |
2104 | 0 | return NS_ERROR_NULL_POINTER; |
2105 | 0 | } |
2106 | 0 | |
2107 | 0 | // Protect the edit rules object from dying |
2108 | 0 | RefPtr<TextEditRules> rules(mRules); |
2109 | 0 |
|
2110 | 0 | // is doc empty? |
2111 | 0 | if (rules->DocumentIsEmpty()) { |
2112 | 0 | // get root node |
2113 | 0 | Element* rootElement = GetRoot(); |
2114 | 0 | if (NS_WARN_IF(!rootElement)) { |
2115 | 0 | return NS_ERROR_FAILURE; |
2116 | 0 | } |
2117 | 0 | |
2118 | 0 | // if it's empty don't select entire doc - that would select the bogus node |
2119 | 0 | return aSelection->Collapse(rootElement, 0); |
2120 | 0 | } |
2121 | 0 | |
2122 | 0 | SelectionBatcher selectionBatcher(aSelection); |
2123 | 0 | nsresult rv = EditorBase::SelectEntireDocument(aSelection); |
2124 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2125 | 0 |
|
2126 | 0 | // Don't select the trailing BR node if we have one |
2127 | 0 | nsCOMPtr<nsIContent> childNode; |
2128 | 0 | rv = GetEndChildNode(aSelection, getter_AddRefs(childNode)); |
2129 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2130 | 0 | return rv; |
2131 | 0 | } |
2132 | 0 | if (childNode) { |
2133 | 0 | childNode = childNode->GetPreviousSibling(); |
2134 | 0 | } |
2135 | 0 |
|
2136 | 0 | if (childNode && TextEditUtils::IsMozBR(childNode)) { |
2137 | 0 | int32_t parentOffset; |
2138 | 0 | nsINode* parentNode = GetNodeLocation(childNode, &parentOffset); |
2139 | 0 |
|
2140 | 0 | return aSelection->Extend(parentNode, parentOffset); |
2141 | 0 | } |
2142 | 0 | |
2143 | 0 | return NS_OK; |
2144 | 0 | } |
2145 | | |
2146 | | EventTarget* |
2147 | | TextEditor::GetDOMEventTarget() |
2148 | 0 | { |
2149 | 0 | return mEventTarget; |
2150 | 0 | } |
2151 | | |
2152 | | |
2153 | | nsresult |
2154 | | TextEditor::SetAttributeOrEquivalent(Element* aElement, |
2155 | | nsAtom* aAttribute, |
2156 | | const nsAString& aValue, |
2157 | | bool aSuppressTransaction) |
2158 | 0 | { |
2159 | 0 | if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) { |
2160 | 0 | return NS_ERROR_INVALID_ARG; |
2161 | 0 | } |
2162 | 0 | return SetAttributeWithTransaction(*aElement, *aAttribute, aValue); |
2163 | 0 | } |
2164 | | |
2165 | | nsresult |
2166 | | TextEditor::RemoveAttributeOrEquivalent(Element* aElement, |
2167 | | nsAtom* aAttribute, |
2168 | | bool aSuppressTransaction) |
2169 | 0 | { |
2170 | 0 | if (NS_WARN_IF(!aElement) || NS_WARN_IF(!aAttribute)) { |
2171 | 0 | return NS_ERROR_INVALID_ARG; |
2172 | 0 | } |
2173 | 0 | return RemoveAttributeWithTransaction(*aElement, *aAttribute); |
2174 | 0 | } |
2175 | | |
2176 | | } // namespace mozilla |