/src/mozilla-central/editor/libeditor/HTMLEditRules.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set ts=2 sw=2 et tw=79: */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "HTMLEditRules.h" |
8 | | |
9 | | #include <stdlib.h> |
10 | | |
11 | | #include "HTMLEditUtils.h" |
12 | | #include "TextEditUtils.h" |
13 | | #include "WSRunObject.h" |
14 | | #include "mozilla/Assertions.h" |
15 | | #include "mozilla/CSSEditUtils.h" |
16 | | #include "mozilla/EditAction.h" |
17 | | #include "mozilla/EditorDOMPoint.h" |
18 | | #include "mozilla/EditorUtils.h" |
19 | | #include "mozilla/HTMLEditor.h" |
20 | | #include "mozilla/MathAlgorithms.h" |
21 | | #include "mozilla/Move.h" |
22 | | #include "mozilla/Preferences.h" |
23 | | #include "mozilla/UniquePtr.h" |
24 | | #include "mozilla/Unused.h" |
25 | | #include "mozilla/dom/Selection.h" |
26 | | #include "mozilla/dom/Element.h" |
27 | | #include "mozilla/dom/RangeBinding.h" |
28 | | #include "mozilla/OwningNonNull.h" |
29 | | #include "mozilla/mozalloc.h" |
30 | | #include "nsAString.h" |
31 | | #include "nsAlgorithm.h" |
32 | | #include "nsCRT.h" |
33 | | #include "nsCRTGlue.h" |
34 | | #include "nsComponentManagerUtils.h" |
35 | | #include "nsContentUtils.h" |
36 | | #include "nsDebug.h" |
37 | | #include "nsError.h" |
38 | | #include "nsGkAtoms.h" |
39 | | #include "nsAtom.h" |
40 | | #include "nsHTMLDocument.h" |
41 | | #include "nsIContent.h" |
42 | | #include "nsIContentIterator.h" |
43 | | #include "nsID.h" |
44 | | #include "nsIFrame.h" |
45 | | #include "nsIHTMLAbsPosEditor.h" |
46 | | #include "nsINode.h" |
47 | | #include "nsLiteralString.h" |
48 | | #include "nsRange.h" |
49 | | #include "nsReadableUtils.h" |
50 | | #include "nsString.h" |
51 | | #include "nsStringFwd.h" |
52 | | #include "nsTArray.h" |
53 | | #include "nsTextNode.h" |
54 | | #include "nsThreadUtils.h" |
55 | | #include "nsUnicharUtils.h" |
56 | | #include <algorithm> |
57 | | |
58 | | // Workaround for windows headers |
59 | | #ifdef SetProp |
60 | | #undef SetProp |
61 | | #endif |
62 | | |
63 | | class nsISupports; |
64 | | |
65 | | namespace mozilla { |
66 | | |
67 | | using namespace dom; |
68 | | |
69 | | //const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE"; |
70 | | //const static char* kMOZEditorBogusNodeValue="TRUE"; |
71 | | |
72 | | enum |
73 | | { |
74 | | kLonely = 0, |
75 | | kPrevSib = 1, |
76 | | kNextSib = 2, |
77 | | kBothSibs = 3 |
78 | | }; |
79 | | |
80 | | /******************************************************** |
81 | | * first some helpful functors we will use |
82 | | ********************************************************/ |
83 | | |
84 | | static bool |
85 | | IsStyleCachePreservingSubAction(EditSubAction aEditSubAction) |
86 | 0 | { |
87 | 0 | return aEditSubAction == EditSubAction::eDeleteSelectedContent || |
88 | 0 | aEditSubAction == EditSubAction::eInsertParagraphSeparator || |
89 | 0 | aEditSubAction == EditSubAction::eCreateOrChangeList || |
90 | 0 | aEditSubAction == EditSubAction::eIndent || |
91 | 0 | aEditSubAction == EditSubAction::eOutdent || |
92 | 0 | aEditSubAction == EditSubAction::eSetOrClearAlignment || |
93 | 0 | aEditSubAction == EditSubAction::eCreateOrRemoveBlock || |
94 | 0 | aEditSubAction == EditSubAction::eRemoveList || |
95 | 0 | aEditSubAction == EditSubAction::eCreateOrChangeDefinitionList || |
96 | 0 | aEditSubAction == EditSubAction::eInsertElement || |
97 | 0 | aEditSubAction == EditSubAction::eInsertQuotation; |
98 | 0 | } |
99 | | |
100 | | static nsAtom& |
101 | | ParagraphSeparatorElement(ParagraphSeparator separator) |
102 | 0 | { |
103 | 0 | switch (separator) { |
104 | 0 | default: |
105 | 0 | MOZ_FALLTHROUGH_ASSERT("Unexpected paragraph separator!"); |
106 | 0 |
|
107 | 0 | case ParagraphSeparator::div: |
108 | 0 | return *nsGkAtoms::div; |
109 | 0 |
|
110 | 0 | case ParagraphSeparator::p: |
111 | 0 | return *nsGkAtoms::p; |
112 | 0 |
|
113 | 0 | case ParagraphSeparator::br: |
114 | 0 | return *nsGkAtoms::br; |
115 | 0 | } |
116 | 0 | } |
117 | | |
118 | | class TableCellAndListItemFunctor final : public BoolDomIterFunctor |
119 | | { |
120 | | public: |
121 | | // Used to build list of all li's, td's & th's iterator covers |
122 | | virtual bool operator()(nsINode* aNode) const override |
123 | 0 | { |
124 | 0 | return HTMLEditUtils::IsTableCell(aNode) || |
125 | 0 | HTMLEditUtils::IsListItem(aNode); |
126 | 0 | } |
127 | | }; |
128 | | |
129 | | class BRNodeFunctor final : public BoolDomIterFunctor |
130 | | { |
131 | | public: |
132 | | virtual bool operator()(nsINode* aNode) const override |
133 | 0 | { |
134 | 0 | return aNode->IsHTMLElement(nsGkAtoms::br); |
135 | 0 | } |
136 | | }; |
137 | | |
138 | | class EmptyEditableFunctor final : public BoolDomIterFunctor |
139 | | { |
140 | | public: |
141 | | explicit EmptyEditableFunctor(HTMLEditor* aHTMLEditor) |
142 | | : mHTMLEditor(aHTMLEditor) |
143 | 0 | {} |
144 | | |
145 | | virtual bool operator()(nsINode* aNode) const override |
146 | 0 | { |
147 | 0 | if (mHTMLEditor->IsEditable(aNode) && |
148 | 0 | (HTMLEditUtils::IsListItem(aNode) || |
149 | 0 | HTMLEditUtils::IsTableCellOrCaption(*aNode))) { |
150 | 0 | bool bIsEmptyNode; |
151 | 0 | nsresult rv = |
152 | 0 | mHTMLEditor->IsEmptyNode(aNode, &bIsEmptyNode, false, false); |
153 | 0 | NS_ENSURE_SUCCESS(rv, false); |
154 | 0 | if (bIsEmptyNode) { |
155 | 0 | return true; |
156 | 0 | } |
157 | 0 | } |
158 | 0 | return false; |
159 | 0 | } |
160 | | |
161 | | protected: |
162 | | HTMLEditor* mHTMLEditor; |
163 | | }; |
164 | | |
165 | | /******************************************************** |
166 | | * mozilla::HTMLEditRules |
167 | | ********************************************************/ |
168 | | |
169 | | HTMLEditRules::HTMLEditRules() |
170 | | : mHTMLEditor(nullptr) |
171 | | , mListenerEnabled(false) |
172 | | , mReturnInEmptyLIKillsList(false) |
173 | | , mDidDeleteSelection(false) |
174 | | , mDidRangedDelete(false) |
175 | | , mRestoreContentEditableCount(false) |
176 | | , mJoinOffset(0) |
177 | 0 | { |
178 | 0 | mIsHTMLEditRules = true; |
179 | 0 | InitFields(); |
180 | 0 | } |
181 | | |
182 | | void |
183 | | HTMLEditRules::InitFields() |
184 | 0 | { |
185 | 0 | mHTMLEditor = nullptr; |
186 | 0 | mDocChangeRange = nullptr; |
187 | 0 | mReturnInEmptyLIKillsList = true; |
188 | 0 | mDidDeleteSelection = false; |
189 | 0 | mDidRangedDelete = false; |
190 | 0 | mRestoreContentEditableCount = false; |
191 | 0 | mUtilRange = nullptr; |
192 | 0 | mJoinOffset = 0; |
193 | 0 | mNewBlock = nullptr; |
194 | 0 | mRangeItem = new RangeItem(); |
195 | 0 |
|
196 | 0 | InitStyleCacheArray(mCachedStyles); |
197 | 0 | } |
198 | | |
199 | | void |
200 | | HTMLEditRules::InitStyleCacheArray(StyleCache aStyleCache[SIZE_STYLE_TABLE]) |
201 | 0 | { |
202 | 0 | aStyleCache[0] = StyleCache(nsGkAtoms::b, nullptr); |
203 | 0 | aStyleCache[1] = StyleCache(nsGkAtoms::i, nullptr); |
204 | 0 | aStyleCache[2] = StyleCache(nsGkAtoms::u, nullptr); |
205 | 0 | aStyleCache[3] = StyleCache(nsGkAtoms::font, nsGkAtoms::face); |
206 | 0 | aStyleCache[4] = StyleCache(nsGkAtoms::font, nsGkAtoms::size); |
207 | 0 | aStyleCache[5] = StyleCache(nsGkAtoms::font, nsGkAtoms::color); |
208 | 0 | aStyleCache[6] = StyleCache(nsGkAtoms::tt, nullptr); |
209 | 0 | aStyleCache[7] = StyleCache(nsGkAtoms::em, nullptr); |
210 | 0 | aStyleCache[8] = StyleCache(nsGkAtoms::strong, nullptr); |
211 | 0 | aStyleCache[9] = StyleCache(nsGkAtoms::dfn, nullptr); |
212 | 0 | aStyleCache[10] = StyleCache(nsGkAtoms::code, nullptr); |
213 | 0 | aStyleCache[11] = StyleCache(nsGkAtoms::samp, nullptr); |
214 | 0 | aStyleCache[12] = StyleCache(nsGkAtoms::var, nullptr); |
215 | 0 | aStyleCache[13] = StyleCache(nsGkAtoms::cite, nullptr); |
216 | 0 | aStyleCache[14] = StyleCache(nsGkAtoms::abbr, nullptr); |
217 | 0 | aStyleCache[15] = StyleCache(nsGkAtoms::acronym, nullptr); |
218 | 0 | aStyleCache[16] = StyleCache(nsGkAtoms::backgroundColor, nullptr); |
219 | 0 | aStyleCache[17] = StyleCache(nsGkAtoms::sub, nullptr); |
220 | 0 | aStyleCache[18] = StyleCache(nsGkAtoms::sup, nullptr); |
221 | 0 | } |
222 | | |
223 | | HTMLEditRules::~HTMLEditRules() |
224 | 0 | { |
225 | 0 | } |
226 | | |
227 | | NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLEditRules, TextEditRules) |
228 | | |
229 | | NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLEditRules, TextEditRules, |
230 | | mDocChangeRange, mUtilRange, mNewBlock, |
231 | | mRangeItem) |
232 | | |
233 | | nsresult |
234 | | HTMLEditRules::Init(TextEditor* aTextEditor) |
235 | 0 | { |
236 | 0 | if (NS_WARN_IF(!aTextEditor) || |
237 | 0 | NS_WARN_IF(!aTextEditor->AsHTMLEditor())) { |
238 | 0 | return NS_ERROR_INVALID_ARG; |
239 | 0 | } |
240 | 0 | |
241 | 0 | InitFields(); |
242 | 0 |
|
243 | 0 | mHTMLEditor = aTextEditor->AsHTMLEditor(); |
244 | 0 | if (NS_WARN_IF(!mHTMLEditor)) { |
245 | 0 | return NS_ERROR_FAILURE; |
246 | 0 | } |
247 | 0 | Selection* selection = aTextEditor->GetSelection(); |
248 | 0 | if (NS_WARN_IF(!selection)) { |
249 | 0 | return NS_ERROR_FAILURE; |
250 | 0 | } |
251 | 0 | |
252 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *selection); |
253 | 0 |
|
254 | 0 | nsresult rv = TextEditRules::Init(aTextEditor); |
255 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
256 | 0 | return rv; |
257 | 0 | } |
258 | 0 | |
259 | 0 | if (NS_WARN_IF(!mHTMLEditor)) { |
260 | 0 | return NS_ERROR_FAILURE; |
261 | 0 | } |
262 | 0 | |
263 | 0 | // cache any prefs we care about |
264 | 0 | static const char kPrefName[] = |
265 | 0 | "editor.html.typing.returnInEmptyListItemClosesList"; |
266 | 0 | nsAutoCString returnInEmptyLIKillsList; |
267 | 0 | Preferences::GetCString(kPrefName, returnInEmptyLIKillsList); |
268 | 0 |
|
269 | 0 | // only when "false", becomes FALSE. Otherwise (including empty), TRUE. |
270 | 0 | // XXX Why was this pref designed as a string and not bool? |
271 | 0 | mReturnInEmptyLIKillsList = !returnInEmptyLIKillsList.EqualsLiteral("false"); |
272 | 0 |
|
273 | 0 | // make a utility range for use by the listenter |
274 | 0 | nsCOMPtr<nsINode> node = HTMLEditorRef().GetRoot(); |
275 | 0 | if (!node) { |
276 | 0 | node = HTMLEditorRef().GetDocument(); |
277 | 0 | if (NS_WARN_IF(!node)) { |
278 | 0 | return NS_ERROR_FAILURE; |
279 | 0 | } |
280 | 0 | } |
281 | 0 | |
282 | 0 | mUtilRange = new nsRange(node); |
283 | 0 |
|
284 | 0 | // set up mDocChangeRange to be whole doc |
285 | 0 | // temporarily turn off rules sniffing |
286 | 0 | AutoLockRulesSniffing lockIt(this); |
287 | 0 | if (!mDocChangeRange) { |
288 | 0 | mDocChangeRange = new nsRange(node); |
289 | 0 | } |
290 | 0 |
|
291 | 0 | if (node->IsElement()) { |
292 | 0 | ErrorResult error; |
293 | 0 | mDocChangeRange->SelectNode(*node, error); |
294 | 0 | if (NS_WARN_IF(error.Failed())) { |
295 | 0 | return error.StealNSResult(); |
296 | 0 | } |
297 | 0 | nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInChangedRange(); |
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 insert <br> elements to empty list items and table cells"); |
303 | 0 | } |
304 | 0 |
|
305 | 0 | StartToListenToEditSubActions(); |
306 | 0 |
|
307 | 0 | return NS_OK; |
308 | 0 | } |
309 | | |
310 | | nsresult |
311 | | HTMLEditRules::DetachEditor() |
312 | 0 | { |
313 | 0 | EndListeningToEditSubActions(); |
314 | 0 | mHTMLEditor = nullptr; |
315 | 0 | return TextEditRules::DetachEditor(); |
316 | 0 | } |
317 | | |
318 | | nsresult |
319 | | HTMLEditRules::BeforeEdit(EditSubAction aEditSubAction, |
320 | | nsIEditor::EDirection aDirection) |
321 | 0 | { |
322 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
323 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
324 | 0 | } |
325 | 0 | |
326 | 0 | if (mLockRulesSniffing) { |
327 | 0 | return NS_OK; |
328 | 0 | } |
329 | 0 | |
330 | 0 | AutoLockRulesSniffing lockIt(this); |
331 | 0 | mDidExplicitlySetInterline = false; |
332 | 0 |
|
333 | 0 | if (!mActionNesting) { |
334 | 0 | mActionNesting++; |
335 | 0 |
|
336 | 0 | // Clear our flag about if just deleted a range |
337 | 0 | mDidRangedDelete = false; |
338 | 0 |
|
339 | 0 | Selection* selection = mHTMLEditor->GetSelection(); |
340 | 0 | if (NS_WARN_IF(!selection)) { |
341 | 0 | return NS_ERROR_FAILURE; |
342 | 0 | } |
343 | 0 | |
344 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *selection); |
345 | 0 |
|
346 | 0 | // Remember where our selection was before edit action took place: |
347 | 0 |
|
348 | 0 | // Get the selection location |
349 | 0 | if (!SelectionRef().RangeCount()) { |
350 | 0 | return NS_ERROR_UNEXPECTED; |
351 | 0 | } |
352 | 0 | mRangeItem->StoreRange(SelectionRef().GetRangeAt(0)); |
353 | 0 | nsCOMPtr<nsINode> selStartNode = mRangeItem->mStartContainer; |
354 | 0 | nsCOMPtr<nsINode> selEndNode = mRangeItem->mEndContainer; |
355 | 0 |
|
356 | 0 | // Register with range updater to track this as we perturb the doc |
357 | 0 | HTMLEditorRef().mRangeUpdater.RegisterRangeItem(mRangeItem); |
358 | 0 |
|
359 | 0 | // Clear deletion state bool |
360 | 0 | mDidDeleteSelection = false; |
361 | 0 |
|
362 | 0 | // Clear out mDocChangeRange and mUtilRange |
363 | 0 | if (mDocChangeRange) { |
364 | 0 | // Clear out our accounting of what changed |
365 | 0 | mDocChangeRange->Reset(); |
366 | 0 | } |
367 | 0 | if (mUtilRange) { |
368 | 0 | // Ditto for mUtilRange. |
369 | 0 | mUtilRange->Reset(); |
370 | 0 | } |
371 | 0 |
|
372 | 0 | // Remember current inline styles for deletion and normal insertion ops |
373 | 0 | if (aEditSubAction == EditSubAction::eInsertText || |
374 | 0 | aEditSubAction == EditSubAction::eInsertTextComingFromIME || |
375 | 0 | aEditSubAction == EditSubAction::eDeleteSelectedContent || |
376 | 0 | IsStyleCachePreservingSubAction(aEditSubAction)) { |
377 | 0 | nsCOMPtr<nsINode> selNode = |
378 | 0 | aDirection == nsIEditor::eNext ? selEndNode : selStartNode; |
379 | 0 | nsresult rv = CacheInlineStyles(selNode); |
380 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
381 | 0 | return rv; |
382 | 0 | } |
383 | 0 | } |
384 | 0 | |
385 | 0 | // Stabilize the document against contenteditable count changes |
386 | 0 | nsHTMLDocument* htmlDoc = HTMLEditorRef().GetHTMLDocument(); |
387 | 0 | if (NS_WARN_IF(!htmlDoc)) { |
388 | 0 | return NS_ERROR_FAILURE; |
389 | 0 | } |
390 | 0 | if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) { |
391 | 0 | htmlDoc->ChangeContentEditableCount(nullptr, +1); |
392 | 0 | mRestoreContentEditableCount = true; |
393 | 0 | } |
394 | 0 |
|
395 | 0 | // Check that selection is in subtree defined by body node |
396 | 0 | nsresult rv = ConfirmSelectionInBody(); |
397 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
398 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
399 | 0 | } |
400 | 0 | // Let rules remember the top level action |
401 | 0 | mTopLevelEditSubAction = aEditSubAction; |
402 | 0 | } |
403 | 0 | return NS_OK; |
404 | 0 | } |
405 | | |
406 | | |
407 | | nsresult |
408 | | HTMLEditRules::AfterEdit(EditSubAction aEditSubAction, |
409 | | nsIEditor::EDirection aDirection) |
410 | 0 | { |
411 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
412 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
413 | 0 | } |
414 | 0 | |
415 | 0 | if (mLockRulesSniffing) { |
416 | 0 | return NS_OK; |
417 | 0 | } |
418 | 0 | |
419 | 0 | AutoLockRulesSniffing lockIt(this); |
420 | 0 |
|
421 | 0 | MOZ_ASSERT(mActionNesting > 0); |
422 | 0 | nsresult rv = NS_OK; |
423 | 0 | mActionNesting--; |
424 | 0 | if (!mActionNesting) { |
425 | 0 | Selection* selection = mHTMLEditor->GetSelection(); |
426 | 0 | if (NS_WARN_IF(!selection)) { |
427 | 0 | return NS_ERROR_FAILURE; |
428 | 0 | } |
429 | 0 | |
430 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *selection); |
431 | 0 |
|
432 | 0 | // Do all the tricky stuff |
433 | 0 | rv = AfterEditInner(aEditSubAction, aDirection); |
434 | 0 | // Perhaps, we need to do the following jobs even if the editor has been |
435 | 0 | // destroyed since they adjust some states of HTML document but don't |
436 | 0 | // modify the DOM tree nor Selection. |
437 | 0 |
|
438 | 0 | // Free up selectionState range item |
439 | 0 | HTMLEditorRef().mRangeUpdater.DropRangeItem(mRangeItem); |
440 | 0 |
|
441 | 0 | // Reset the contenteditable count to its previous value |
442 | 0 | if (mRestoreContentEditableCount) { |
443 | 0 | nsHTMLDocument* htmlDoc = HTMLEditorRef().GetHTMLDocument(); |
444 | 0 | if (NS_WARN_IF(!htmlDoc)) { |
445 | 0 | return NS_ERROR_FAILURE; |
446 | 0 | } |
447 | 0 | if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) { |
448 | 0 | htmlDoc->ChangeContentEditableCount(nullptr, -1); |
449 | 0 | } |
450 | 0 | mRestoreContentEditableCount = false; |
451 | 0 | } |
452 | 0 | } |
453 | 0 |
|
454 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
455 | 0 | return rv; |
456 | 0 | } |
457 | 0 | return NS_OK; |
458 | 0 | } |
459 | | |
460 | | nsresult |
461 | | HTMLEditRules::AfterEditInner(EditSubAction aEditSubAction, |
462 | | nsIEditor::EDirection aDirection) |
463 | 0 | { |
464 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
465 | 0 |
|
466 | 0 | nsresult rv = ConfirmSelectionInBody(); |
467 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
468 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
469 | 0 | } |
470 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to normalize Selection"); |
471 | 0 | if (aEditSubAction == EditSubAction::eReplaceHeadWithHTMLSource || |
472 | 0 | aEditSubAction == EditSubAction::eCreateBogusNode) { |
473 | 0 | return NS_OK; |
474 | 0 | } |
475 | 0 | |
476 | 0 | nsCOMPtr<nsINode> rangeStartContainer, rangeEndContainer; |
477 | 0 | uint32_t rangeStartOffset = 0, rangeEndOffset = 0; |
478 | 0 | // do we have a real range to act on? |
479 | 0 | bool bDamagedRange = false; |
480 | 0 | if (mDocChangeRange) { |
481 | 0 | rangeStartContainer = mDocChangeRange->GetStartContainer(); |
482 | 0 | rangeEndContainer = mDocChangeRange->GetEndContainer(); |
483 | 0 | rangeStartOffset = mDocChangeRange->StartOffset(); |
484 | 0 | rangeEndOffset = mDocChangeRange->EndOffset(); |
485 | 0 | if (rangeStartContainer && rangeEndContainer) { |
486 | 0 | bDamagedRange = true; |
487 | 0 | } |
488 | 0 | } |
489 | 0 |
|
490 | 0 | if (bDamagedRange && !((aEditSubAction == EditSubAction::eUndo) || |
491 | 0 | (aEditSubAction == EditSubAction::eRedo))) { |
492 | 0 | // don't let any txns in here move the selection around behind our back. |
493 | 0 | // Note that this won't prevent explicit selection setting from working. |
494 | 0 | AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef()); |
495 | 0 |
|
496 | 0 | // expand the "changed doc range" as needed |
497 | 0 | PromoteRange(*mDocChangeRange, aEditSubAction); |
498 | 0 |
|
499 | 0 | // if we did a ranged deletion or handling backspace key, make sure we have |
500 | 0 | // a place to put caret. |
501 | 0 | // Note we only want to do this if the overall operation was deletion, |
502 | 0 | // not if deletion was done along the way for |
503 | 0 | // EditSubAction::eInsertHTMLSource, EditSubAction::eInsertText, etc. |
504 | 0 | // That's why this is here rather than DidDeleteSelection(). |
505 | 0 | if (aEditSubAction == EditSubAction::eDeleteSelectedContent && |
506 | 0 | mDidRangedDelete) { |
507 | 0 | nsresult rv = InsertBRIfNeeded(); |
508 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
509 | 0 | return rv; |
510 | 0 | } |
511 | 0 | } |
512 | 0 | |
513 | 0 | // add in any needed <br>s, and remove any unneeded ones. |
514 | 0 | nsresult rv = InsertBRElementToEmptyListItemsAndTableCellsInChangedRange(); |
515 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
516 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
517 | 0 | } |
518 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
519 | 0 | "Failed to insert <br> elements to empty list items and table cells"); |
520 | 0 |
|
521 | 0 | // merge any adjacent text nodes |
522 | 0 | if (aEditSubAction != EditSubAction::eInsertText && |
523 | 0 | aEditSubAction != EditSubAction::eInsertTextComingFromIME) { |
524 | 0 | nsresult rv = HTMLEditorRef().CollapseAdjacentTextNodes(mDocChangeRange); |
525 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
526 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
527 | 0 | } |
528 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
529 | 0 | return rv; |
530 | 0 | } |
531 | 0 | } |
532 | 0 | |
533 | 0 | // clean up any empty nodes in the selection |
534 | 0 | rv = RemoveEmptyNodesInChangedRange(); |
535 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
536 | 0 | return rv; |
537 | 0 | } |
538 | 0 | |
539 | 0 | // attempt to transform any unneeded nbsp's into spaces after doing various operations |
540 | 0 | if (aEditSubAction == EditSubAction::eInsertText || |
541 | 0 | aEditSubAction == EditSubAction::eInsertTextComingFromIME || |
542 | 0 | aEditSubAction == EditSubAction::eDeleteSelectedContent || |
543 | 0 | aEditSubAction == EditSubAction::eInsertParagraphSeparator || |
544 | 0 | aEditSubAction == EditSubAction::ePasteHTMLContent || |
545 | 0 | aEditSubAction == EditSubAction::eInsertHTMLSource) { |
546 | 0 | rv = AdjustWhitespace(); |
547 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
548 | 0 | return rv; |
549 | 0 | } |
550 | 0 | |
551 | 0 | // also do this for original selection endpoints. |
552 | 0 | NS_ENSURE_STATE(mRangeItem->mStartContainer); |
553 | 0 | NS_ENSURE_STATE(mRangeItem->mEndContainer); |
554 | 0 | WSRunObject(&HTMLEditorRef(), mRangeItem->mStartContainer, |
555 | 0 | mRangeItem->mStartOffset).AdjustWhitespace(); |
556 | 0 | // we only need to handle old selection endpoint if it was different from start |
557 | 0 | if (mRangeItem->mStartContainer != mRangeItem->mEndContainer || |
558 | 0 | mRangeItem->mStartOffset != mRangeItem->mEndOffset) { |
559 | 0 | WSRunObject(&HTMLEditorRef(), mRangeItem->mEndContainer, |
560 | 0 | mRangeItem->mEndOffset).AdjustWhitespace(); |
561 | 0 | } |
562 | 0 | } |
563 | 0 |
|
564 | 0 | // if we created a new block, make sure selection lands in it |
565 | 0 | if (mNewBlock) { |
566 | 0 | rv = PinSelectionToNewBlock(); |
567 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
568 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
569 | 0 | } |
570 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
571 | 0 | "Failed to pin selection to the new block"); |
572 | 0 | mNewBlock = nullptr; |
573 | 0 | } |
574 | 0 |
|
575 | 0 | // adjust selection for insert text, html paste, and delete actions |
576 | 0 | if (aEditSubAction == EditSubAction::eInsertText || |
577 | 0 | aEditSubAction == EditSubAction::eInsertTextComingFromIME || |
578 | 0 | aEditSubAction == EditSubAction::eDeleteSelectedContent || |
579 | 0 | aEditSubAction == EditSubAction::eInsertParagraphSeparator || |
580 | 0 | aEditSubAction == EditSubAction::ePasteHTMLContent || |
581 | 0 | aEditSubAction == EditSubAction::eInsertHTMLSource) { |
582 | 0 | rv = AdjustSelection(aDirection); |
583 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
584 | 0 | return rv; |
585 | 0 | } |
586 | 0 | } |
587 | 0 | |
588 | 0 | // check for any styles which were removed inappropriately |
589 | 0 | if (aEditSubAction == EditSubAction::eInsertText || |
590 | 0 | aEditSubAction == EditSubAction::eInsertTextComingFromIME || |
591 | 0 | aEditSubAction == EditSubAction::eDeleteSelectedContent || |
592 | 0 | IsStyleCachePreservingSubAction(aEditSubAction)) { |
593 | 0 | HTMLEditorRef().mTypeInState->UpdateSelState(&SelectionRef()); |
594 | 0 | rv = ReapplyCachedStyles(); |
595 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
596 | 0 | return rv; |
597 | 0 | } |
598 | 0 | ClearCachedStyles(); |
599 | 0 | } |
600 | 0 | } |
601 | 0 |
|
602 | 0 | rv = HTMLEditorRef().HandleInlineSpellCheck(aEditSubAction, SelectionRef(), |
603 | 0 | mRangeItem->mStartContainer, |
604 | 0 | mRangeItem->mStartOffset, |
605 | 0 | rangeStartContainer, |
606 | 0 | rangeStartOffset, |
607 | 0 | rangeEndContainer, |
608 | 0 | rangeEndOffset); |
609 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
610 | 0 | return rv; |
611 | 0 | } |
612 | 0 | |
613 | 0 | // detect empty doc |
614 | 0 | rv = CreateBogusNodeIfNeeded(); |
615 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
616 | 0 | return rv; |
617 | 0 | } |
618 | 0 | |
619 | 0 | // adjust selection HINT if needed |
620 | 0 | if (!mDidExplicitlySetInterline) { |
621 | 0 | CheckInterlinePosition(); |
622 | 0 | } |
623 | 0 |
|
624 | 0 | return NS_OK; |
625 | 0 | } |
626 | | |
627 | | nsresult |
628 | | HTMLEditRules::WillDoAction(Selection* aSelection, |
629 | | EditSubActionInfo& aInfo, |
630 | | bool* aCancel, |
631 | | bool* aHandled) |
632 | 0 | { |
633 | 0 | if (NS_WARN_IF(!aSelection)) { |
634 | 0 | return NS_ERROR_INVALID_ARG; |
635 | 0 | } |
636 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
637 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
638 | 0 | } |
639 | 0 | |
640 | 0 | MOZ_ASSERT(aCancel); |
641 | 0 | MOZ_ASSERT(aHandled); |
642 | 0 |
|
643 | 0 | *aCancel = false; |
644 | 0 | *aHandled = false; |
645 | 0 |
|
646 | 0 | // Deal with actions for which we don't need to check whether the selection is |
647 | 0 | // editable. |
648 | 0 | if (aInfo.mEditSubAction == EditSubAction::eComputeTextToOutput || |
649 | 0 | aInfo.mEditSubAction == EditSubAction::eUndo || |
650 | 0 | aInfo.mEditSubAction == EditSubAction::eRedo) { |
651 | 0 | return TextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled); |
652 | 0 | } |
653 | 0 | |
654 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *aSelection); |
655 | 0 |
|
656 | 0 | // Nothing to do if there's no selection to act on |
657 | 0 | if (NS_WARN_IF(!SelectionRef().RangeCount())) { |
658 | 0 | return NS_OK; |
659 | 0 | } |
660 | 0 | |
661 | 0 | RefPtr<nsRange> range = SelectionRef().GetRangeAt(0); |
662 | 0 | nsCOMPtr<nsINode> selStartNode = range->GetStartContainer(); |
663 | 0 | if (NS_WARN_IF(!selStartNode)) { |
664 | 0 | return NS_ERROR_FAILURE; |
665 | 0 | } |
666 | 0 | |
667 | 0 | if (!HTMLEditorRef().IsModifiableNode(*selStartNode)) { |
668 | 0 | *aCancel = true; |
669 | 0 | return NS_OK; |
670 | 0 | } |
671 | 0 | |
672 | 0 | nsCOMPtr<nsINode> selEndNode = range->GetEndContainer(); |
673 | 0 | if (NS_WARN_IF(!selEndNode)) { |
674 | 0 | return NS_ERROR_FAILURE; |
675 | 0 | } |
676 | 0 | |
677 | 0 | if (selStartNode != selEndNode) { |
678 | 0 | if (!HTMLEditorRef().IsModifiableNode(*selEndNode)) { |
679 | 0 | *aCancel = true; |
680 | 0 | return NS_OK; |
681 | 0 | } |
682 | 0 | |
683 | 0 | nsINode* commonAncestor = range->GetCommonAncestor(); |
684 | 0 | if (NS_WARN_IF(!commonAncestor)) { |
685 | 0 | return NS_ERROR_FAILURE; |
686 | 0 | } |
687 | 0 | if (!HTMLEditorRef().IsModifiableNode(*commonAncestor)) { |
688 | 0 | *aCancel = true; |
689 | 0 | return NS_OK; |
690 | 0 | } |
691 | 0 | } |
692 | 0 | |
693 | 0 | switch (aInfo.mEditSubAction) { |
694 | 0 | case EditSubAction::eInsertText: |
695 | 0 | case EditSubAction::eInsertTextComingFromIME: |
696 | 0 | UndefineCaretBidiLevel(); |
697 | 0 | return WillInsertText(aInfo.mEditSubAction, aCancel, aHandled, |
698 | 0 | aInfo.inString, aInfo.outString, |
699 | 0 | aInfo.maxLength); |
700 | 0 | case EditSubAction::eInsertHTMLSource: |
701 | 0 | return WillLoadHTML(); |
702 | 0 | case EditSubAction::eInsertParagraphSeparator: |
703 | 0 | UndefineCaretBidiLevel(); |
704 | 0 | return WillInsertBreak(aCancel, aHandled); |
705 | 0 | case EditSubAction::eDeleteSelectedContent: |
706 | 0 | return WillDeleteSelection(aInfo.collapsedAction, aInfo.stripWrappers, |
707 | 0 | aCancel, aHandled); |
708 | 0 | case EditSubAction::eCreateOrChangeList: |
709 | 0 | return WillMakeList(aInfo.blockType, aInfo.entireList, |
710 | 0 | aInfo.bulletType, aCancel, aHandled); |
711 | 0 | case EditSubAction::eIndent: |
712 | 0 | return WillIndent(aCancel, aHandled); |
713 | 0 | case EditSubAction::eOutdent: |
714 | 0 | return WillOutdent(aCancel, aHandled); |
715 | 0 | case EditSubAction::eSetPositionToAbsolute: |
716 | 0 | return WillAbsolutePosition(aCancel, aHandled); |
717 | 0 | case EditSubAction::eSetPositionToStatic: |
718 | 0 | return WillRemoveAbsolutePosition(aCancel, aHandled); |
719 | 0 | case EditSubAction::eSetOrClearAlignment: |
720 | 0 | return WillAlign(*aInfo.alignType, aCancel, aHandled); |
721 | 0 | case EditSubAction::eCreateOrRemoveBlock: |
722 | 0 | return WillMakeBasicBlock(*aInfo.blockType, aCancel, aHandled); |
723 | 0 | case EditSubAction::eRemoveList: { |
724 | 0 | nsresult rv = WillRemoveList(aCancel, aHandled); |
725 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) || |
726 | 0 | NS_WARN_IF(!CanHandleEditAction())) { |
727 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
728 | 0 | } |
729 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
730 | 0 | return rv; |
731 | 0 | } |
732 | 0 | return NS_OK; |
733 | 0 | } |
734 | 0 | case EditSubAction::eCreateOrChangeDefinitionList: |
735 | 0 | return WillMakeDefListItem(aInfo.blockType, |
736 | 0 | aInfo.entireList, aCancel, aHandled); |
737 | 0 | case EditSubAction::eInsertElement: { |
738 | 0 | nsresult rv = WillInsert(aCancel); |
739 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
740 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
741 | 0 | } |
742 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
743 | 0 | return NS_OK; |
744 | 0 | } |
745 | 0 | case EditSubAction::eDecreaseZIndex: |
746 | 0 | return WillRelativeChangeZIndex(-1, aCancel, aHandled); |
747 | 0 | case EditSubAction::eIncreaseZIndex: |
748 | 0 | return WillRelativeChangeZIndex(1, aCancel, aHandled); |
749 | 0 | default: |
750 | 0 | return TextEditRules::WillDoAction(&SelectionRef(), aInfo, |
751 | 0 | aCancel, aHandled); |
752 | 0 | } |
753 | 0 | } |
754 | | |
755 | | nsresult |
756 | | HTMLEditRules::DidDoAction(Selection* aSelection, |
757 | | EditSubActionInfo& aInfo, |
758 | | nsresult aResult) |
759 | 0 | { |
760 | 0 | if (NS_WARN_IF(!aSelection)) { |
761 | 0 | return NS_ERROR_INVALID_ARG; |
762 | 0 | } |
763 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
764 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
765 | 0 | } |
766 | 0 | |
767 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *aSelection); |
768 | 0 |
|
769 | 0 | switch (aInfo.mEditSubAction) { |
770 | 0 | case EditSubAction::eInsertText: |
771 | 0 | case EditSubAction::eInsertParagraphSeparator: |
772 | 0 | case EditSubAction::eInsertTextComingFromIME: |
773 | 0 | return NS_OK; |
774 | 0 | case EditSubAction::eDeleteSelectedContent: |
775 | 0 | return DidDeleteSelection(); |
776 | 0 | case EditSubAction::eCreateOrRemoveBlock: |
777 | 0 | case EditSubAction::eIndent: |
778 | 0 | case EditSubAction::eOutdent: |
779 | 0 | case EditSubAction::eSetOrClearAlignment: |
780 | 0 | return DidMakeBasicBlock(); |
781 | 0 | case EditSubAction::eSetPositionToAbsolute: { |
782 | 0 | nsresult rv = DidMakeBasicBlock(); |
783 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
784 | 0 | return rv; |
785 | 0 | } |
786 | 0 | return DidAbsolutePosition(); |
787 | 0 | } |
788 | 0 | default: |
789 | 0 | return TextEditRules::DidDoAction(aSelection, aInfo, aResult); |
790 | 0 | } |
791 | 0 | } |
792 | | |
793 | | bool |
794 | | HTMLEditRules::DocumentIsEmpty() |
795 | 0 | { |
796 | 0 | return !!mBogusNode; |
797 | 0 | } |
798 | | |
799 | | nsresult |
800 | | HTMLEditRules::GetListState(bool* aMixed, |
801 | | bool* aOL, |
802 | | bool* aUL, |
803 | | bool* aDL) |
804 | 0 | { |
805 | 0 | NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER); |
806 | 0 | *aMixed = false; |
807 | 0 | *aOL = false; |
808 | 0 | *aUL = false; |
809 | 0 | *aDL = false; |
810 | 0 | bool bNonList = false; |
811 | 0 |
|
812 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
813 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
814 | 0 | } |
815 | 0 | |
816 | 0 | Selection* selection = mHTMLEditor->GetSelection(); |
817 | 0 | if (NS_WARN_IF(!selection)) { |
818 | 0 | return NS_ERROR_FAILURE; |
819 | 0 | } |
820 | 0 | |
821 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *selection); |
822 | 0 |
|
823 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
824 | 0 | nsresult rv = GetListActionNodes(arrayOfNodes, EntireList::no, |
825 | 0 | TouchContent::no); |
826 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
827 | 0 | return rv; |
828 | 0 | } |
829 | 0 | |
830 | 0 | // Examine list type for nodes in selection. |
831 | 0 | for (const auto& curNode : arrayOfNodes) { |
832 | 0 | if (!curNode->IsElement()) { |
833 | 0 | bNonList = true; |
834 | 0 | } else if (curNode->IsHTMLElement(nsGkAtoms::ul)) { |
835 | 0 | *aUL = true; |
836 | 0 | } else if (curNode->IsHTMLElement(nsGkAtoms::ol)) { |
837 | 0 | *aOL = true; |
838 | 0 | } else if (curNode->IsHTMLElement(nsGkAtoms::li)) { |
839 | 0 | if (dom::Element* parent = curNode->GetParentElement()) { |
840 | 0 | if (parent->IsHTMLElement(nsGkAtoms::ul)) { |
841 | 0 | *aUL = true; |
842 | 0 | } else if (parent->IsHTMLElement(nsGkAtoms::ol)) { |
843 | 0 | *aOL = true; |
844 | 0 | } |
845 | 0 | } |
846 | 0 | } else if (curNode->IsAnyOfHTMLElements(nsGkAtoms::dl, |
847 | 0 | nsGkAtoms::dt, |
848 | 0 | nsGkAtoms::dd)) { |
849 | 0 | *aDL = true; |
850 | 0 | } else { |
851 | 0 | bNonList = true; |
852 | 0 | } |
853 | 0 | } |
854 | 0 |
|
855 | 0 | // hokey arithmetic with booleans |
856 | 0 | if ((*aUL + *aOL + *aDL + bNonList) > 1) { |
857 | 0 | *aMixed = true; |
858 | 0 | } |
859 | 0 |
|
860 | 0 | return NS_OK; |
861 | 0 | } |
862 | | |
863 | | nsresult |
864 | | HTMLEditRules::GetListItemState(bool* aMixed, |
865 | | bool* aLI, |
866 | | bool* aDT, |
867 | | bool* aDD) |
868 | 0 | { |
869 | 0 | NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER); |
870 | 0 | *aMixed = false; |
871 | 0 | *aLI = false; |
872 | 0 | *aDT = false; |
873 | 0 | *aDD = false; |
874 | 0 | bool bNonList = false; |
875 | 0 |
|
876 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
877 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
878 | 0 | } |
879 | 0 | |
880 | 0 | Selection* selection = mHTMLEditor->GetSelection(); |
881 | 0 | if (NS_WARN_IF(!selection)) { |
882 | 0 | return NS_ERROR_FAILURE; |
883 | 0 | } |
884 | 0 | |
885 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *selection); |
886 | 0 |
|
887 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
888 | 0 | nsresult rv = GetListActionNodes(arrayOfNodes, EntireList::no, |
889 | 0 | TouchContent::no); |
890 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
891 | 0 | return rv; |
892 | 0 | } |
893 | 0 | |
894 | 0 | // examine list type for nodes in selection |
895 | 0 | for (const auto& node : arrayOfNodes) { |
896 | 0 | if (!node->IsElement()) { |
897 | 0 | bNonList = true; |
898 | 0 | } else if (node->IsAnyOfHTMLElements(nsGkAtoms::ul, |
899 | 0 | nsGkAtoms::ol, |
900 | 0 | nsGkAtoms::li)) { |
901 | 0 | *aLI = true; |
902 | 0 | } else if (node->IsHTMLElement(nsGkAtoms::dt)) { |
903 | 0 | *aDT = true; |
904 | 0 | } else if (node->IsHTMLElement(nsGkAtoms::dd)) { |
905 | 0 | *aDD = true; |
906 | 0 | } else if (node->IsHTMLElement(nsGkAtoms::dl)) { |
907 | 0 | // need to look inside dl and see which types of items it has |
908 | 0 | bool bDT, bDD; |
909 | 0 | GetDefinitionListItemTypes(node->AsElement(), &bDT, &bDD); |
910 | 0 | *aDT |= bDT; |
911 | 0 | *aDD |= bDD; |
912 | 0 | } else { |
913 | 0 | bNonList = true; |
914 | 0 | } |
915 | 0 | } |
916 | 0 |
|
917 | 0 | // hokey arithmetic with booleans |
918 | 0 | if (*aDT + *aDD + bNonList > 1) { |
919 | 0 | *aMixed = true; |
920 | 0 | } |
921 | 0 |
|
922 | 0 | return NS_OK; |
923 | 0 | } |
924 | | |
925 | | nsresult |
926 | | HTMLEditRules::GetAlignment(bool* aMixed, |
927 | | nsIHTMLEditor::EAlignment* aAlign) |
928 | 0 | { |
929 | 0 | MOZ_ASSERT(aMixed && aAlign); |
930 | 0 |
|
931 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
932 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
933 | 0 | } |
934 | 0 | |
935 | 0 | Selection* selection = mHTMLEditor->GetSelection(); |
936 | 0 | if (NS_WARN_IF(!selection)) { |
937 | 0 | return NS_ERROR_FAILURE; |
938 | 0 | } |
939 | 0 | |
940 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *selection); |
941 | 0 |
|
942 | 0 | // For now, just return first alignment. We'll lie about if it's mixed. |
943 | 0 | // This is for efficiency given that our current ui doesn't care if it's |
944 | 0 | // mixed. |
945 | 0 | // cmanske: NOT TRUE! We would like to pay attention to mixed state in Format |
946 | 0 | // | Align submenu! |
947 | 0 |
|
948 | 0 | // This routine assumes that alignment is done ONLY via divs |
949 | 0 |
|
950 | 0 | // Default alignment is left |
951 | 0 | *aMixed = false; |
952 | 0 | *aAlign = nsIHTMLEditor::eLeft; |
953 | 0 |
|
954 | 0 | // Get selection location |
955 | 0 | if (NS_WARN_IF(!HTMLEditorRef().GetRoot())) { |
956 | 0 | return NS_ERROR_FAILURE; |
957 | 0 | } |
958 | 0 | OwningNonNull<Element> root = *HTMLEditorRef().GetRoot(); |
959 | 0 |
|
960 | 0 | int32_t rootOffset = root->GetParentNode() ? |
961 | 0 | root->GetParentNode()->ComputeIndexOf(root) : -1; |
962 | 0 |
|
963 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
964 | 0 | if (NS_WARN_IF(!firstRange)) { |
965 | 0 | return NS_ERROR_FAILURE; |
966 | 0 | } |
967 | 0 | EditorRawDOMPoint atStartOfSelection(firstRange->StartRef()); |
968 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
969 | 0 | return NS_ERROR_FAILURE; |
970 | 0 | } |
971 | 0 | MOZ_ASSERT(atStartOfSelection.IsSetAndValid()); |
972 | 0 |
|
973 | 0 | // Is the selection collapsed? |
974 | 0 | nsCOMPtr<nsINode> nodeToExamine; |
975 | 0 | if (SelectionRef().IsCollapsed() || atStartOfSelection.GetContainerAsText()) { |
976 | 0 | // If selection is collapsed, we want to look at the container of selection |
977 | 0 | // start and its ancestors for divs with alignment on them. If we are in a |
978 | 0 | // text node, then that is the node of interest. |
979 | 0 | nodeToExamine = atStartOfSelection.GetContainer(); |
980 | 0 | if (NS_WARN_IF(!nodeToExamine)) { |
981 | 0 | return NS_ERROR_FAILURE; |
982 | 0 | } |
983 | 0 | } else if (atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::html) && |
984 | 0 | atStartOfSelection.Offset() == static_cast<uint32_t>(rootOffset)) { |
985 | 0 | // If we have selected the body, let's look at the first editable node |
986 | 0 | nodeToExamine = HTMLEditorRef().GetNextEditableNode(atStartOfSelection); |
987 | 0 | if (NS_WARN_IF(!nodeToExamine)) { |
988 | 0 | return NS_ERROR_FAILURE; |
989 | 0 | } |
990 | 0 | } else { |
991 | 0 | nsTArray<RefPtr<nsRange>> arrayOfRanges; |
992 | 0 | GetPromotedRanges(arrayOfRanges, EditSubAction::eSetOrClearAlignment); |
993 | 0 |
|
994 | 0 | // Use these ranges to construct a list of nodes to act on. |
995 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
996 | 0 | nsresult rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes, |
997 | 0 | EditSubAction::eSetOrClearAlignment, |
998 | 0 | TouchContent::no); |
999 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1000 | 0 | return rv; |
1001 | 0 | } |
1002 | 0 | nodeToExamine = arrayOfNodes.SafeElementAt(0); |
1003 | 0 | if (NS_WARN_IF(!nodeToExamine)) { |
1004 | 0 | return NS_ERROR_FAILURE; |
1005 | 0 | } |
1006 | 0 | } |
1007 | 0 | |
1008 | 0 | RefPtr<Element> blockParent = HTMLEditorRef().GetBlock(*nodeToExamine); |
1009 | 0 | if (NS_WARN_IF(!blockParent)) { |
1010 | 0 | return NS_ERROR_FAILURE; |
1011 | 0 | } |
1012 | 0 | |
1013 | 0 | if (HTMLEditorRef().IsCSSEnabled() && |
1014 | 0 | CSSEditUtils::IsCSSEditableProperty(blockParent, nullptr, |
1015 | 0 | nsGkAtoms::align)) { |
1016 | 0 | // We are in CSS mode and we know how to align this element with CSS |
1017 | 0 | nsAutoString value; |
1018 | 0 | // Let's get the value(s) of text-align or margin-left/margin-right |
1019 | 0 | CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSet( |
1020 | 0 | blockParent, nullptr, nsGkAtoms::align, value, CSSEditUtils::eComputed); |
1021 | 0 | if (value.EqualsLiteral("center") || |
1022 | 0 | value.EqualsLiteral("-moz-center") || |
1023 | 0 | value.EqualsLiteral("auto auto")) { |
1024 | 0 | *aAlign = nsIHTMLEditor::eCenter; |
1025 | 0 | return NS_OK; |
1026 | 0 | } |
1027 | 0 | if (value.EqualsLiteral("right") || |
1028 | 0 | value.EqualsLiteral("-moz-right") || |
1029 | 0 | value.EqualsLiteral("auto 0px")) { |
1030 | 0 | *aAlign = nsIHTMLEditor::eRight; |
1031 | 0 | return NS_OK; |
1032 | 0 | } |
1033 | 0 | if (value.EqualsLiteral("justify")) { |
1034 | 0 | *aAlign = nsIHTMLEditor::eJustify; |
1035 | 0 | return NS_OK; |
1036 | 0 | } |
1037 | 0 | *aAlign = nsIHTMLEditor::eLeft; |
1038 | 0 | return NS_OK; |
1039 | 0 | } |
1040 | 0 | |
1041 | 0 | // Check up the ladder for divs with alignment |
1042 | 0 | bool isFirstNodeToExamine = true; |
1043 | 0 | for (; nodeToExamine; nodeToExamine = nodeToExamine->GetParentNode()) { |
1044 | 0 | if (!isFirstNodeToExamine && |
1045 | 0 | nodeToExamine->IsHTMLElement(nsGkAtoms::table)) { |
1046 | 0 | // The node to examine is a table and this is not the first node we |
1047 | 0 | // examine; let's break here to materialize the 'inline-block' behaviour |
1048 | 0 | // of html tables regarding to text alignment |
1049 | 0 | return NS_OK; |
1050 | 0 | } |
1051 | 0 | |
1052 | 0 | if (CSSEditUtils::IsCSSEditableProperty(nodeToExamine, nullptr, |
1053 | 0 | nsGkAtoms::align)) { |
1054 | 0 | nsAutoString value; |
1055 | 0 | CSSEditUtils::GetSpecifiedProperty(*nodeToExamine, |
1056 | 0 | *nsGkAtoms::textAlign, |
1057 | 0 | value); |
1058 | 0 | if (!value.IsEmpty()) { |
1059 | 0 | if (value.EqualsLiteral("center")) { |
1060 | 0 | *aAlign = nsIHTMLEditor::eCenter; |
1061 | 0 | return NS_OK; |
1062 | 0 | } |
1063 | 0 | if (value.EqualsLiteral("right")) { |
1064 | 0 | *aAlign = nsIHTMLEditor::eRight; |
1065 | 0 | return NS_OK; |
1066 | 0 | } |
1067 | 0 | if (value.EqualsLiteral("justify")) { |
1068 | 0 | *aAlign = nsIHTMLEditor::eJustify; |
1069 | 0 | return NS_OK; |
1070 | 0 | } |
1071 | 0 | if (value.EqualsLiteral("left")) { |
1072 | 0 | *aAlign = nsIHTMLEditor::eLeft; |
1073 | 0 | return NS_OK; |
1074 | 0 | } |
1075 | 0 | // XXX |
1076 | 0 | // text-align: start and end aren't supported yet |
1077 | 0 | } |
1078 | 0 | } |
1079 | 0 | |
1080 | 0 | if (HTMLEditUtils::SupportsAlignAttr(*nodeToExamine)) { |
1081 | 0 | // Check for alignment |
1082 | 0 | nsAutoString typeAttrVal; |
1083 | 0 | nodeToExamine->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::align, |
1084 | 0 | typeAttrVal); |
1085 | 0 | ToLowerCase(typeAttrVal); |
1086 | 0 | if (!typeAttrVal.IsEmpty()) { |
1087 | 0 | if (typeAttrVal.EqualsLiteral("center")) { |
1088 | 0 | *aAlign = nsIHTMLEditor::eCenter; |
1089 | 0 | } else if (typeAttrVal.EqualsLiteral("right")) { |
1090 | 0 | *aAlign = nsIHTMLEditor::eRight; |
1091 | 0 | } else if (typeAttrVal.EqualsLiteral("justify")) { |
1092 | 0 | *aAlign = nsIHTMLEditor::eJustify; |
1093 | 0 | } else { |
1094 | 0 | *aAlign = nsIHTMLEditor::eLeft; |
1095 | 0 | } |
1096 | 0 | return NS_OK; |
1097 | 0 | } |
1098 | 0 | } |
1099 | 0 | isFirstNodeToExamine = false; |
1100 | 0 | } |
1101 | 0 | return NS_OK; |
1102 | 0 | } |
1103 | | |
1104 | | static nsAtom& |
1105 | | MarginPropertyAtomForIndent(nsINode& aNode) |
1106 | 0 | { |
1107 | 0 | nsAutoString direction; |
1108 | 0 | CSSEditUtils::GetComputedProperty(aNode, *nsGkAtoms::direction, direction); |
1109 | 0 | return direction.EqualsLiteral("rtl") ? |
1110 | 0 | *nsGkAtoms::marginRight : *nsGkAtoms::marginLeft; |
1111 | 0 | } |
1112 | | |
1113 | | nsresult |
1114 | | HTMLEditRules::GetParagraphState(bool* aMixed, |
1115 | | nsAString& outFormat) |
1116 | 0 | { |
1117 | 0 | if (NS_WARN_IF(!aMixed)) { |
1118 | 0 | return NS_ERROR_INVALID_ARG; |
1119 | 0 | } |
1120 | 0 | |
1121 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1122 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1123 | 0 | } |
1124 | 0 | |
1125 | 0 | // This routine is *heavily* tied to our ui choices in the paragraph |
1126 | 0 | // style popup. I can't see a way around that. |
1127 | 0 | *aMixed = true; |
1128 | 0 | outFormat.Truncate(0); |
1129 | 0 |
|
1130 | 0 | Selection* selection = mHTMLEditor->GetSelection(); |
1131 | 0 | if (NS_WARN_IF(!selection)) { |
1132 | 0 | return NS_ERROR_FAILURE; |
1133 | 0 | } |
1134 | 0 | |
1135 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *selection); |
1136 | 0 |
|
1137 | 0 | bool bMixed = false; |
1138 | 0 | // using "x" as an uninitialized value, since "" is meaningful |
1139 | 0 | nsAutoString formatStr(NS_LITERAL_STRING("x")); |
1140 | 0 |
|
1141 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
1142 | 0 | nsresult rv = GetParagraphFormatNodes(arrayOfNodes); |
1143 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1144 | 0 | return rv; |
1145 | 0 | } |
1146 | 0 | |
1147 | 0 | // post process list. We need to replace any block nodes that are not format |
1148 | 0 | // nodes with their content. This is so we only have to look "up" the hierarchy |
1149 | 0 | // to find format nodes, instead of both up and down. |
1150 | 0 | for (int32_t i = arrayOfNodes.Length() - 1; i >= 0; i--) { |
1151 | 0 | auto& curNode = arrayOfNodes[i]; |
1152 | 0 | nsAutoString format; |
1153 | 0 | // if it is a known format node we have it easy |
1154 | 0 | if (IsBlockNode(curNode) && !HTMLEditUtils::IsFormatNode(curNode)) { |
1155 | 0 | // arrayOfNodes.RemoveObject(curNode); |
1156 | 0 | rv = AppendInnerFormatNodes(arrayOfNodes, curNode); |
1157 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1158 | 0 | return rv; |
1159 | 0 | } |
1160 | 0 | } |
1161 | 0 | } |
1162 | 0 |
|
1163 | 0 | // we might have an empty node list. if so, find selection parent |
1164 | 0 | // and put that on the list |
1165 | 0 | if (arrayOfNodes.IsEmpty()) { |
1166 | 0 | EditorRawDOMPoint selectionStartPoint( |
1167 | 0 | EditorBase::GetStartPoint(&SelectionRef())); |
1168 | 0 | if (NS_WARN_IF(!selectionStartPoint.IsSet())) { |
1169 | 0 | return NS_ERROR_FAILURE; |
1170 | 0 | } |
1171 | 0 | arrayOfNodes.AppendElement(*selectionStartPoint.GetContainer()); |
1172 | 0 | } |
1173 | 0 |
|
1174 | 0 | // remember root node |
1175 | 0 | Element* rootElement = HTMLEditorRef().GetRoot(); |
1176 | 0 | if (NS_WARN_IF(!rootElement)) { |
1177 | 0 | return NS_ERROR_FAILURE; |
1178 | 0 | } |
1179 | 0 | |
1180 | 0 | // loop through the nodes in selection and examine their paragraph format |
1181 | 0 | for (auto& curNode : Reversed(arrayOfNodes)) { |
1182 | 0 | nsAutoString format; |
1183 | 0 | // if it is a known format node we have it easy |
1184 | 0 | if (HTMLEditUtils::IsFormatNode(curNode)) { |
1185 | 0 | GetFormatString(curNode, format); |
1186 | 0 | } else if (IsBlockNode(curNode)) { |
1187 | 0 | // this is a div or some other non-format block. |
1188 | 0 | // we should ignore it. Its children were appended to this list |
1189 | 0 | // by AppendInnerFormatNodes() call above. We will get needed |
1190 | 0 | // info when we examine them instead. |
1191 | 0 | continue; |
1192 | 0 | } else { |
1193 | 0 | nsINode* node = curNode->GetParentNode(); |
1194 | 0 | while (node) { |
1195 | 0 | if (node == rootElement) { |
1196 | 0 | format.Truncate(0); |
1197 | 0 | break; |
1198 | 0 | } else if (HTMLEditUtils::IsFormatNode(node)) { |
1199 | 0 | GetFormatString(node, format); |
1200 | 0 | break; |
1201 | 0 | } |
1202 | 0 | // else keep looking up |
1203 | 0 | node = node->GetParentNode(); |
1204 | 0 | } |
1205 | 0 | } |
1206 | 0 |
|
1207 | 0 | // if this is the first node, we've found, remember it as the format |
1208 | 0 | if (formatStr.EqualsLiteral("x")) { |
1209 | 0 | formatStr = format; |
1210 | 0 | } |
1211 | 0 | // else make sure it matches previously found format |
1212 | 0 | else if (format != formatStr) { |
1213 | 0 | bMixed = true; |
1214 | 0 | break; |
1215 | 0 | } |
1216 | 0 | } |
1217 | 0 |
|
1218 | 0 | *aMixed = bMixed; |
1219 | 0 | outFormat = formatStr; |
1220 | 0 | return NS_OK; |
1221 | 0 | } |
1222 | | |
1223 | | nsresult |
1224 | | HTMLEditRules::AppendInnerFormatNodes(nsTArray<OwningNonNull<nsINode>>& aArray, |
1225 | | nsINode* aNode) |
1226 | 0 | { |
1227 | 0 | MOZ_ASSERT(aNode); |
1228 | 0 |
|
1229 | 0 | // we only need to place any one inline inside this node onto |
1230 | 0 | // the list. They are all the same for purposes of determining |
1231 | 0 | // paragraph style. We use foundInline to track this as we are |
1232 | 0 | // going through the children in the loop below. |
1233 | 0 | bool foundInline = false; |
1234 | 0 | for (nsIContent* child = aNode->GetFirstChild(); |
1235 | 0 | child; |
1236 | 0 | child = child->GetNextSibling()) { |
1237 | 0 | bool isBlock = IsBlockNode(*child); |
1238 | 0 | bool isFormat = HTMLEditUtils::IsFormatNode(child); |
1239 | 0 | if (isBlock && !isFormat) { |
1240 | 0 | // if it's a div, etc., recurse |
1241 | 0 | AppendInnerFormatNodes(aArray, child); |
1242 | 0 | } else if (isFormat) { |
1243 | 0 | aArray.AppendElement(*child); |
1244 | 0 | } else if (!foundInline) { |
1245 | 0 | // if this is the first inline we've found, use it |
1246 | 0 | foundInline = true; |
1247 | 0 | aArray.AppendElement(*child); |
1248 | 0 | } |
1249 | 0 | } |
1250 | 0 | return NS_OK; |
1251 | 0 | } |
1252 | | |
1253 | | nsresult |
1254 | | HTMLEditRules::GetFormatString(nsINode* aNode, |
1255 | | nsAString& outFormat) |
1256 | 0 | { |
1257 | 0 | NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); |
1258 | 0 |
|
1259 | 0 | if (HTMLEditUtils::IsFormatNode(aNode)) { |
1260 | 0 | aNode->NodeInfo()->NameAtom()->ToString(outFormat); |
1261 | 0 | } else { |
1262 | 0 | outFormat.Truncate(); |
1263 | 0 | } |
1264 | 0 | return NS_OK; |
1265 | 0 | } |
1266 | | |
1267 | | nsresult |
1268 | | HTMLEditRules::WillInsert(bool* aCancel) |
1269 | 0 | { |
1270 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
1271 | 0 |
|
1272 | 0 | nsresult rv = TextEditRules::WillInsert(aCancel); |
1273 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1274 | 0 | return rv; |
1275 | 0 | } |
1276 | 0 | |
1277 | 0 | // Adjust selection to prevent insertion after a moz-BR. This next only |
1278 | 0 | // works for collapsed selections right now, because selection is a pain to |
1279 | 0 | // work with when not collapsed. (no good way to extend start or end of |
1280 | 0 | // selection), so we ignore those types of selections. |
1281 | 0 | if (!SelectionRef().IsCollapsed()) { |
1282 | 0 | return NS_OK; |
1283 | 0 | } |
1284 | 0 | |
1285 | 0 | // If we are after a mozBR in the same block, then move selection to be |
1286 | 0 | // before it |
1287 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
1288 | 0 | if (NS_WARN_IF(!firstRange)) { |
1289 | 0 | return NS_ERROR_FAILURE; |
1290 | 0 | } |
1291 | 0 | |
1292 | 0 | EditorRawDOMPoint atStartOfSelection(firstRange->StartRef()); |
1293 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
1294 | 0 | return NS_ERROR_FAILURE; |
1295 | 0 | } |
1296 | 0 | MOZ_ASSERT(atStartOfSelection.IsSetAndValid()); |
1297 | 0 |
|
1298 | 0 | // Get prior node |
1299 | 0 | nsCOMPtr<nsIContent> priorNode = |
1300 | 0 | HTMLEditorRef().GetPreviousEditableHTMLNode(atStartOfSelection); |
1301 | 0 | if (priorNode && TextEditUtils::IsMozBR(priorNode)) { |
1302 | 0 | RefPtr<Element> block1 = |
1303 | 0 | HTMLEditorRef().GetBlock(*atStartOfSelection.GetContainer()); |
1304 | 0 | RefPtr<Element> block2 = HTMLEditorRef().GetBlockNodeParent(priorNode); |
1305 | 0 |
|
1306 | 0 | if (block1 && block1 == block2) { |
1307 | 0 | // If we are here then the selection is right after a mozBR that is in |
1308 | 0 | // the same block as the selection. We need to move the selection start |
1309 | 0 | // to be before the mozBR. |
1310 | 0 | EditorRawDOMPoint point(priorNode); |
1311 | 0 | ErrorResult error; |
1312 | 0 | SelectionRef().Collapse(point, error); |
1313 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1314 | 0 | error.SuppressException(); |
1315 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1316 | 0 | } |
1317 | 0 | if (NS_WARN_IF(error.Failed())) { |
1318 | 0 | return error.StealNSResult(); |
1319 | 0 | } |
1320 | 0 | } |
1321 | 0 | } |
1322 | 0 | |
1323 | 0 | if (mDidDeleteSelection && |
1324 | 0 | (mTopLevelEditSubAction == EditSubAction::eInsertText || |
1325 | 0 | mTopLevelEditSubAction == EditSubAction::eInsertTextComingFromIME || |
1326 | 0 | mTopLevelEditSubAction == EditSubAction::eDeleteSelectedContent)) { |
1327 | 0 | nsresult rv = ReapplyCachedStyles(); |
1328 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1329 | 0 | return rv; |
1330 | 0 | } |
1331 | 0 | } |
1332 | 0 | // For most actions we want to clear the cached styles, but there are |
1333 | 0 | // exceptions |
1334 | 0 | if (!IsStyleCachePreservingSubAction(mTopLevelEditSubAction)) { |
1335 | 0 | ClearCachedStyles(); |
1336 | 0 | } |
1337 | 0 | return NS_OK; |
1338 | 0 | } |
1339 | | |
1340 | | nsresult |
1341 | | HTMLEditRules::WillInsertText(EditSubAction aEditSubAction, |
1342 | | bool* aCancel, |
1343 | | bool* aHandled, |
1344 | | const nsAString* inString, |
1345 | | nsAString* outString, |
1346 | | int32_t aMaxLength) |
1347 | 0 | { |
1348 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
1349 | 0 |
|
1350 | 0 | if (NS_WARN_IF(!aCancel) || |
1351 | 0 | NS_WARN_IF(!aHandled)) { |
1352 | 0 | return NS_ERROR_NULL_POINTER; |
1353 | 0 | } |
1354 | 0 | |
1355 | 0 | // initialize out param |
1356 | 0 | *aCancel = false; |
1357 | 0 | *aHandled = true; |
1358 | 0 | // If the selection isn't collapsed, delete it. Don't delete existing inline |
1359 | 0 | // tags, because we're hopefully going to insert text (bug 787432). |
1360 | 0 | if (!SelectionRef().IsCollapsed()) { |
1361 | 0 | nsresult rv = |
1362 | 0 | HTMLEditorRef().DeleteSelectionAsSubAction(nsIEditor::eNone, |
1363 | 0 | nsIEditor::eNoStrip); |
1364 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1365 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1366 | 0 | } |
1367 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1368 | 0 | return rv; |
1369 | 0 | } |
1370 | 0 | } |
1371 | 0 | |
1372 | 0 | // FYI: Ignore cancel result of WillInsert(). |
1373 | 0 | nsresult rv = WillInsert(); |
1374 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
1375 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1376 | 0 | } |
1377 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
1378 | 0 |
|
1379 | 0 | // we need to get the doc |
1380 | 0 | nsCOMPtr<nsIDocument> doc = HTMLEditorRef().GetDocument(); |
1381 | 0 | if (NS_WARN_IF(!doc)) { |
1382 | 0 | return NS_ERROR_FAILURE; |
1383 | 0 | } |
1384 | 0 | |
1385 | 0 | // for every property that is set, insert a new inline style node |
1386 | 0 | rv = CreateStyleForInsertText(*doc); |
1387 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1388 | 0 | return rv; |
1389 | 0 | } |
1390 | 0 | |
1391 | 0 | // get the (collapsed) selection location |
1392 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
1393 | 0 | if (NS_WARN_IF(!firstRange)) { |
1394 | 0 | return NS_ERROR_FAILURE; |
1395 | 0 | } |
1396 | 0 | EditorDOMPoint pointToInsert(firstRange->StartRef()); |
1397 | 0 | if (NS_WARN_IF(!pointToInsert.IsSet())) { |
1398 | 0 | return NS_ERROR_FAILURE; |
1399 | 0 | } |
1400 | 0 | MOZ_ASSERT(pointToInsert.IsSetAndValid()); |
1401 | 0 |
|
1402 | 0 | // dont put text in places that can't have it |
1403 | 0 | if (!EditorBase::IsTextNode(pointToInsert.GetContainer()) && |
1404 | 0 | !HTMLEditorRef().CanContainTag(*pointToInsert.GetContainer(), |
1405 | 0 | *nsGkAtoms::textTagName)) { |
1406 | 0 | return NS_ERROR_FAILURE; |
1407 | 0 | } |
1408 | 0 | |
1409 | 0 | if (aEditSubAction == EditSubAction::eInsertTextComingFromIME) { |
1410 | 0 | // Right now the WSRunObject code bails on empty strings, but IME needs |
1411 | 0 | // the InsertTextWithTransaction() call to still happen since empty strings |
1412 | 0 | // are meaningful there. |
1413 | 0 | // If there is one or more IME selections, its minimum offset should be |
1414 | 0 | // the insertion point. |
1415 | 0 | int32_t IMESelectionOffset = |
1416 | 0 | HTMLEditorRef().GetIMESelectionStartOffsetIn( |
1417 | 0 | pointToInsert.GetContainer()); |
1418 | 0 | if (IMESelectionOffset >= 0) { |
1419 | 0 | pointToInsert.Set(pointToInsert.GetContainer(), IMESelectionOffset); |
1420 | 0 | } |
1421 | 0 |
|
1422 | 0 | if (inString->IsEmpty()) { |
1423 | 0 | rv = HTMLEditorRef().InsertTextWithTransaction( |
1424 | 0 | *doc, *inString, EditorRawDOMPoint(pointToInsert)); |
1425 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1426 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1427 | 0 | } |
1428 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1429 | 0 | return rv; |
1430 | 0 | } |
1431 | 0 | return NS_OK; |
1432 | 0 | } |
1433 | 0 | |
1434 | 0 | WSRunObject wsObj(&HTMLEditorRef(), pointToInsert); |
1435 | 0 | rv = wsObj.InsertText(*doc, *inString, pointToInsert); |
1436 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1437 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1438 | 0 | } |
1439 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1440 | 0 | return rv; |
1441 | 0 | } |
1442 | 0 | return NS_OK; |
1443 | 0 | } |
1444 | 0 | |
1445 | 0 | // aEditSubAction == kInsertText |
1446 | 0 | |
1447 | 0 | // find where we are |
1448 | 0 | EditorDOMPoint currentPoint(pointToInsert); |
1449 | 0 |
|
1450 | 0 | // is our text going to be PREformatted? |
1451 | 0 | // We remember this so that we know how to handle tabs. |
1452 | 0 | bool isPRE = EditorBase::IsPreformatted(pointToInsert.GetContainer()); |
1453 | 0 |
|
1454 | 0 | // turn off the edit listener: we know how to |
1455 | 0 | // build the "doc changed range" ourselves, and it's |
1456 | 0 | // must faster to do it once here than to track all |
1457 | 0 | // the changes one at a time. |
1458 | 0 | AutoLockListener lockit(&mListenerEnabled); |
1459 | 0 |
|
1460 | 0 | // don't change my selection in subtransactions |
1461 | 0 | AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef()); |
1462 | 0 | nsAutoString tString(*inString); |
1463 | 0 | const char16_t *unicodeBuf = tString.get(); |
1464 | 0 | int32_t pos = 0; |
1465 | 0 | NS_NAMED_LITERAL_STRING(newlineStr, LFSTR); |
1466 | 0 |
|
1467 | 0 | { |
1468 | 0 | AutoTrackDOMPoint tracker(HTMLEditorRef().mRangeUpdater, &pointToInsert); |
1469 | 0 |
|
1470 | 0 | // for efficiency, break out the pre case separately. This is because |
1471 | 0 | // its a lot cheaper to search the input string for only newlines than |
1472 | 0 | // it is to search for both tabs and newlines. |
1473 | 0 | if (isPRE || IsPlaintextEditor()) { |
1474 | 0 | while (unicodeBuf && pos != -1 && |
1475 | 0 | pos < static_cast<int32_t>(inString->Length())) { |
1476 | 0 | int32_t oldPos = pos; |
1477 | 0 | int32_t subStrLen; |
1478 | 0 | pos = tString.FindChar(nsCRT::LF, oldPos); |
1479 | 0 |
|
1480 | 0 | if (pos != -1) { |
1481 | 0 | subStrLen = pos - oldPos; |
1482 | 0 | // if first char is newline, then use just it |
1483 | 0 | if (!subStrLen) { |
1484 | 0 | subStrLen = 1; |
1485 | 0 | } |
1486 | 0 | } else { |
1487 | 0 | subStrLen = tString.Length() - oldPos; |
1488 | 0 | pos = tString.Length(); |
1489 | 0 | } |
1490 | 0 |
|
1491 | 0 | nsDependentSubstring subStr(tString, oldPos, subStrLen); |
1492 | 0 |
|
1493 | 0 | // is it a return? |
1494 | 0 | if (subStr.Equals(newlineStr)) { |
1495 | 0 | RefPtr<Element> brElement = |
1496 | 0 | HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(), |
1497 | 0 | currentPoint, |
1498 | 0 | nsIEditor::eNone); |
1499 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1500 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1501 | 0 | } |
1502 | 0 | if (NS_WARN_IF(!brElement)) { |
1503 | 0 | return NS_ERROR_FAILURE; |
1504 | 0 | } |
1505 | 0 | pos++; |
1506 | 0 | if (brElement->GetNextSibling()) { |
1507 | 0 | pointToInsert.Set(brElement->GetNextSibling()); |
1508 | 0 | } else { |
1509 | 0 | pointToInsert.SetToEndOf(currentPoint.GetContainer()); |
1510 | 0 | } |
1511 | 0 | // XXX In most cases, pointToInsert and currentPoint are same here. |
1512 | 0 | // But if the <br> element has been moved to different point by |
1513 | 0 | // mutation observer, those points become different. |
1514 | 0 | currentPoint.Set(brElement); |
1515 | 0 | DebugOnly<bool> advanced = currentPoint.AdvanceOffset(); |
1516 | 0 | NS_WARNING_ASSERTION(advanced, |
1517 | 0 | "Failed to advance offset after the new <br> element"); |
1518 | 0 | NS_WARNING_ASSERTION(currentPoint == pointToInsert, |
1519 | 0 | "Perhaps, <br> element position has been moved to different point " |
1520 | 0 | "by mutation observer"); |
1521 | 0 | } else { |
1522 | 0 | EditorRawDOMPoint pointAfterInsertedString; |
1523 | 0 | rv = HTMLEditorRef().InsertTextWithTransaction( |
1524 | 0 | *doc, subStr, |
1525 | 0 | EditorRawDOMPoint(currentPoint), |
1526 | 0 | &pointAfterInsertedString); |
1527 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1528 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1529 | 0 | } |
1530 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1531 | 0 | return rv; |
1532 | 0 | } |
1533 | 0 | currentPoint = pointAfterInsertedString; |
1534 | 0 | pointToInsert = pointAfterInsertedString; |
1535 | 0 | } |
1536 | 0 | } |
1537 | 0 | } else { |
1538 | 0 | NS_NAMED_LITERAL_STRING(tabStr, "\t"); |
1539 | 0 | NS_NAMED_LITERAL_STRING(spacesStr, " "); |
1540 | 0 | char specialChars[] = {TAB, nsCRT::LF, 0}; |
1541 | 0 | while (unicodeBuf && pos != -1 && |
1542 | 0 | pos < static_cast<int32_t>(inString->Length())) { |
1543 | 0 | int32_t oldPos = pos; |
1544 | 0 | int32_t subStrLen; |
1545 | 0 | pos = tString.FindCharInSet(specialChars, oldPos); |
1546 | 0 |
|
1547 | 0 | if (pos != -1) { |
1548 | 0 | subStrLen = pos - oldPos; |
1549 | 0 | // if first char is newline, then use just it |
1550 | 0 | if (!subStrLen) { |
1551 | 0 | subStrLen = 1; |
1552 | 0 | } |
1553 | 0 | } else { |
1554 | 0 | subStrLen = tString.Length() - oldPos; |
1555 | 0 | pos = tString.Length(); |
1556 | 0 | } |
1557 | 0 |
|
1558 | 0 | nsDependentSubstring subStr(tString, oldPos, subStrLen); |
1559 | 0 | WSRunObject wsObj(&HTMLEditorRef(), currentPoint); |
1560 | 0 |
|
1561 | 0 | // is it a tab? |
1562 | 0 | if (subStr.Equals(tabStr)) { |
1563 | 0 | EditorRawDOMPoint pointAfterInsertedSpaces; |
1564 | 0 | rv = wsObj.InsertText(*doc, spacesStr, currentPoint, |
1565 | 0 | &pointAfterInsertedSpaces); |
1566 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1567 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1568 | 0 | } |
1569 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1570 | 0 | return rv; |
1571 | 0 | } |
1572 | 0 | pos++; |
1573 | 0 | currentPoint = pointAfterInsertedSpaces; |
1574 | 0 | pointToInsert = pointAfterInsertedSpaces; |
1575 | 0 | } |
1576 | 0 | // is it a return? |
1577 | 0 | else if (subStr.Equals(newlineStr)) { |
1578 | 0 | RefPtr<Element> newBRElement = |
1579 | 0 | wsObj.InsertBreak(SelectionRef(), currentPoint, nsIEditor::eNone); |
1580 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1581 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1582 | 0 | } |
1583 | 0 | if (NS_WARN_IF(!newBRElement)) { |
1584 | 0 | return NS_ERROR_FAILURE; |
1585 | 0 | } |
1586 | 0 | pos++; |
1587 | 0 | if (newBRElement->GetNextSibling()) { |
1588 | 0 | pointToInsert.Set(newBRElement->GetNextSibling()); |
1589 | 0 | } else { |
1590 | 0 | pointToInsert.SetToEndOf(currentPoint.GetContainer()); |
1591 | 0 | } |
1592 | 0 | currentPoint.Set(newBRElement); |
1593 | 0 | DebugOnly<bool> advanced = currentPoint.AdvanceOffset(); |
1594 | 0 | NS_WARNING_ASSERTION(advanced, |
1595 | 0 | "Failed to advance offset to after the new <br> node"); |
1596 | 0 | // XXX If the newBRElement has been moved or removed by mutation |
1597 | 0 | // observer, we hit this assert. We need to check if |
1598 | 0 | // newBRElement is in expected point, though, we must have |
1599 | 0 | // a lot of same bugs... |
1600 | 0 | NS_WARNING_ASSERTION(currentPoint == pointToInsert, |
1601 | 0 | "Perhaps, newBRElement has been moved or removed unexpectedly"); |
1602 | 0 | } else { |
1603 | 0 | EditorRawDOMPoint pointAfterInsertedString; |
1604 | 0 | rv = wsObj.InsertText(*doc, subStr, currentPoint, |
1605 | 0 | &pointAfterInsertedString); |
1606 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1607 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1608 | 0 | } |
1609 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1610 | 0 | return rv; |
1611 | 0 | } |
1612 | 0 | currentPoint = pointAfterInsertedString; |
1613 | 0 | pointToInsert = pointAfterInsertedString; |
1614 | 0 | } |
1615 | 0 | } |
1616 | 0 | } |
1617 | 0 |
|
1618 | 0 | // After this block, pointToInsert is updated by AutoTrackDOMPoint. |
1619 | 0 | } |
1620 | 0 |
|
1621 | 0 | IgnoredErrorResult ignoredError; |
1622 | 0 | SelectionRef().SetInterlinePosition(false, ignoredError); |
1623 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
1624 | 0 | "Failed to unset interline position"); |
1625 | 0 |
|
1626 | 0 | if (currentPoint.IsSet()) { |
1627 | 0 | IgnoredErrorResult ignoredError; |
1628 | 0 | SelectionRef().Collapse(currentPoint, ignoredError); |
1629 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1630 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1631 | 0 | } |
1632 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
1633 | 0 | "Failed to collapse at current point"); |
1634 | 0 | } |
1635 | 0 |
|
1636 | 0 | // manually update the doc changed range so that AfterEdit will clean up |
1637 | 0 | // the correct portion of the document. |
1638 | 0 | if (!mDocChangeRange) { |
1639 | 0 | mDocChangeRange = new nsRange(pointToInsert.GetContainer()); |
1640 | 0 | } |
1641 | 0 |
|
1642 | 0 | if (currentPoint.IsSet()) { |
1643 | 0 | rv = mDocChangeRange->SetStartAndEnd(pointToInsert, currentPoint); |
1644 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1645 | 0 | return rv; |
1646 | 0 | } |
1647 | 0 | return NS_OK; |
1648 | 0 | } |
1649 | 0 | |
1650 | 0 | rv = mDocChangeRange->CollapseTo(pointToInsert); |
1651 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1652 | 0 | return rv; |
1653 | 0 | } |
1654 | 0 | return NS_OK; |
1655 | 0 | } |
1656 | | |
1657 | | nsresult |
1658 | | HTMLEditRules::WillLoadHTML() |
1659 | 0 | { |
1660 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
1661 | 0 |
|
1662 | 0 | // Delete mBogusNode if it exists. If we really need one, |
1663 | 0 | // it will be added during post-processing in AfterEditInner(). |
1664 | 0 |
|
1665 | 0 | if (mBogusNode) { |
1666 | 0 | DebugOnly<nsresult> rv = |
1667 | 0 | HTMLEditorRef().DeleteNodeWithTransaction(*mBogusNode); |
1668 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1669 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1670 | 0 | } |
1671 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
1672 | 0 | "Failed to remove the bogus node"); |
1673 | 0 | mBogusNode = nullptr; |
1674 | 0 | } |
1675 | 0 |
|
1676 | 0 | return NS_OK; |
1677 | 0 | } |
1678 | | |
1679 | | bool |
1680 | | HTMLEditRules::CanContainParagraph(Element& aElement) const |
1681 | 0 | { |
1682 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
1683 | 0 |
|
1684 | 0 | if (HTMLEditorRef().CanContainTag(aElement, *nsGkAtoms::p)) { |
1685 | 0 | return true; |
1686 | 0 | } |
1687 | 0 | |
1688 | 0 | // Even if the element cannot have a <p> element as a child, it can contain |
1689 | 0 | // <p> element as a descendant if it's one of the following elements. |
1690 | 0 | if (aElement.IsAnyOfHTMLElements(nsGkAtoms::ol, |
1691 | 0 | nsGkAtoms::ul, |
1692 | 0 | nsGkAtoms::dl, |
1693 | 0 | nsGkAtoms::table, |
1694 | 0 | nsGkAtoms::thead, |
1695 | 0 | nsGkAtoms::tbody, |
1696 | 0 | nsGkAtoms::tfoot, |
1697 | 0 | nsGkAtoms::tr)) { |
1698 | 0 | return true; |
1699 | 0 | } |
1700 | 0 | |
1701 | 0 | // XXX Otherwise, Chromium checks the CSS box is a block, but we don't do it |
1702 | 0 | // for now. |
1703 | 0 | return false; |
1704 | 0 | } |
1705 | | |
1706 | | nsresult |
1707 | | HTMLEditRules::WillInsertBreak(bool* aCancel, |
1708 | | bool* aHandled) |
1709 | 0 | { |
1710 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
1711 | 0 |
|
1712 | 0 | MOZ_ASSERT(aCancel && aHandled); |
1713 | 0 | *aCancel = false; |
1714 | 0 | *aHandled = false; |
1715 | 0 |
|
1716 | 0 | // If the selection isn't collapsed, delete it. |
1717 | 0 | if (!SelectionRef().IsCollapsed()) { |
1718 | 0 | nsresult rv = |
1719 | 0 | HTMLEditorRef().DeleteSelectionAsSubAction(nsIEditor::eNone, |
1720 | 0 | nsIEditor::eStrip); |
1721 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1722 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1723 | 0 | } |
1724 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1725 | 0 | return rv; |
1726 | 0 | } |
1727 | 0 | } |
1728 | 0 | |
1729 | 0 | // FYI: Ignore cancel result of WillInsert(). |
1730 | 0 | nsresult rv = WillInsert(); |
1731 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
1732 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1733 | 0 | } |
1734 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
1735 | 0 |
|
1736 | 0 | // Split any mailcites in the way. Should we abort this if we encounter |
1737 | 0 | // table cell boundaries? |
1738 | 0 | if (IsMailEditor()) { |
1739 | 0 | nsresult rv = SplitMailCites(aHandled); |
1740 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1741 | 0 | return rv; |
1742 | 0 | } |
1743 | 0 | if (*aHandled) { |
1744 | 0 | return NS_OK; |
1745 | 0 | } |
1746 | 0 | } |
1747 | 0 | |
1748 | 0 | // Smart splitting rules |
1749 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
1750 | 0 | if (NS_WARN_IF(!firstRange)) { |
1751 | 0 | return NS_ERROR_FAILURE; |
1752 | 0 | } |
1753 | 0 | |
1754 | 0 | EditorDOMPoint atStartOfSelection(firstRange->StartRef()); |
1755 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
1756 | 0 | return NS_ERROR_FAILURE; |
1757 | 0 | } |
1758 | 0 | MOZ_ASSERT(atStartOfSelection.IsSetAndValid()); |
1759 | 0 |
|
1760 | 0 | // Do nothing if the node is read-only |
1761 | 0 | if (!HTMLEditorRef().IsModifiableNode(*atStartOfSelection.GetContainer())) { |
1762 | 0 | *aCancel = true; |
1763 | 0 | return NS_OK; |
1764 | 0 | } |
1765 | 0 | |
1766 | 0 | // If the active editing host is an inline element, or if the active editing |
1767 | 0 | // host is the block parent itself and we're configured to use <br> as a |
1768 | 0 | // paragraph separator, just append a <br>. |
1769 | 0 | RefPtr<Element> host = HTMLEditorRef().GetActiveEditingHost(); |
1770 | 0 | if (NS_WARN_IF(!host)) { |
1771 | 0 | return NS_ERROR_FAILURE; |
1772 | 0 | } |
1773 | 0 | |
1774 | 0 | // Look for the nearest parent block. However, don't return error even if |
1775 | 0 | // there is no block parent here because in such case, i.e., editing host |
1776 | 0 | // is an inline element, we should insert <br> simply. |
1777 | 0 | RefPtr<Element> blockParent = |
1778 | 0 | HTMLEditor::GetBlock(*atStartOfSelection.GetContainer(), host); |
1779 | 0 |
|
1780 | 0 | ParagraphSeparator separator = HTMLEditorRef().GetDefaultParagraphSeparator(); |
1781 | 0 | bool insertBRElement; |
1782 | 0 | // If there is no block parent in the editing host, i.e., the editing host |
1783 | 0 | // itself is also a non-block element, we should insert a <br> element. |
1784 | 0 | if (!blockParent) { |
1785 | 0 | // XXX Chromium checks if the CSS box of the editing host is block. |
1786 | 0 | insertBRElement = true; |
1787 | 0 | } |
1788 | 0 | // If only the editing host is block, and the default paragraph separator |
1789 | 0 | // is <br> or the editing host cannot contain a <p> element, we should |
1790 | 0 | // insert a <br> element. |
1791 | 0 | else if (host == blockParent) { |
1792 | 0 | insertBRElement = |
1793 | 0 | separator == ParagraphSeparator::br || !CanContainParagraph(*host); |
1794 | 0 | } |
1795 | 0 | // If the nearest block parent is a single-line container declared in |
1796 | 0 | // the execCommand spec and not the editing host, we should separate the |
1797 | 0 | // block even if the default paragraph separator is <br> element. |
1798 | 0 | else if (HTMLEditUtils::IsSingleLineContainer(*blockParent)) { |
1799 | 0 | insertBRElement = false; |
1800 | 0 | } |
1801 | 0 | // Otherwise, unless there is no block ancestor which can contain <p> |
1802 | 0 | // element, we shouldn't insert a <br> element here. |
1803 | 0 | else { |
1804 | 0 | insertBRElement = true; |
1805 | 0 | for (Element* blockAncestor = blockParent; |
1806 | 0 | blockAncestor && insertBRElement; |
1807 | 0 | blockAncestor = HTMLEditor::GetBlockNodeParent(blockAncestor, host)) { |
1808 | 0 | insertBRElement = !CanContainParagraph(*blockAncestor); |
1809 | 0 | } |
1810 | 0 | } |
1811 | 0 |
|
1812 | 0 | // If we cannot insert a <p>/<div> element at the selection, we should insert |
1813 | 0 | // a <br> element instead. |
1814 | 0 | if (insertBRElement) { |
1815 | 0 | nsresult rv = InsertBRElement(atStartOfSelection); |
1816 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1817 | 0 | return rv; |
1818 | 0 | } |
1819 | 0 | *aHandled = true; |
1820 | 0 | return NS_OK; |
1821 | 0 | } |
1822 | 0 | |
1823 | 0 | if (host == blockParent && separator != ParagraphSeparator::br) { |
1824 | 0 | // Insert a new block first |
1825 | 0 | MOZ_ASSERT(separator == ParagraphSeparator::div || |
1826 | 0 | separator == ParagraphSeparator::p); |
1827 | 0 | // MakeBasicBlock() creates AutoSelectionRestorer. |
1828 | 0 | // Therefore, even if it returns NS_OK, editor might have been destroyed |
1829 | 0 | // at restoring Selection. |
1830 | 0 | nsresult rv = MakeBasicBlock(ParagraphSeparatorElement(separator)); |
1831 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) || |
1832 | 0 | NS_WARN_IF(!CanHandleEditAction())) { |
1833 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1834 | 0 | } |
1835 | 0 | // We warn on failure, but don't handle it, because it might be harmless. |
1836 | 0 | // Instead we just check that a new block was actually created. |
1837 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
1838 | 0 | "HTMLEditRules::MakeBasicBlock() failed"); |
1839 | 0 |
|
1840 | 0 | firstRange = SelectionRef().GetRangeAt(0); |
1841 | 0 | if (NS_WARN_IF(!firstRange)) { |
1842 | 0 | return NS_ERROR_FAILURE; |
1843 | 0 | } |
1844 | 0 | |
1845 | 0 | atStartOfSelection = firstRange->StartRef(); |
1846 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
1847 | 0 | return NS_ERROR_FAILURE; |
1848 | 0 | } |
1849 | 0 | MOZ_ASSERT(atStartOfSelection.IsSetAndValid()); |
1850 | 0 |
|
1851 | 0 | blockParent = |
1852 | 0 | HTMLEditor::GetBlock(*atStartOfSelection.GetContainer(), host); |
1853 | 0 | if (NS_WARN_IF(!blockParent)) { |
1854 | 0 | return NS_ERROR_UNEXPECTED; |
1855 | 0 | } |
1856 | 0 | if (NS_WARN_IF(blockParent == host)) { |
1857 | 0 | // Didn't create a new block for some reason, fall back to <br> |
1858 | 0 | rv = InsertBRElement(atStartOfSelection); |
1859 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1860 | 0 | return rv; |
1861 | 0 | } |
1862 | 0 | *aHandled = true; |
1863 | 0 | return NS_OK; |
1864 | 0 | } |
1865 | 0 | // Now, mNewBlock is last created block element for wrapping inline |
1866 | 0 | // elements around the caret position and AfterEditInner() will move |
1867 | 0 | // caret into it. However, it may be different from block parent of |
1868 | 0 | // the caret position. E.g., MakeBasicBlock() may wrap following |
1869 | 0 | // inline elements of a <br> element which is next sibling of container |
1870 | 0 | // of the caret. So, we need to adjust mNewBlock here for avoiding |
1871 | 0 | // jumping caret to odd position. |
1872 | 0 | mNewBlock = blockParent; |
1873 | 0 | } |
1874 | 0 |
|
1875 | 0 | // If block is empty, populate with br. (For example, imagine a div that |
1876 | 0 | // contains the word "text". The user selects "text" and types return. |
1877 | 0 | // "Text" is deleted leaving an empty block. We want to put in one br to |
1878 | 0 | // make block have a line. Then code further below will put in a second br.) |
1879 | 0 | if (IsEmptyBlockElement(*blockParent, IgnoreSingleBR::eNo)) { |
1880 | 0 | AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection); |
1881 | 0 | EditorRawDOMPoint endOfBlockParent; |
1882 | 0 | endOfBlockParent.SetToEndOf(blockParent); |
1883 | 0 | RefPtr<Element> brElement = |
1884 | 0 | HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(), |
1885 | 0 | endOfBlockParent); |
1886 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1887 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1888 | 0 | } |
1889 | 0 | if (NS_WARN_IF(!brElement)) { |
1890 | 0 | return NS_ERROR_FAILURE; |
1891 | 0 | } |
1892 | 0 | } |
1893 | 0 | |
1894 | 0 | nsCOMPtr<Element> listItem = IsInListItem(blockParent); |
1895 | 0 | if (listItem && listItem != host) { |
1896 | 0 | nsresult rv = |
1897 | 0 | ReturnInListItem(*listItem, *atStartOfSelection.GetContainer(), |
1898 | 0 | atStartOfSelection.Offset()); |
1899 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
1900 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1901 | 0 | } |
1902 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
1903 | 0 | "Failed to insert break into list item"); |
1904 | 0 | *aHandled = true; |
1905 | 0 | return NS_OK; |
1906 | 0 | } |
1907 | 0 | |
1908 | 0 | if (HTMLEditUtils::IsHeader(*blockParent)) { |
1909 | 0 | // Headers: close (or split) header |
1910 | 0 | nsresult rv = |
1911 | 0 | ReturnInHeader(*blockParent, *atStartOfSelection.GetContainer(), |
1912 | 0 | atStartOfSelection.Offset()); |
1913 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
1914 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1915 | 0 | } |
1916 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
1917 | 0 | "Failed to handle insertParagraph in the heading element"); |
1918 | 0 | *aHandled = true; |
1919 | 0 | return NS_OK; |
1920 | 0 | } |
1921 | 0 | |
1922 | 0 | // XXX Ideally, we should take same behavior with both <p> container and |
1923 | 0 | // <div> container. However, we are still using <br> as default |
1924 | 0 | // paragraph separator (non-standard) and we've split only <p> container |
1925 | 0 | // long time. Therefore, some web apps may depend on this behavior like |
1926 | 0 | // Gmail. So, let's use traditional odd behavior only when the default |
1927 | 0 | // paragraph separator is <br>. Otherwise, take consistent behavior |
1928 | 0 | // between <p> container and <div> container. |
1929 | 0 | if ((separator == ParagraphSeparator::br && |
1930 | 0 | blockParent->IsHTMLElement(nsGkAtoms::p)) || |
1931 | 0 | (separator != ParagraphSeparator::br && |
1932 | 0 | blockParent->IsAnyOfHTMLElements(nsGkAtoms::p, nsGkAtoms::div))) { |
1933 | 0 | AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection); |
1934 | 0 | // Paragraphs: special rules to look for <br>s |
1935 | 0 | EditActionResult result = ReturnInParagraph(*blockParent); |
1936 | 0 | if (NS_WARN_IF(result.Failed())) { |
1937 | 0 | return result.Rv(); |
1938 | 0 | } |
1939 | 0 | *aHandled = result.Handled(); |
1940 | 0 | *aCancel = result.Canceled(); |
1941 | 0 | if (result.Handled()) { |
1942 | 0 | // Now, atStartOfSelection may be invalid because the left paragraph |
1943 | 0 | // may have less children than its offset. For avoiding warnings of |
1944 | 0 | // validation of EditorDOMPoint, we should not touch it anymore. |
1945 | 0 | lockOffset.Cancel(); |
1946 | 0 | return NS_OK; |
1947 | 0 | } |
1948 | 0 | // Fall through, if ReturnInParagraph() didn't handle it. |
1949 | 0 | MOZ_ASSERT(!*aCancel, "ReturnInParagraph canceled this edit action, " |
1950 | 0 | "WillInsertBreak() needs to handle such case"); |
1951 | 0 | } |
1952 | 0 |
|
1953 | 0 | // If nobody handles this edit action, let's insert new <br> at the selection. |
1954 | 0 | MOZ_ASSERT(!*aHandled, "Reached last resort of WillInsertBreak() " |
1955 | 0 | "after the edit action is handled"); |
1956 | 0 | rv = InsertBRElement(atStartOfSelection); |
1957 | 0 | *aHandled = true; |
1958 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
1959 | 0 | return rv; |
1960 | 0 | } |
1961 | 0 | return NS_OK; |
1962 | 0 | } |
1963 | | |
1964 | | nsresult |
1965 | | HTMLEditRules::InsertBRElement(const EditorDOMPoint& aPointToBreak) |
1966 | 0 | { |
1967 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
1968 | 0 |
|
1969 | 0 | if (NS_WARN_IF(!aPointToBreak.IsSet())) { |
1970 | 0 | return NS_ERROR_INVALID_ARG; |
1971 | 0 | } |
1972 | 0 | |
1973 | 0 | bool brElementIsAfterBlock = false; |
1974 | 0 | bool brElementIsBeforeBlock = false; |
1975 | 0 |
|
1976 | 0 | // First, insert a <br> element. |
1977 | 0 | RefPtr<Element> brElement; |
1978 | 0 | if (IsPlaintextEditor()) { |
1979 | 0 | brElement = |
1980 | 0 | HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(), |
1981 | 0 | aPointToBreak); |
1982 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
1983 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
1984 | 0 | } |
1985 | 0 | if (NS_WARN_IF(!brElement)) { |
1986 | 0 | return NS_ERROR_FAILURE; |
1987 | 0 | } |
1988 | 0 | } else { |
1989 | 0 | EditorDOMPoint pointToBreak(aPointToBreak); |
1990 | 0 | WSRunObject wsObj(&HTMLEditorRef(), pointToBreak); |
1991 | 0 | WSType wsType; |
1992 | 0 | wsObj.PriorVisibleNode(pointToBreak, &wsType); |
1993 | 0 | if (wsType & WSType::block) { |
1994 | 0 | brElementIsAfterBlock = true; |
1995 | 0 | } |
1996 | 0 | wsObj.NextVisibleNode(pointToBreak, &wsType); |
1997 | 0 | if (wsType & WSType::block) { |
1998 | 0 | brElementIsBeforeBlock = true; |
1999 | 0 | } |
2000 | 0 | // If the container of the break is a link, we need to split it and |
2001 | 0 | // insert new <br> between the split links. |
2002 | 0 | RefPtr<Element> linkNode = |
2003 | 0 | HTMLEditor::GetLinkElement(pointToBreak.GetContainer()); |
2004 | 0 | if (linkNode) { |
2005 | 0 | SplitNodeResult splitLinkNodeResult = |
2006 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
2007 | 0 | *linkNode, pointToBreak, |
2008 | 0 | SplitAtEdges::eDoNotCreateEmptyContainer); |
2009 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2010 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2011 | 0 | } |
2012 | 0 | if (NS_WARN_IF(splitLinkNodeResult.Failed())) { |
2013 | 0 | return splitLinkNodeResult.Rv(); |
2014 | 0 | } |
2015 | 0 | pointToBreak = splitLinkNodeResult.SplitPoint(); |
2016 | 0 | } |
2017 | 0 | brElement = |
2018 | 0 | wsObj.InsertBreak(SelectionRef(), pointToBreak, nsIEditor::eNone); |
2019 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2020 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2021 | 0 | } |
2022 | 0 | if (NS_WARN_IF(!brElement)) { |
2023 | 0 | return NS_ERROR_FAILURE; |
2024 | 0 | } |
2025 | 0 | } |
2026 | 0 | |
2027 | 0 | // If the <br> element has already been removed from the DOM tree by a |
2028 | 0 | // mutation observer, don't continue handling this. |
2029 | 0 | if (NS_WARN_IF(!brElement->GetParentNode())) { |
2030 | 0 | return NS_ERROR_FAILURE; |
2031 | 0 | } |
2032 | 0 | |
2033 | 0 | if (brElementIsAfterBlock && brElementIsBeforeBlock) { |
2034 | 0 | // We just placed a <br> between block boundaries. This is the one case |
2035 | 0 | // where we want the selection to be before the br we just placed, as the |
2036 | 0 | // br will be on a new line, rather than at end of prior line. |
2037 | 0 | // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before |
2038 | 0 | // modifying the DOM tree. So, now, the <br> element may not be |
2039 | 0 | // between blocks. |
2040 | 0 | ErrorResult error; |
2041 | 0 | SelectionRef().SetInterlinePosition(true, error); |
2042 | 0 | NS_WARNING_ASSERTION(!error.Failed(), "Failed to set interline position"); |
2043 | 0 | EditorRawDOMPoint point(brElement); |
2044 | 0 | error = NS_OK; |
2045 | 0 | SelectionRef().Collapse(point, error); |
2046 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2047 | 0 | error.SuppressException(); |
2048 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2049 | 0 | } |
2050 | 0 | if (NS_WARN_IF(error.Failed())) { |
2051 | 0 | return error.StealNSResult(); |
2052 | 0 | } |
2053 | 0 | return NS_OK; |
2054 | 0 | } |
2055 | 0 | |
2056 | 0 | EditorDOMPoint afterBRElement(brElement); |
2057 | 0 | DebugOnly<bool> advanced = afterBRElement.AdvanceOffset(); |
2058 | 0 | NS_WARNING_ASSERTION(advanced, |
2059 | 0 | "Failed to advance offset after the new <br> element"); |
2060 | 0 | WSRunObject wsObj(&HTMLEditorRef(), afterBRElement); |
2061 | 0 | nsCOMPtr<nsINode> maybeSecondBRNode; |
2062 | 0 | WSType wsType; |
2063 | 0 | wsObj.NextVisibleNode(afterBRElement, |
2064 | 0 | address_of(maybeSecondBRNode), nullptr, &wsType); |
2065 | 0 | if (wsType == WSType::br) { |
2066 | 0 | // The next thing after the break we inserted is another break. Move the |
2067 | 0 | // second break to be the first break's sibling. This will prevent them |
2068 | 0 | // from being in different inline nodes, which would break |
2069 | 0 | // SetInterlinePosition(). It will also assure that if the user clicks |
2070 | 0 | // away and then clicks back on their new blank line, they will still get |
2071 | 0 | // the style from the line above. |
2072 | 0 | EditorDOMPoint atSecondBRElement(maybeSecondBRNode); |
2073 | 0 | if (brElement->GetNextSibling() != maybeSecondBRNode) { |
2074 | 0 | nsresult rv = |
2075 | 0 | HTMLEditorRef().MoveNodeWithTransaction(*maybeSecondBRNode->AsContent(), |
2076 | 0 | afterBRElement); |
2077 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2078 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2079 | 0 | } |
2080 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2081 | 0 | return rv; |
2082 | 0 | } |
2083 | 0 | } |
2084 | 0 | } |
2085 | 0 | |
2086 | 0 | // SetInterlinePosition(true) means we want the caret to stick to the |
2087 | 0 | // content on the "right". We want the caret to stick to whatever is past |
2088 | 0 | // the break. This is because the break is on the same line we were on, |
2089 | 0 | // but the next content will be on the following line. |
2090 | 0 | |
2091 | 0 | // An exception to this is if the break has a next sibling that is a block |
2092 | 0 | // node. Then we stick to the left to avoid an uber caret. |
2093 | 0 | nsIContent* nextSiblingOfBRElement = brElement->GetNextSibling(); |
2094 | 0 | ErrorResult error; |
2095 | 0 | SelectionRef().SetInterlinePosition(!(nextSiblingOfBRElement && |
2096 | 0 | IsBlockNode(*nextSiblingOfBRElement)), |
2097 | 0 | error); |
2098 | 0 | NS_WARNING_ASSERTION(!error.Failed(), |
2099 | 0 | "Failed to set or unset interline position"); |
2100 | 0 | error = NS_OK; |
2101 | 0 | SelectionRef().Collapse(afterBRElement, error); |
2102 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2103 | 0 | error.SuppressException(); |
2104 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2105 | 0 | } |
2106 | 0 | if (NS_WARN_IF(error.Failed())) { |
2107 | 0 | return error.StealNSResult(); |
2108 | 0 | } |
2109 | 0 | return NS_OK; |
2110 | 0 | } |
2111 | | |
2112 | | nsresult |
2113 | | HTMLEditRules::SplitMailCites(bool* aHandled) |
2114 | 0 | { |
2115 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
2116 | 0 |
|
2117 | 0 | if (NS_WARN_IF(!aHandled)) { |
2118 | 0 | return NS_ERROR_INVALID_ARG; |
2119 | 0 | } |
2120 | 0 | |
2121 | 0 | EditorRawDOMPoint pointToSplit(EditorBase::GetStartPoint(&SelectionRef())); |
2122 | 0 | if (NS_WARN_IF(!pointToSplit.IsSet())) { |
2123 | 0 | return NS_ERROR_FAILURE; |
2124 | 0 | } |
2125 | 0 | |
2126 | 0 | RefPtr<Element> citeNode = |
2127 | 0 | GetTopEnclosingMailCite(*pointToSplit.GetContainer()); |
2128 | 0 | if (!citeNode) { |
2129 | 0 | return NS_OK; |
2130 | 0 | } |
2131 | 0 | |
2132 | 0 | // If our selection is just before a break, nudge it to be just after it. |
2133 | 0 | // This does two things for us. It saves us the trouble of having to add |
2134 | 0 | // a break here ourselves to preserve the "blockness" of the inline span |
2135 | 0 | // mailquote (in the inline case), and : |
2136 | 0 | // it means the break won't end up making an empty line that happens to be |
2137 | 0 | // inside a mailquote (in either inline or block case). |
2138 | 0 | // The latter can confuse a user if they click there and start typing, |
2139 | 0 | // because being in the mailquote may affect wrapping behavior, or font |
2140 | 0 | // color, etc. |
2141 | 0 | WSRunObject wsObj(&HTMLEditorRef(), pointToSplit); |
2142 | 0 | nsCOMPtr<nsINode> visNode; |
2143 | 0 | WSType wsType; |
2144 | 0 | wsObj.NextVisibleNode(pointToSplit, address_of(visNode), nullptr, &wsType); |
2145 | 0 | // If selection start point is before a break and it's inside the mailquote, |
2146 | 0 | // let's split it after the visible node. |
2147 | 0 | if (wsType == WSType::br && |
2148 | 0 | visNode != citeNode && citeNode->Contains(visNode)) { |
2149 | 0 | pointToSplit.Set(visNode); |
2150 | 0 | DebugOnly<bool> advanced = pointToSplit.AdvanceOffset(); |
2151 | 0 | NS_WARNING_ASSERTION(advanced, |
2152 | 0 | "Failed to advance offset to after the visible node"); |
2153 | 0 | } |
2154 | 0 |
|
2155 | 0 | if (NS_WARN_IF(!pointToSplit.GetContainerAsContent())) { |
2156 | 0 | return NS_ERROR_FAILURE; |
2157 | 0 | } |
2158 | 0 | |
2159 | 0 | SplitNodeResult splitCiteNodeResult = |
2160 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
2161 | 0 | *citeNode, pointToSplit, |
2162 | 0 | SplitAtEdges::eDoNotCreateEmptyContainer); |
2163 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2164 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2165 | 0 | } |
2166 | 0 | if (NS_WARN_IF(splitCiteNodeResult.Failed())) { |
2167 | 0 | return splitCiteNodeResult.Rv(); |
2168 | 0 | } |
2169 | 0 | pointToSplit.Clear(); |
2170 | 0 |
|
2171 | 0 | // Add an invisible <br> to the end of current cite node (If new left cite |
2172 | 0 | // has not been created, we're at the end of it. Otherwise, we're still at |
2173 | 0 | // the right node) if it was a <span> of style="display: block". This is |
2174 | 0 | // important, since when serializing the cite to plain text, the span which |
2175 | 0 | // caused the visual break is discarded. So the added <br> will guarantee |
2176 | 0 | // that the serializer will insert a break where the user saw one. |
2177 | 0 | // FYI: splitCiteNodeResult grabs the previous node with nsCOMPtr. So, it's |
2178 | 0 | // safe to access previousNodeOfSplitPoint even after changing the DOM |
2179 | 0 | // tree and/or selection even though it's raw pointer. |
2180 | 0 | nsIContent* previousNodeOfSplitPoint = |
2181 | 0 | splitCiteNodeResult.GetPreviousNode(); |
2182 | 0 | if (previousNodeOfSplitPoint && |
2183 | 0 | previousNodeOfSplitPoint->IsHTMLElement(nsGkAtoms::span) && |
2184 | 0 | previousNodeOfSplitPoint->GetPrimaryFrame() && |
2185 | 0 | previousNodeOfSplitPoint->GetPrimaryFrame()-> |
2186 | 0 | IsFrameOfType(nsIFrame::eBlockFrame)) { |
2187 | 0 | nsCOMPtr<nsINode> lastChild = |
2188 | 0 | previousNodeOfSplitPoint->GetLastChild(); |
2189 | 0 | if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) { |
2190 | 0 | // We ignore the result here. |
2191 | 0 | EditorRawDOMPoint endOfPreviousNodeOfSplitPoint; |
2192 | 0 | endOfPreviousNodeOfSplitPoint.SetToEndOf(previousNodeOfSplitPoint); |
2193 | 0 | RefPtr<Element> invisibleBrElement = |
2194 | 0 | HTMLEditorRef().InsertBrElementWithTransaction( |
2195 | 0 | SelectionRef(), |
2196 | 0 | endOfPreviousNodeOfSplitPoint); |
2197 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2198 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2199 | 0 | } |
2200 | 0 | NS_WARNING_ASSERTION(invisibleBrElement, |
2201 | 0 | "Failed to create an invisible <br> element"); |
2202 | 0 | } |
2203 | 0 | } |
2204 | 0 |
|
2205 | 0 | // In most cases, <br> should be inserted after current cite. However, if |
2206 | 0 | // left cite hasn't been created because the split point was start of the |
2207 | 0 | // cite node, <br> should be inserted before the current cite. |
2208 | 0 | EditorRawDOMPoint pointToInsertBrNode(splitCiteNodeResult.SplitPoint()); |
2209 | 0 | RefPtr<Element> brElement = |
2210 | 0 | HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(), |
2211 | 0 | pointToInsertBrNode); |
2212 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2213 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2214 | 0 | } |
2215 | 0 | if (NS_WARN_IF(!brElement)) { |
2216 | 0 | return NS_ERROR_FAILURE; |
2217 | 0 | } |
2218 | 0 | // Now, offset of pointToInsertBrNode is invalid. Let's clear it. |
2219 | 0 | pointToInsertBrNode.Clear(); |
2220 | 0 |
|
2221 | 0 | // Want selection before the break, and on same line. |
2222 | 0 | EditorDOMPoint atBrNode(brElement); |
2223 | 0 | Unused << atBrNode.Offset(); // Needs offset after collapsing the selection. |
2224 | 0 | ErrorResult error; |
2225 | 0 | SelectionRef().SetInterlinePosition(true, error); |
2226 | 0 | NS_WARNING_ASSERTION(!error.Failed(), |
2227 | 0 | "Failed to set interline position"); |
2228 | 0 | error = NS_OK; |
2229 | 0 | SelectionRef().Collapse(atBrNode, error); |
2230 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2231 | 0 | error.SuppressException(); |
2232 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2233 | 0 | } |
2234 | 0 | if (NS_WARN_IF(error.Failed())) { |
2235 | 0 | return error.StealNSResult(); |
2236 | 0 | } |
2237 | 0 | |
2238 | 0 | // if citeNode wasn't a block, we might also want another break before it. |
2239 | 0 | // We need to examine the content both before the br we just added and also |
2240 | 0 | // just after it. If we don't have another br or block boundary adjacent, |
2241 | 0 | // then we will need a 2nd br added to achieve blank line that user expects. |
2242 | 0 | if (IsInlineNode(*citeNode)) { |
2243 | 0 | // Use DOM point which we tried to collapse to. |
2244 | 0 | EditorRawDOMPoint pointToCreateNewBrNode(atBrNode.GetContainer(), |
2245 | 0 | atBrNode.Offset()); |
2246 | 0 |
|
2247 | 0 | WSRunObject wsObj(&HTMLEditorRef(), pointToCreateNewBrNode); |
2248 | 0 | WSType wsType; |
2249 | 0 | wsObj.PriorVisibleNode(pointToCreateNewBrNode, nullptr, nullptr, |
2250 | 0 | &wsType); |
2251 | 0 | if (wsType == WSType::normalWS || wsType == WSType::text || |
2252 | 0 | wsType == WSType::special) { |
2253 | 0 | EditorRawDOMPoint pointAfterNewBrNode(pointToCreateNewBrNode); |
2254 | 0 | DebugOnly<bool> advanced = pointAfterNewBrNode.AdvanceOffset(); |
2255 | 0 | NS_WARNING_ASSERTION(advanced, |
2256 | 0 | "Failed to advance offset after the <br> node"); |
2257 | 0 | WSRunObject wsObjAfterBR(&HTMLEditorRef(), pointAfterNewBrNode); |
2258 | 0 | wsObjAfterBR.NextVisibleNode(pointAfterNewBrNode, &wsType); |
2259 | 0 | if (wsType == WSType::normalWS || wsType == WSType::text || |
2260 | 0 | wsType == WSType::special || |
2261 | 0 | // In case we're at the very end. |
2262 | 0 | wsType == WSType::thisBlock) { |
2263 | 0 | brElement = |
2264 | 0 | HTMLEditorRef().InsertBrElementWithTransaction( |
2265 | 0 | SelectionRef(), pointToCreateNewBrNode); |
2266 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2267 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2268 | 0 | } |
2269 | 0 | if (NS_WARN_IF(!brElement)) { |
2270 | 0 | return NS_ERROR_FAILURE; |
2271 | 0 | } |
2272 | 0 | // Now, those points may be invalid. |
2273 | 0 | pointToCreateNewBrNode.Clear(); |
2274 | 0 | pointAfterNewBrNode.Clear(); |
2275 | 0 | } |
2276 | 0 | } |
2277 | 0 | } |
2278 | 0 |
|
2279 | 0 | // delete any empty cites |
2280 | 0 | bool bEmptyCite = false; |
2281 | 0 | if (previousNodeOfSplitPoint) { |
2282 | 0 | nsresult rv = |
2283 | 0 | HTMLEditorRef().IsEmptyNode(previousNodeOfSplitPoint, &bEmptyCite, |
2284 | 0 | true, false); |
2285 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2286 | 0 | return rv; |
2287 | 0 | } |
2288 | 0 | if (bEmptyCite) { |
2289 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*previousNodeOfSplitPoint); |
2290 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2291 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2292 | 0 | } |
2293 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2294 | 0 | return rv; |
2295 | 0 | } |
2296 | 0 | } |
2297 | 0 | } |
2298 | 0 | |
2299 | 0 | if (citeNode) { |
2300 | 0 | nsresult rv = |
2301 | 0 | HTMLEditorRef().IsEmptyNode(citeNode, &bEmptyCite, true, false); |
2302 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2303 | 0 | return rv; |
2304 | 0 | } |
2305 | 0 | if (bEmptyCite) { |
2306 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*citeNode); |
2307 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2308 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2309 | 0 | } |
2310 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2311 | 0 | return rv; |
2312 | 0 | } |
2313 | 0 | } |
2314 | 0 | } |
2315 | 0 | |
2316 | 0 | *aHandled = true; |
2317 | 0 | return NS_OK; |
2318 | 0 | } |
2319 | | |
2320 | | |
2321 | | nsresult |
2322 | | HTMLEditRules::WillDeleteSelection(nsIEditor::EDirection aAction, |
2323 | | nsIEditor::EStripWrappers aStripWrappers, |
2324 | | bool* aCancel, |
2325 | | bool* aHandled) |
2326 | 0 | { |
2327 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
2328 | 0 | MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip || |
2329 | 0 | aStripWrappers == nsIEditor::eNoStrip); |
2330 | 0 |
|
2331 | 0 | if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) { |
2332 | 0 | return NS_ERROR_INVALID_ARG; |
2333 | 0 | } |
2334 | 0 | // Initialize out params |
2335 | 0 | *aCancel = false; |
2336 | 0 | *aHandled = false; |
2337 | 0 |
|
2338 | 0 | // Remember that we did a selection deletion. Used by |
2339 | 0 | // CreateStyleForInsertText() |
2340 | 0 | mDidDeleteSelection = true; |
2341 | 0 |
|
2342 | 0 | // If there is only bogus content, cancel the operation |
2343 | 0 | if (mBogusNode) { |
2344 | 0 | *aCancel = true; |
2345 | 0 | return NS_OK; |
2346 | 0 | } |
2347 | 0 | |
2348 | 0 | // First check for table selection mode. If so, hand off to table editor. |
2349 | 0 | ErrorResult error; |
2350 | 0 | RefPtr<Element> cellElement = |
2351 | 0 | HTMLEditorRef().GetFirstSelectedTableCellElement(SelectionRef(), |
2352 | 0 | error); |
2353 | 0 | if (cellElement) { |
2354 | 0 | error.SuppressException(); |
2355 | 0 | nsresult rv = HTMLEditorRef().DeleteTableCellContentsWithTransaction(); |
2356 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2357 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2358 | 0 | } |
2359 | 0 | *aHandled = true; |
2360 | 0 | return rv; |
2361 | 0 | } |
2362 | 0 | nsresult rv = error.StealNSResult(); |
2363 | 0 | cellElement = nullptr; |
2364 | 0 |
|
2365 | 0 | // origCollapsed is used later to determine whether we should join blocks. We |
2366 | 0 | // don't really care about bCollapsed because it will be modified by |
2367 | 0 | // ExtendSelectionForDelete later. TryToJoinBlocksWithTransaction() should |
2368 | 0 | // happen if the original selection is collapsed and the cursor is at the end |
2369 | 0 | // of a block element, in which case ExtendSelectionForDelete would always |
2370 | 0 | // make the selection not collapsed. |
2371 | 0 | bool join = false; |
2372 | 0 | bool origCollapsed = SelectionRef().IsCollapsed(); |
2373 | 0 |
|
2374 | 0 | if (origCollapsed) { |
2375 | 0 | EditorDOMPoint startPoint(EditorBase::GetStartPoint(&SelectionRef())); |
2376 | 0 | if (NS_WARN_IF(!startPoint.IsSet())) { |
2377 | 0 | return NS_ERROR_FAILURE; |
2378 | 0 | } |
2379 | 0 | |
2380 | 0 | // If we are inside an empty block, delete it. |
2381 | 0 | RefPtr<Element> host = HTMLEditorRef().GetActiveEditingHost(); |
2382 | 0 | if (NS_WARN_IF(!host)) { |
2383 | 0 | return NS_ERROR_FAILURE; |
2384 | 0 | } |
2385 | 0 | |
2386 | 0 | { |
2387 | 0 | AutoEditorDOMPointChildInvalidator lockOffset(startPoint); |
2388 | 0 |
|
2389 | 0 | rv = MaybeDeleteTopMostEmptyAncestor(*startPoint.GetContainer(), *host, |
2390 | 0 | aAction, aHandled); |
2391 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2392 | 0 | return rv; |
2393 | 0 | } |
2394 | 0 | if (*aHandled) { |
2395 | 0 | return NS_OK; |
2396 | 0 | } |
2397 | 0 | } |
2398 | 0 | |
2399 | 0 | // Test for distance between caret and text that will be deleted |
2400 | 0 | rv = CheckBidiLevelForDeletion(startPoint, aAction, aCancel); |
2401 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2402 | 0 | return rv; |
2403 | 0 | } |
2404 | 0 | if (*aCancel) { |
2405 | 0 | return NS_OK; |
2406 | 0 | } |
2407 | 0 | |
2408 | 0 | rv = HTMLEditorRef().ExtendSelectionForDelete(&SelectionRef(), &aAction); |
2409 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2410 | 0 | return rv; |
2411 | 0 | } |
2412 | 0 | |
2413 | 0 | // We should delete nothing. |
2414 | 0 | if (aAction == nsIEditor::eNone) { |
2415 | 0 | return NS_OK; |
2416 | 0 | } |
2417 | 0 | } |
2418 | 0 | |
2419 | 0 | if (SelectionRef().IsCollapsed()) { |
2420 | 0 | // ExtendSelectionForDelete() won't change the selection. |
2421 | 0 |
|
2422 | 0 | EditorDOMPoint startPoint(EditorBase::GetStartPoint(&SelectionRef())); |
2423 | 0 | if (NS_WARN_IF(!startPoint.IsSet())) { |
2424 | 0 | return NS_ERROR_FAILURE; |
2425 | 0 | } |
2426 | 0 | |
2427 | 0 | // What's in the direction we are deleting? |
2428 | 0 | WSRunObject wsObj(&HTMLEditorRef(), startPoint); |
2429 | 0 | nsCOMPtr<nsINode> visNode; |
2430 | 0 | int32_t visOffset; |
2431 | 0 | WSType wsType; |
2432 | 0 |
|
2433 | 0 | // Find next visible node |
2434 | 0 | if (aAction == nsIEditor::eNext) { |
2435 | 0 | wsObj.NextVisibleNode(startPoint, |
2436 | 0 | address_of(visNode), &visOffset, &wsType); |
2437 | 0 | } else { |
2438 | 0 | wsObj.PriorVisibleNode(startPoint, |
2439 | 0 | address_of(visNode), &visOffset, &wsType); |
2440 | 0 | } |
2441 | 0 |
|
2442 | 0 | if (!visNode) { |
2443 | 0 | // Can't find anything to delete! |
2444 | 0 | *aCancel = true; |
2445 | 0 | // XXX This is the result of |
2446 | 0 | // HTMLEditorRef().GetFirstSelectedTableCellElement(). |
2447 | 0 | // The value could be both an error and NS_OK. |
2448 | 0 | return rv; |
2449 | 0 | } |
2450 | 0 | |
2451 | 0 | if (wsType == WSType::normalWS) { |
2452 | 0 | // We found some visible ws to delete. Let ws code handle it. |
2453 | 0 | *aHandled = true; |
2454 | 0 | if (aAction == nsIEditor::eNext) { |
2455 | 0 | rv = wsObj.DeleteWSForward(); |
2456 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2457 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2458 | 0 | } |
2459 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2460 | 0 | return rv; |
2461 | 0 | } |
2462 | 0 | } else { |
2463 | 0 | rv = wsObj.DeleteWSBackward(); |
2464 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2465 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2466 | 0 | } |
2467 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2468 | 0 | return rv; |
2469 | 0 | } |
2470 | 0 | } |
2471 | 0 | rv = InsertBRIfNeeded(); |
2472 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2473 | 0 | return rv; |
2474 | 0 | } |
2475 | 0 | return NS_OK; |
2476 | 0 | } |
2477 | 0 | |
2478 | 0 | if (wsType == WSType::text) { |
2479 | 0 | // Found normal text to delete. |
2480 | 0 | OwningNonNull<Text> nodeAsText = *visNode->GetAsText(); |
2481 | 0 | int32_t so = visOffset; |
2482 | 0 | int32_t eo = visOffset + 1; |
2483 | 0 | if (aAction == nsIEditor::ePrevious) { |
2484 | 0 | if (!so) { |
2485 | 0 | return NS_ERROR_UNEXPECTED; |
2486 | 0 | } |
2487 | 0 | so--; |
2488 | 0 | eo--; |
2489 | 0 | // Bug 1068979: delete both codepoints if surrogate pair |
2490 | 0 | if (so > 0) { |
2491 | 0 | const nsTextFragment *text = nodeAsText->GetText(); |
2492 | 0 | if (NS_IS_LOW_SURROGATE(text->CharAt(so)) && |
2493 | 0 | NS_IS_HIGH_SURROGATE(text->CharAt(so - 1))) { |
2494 | 0 | so--; |
2495 | 0 | } |
2496 | 0 | } |
2497 | 0 | } else { |
2498 | 0 | RefPtr<nsRange> range = SelectionRef().GetRangeAt(0); |
2499 | 0 | if (NS_WARN_IF(!range)) { |
2500 | 0 | return NS_ERROR_FAILURE; |
2501 | 0 | } |
2502 | 0 | |
2503 | 0 | NS_ASSERTION(range->GetStartContainer() == visNode, |
2504 | 0 | "selection start not in visNode"); |
2505 | 0 | NS_ASSERTION(range->GetEndContainer() == visNode, |
2506 | 0 | "selection end not in visNode"); |
2507 | 0 |
|
2508 | 0 | so = range->StartOffset(); |
2509 | 0 | eo = range->EndOffset(); |
2510 | 0 | } |
2511 | 0 | rv = WSRunObject::PrepareToDeleteRange(&HTMLEditorRef(), |
2512 | 0 | address_of(visNode), |
2513 | 0 | &so, address_of(visNode), &eo); |
2514 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2515 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2516 | 0 | } |
2517 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2518 | 0 | return rv; |
2519 | 0 | } |
2520 | 0 | *aHandled = true; |
2521 | 0 | rv = HTMLEditorRef().DeleteTextWithTransaction(nodeAsText, |
2522 | 0 | std::min(so, eo), |
2523 | 0 | DeprecatedAbs(eo - so)); |
2524 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2525 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2526 | 0 | } |
2527 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2528 | 0 | return rv; |
2529 | 0 | } |
2530 | 0 | |
2531 | 0 | // XXX When Backspace key is pressed, Chromium removes following empty |
2532 | 0 | // text nodes when removing the last character of the non-empty text |
2533 | 0 | // node. However, Edge never removes empty text nodes even if |
2534 | 0 | // selection is in the following empty text node(s). For now, we |
2535 | 0 | // should keep our traditional behavior same as Edge for backward |
2536 | 0 | // compatibility. |
2537 | 0 | // XXX When Delete key is pressed, Edge removes all preceding empty |
2538 | 0 | // text nodes when removing the first character of the non-empty |
2539 | 0 | // text node. Chromium removes only selected empty text node and |
2540 | 0 | // following empty text nodes and the first character of the |
2541 | 0 | // non-empty text node. For now, we should keep our traditional |
2542 | 0 | // behavior same as Chromium for backward compatibility. |
2543 | 0 | |
2544 | 0 | rv = DeleteNodeIfCollapsedText(nodeAsText); |
2545 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
2546 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2547 | 0 | } |
2548 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to remove collapsed text"); |
2549 | 0 |
|
2550 | 0 | rv = InsertBRIfNeeded(); |
2551 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2552 | 0 | return rv; |
2553 | 0 | } |
2554 | 0 | |
2555 | 0 | // Remember that we did a ranged delete for the benefit of |
2556 | 0 | // AfterEditInner(). |
2557 | 0 | mDidRangedDelete = true; |
2558 | 0 |
|
2559 | 0 | return NS_OK; |
2560 | 0 | } |
2561 | 0 | |
2562 | 0 | if (wsType == WSType::special || wsType == WSType::br || |
2563 | 0 | visNode->IsHTMLElement(nsGkAtoms::hr)) { |
2564 | 0 | // Short circuit for invisible breaks. delete them and recurse. |
2565 | 0 | if (visNode->IsHTMLElement(nsGkAtoms::br) && |
2566 | 0 | !HTMLEditorRef().IsVisibleBRElement(visNode)) { |
2567 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*visNode); |
2568 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2569 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2570 | 0 | } |
2571 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2572 | 0 | return rv; |
2573 | 0 | } |
2574 | 0 | rv = WillDeleteSelection(aAction, aStripWrappers, aCancel, aHandled); |
2575 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2576 | 0 | return rv; |
2577 | 0 | } |
2578 | 0 | return NS_OK; |
2579 | 0 | } |
2580 | 0 | |
2581 | 0 | // Special handling for backspace when positioned after <hr> |
2582 | 0 | if (aAction == nsIEditor::ePrevious && |
2583 | 0 | visNode->IsHTMLElement(nsGkAtoms::hr)) { |
2584 | 0 | // Only if the caret is positioned at the end-of-hr-line position, we |
2585 | 0 | // want to delete the <hr>. |
2586 | 0 | // |
2587 | 0 | // In other words, we only want to delete, if our selection position |
2588 | 0 | // (indicated by startPoint) is the position directly |
2589 | 0 | // after the <hr>, on the same line as the <hr>. |
2590 | 0 | // |
2591 | 0 | // To detect this case we check: |
2592 | 0 | // startPoint's container == parentOfVisNode |
2593 | 0 | // and |
2594 | 0 | // startPoint's offset -1 == visNodeOffsetToVisNodeParent |
2595 | 0 | // and |
2596 | 0 | // interline position is false (left) |
2597 | 0 | // |
2598 | 0 | // In any other case we set the position to startPoint's container -1 |
2599 | 0 | // and interlineposition to false, only moving the caret to the |
2600 | 0 | // end-of-hr-line position. |
2601 | 0 | bool moveOnly = true; |
2602 | 0 |
|
2603 | 0 | EditorDOMPoint selPoint(visNode); |
2604 | 0 |
|
2605 | 0 | ErrorResult err; |
2606 | 0 | bool interLineIsRight = SelectionRef().GetInterlinePosition(err); |
2607 | 0 | if (NS_WARN_IF(err.Failed())) { |
2608 | 0 | return err.StealNSResult(); |
2609 | 0 | } |
2610 | 0 | |
2611 | 0 | if (startPoint.GetContainer() == selPoint.GetContainer() && |
2612 | 0 | startPoint.Offset() - 1 == selPoint.Offset() && |
2613 | 0 | !interLineIsRight) { |
2614 | 0 | moveOnly = false; |
2615 | 0 | } |
2616 | 0 |
|
2617 | 0 | if (moveOnly) { |
2618 | 0 | // Go to the position after the <hr>, but to the end of the <hr> line |
2619 | 0 | // by setting the interline position to left. |
2620 | 0 | DebugOnly<bool> advanced = selPoint.AdvanceOffset(); |
2621 | 0 | NS_WARNING_ASSERTION(advanced, |
2622 | 0 | "Failed to advance offset after <hr> element"); |
2623 | 0 |
|
2624 | 0 | { |
2625 | 0 | AutoEditorDOMPointChildInvalidator lockOffset(selPoint); |
2626 | 0 |
|
2627 | 0 | IgnoredErrorResult ignoredError; |
2628 | 0 | SelectionRef().Collapse(selPoint, ignoredError); |
2629 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2630 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2631 | 0 | } |
2632 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
2633 | 0 | "Failed to collapse selection at after the <hr>"); |
2634 | 0 | } |
2635 | 0 |
|
2636 | 0 | IgnoredErrorResult ignoredError; |
2637 | 0 | SelectionRef().SetInterlinePosition(false, ignoredError); |
2638 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
2639 | 0 | "Failed to unset interline position"); |
2640 | 0 | mDidExplicitlySetInterline = true; |
2641 | 0 | *aHandled = true; |
2642 | 0 |
|
2643 | 0 | // There is one exception to the move only case. If the <hr> is |
2644 | 0 | // followed by a <br> we want to delete the <br>. |
2645 | 0 |
|
2646 | 0 | WSType otherWSType; |
2647 | 0 | nsCOMPtr<nsINode> otherNode; |
2648 | 0 |
|
2649 | 0 | wsObj.NextVisibleNode(startPoint, |
2650 | 0 | address_of(otherNode), nullptr, |
2651 | 0 | &otherWSType); |
2652 | 0 |
|
2653 | 0 | if (otherWSType == WSType::br) { |
2654 | 0 | // Delete the <br> |
2655 | 0 | if (NS_WARN_IF(!otherNode->IsContent())) { |
2656 | 0 | return NS_ERROR_FAILURE; |
2657 | 0 | } |
2658 | 0 | nsIContent* otherContent = otherNode->AsContent(); |
2659 | 0 | rv = WSRunObject::PrepareToDeleteNode(&HTMLEditorRef(), |
2660 | 0 | otherContent); |
2661 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2662 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2663 | 0 | } |
2664 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2665 | 0 | return rv; |
2666 | 0 | } |
2667 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*otherContent); |
2668 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2669 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2670 | 0 | } |
2671 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2672 | 0 | return rv; |
2673 | 0 | } |
2674 | 0 | } |
2675 | 0 | |
2676 | 0 | return NS_OK; |
2677 | 0 | } |
2678 | 0 | // Else continue with normal delete code |
2679 | 0 | } |
2680 | 0 |
|
2681 | 0 | if (NS_WARN_IF(!visNode->IsContent())) { |
2682 | 0 | return NS_ERROR_FAILURE; |
2683 | 0 | } |
2684 | 0 | // Found break or image, or hr. |
2685 | 0 | rv = WSRunObject::PrepareToDeleteNode(&HTMLEditorRef(), |
2686 | 0 | visNode->AsContent()); |
2687 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2688 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2689 | 0 | } |
2690 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2691 | 0 | return rv; |
2692 | 0 | } |
2693 | 0 | // Remember sibling to visnode, if any |
2694 | 0 | nsCOMPtr<nsIContent> sibling = |
2695 | 0 | HTMLEditorRef().GetPriorHTMLSibling(visNode); |
2696 | 0 | // Delete the node, and join like nodes if appropriate |
2697 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*visNode); |
2698 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2699 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2700 | 0 | } |
2701 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2702 | 0 | return rv; |
2703 | 0 | } |
2704 | 0 | // We did something, so let's say so. |
2705 | 0 | *aHandled = true; |
2706 | 0 | // Is there a prior node and are they siblings? |
2707 | 0 | nsCOMPtr<nsINode> stepbrother; |
2708 | 0 | if (sibling) { |
2709 | 0 | stepbrother = HTMLEditorRef().GetNextHTMLSibling(sibling); |
2710 | 0 | } |
2711 | 0 | // Are they both text nodes? If so, join them! |
2712 | 0 | if (startPoint.GetContainer() == stepbrother && |
2713 | 0 | startPoint.GetContainerAsText() && |
2714 | 0 | sibling->GetAsText()) { |
2715 | 0 | EditorDOMPoint pt; |
2716 | 0 | nsresult rv = |
2717 | 0 | JoinNearestEditableNodesWithTransaction( |
2718 | 0 | *sibling, *startPoint.GetContainerAsContent(), &pt); |
2719 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2720 | 0 | return rv; |
2721 | 0 | } |
2722 | 0 | if (NS_WARN_IF(!pt.IsSet())) { |
2723 | 0 | return NS_ERROR_FAILURE; |
2724 | 0 | } |
2725 | 0 | // Fix up selection |
2726 | 0 | ErrorResult error; |
2727 | 0 | SelectionRef().Collapse(pt, error); |
2728 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2729 | 0 | error.SuppressException(); |
2730 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2731 | 0 | } |
2732 | 0 | if (NS_WARN_IF(error.Failed())) { |
2733 | 0 | return error.StealNSResult(); |
2734 | 0 | } |
2735 | 0 | } |
2736 | 0 | rv = InsertBRIfNeeded(); |
2737 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2738 | 0 | return rv; |
2739 | 0 | } |
2740 | 0 | return NS_OK; |
2741 | 0 | } |
2742 | 0 | |
2743 | 0 | if (wsType == WSType::otherBlock) { |
2744 | 0 | // Make sure it's not a table element. If so, cancel the operation |
2745 | 0 | // (translation: users cannot backspace or delete across table cells) |
2746 | 0 | if (HTMLEditUtils::IsTableElement(visNode)) { |
2747 | 0 | *aCancel = true; |
2748 | 0 | return NS_OK; |
2749 | 0 | } |
2750 | 0 | |
2751 | 0 | // Next to a block. See if we are between a block and a br. If so, we |
2752 | 0 | // really want to delete the br. Else join content at selection to the |
2753 | 0 | // block. |
2754 | 0 | bool bDeletedBR = false; |
2755 | 0 | WSType otherWSType; |
2756 | 0 | nsCOMPtr<nsINode> otherNode; |
2757 | 0 |
|
2758 | 0 | // Find node in other direction |
2759 | 0 | if (aAction == nsIEditor::eNext) { |
2760 | 0 | wsObj.PriorVisibleNode(startPoint, |
2761 | 0 | address_of(otherNode), nullptr, |
2762 | 0 | &otherWSType); |
2763 | 0 | } else { |
2764 | 0 | wsObj.NextVisibleNode(startPoint, |
2765 | 0 | address_of(otherNode), nullptr, |
2766 | 0 | &otherWSType); |
2767 | 0 | } |
2768 | 0 |
|
2769 | 0 | // First find the adjacent node in the block |
2770 | 0 | nsCOMPtr<nsIContent> leafNode; |
2771 | 0 | nsCOMPtr<nsINode> leftNode, rightNode; |
2772 | 0 | if (aAction == nsIEditor::ePrevious) { |
2773 | 0 | leafNode = HTMLEditorRef().GetLastEditableLeaf(*visNode); |
2774 | 0 | leftNode = leafNode; |
2775 | 0 | rightNode = startPoint.GetContainer(); |
2776 | 0 | } else { |
2777 | 0 | leafNode = HTMLEditorRef().GetFirstEditableLeaf(*visNode); |
2778 | 0 | leftNode = startPoint.GetContainer(); |
2779 | 0 | rightNode = leafNode; |
2780 | 0 | } |
2781 | 0 |
|
2782 | 0 | if (otherNode->IsHTMLElement(nsGkAtoms::br)) { |
2783 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*otherNode); |
2784 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2785 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2786 | 0 | } |
2787 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2788 | 0 | return rv; |
2789 | 0 | } |
2790 | 0 | // XXX Only in this case, setting "handled" to true only when it |
2791 | 0 | // succeeds? |
2792 | 0 | *aHandled = true; |
2793 | 0 | bDeletedBR = true; |
2794 | 0 | } |
2795 | 0 |
|
2796 | 0 | // Don't cross table boundaries |
2797 | 0 | if (leftNode && rightNode && |
2798 | 0 | InDifferentTableElements(leftNode, rightNode)) { |
2799 | 0 | return NS_OK; |
2800 | 0 | } |
2801 | 0 | |
2802 | 0 | if (bDeletedBR) { |
2803 | 0 | // Put selection at edge of block and we are done. |
2804 | 0 | if (NS_WARN_IF(!leafNode)) { |
2805 | 0 | return NS_ERROR_FAILURE; |
2806 | 0 | } |
2807 | 0 | EditorDOMPoint newSel = GetGoodSelPointForNode(*leafNode, aAction); |
2808 | 0 | if (NS_WARN_IF(!newSel.IsSet())) { |
2809 | 0 | return NS_ERROR_FAILURE; |
2810 | 0 | } |
2811 | 0 | IgnoredErrorResult error; |
2812 | 0 | SelectionRef().Collapse(newSel, error); |
2813 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2814 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2815 | 0 | } |
2816 | 0 | NS_WARNING_ASSERTION(!error.Failed(), |
2817 | 0 | "Failed to collapse selection at edge of the block"); |
2818 | 0 | return NS_OK; |
2819 | 0 | } |
2820 | 0 | |
2821 | 0 | // Else we are joining content to block |
2822 | 0 | |
2823 | 0 | EditorDOMPoint selPoint(startPoint); |
2824 | 0 | { |
2825 | 0 | AutoTrackDOMPoint tracker(HTMLEditorRef().mRangeUpdater, &selPoint); |
2826 | 0 | if (NS_WARN_IF(!leftNode) || |
2827 | 0 | NS_WARN_IF(!leftNode->IsContent()) || |
2828 | 0 | NS_WARN_IF(!rightNode) || |
2829 | 0 | NS_WARN_IF(!rightNode->IsContent())) { |
2830 | 0 | return NS_ERROR_FAILURE; |
2831 | 0 | } |
2832 | 0 | EditActionResult ret = |
2833 | 0 | TryToJoinBlocksWithTransaction(*leftNode->AsContent(), |
2834 | 0 | *rightNode->AsContent()); |
2835 | 0 | *aHandled |= ret.Handled(); |
2836 | 0 | *aCancel |= ret.Canceled(); |
2837 | 0 | if (NS_WARN_IF(ret.Failed())) { |
2838 | 0 | return ret.Rv(); |
2839 | 0 | } |
2840 | 0 | } |
2841 | 0 | |
2842 | 0 | // If TryToJoinBlocksWithTransaction() didn't handle it and it's not |
2843 | 0 | // canceled, user may want to modify the start leaf node or the last leaf |
2844 | 0 | // node of the block. |
2845 | 0 | if (!*aHandled && !*aCancel && leafNode != startPoint.GetContainer()) { |
2846 | 0 | int32_t offset = |
2847 | 0 | aAction == nsIEditor::ePrevious ? |
2848 | 0 | static_cast<int32_t>(leafNode->Length()) : 0; |
2849 | 0 | rv = SelectionRef().Collapse(leafNode, offset); |
2850 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2851 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2852 | 0 | } |
2853 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
2854 | 0 | "Failed to collapse selection at the leaf node"); |
2855 | 0 | rv = WillDeleteSelection(aAction, aStripWrappers, aCancel, aHandled); |
2856 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2857 | 0 | return rv; |
2858 | 0 | } |
2859 | 0 | return NS_OK; |
2860 | 0 | } |
2861 | 0 | |
2862 | 0 | // Otherwise, we must have deleted the selection as user expected. |
2863 | 0 | IgnoredErrorResult ignored; |
2864 | 0 | SelectionRef().Collapse(selPoint, ignored); |
2865 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2866 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2867 | 0 | } |
2868 | 0 | NS_WARNING_ASSERTION(!ignored.Failed(), |
2869 | 0 | "Failed to selection at deleted point"); |
2870 | 0 | return NS_OK; |
2871 | 0 | } |
2872 | 0 | |
2873 | 0 | if (wsType == WSType::thisBlock) { |
2874 | 0 | // At edge of our block. Look beside it and see if we can join to an |
2875 | 0 | // adjacent block |
2876 | 0 |
|
2877 | 0 | // Make sure it's not a table element. If so, cancel the operation |
2878 | 0 | // (translation: users cannot backspace or delete across table cells) |
2879 | 0 | if (HTMLEditUtils::IsTableElement(visNode)) { |
2880 | 0 | *aCancel = true; |
2881 | 0 | return NS_OK; |
2882 | 0 | } |
2883 | 0 | |
2884 | 0 | // First find the relevant nodes |
2885 | 0 | nsCOMPtr<nsINode> leftNode, rightNode; |
2886 | 0 | if (aAction == nsIEditor::ePrevious) { |
2887 | 0 | leftNode = HTMLEditorRef().GetPreviousEditableHTMLNode(*visNode); |
2888 | 0 | rightNode = startPoint.GetContainer(); |
2889 | 0 | } else { |
2890 | 0 | rightNode = HTMLEditorRef().GetNextEditableHTMLNode(*visNode); |
2891 | 0 | leftNode = startPoint.GetContainer(); |
2892 | 0 | } |
2893 | 0 |
|
2894 | 0 | // Nothing to join |
2895 | 0 | if (!leftNode || !rightNode) { |
2896 | 0 | *aCancel = true; |
2897 | 0 | return NS_OK; |
2898 | 0 | } |
2899 | 0 | |
2900 | 0 | // Don't cross table boundaries -- cancel it |
2901 | 0 | if (InDifferentTableElements(leftNode, rightNode)) { |
2902 | 0 | *aCancel = true; |
2903 | 0 | return NS_OK; |
2904 | 0 | } |
2905 | 0 | |
2906 | 0 | EditorDOMPoint selPoint(startPoint); |
2907 | 0 | { |
2908 | 0 | AutoTrackDOMPoint tracker(HTMLEditorRef().mRangeUpdater, &selPoint); |
2909 | 0 | if (NS_WARN_IF(!leftNode->IsContent()) || |
2910 | 0 | NS_WARN_IF(!rightNode->IsContent())) { |
2911 | 0 | return NS_ERROR_FAILURE; |
2912 | 0 | } |
2913 | 0 | EditActionResult ret = |
2914 | 0 | TryToJoinBlocksWithTransaction(*leftNode->AsContent(), |
2915 | 0 | *rightNode->AsContent()); |
2916 | 0 | // This should claim that trying to join the block means that |
2917 | 0 | // this handles the action because the caller shouldn't do anything |
2918 | 0 | // anymore in this case. |
2919 | 0 | *aHandled = true; |
2920 | 0 | *aCancel |= ret.Canceled(); |
2921 | 0 | if (NS_WARN_IF(ret.Failed())) { |
2922 | 0 | return ret.Rv(); |
2923 | 0 | } |
2924 | 0 | } |
2925 | 0 | IgnoredErrorResult ignored; |
2926 | 0 | SelectionRef().Collapse(selPoint, ignored); |
2927 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2928 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2929 | 0 | } |
2930 | 0 | NS_WARNING_ASSERTION(!ignored.Failed(), "Failed to collapse selection"); |
2931 | 0 | return NS_OK; |
2932 | 0 | } |
2933 | 0 | } |
2934 | 0 |
|
2935 | 0 |
|
2936 | 0 | // Else we have a non-collapsed selection. First adjust the selection. |
2937 | 0 | rv = ExpandSelectionForDeletion(); |
2938 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2939 | 0 | return rv; |
2940 | 0 | } |
2941 | 0 | |
2942 | 0 | // Remember that we did a ranged delete for the benefit of AfterEditInner(). |
2943 | 0 | mDidRangedDelete = true; |
2944 | 0 |
|
2945 | 0 | // Refresh start and end points |
2946 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
2947 | 0 | if (NS_WARN_IF(!firstRange)) { |
2948 | 0 | return NS_ERROR_FAILURE; |
2949 | 0 | } |
2950 | 0 | nsCOMPtr<nsINode> startNode = firstRange->GetStartContainer(); |
2951 | 0 | if (NS_WARN_IF(!startNode)) { |
2952 | 0 | return NS_ERROR_FAILURE; |
2953 | 0 | } |
2954 | 0 | int32_t startOffset = firstRange->StartOffset(); |
2955 | 0 | nsCOMPtr<nsINode> endNode = firstRange->GetEndContainer(); |
2956 | 0 | if (NS_WARN_IF(!endNode)) { |
2957 | 0 | return NS_ERROR_FAILURE; |
2958 | 0 | } |
2959 | 0 | int32_t endOffset = firstRange->EndOffset(); |
2960 | 0 |
|
2961 | 0 | // Figure out if the endpoints are in nodes that can be merged. Adjust |
2962 | 0 | // surrounding whitespace in preparation to delete selection. |
2963 | 0 | if (!IsPlaintextEditor()) { |
2964 | 0 | AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef()); |
2965 | 0 | rv = WSRunObject::PrepareToDeleteRange(&HTMLEditorRef(), |
2966 | 0 | address_of(startNode), &startOffset, |
2967 | 0 | address_of(endNode), &endOffset); |
2968 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2969 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2970 | 0 | } |
2971 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2972 | 0 | return rv; |
2973 | 0 | } |
2974 | 0 | } |
2975 | 0 | |
2976 | 0 | { |
2977 | 0 | // Track location of where we are deleting |
2978 | 0 | AutoTrackDOMPoint startTracker(HTMLEditorRef().mRangeUpdater, |
2979 | 0 | address_of(startNode), &startOffset); |
2980 | 0 | AutoTrackDOMPoint endTracker(HTMLEditorRef().mRangeUpdater, |
2981 | 0 | address_of(endNode), &endOffset); |
2982 | 0 | // We are handling all ranged deletions directly now. |
2983 | 0 | *aHandled = true; |
2984 | 0 |
|
2985 | 0 | if (endNode == startNode) { |
2986 | 0 | rv = HTMLEditorRef().DeleteSelectionWithTransaction(aAction, |
2987 | 0 | aStripWrappers); |
2988 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
2989 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
2990 | 0 | } |
2991 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2992 | 0 | return rv; |
2993 | 0 | } |
2994 | 0 | } else { |
2995 | 0 | // Figure out mailcite ancestors |
2996 | 0 | nsCOMPtr<Element> startCiteNode = GetTopEnclosingMailCite(*startNode); |
2997 | 0 | nsCOMPtr<Element> endCiteNode = GetTopEnclosingMailCite(*endNode); |
2998 | 0 |
|
2999 | 0 | // If we only have a mailcite at one of the two endpoints, set the |
3000 | 0 | // directionality of the deletion so that the selection will end up |
3001 | 0 | // outside the mailcite. |
3002 | 0 | if (startCiteNode && !endCiteNode) { |
3003 | 0 | aAction = nsIEditor::eNext; |
3004 | 0 | } else if (!startCiteNode && endCiteNode) { |
3005 | 0 | aAction = nsIEditor::ePrevious; |
3006 | 0 | } |
3007 | 0 |
|
3008 | 0 | // Figure out block parents |
3009 | 0 | nsCOMPtr<Element> leftParent = HTMLEditor::GetBlock(*startNode); |
3010 | 0 | nsCOMPtr<Element> rightParent = HTMLEditor::GetBlock(*endNode); |
3011 | 0 |
|
3012 | 0 | // Are endpoint block parents the same? Use default deletion |
3013 | 0 | if (leftParent && leftParent == rightParent) { |
3014 | 0 | HTMLEditorRef().DeleteSelectionWithTransaction(aAction, aStripWrappers); |
3015 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3016 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3017 | 0 | } |
3018 | 0 | } else { |
3019 | 0 | // Deleting across blocks. Are the blocks of same type? |
3020 | 0 | if (NS_WARN_IF(!leftParent) || NS_WARN_IF(!rightParent)) { |
3021 | 0 | return NS_ERROR_FAILURE; |
3022 | 0 | } |
3023 | 0 | |
3024 | 0 | // Are the blocks siblings? |
3025 | 0 | nsCOMPtr<nsINode> leftBlockParent = leftParent->GetParentNode(); |
3026 | 0 | nsCOMPtr<nsINode> rightBlockParent = rightParent->GetParentNode(); |
3027 | 0 |
|
3028 | 0 | // MOOSE: this could conceivably screw up a table.. fix me. |
3029 | 0 | if (leftBlockParent == rightBlockParent && |
3030 | 0 | HTMLEditorRef().AreNodesSameType(*leftParent, *rightParent) && |
3031 | 0 | // XXX What's special about these three types of block? |
3032 | 0 | (leftParent->IsHTMLElement(nsGkAtoms::p) || |
3033 | 0 | HTMLEditUtils::IsListItem(leftParent) || |
3034 | 0 | HTMLEditUtils::IsHeader(*leftParent))) { |
3035 | 0 | // First delete the selection |
3036 | 0 | rv = HTMLEditorRef().DeleteSelectionWithTransaction(aAction, |
3037 | 0 | aStripWrappers); |
3038 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3039 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3040 | 0 | } |
3041 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3042 | 0 | return rv; |
3043 | 0 | } |
3044 | 0 | // Join blocks |
3045 | 0 | EditorDOMPoint pt = |
3046 | 0 | HTMLEditorRef().JoinNodesDeepWithTransaction(*leftParent, |
3047 | 0 | *rightParent); |
3048 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3049 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3050 | 0 | } |
3051 | 0 | if (NS_WARN_IF(!pt.IsSet())) { |
3052 | 0 | return NS_ERROR_FAILURE; |
3053 | 0 | } |
3054 | 0 | // Fix up selection |
3055 | 0 | ErrorResult error; |
3056 | 0 | SelectionRef().Collapse(pt, error); |
3057 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3058 | 0 | error.SuppressException(); |
3059 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3060 | 0 | } |
3061 | 0 | if (NS_WARN_IF(error.Failed())) { |
3062 | 0 | return error.StealNSResult(); |
3063 | 0 | } |
3064 | 0 | return NS_OK; |
3065 | 0 | } |
3066 | 0 | |
3067 | 0 | // Else blocks not same type, or not siblings. Delete everything |
3068 | 0 | // except table elements. |
3069 | 0 | join = true; |
3070 | 0 |
|
3071 | 0 | AutoRangeArray arrayOfRanges(&SelectionRef()); |
3072 | 0 | for (auto& range : arrayOfRanges.mRanges) { |
3073 | 0 | // Build a list of nodes in the range |
3074 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
3075 | 0 | TrivialFunctor functor; |
3076 | 0 | DOMSubtreeIterator iter; |
3077 | 0 | nsresult rv = iter.Init(*range); |
3078 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3079 | 0 | return rv; |
3080 | 0 | } |
3081 | 0 | iter.AppendList(functor, arrayOfNodes); |
3082 | 0 |
|
3083 | 0 | // Now that we have the list, delete non-table elements |
3084 | 0 | int32_t listCount = arrayOfNodes.Length(); |
3085 | 0 | for (int32_t j = 0; j < listCount; j++) { |
3086 | 0 | OwningNonNull<nsINode> node = arrayOfNodes[0]; |
3087 | 0 | nsresult rv = DeleteElementsExceptTableRelatedElements(node); |
3088 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
3089 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3090 | 0 | } |
3091 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
3092 | 0 | "Failed to elements except table related elements"); |
3093 | 0 | arrayOfNodes.RemoveElementAt(0); |
3094 | 0 | // If something visible is deleted, no need to join. Visible means |
3095 | 0 | // all nodes except non-visible textnodes and breaks. |
3096 | 0 | if (join && origCollapsed) { |
3097 | 0 | if (!node->IsContent()) { |
3098 | 0 | join = false; |
3099 | 0 | continue; |
3100 | 0 | } |
3101 | 0 | nsIContent* content = node->AsContent(); |
3102 | 0 | if (Text* text = content->GetAsText()) { |
3103 | 0 | join = !HTMLEditorRef().IsInVisibleTextFrames(*text); |
3104 | 0 | } else { |
3105 | 0 | join = content->IsHTMLElement(nsGkAtoms::br) && |
3106 | 0 | !HTMLEditorRef().IsVisibleBRElement(node); |
3107 | 0 | } |
3108 | 0 | } |
3109 | 0 | } |
3110 | 0 | } |
3111 | 0 |
|
3112 | 0 | // Check endpoints for possible text deletion. We can assume that if |
3113 | 0 | // text node is found, we can delete to end or to begining as |
3114 | 0 | // appropriate, since the case where both sel endpoints in same text |
3115 | 0 | // node was already handled (we wouldn't be here) |
3116 | 0 | if (startNode->GetAsText() && |
3117 | 0 | startNode->Length() > static_cast<uint32_t>(startOffset)) { |
3118 | 0 | // Delete to last character |
3119 | 0 | OwningNonNull<CharacterData> dataNode = |
3120 | 0 | *static_cast<CharacterData*>(startNode.get()); |
3121 | 0 | rv = HTMLEditorRef().DeleteTextWithTransaction( |
3122 | 0 | dataNode, startOffset, |
3123 | 0 | startNode->Length() - startOffset); |
3124 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3125 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3126 | 0 | } |
3127 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3128 | 0 | return rv; |
3129 | 0 | } |
3130 | 0 | } |
3131 | 0 | if (endNode->GetAsText() && endOffset) { |
3132 | 0 | // Delete to first character |
3133 | 0 | OwningNonNull<CharacterData> dataNode = |
3134 | 0 | *static_cast<CharacterData*>(endNode.get()); |
3135 | 0 | rv = HTMLEditorRef().DeleteTextWithTransaction(dataNode, 0, |
3136 | 0 | endOffset); |
3137 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3138 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3139 | 0 | } |
3140 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3141 | 0 | return rv; |
3142 | 0 | } |
3143 | 0 | } |
3144 | 0 | |
3145 | 0 | if (join) { |
3146 | 0 | EditActionResult ret = |
3147 | 0 | TryToJoinBlocksWithTransaction(*leftParent, *rightParent); |
3148 | 0 | MOZ_ASSERT(*aHandled); |
3149 | 0 | *aCancel |= ret.Canceled(); |
3150 | 0 | if (NS_WARN_IF(ret.Failed())) { |
3151 | 0 | return ret.Rv(); |
3152 | 0 | } |
3153 | 0 | } |
3154 | 0 | } |
3155 | 0 | } |
3156 | 0 | } |
3157 | 0 | |
3158 | 0 | // We might have left only collapsed whitespace in the start/end nodes |
3159 | 0 | { |
3160 | 0 | AutoTrackDOMPoint startTracker(HTMLEditorRef().mRangeUpdater, |
3161 | 0 | address_of(startNode), &startOffset); |
3162 | 0 | AutoTrackDOMPoint endTracker(HTMLEditorRef().mRangeUpdater, |
3163 | 0 | address_of(endNode), &endOffset); |
3164 | 0 |
|
3165 | 0 | nsresult rv = DeleteNodeIfCollapsedText(*startNode); |
3166 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
3167 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3168 | 0 | } |
3169 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
3170 | 0 | "Failed to delete start node even though it's collapsed text"); |
3171 | 0 | rv = DeleteNodeIfCollapsedText(*endNode); |
3172 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
3173 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3174 | 0 | } |
3175 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
3176 | 0 | "Failed to delete end node even though it's collapsed text"); |
3177 | 0 | } |
3178 | 0 |
|
3179 | 0 | // If we're joining blocks: if deleting forward the selection should be |
3180 | 0 | // collapsed to the end of the selection, if deleting backward the selection |
3181 | 0 | // should be collapsed to the beginning of the selection. But if we're not |
3182 | 0 | // joining then the selection should collapse to the beginning of the |
3183 | 0 | // selection if we'redeleting forward, because the end of the selection will |
3184 | 0 | // still be in the next block. And same thing for deleting backwards |
3185 | 0 | // (selection should collapse to the end, because the beginning will still be |
3186 | 0 | // in the first block). See Bug 507936 |
3187 | 0 | if (aAction == (join ? nsIEditor::eNext : nsIEditor::ePrevious)) { |
3188 | 0 | rv = SelectionRef().Collapse(endNode, endOffset); |
3189 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3190 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3191 | 0 | } |
3192 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3193 | 0 | return rv; |
3194 | 0 | } |
3195 | 0 | } else { |
3196 | 0 | rv = SelectionRef().Collapse(startNode, startOffset); |
3197 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3198 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3199 | 0 | } |
3200 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3201 | 0 | return rv; |
3202 | 0 | } |
3203 | 0 | } |
3204 | 0 | return NS_OK; |
3205 | 0 | } |
3206 | | |
3207 | | nsresult |
3208 | | HTMLEditRules::DeleteNodeIfCollapsedText(nsINode& aNode) |
3209 | 0 | { |
3210 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
3211 | 0 |
|
3212 | 0 | Text* text = aNode.GetAsText(); |
3213 | 0 | if (!text) { |
3214 | 0 | return NS_OK; |
3215 | 0 | } |
3216 | 0 | |
3217 | 0 | if (HTMLEditorRef().IsVisibleTextNode(*text)) { |
3218 | 0 | return NS_OK; |
3219 | 0 | } |
3220 | 0 | |
3221 | 0 | nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(aNode); |
3222 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3223 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3224 | 0 | } |
3225 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3226 | 0 | return rv; |
3227 | 0 | } |
3228 | 0 | return NS_OK; |
3229 | 0 | } |
3230 | | |
3231 | | nsresult |
3232 | | HTMLEditRules::InsertBRIfNeeded() |
3233 | 0 | { |
3234 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
3235 | 0 |
|
3236 | 0 | EditorRawDOMPoint atStartOfSelection( |
3237 | 0 | EditorBase::GetStartPoint(&SelectionRef())); |
3238 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
3239 | 0 | return NS_ERROR_FAILURE; |
3240 | 0 | } |
3241 | 0 | |
3242 | 0 | // inline elements don't need any br |
3243 | 0 | if (!IsBlockNode(*atStartOfSelection.GetContainer())) { |
3244 | 0 | return NS_OK; |
3245 | 0 | } |
3246 | 0 | |
3247 | 0 | // examine selection |
3248 | 0 | WSRunObject wsObj(&HTMLEditorRef(), atStartOfSelection); |
3249 | 0 | if (((wsObj.mStartReason & WSType::block) || |
3250 | 0 | (wsObj.mStartReason & WSType::br)) && |
3251 | 0 | (wsObj.mEndReason & WSType::block)) { |
3252 | 0 | // if we are tucked between block boundaries then insert a br |
3253 | 0 | // first check that we are allowed to |
3254 | 0 | if (HTMLEditorRef().CanContainTag(*atStartOfSelection.GetContainer(), |
3255 | 0 | *nsGkAtoms::br)) { |
3256 | 0 | RefPtr<Element> brElement = |
3257 | 0 | HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(), |
3258 | 0 | atStartOfSelection, |
3259 | 0 | nsIEditor::ePrevious); |
3260 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3261 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3262 | 0 | } |
3263 | 0 | if (NS_WARN_IF(!brElement)) { |
3264 | 0 | return NS_ERROR_FAILURE; |
3265 | 0 | } |
3266 | 0 | return NS_OK; |
3267 | 0 | } |
3268 | 0 | } |
3269 | 0 | return NS_OK; |
3270 | 0 | } |
3271 | | |
3272 | | EditorDOMPoint |
3273 | | HTMLEditRules::GetGoodSelPointForNode(nsINode& aNode, |
3274 | | nsIEditor::EDirection aAction) |
3275 | 0 | { |
3276 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
3277 | 0 | MOZ_ASSERT(aAction == nsIEditor::eNext || |
3278 | 0 | aAction == nsIEditor::eNextWord || |
3279 | 0 | aAction == nsIEditor::ePrevious || |
3280 | 0 | aAction == nsIEditor::ePreviousWord || |
3281 | 0 | aAction == nsIEditor::eToBeginningOfLine || |
3282 | 0 | aAction == nsIEditor::eToEndOfLine); |
3283 | 0 |
|
3284 | 0 | bool isPreviousAction = (aAction == nsIEditor::ePrevious || |
3285 | 0 | aAction == nsIEditor::ePreviousWord || |
3286 | 0 | aAction == nsIEditor::eToBeginningOfLine); |
3287 | 0 |
|
3288 | 0 | if (aNode.GetAsText() || HTMLEditorRef().IsContainer(&aNode) || |
3289 | 0 | NS_WARN_IF(!aNode.GetParentNode())) { |
3290 | 0 | return EditorDOMPoint(&aNode, isPreviousAction ? aNode.Length() : 0); |
3291 | 0 | } |
3292 | 0 |
|
3293 | 0 | if (NS_WARN_IF(!aNode.IsContent())) { |
3294 | 0 | return EditorDOMPoint(); |
3295 | 0 | } |
3296 | 0 | |
3297 | 0 | EditorDOMPoint ret(&aNode); |
3298 | 0 | if ((!aNode.IsHTMLElement(nsGkAtoms::br) || |
3299 | 0 | HTMLEditorRef().IsVisibleBRElement(&aNode)) && isPreviousAction) { |
3300 | 0 | ret.AdvanceOffset(); |
3301 | 0 | } |
3302 | 0 | return ret; |
3303 | 0 | } |
3304 | | |
3305 | | EditActionResult |
3306 | | HTMLEditRules::TryToJoinBlocksWithTransaction(nsIContent& aLeftNode, |
3307 | | nsIContent& aRightNode) |
3308 | 0 | { |
3309 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
3310 | 0 |
|
3311 | 0 | RefPtr<Element> leftBlock = HTMLEditorRef().GetBlock(aLeftNode); |
3312 | 0 | RefPtr<Element> rightBlock = HTMLEditorRef().GetBlock(aRightNode); |
3313 | 0 |
|
3314 | 0 | // Sanity checks |
3315 | 0 | if (NS_WARN_IF(!leftBlock) || NS_WARN_IF(!rightBlock)) { |
3316 | 0 | return EditActionIgnored(NS_ERROR_NULL_POINTER); |
3317 | 0 | } |
3318 | 0 | if (NS_WARN_IF(leftBlock == rightBlock)) { |
3319 | 0 | return EditActionIgnored(NS_ERROR_UNEXPECTED); |
3320 | 0 | } |
3321 | 0 | |
3322 | 0 | if (HTMLEditUtils::IsTableElement(leftBlock) || |
3323 | 0 | HTMLEditUtils::IsTableElement(rightBlock)) { |
3324 | 0 | // Do not try to merge table elements |
3325 | 0 | return EditActionCanceled(); |
3326 | 0 | } |
3327 | 0 | |
3328 | 0 | // Make sure we don't try to move things into HR's, which look like blocks |
3329 | 0 | // but aren't containers |
3330 | 0 | if (leftBlock->IsHTMLElement(nsGkAtoms::hr)) { |
3331 | 0 | leftBlock = HTMLEditorRef().GetBlockNodeParent(leftBlock); |
3332 | 0 | if (NS_WARN_IF(!leftBlock)) { |
3333 | 0 | return EditActionIgnored(NS_ERROR_UNEXPECTED); |
3334 | 0 | } |
3335 | 0 | } |
3336 | 0 | if (rightBlock->IsHTMLElement(nsGkAtoms::hr)) { |
3337 | 0 | rightBlock = HTMLEditorRef().GetBlockNodeParent(rightBlock); |
3338 | 0 | if (NS_WARN_IF(!rightBlock)) { |
3339 | 0 | return EditActionIgnored(NS_ERROR_UNEXPECTED); |
3340 | 0 | } |
3341 | 0 | } |
3342 | 0 | |
3343 | 0 | // Bail if both blocks the same |
3344 | 0 | if (leftBlock == rightBlock) { |
3345 | 0 | return EditActionIgnored(); |
3346 | 0 | } |
3347 | 0 | |
3348 | 0 | // Joining a list item to its parent is a NOP. |
3349 | 0 | if (HTMLEditUtils::IsList(leftBlock) && |
3350 | 0 | HTMLEditUtils::IsListItem(rightBlock) && |
3351 | 0 | rightBlock->GetParentNode() == leftBlock) { |
3352 | 0 | return EditActionHandled(); |
3353 | 0 | } |
3354 | 0 | |
3355 | 0 | // Special rule here: if we are trying to join list items, and they are in |
3356 | 0 | // different lists, join the lists instead. |
3357 | 0 | bool mergeLists = false; |
3358 | 0 | nsAtom* existingList = nsGkAtoms::_empty; |
3359 | 0 | EditorDOMPoint atChildInBlock; |
3360 | 0 | nsCOMPtr<Element> leftList, rightList; |
3361 | 0 | if (HTMLEditUtils::IsListItem(leftBlock) && |
3362 | 0 | HTMLEditUtils::IsListItem(rightBlock)) { |
3363 | 0 | leftList = leftBlock->GetParentElement(); |
3364 | 0 | rightList = rightBlock->GetParentElement(); |
3365 | 0 | if (leftList && rightList && leftList != rightList && |
3366 | 0 | !EditorUtils::IsDescendantOf(*leftList, *rightBlock, &atChildInBlock) && |
3367 | 0 | !EditorUtils::IsDescendantOf(*rightList, *leftBlock, &atChildInBlock)) { |
3368 | 0 | // There are some special complications if the lists are descendants of |
3369 | 0 | // the other lists' items. Note that it is okay for them to be |
3370 | 0 | // descendants of the other lists themselves, which is the usual case for |
3371 | 0 | // sublists in our implementation. |
3372 | 0 | MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock.IsSet()); |
3373 | 0 | leftBlock = leftList; |
3374 | 0 | rightBlock = rightList; |
3375 | 0 | mergeLists = true; |
3376 | 0 | existingList = leftList->NodeInfo()->NameAtom(); |
3377 | 0 | } |
3378 | 0 | } |
3379 | 0 |
|
3380 | 0 | AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef()); |
3381 | 0 |
|
3382 | 0 | // offset below is where you find yourself in rightBlock when you traverse |
3383 | 0 | // upwards from leftBlock |
3384 | 0 | EditorDOMPoint atRightBlockChild; |
3385 | 0 | if (EditorUtils::IsDescendantOf(*leftBlock, *rightBlock, |
3386 | 0 | &atRightBlockChild)) { |
3387 | 0 | // Tricky case. Left block is inside right block. Do ws adjustment. This |
3388 | 0 | // just destroys non-visible ws at boundaries we will be joining. |
3389 | 0 | DebugOnly<bool> advanced = atRightBlockChild.AdvanceOffset(); |
3390 | 0 | NS_WARNING_ASSERTION(advanced, |
3391 | 0 | "Failed to advance offset to after child of rightBlock, " |
3392 | 0 | "leftBlock is a descendant of the child"); |
3393 | 0 | nsresult rv = WSRunObject::ScrubBlockBoundary(&HTMLEditorRef(), |
3394 | 0 | WSRunObject::kBlockEnd, |
3395 | 0 | leftBlock); |
3396 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3397 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3398 | 0 | } |
3399 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3400 | 0 | return EditActionIgnored(rv); |
3401 | 0 | } |
3402 | 0 | |
3403 | 0 | { |
3404 | 0 | // We can't just track rightBlock because it's an Element. |
3405 | 0 | AutoTrackDOMPoint tracker(HTMLEditorRef().mRangeUpdater, |
3406 | 0 | &atRightBlockChild); |
3407 | 0 | rv = WSRunObject::ScrubBlockBoundary(&HTMLEditorRef(), |
3408 | 0 | WSRunObject::kAfterBlock, |
3409 | 0 | atRightBlockChild.GetContainer(), |
3410 | 0 | atRightBlockChild.Offset()); |
3411 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3412 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3413 | 0 | } |
3414 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3415 | 0 | return EditActionIgnored(rv); |
3416 | 0 | } |
3417 | 0 | |
3418 | 0 | // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here. |
3419 | 0 | // Do we really need to do update rightBlock here?? |
3420 | 0 | MOZ_ASSERT(rightBlock == atRightBlockChild.GetContainer()); |
3421 | 0 | if (atRightBlockChild.GetContainerAsElement()) { |
3422 | 0 | rightBlock = atRightBlockChild.GetContainerAsElement(); |
3423 | 0 | } else { |
3424 | 0 | if (NS_WARN_IF(!atRightBlockChild.GetContainer()->GetParentElement())) { |
3425 | 0 | return EditActionIgnored(NS_ERROR_UNEXPECTED); |
3426 | 0 | } |
3427 | 0 | rightBlock = atRightBlockChild.GetContainer()->GetParentElement(); |
3428 | 0 | } |
3429 | 0 | } |
3430 | 0 |
|
3431 | 0 | // Do br adjustment. |
3432 | 0 | RefPtr<Element> brNode = |
3433 | 0 | CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd); |
3434 | 0 | EditActionResult ret(NS_OK); |
3435 | 0 | if (NS_WARN_IF(mergeLists)) { |
3436 | 0 | // Since 2002, here was the following comment: |
3437 | 0 | // > The idea here is to take all children in rightList that are past |
3438 | 0 | // > offset, and pull them into leftlist. |
3439 | 0 | // However, this has never been performed because we are here only when |
3440 | 0 | // neither left list nor right list is a descendant of the other but |
3441 | 0 | // in such case, getting a list item in the right list node almost |
3442 | 0 | // always failed since a variable for offset of rightList->GetChildAt() |
3443 | 0 | // was not initialized. So, it might be a bug, but we should keep this |
3444 | 0 | // traditional behavior for now. If you find when we get here, please |
3445 | 0 | // remove this comment if we don't need to do it. Otherwise, please |
3446 | 0 | // move children of the right list node to the end of the left list node. |
3447 | 0 | MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock.IsSet()); |
3448 | 0 |
|
3449 | 0 | // XXX Although, we don't do nothing here, but for keeping traditional |
3450 | 0 | // behavior, we should mark as handled. |
3451 | 0 | ret.MarkAsHandled(); |
3452 | 0 | } else { |
3453 | 0 | // XXX Why do we ignore the result of MoveBlock()? |
3454 | 0 | EditActionResult retMoveBlock = |
3455 | 0 | MoveBlock(*leftBlock, *rightBlock, |
3456 | 0 | -1, atRightBlockChild.Offset()); |
3457 | 0 | if (NS_WARN_IF(retMoveBlock.Rv() == NS_ERROR_EDITOR_DESTROYED)) { |
3458 | 0 | return ret; |
3459 | 0 | } |
3460 | 0 | NS_WARNING_ASSERTION(retMoveBlock.Succeeded(), |
3461 | 0 | "Failed to move contents of the right block to the left block"); |
3462 | 0 | if (retMoveBlock.Handled()) { |
3463 | 0 | ret.MarkAsHandled(); |
3464 | 0 | } |
3465 | 0 | // Now, all children of rightBlock were moved to leftBlock. So, |
3466 | 0 | // atRightBlockChild is now invalid. |
3467 | 0 | atRightBlockChild.Clear(); |
3468 | 0 | } |
3469 | 0 | if (brNode) { |
3470 | 0 | nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*brNode); |
3471 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3472 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3473 | 0 | } |
3474 | 0 | if (NS_SUCCEEDED(rv)) { |
3475 | 0 | ret.MarkAsHandled(); |
3476 | 0 | } else { |
3477 | 0 | NS_WARNING("Failed to remove the <br> element"); |
3478 | 0 | } |
3479 | 0 | } |
3480 | 0 | return ret; |
3481 | 0 | } |
3482 | 0 | |
3483 | 0 | MOZ_DIAGNOSTIC_ASSERT(!atRightBlockChild.IsSet()); |
3484 | 0 |
|
3485 | 0 | // Offset below is where you find yourself in leftBlock when you traverse |
3486 | 0 | // upwards from rightBlock |
3487 | 0 | EditorDOMPoint leftBlockChild; |
3488 | 0 | if (EditorUtils::IsDescendantOf(*rightBlock, *leftBlock, &leftBlockChild)) { |
3489 | 0 | // Tricky case. Right block is inside left block. Do ws adjustment. This |
3490 | 0 | // just destroys non-visible ws at boundaries we will be joining. |
3491 | 0 | nsresult rv = WSRunObject::ScrubBlockBoundary(&HTMLEditorRef(), |
3492 | 0 | WSRunObject::kBlockStart, |
3493 | 0 | rightBlock); |
3494 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3495 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3496 | 0 | } |
3497 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3498 | 0 | return EditActionIgnored(rv); |
3499 | 0 | } |
3500 | 0 | |
3501 | 0 | { |
3502 | 0 | // We can't just track leftBlock because it's an Element, so track |
3503 | 0 | // something else. |
3504 | 0 | AutoTrackDOMPoint tracker(HTMLEditorRef().mRangeUpdater, &leftBlockChild); |
3505 | 0 | rv = WSRunObject::ScrubBlockBoundary(&HTMLEditorRef(), |
3506 | 0 | WSRunObject::kBeforeBlock, |
3507 | 0 | leftBlock, leftBlockChild.Offset()); |
3508 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3509 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3510 | 0 | } |
3511 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3512 | 0 | return EditActionIgnored(rv); |
3513 | 0 | } |
3514 | 0 | // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here. |
3515 | 0 | // Do we really need to do update rightBlock here?? |
3516 | 0 | MOZ_DIAGNOSTIC_ASSERT(leftBlock == leftBlockChild.GetContainer()); |
3517 | 0 | if (leftBlockChild.GetContainerAsElement()) { |
3518 | 0 | leftBlock = leftBlockChild.GetContainerAsElement(); |
3519 | 0 | } else { |
3520 | 0 | if (NS_WARN_IF(!leftBlockChild.GetContainer()->GetParentElement())) { |
3521 | 0 | return EditActionIgnored(NS_ERROR_UNEXPECTED); |
3522 | 0 | } |
3523 | 0 | leftBlock = leftBlockChild.GetContainer()->GetParentElement(); |
3524 | 0 | } |
3525 | 0 | } |
3526 | 0 | // Do br adjustment. |
3527 | 0 | RefPtr<Element> brNode = |
3528 | 0 | CheckForInvisibleBR(*leftBlock, BRLocation::beforeBlock, |
3529 | 0 | leftBlockChild.Offset()); |
3530 | 0 | EditActionResult ret(NS_OK); |
3531 | 0 | if (mergeLists) { |
3532 | 0 | // XXX Why do we ignore the result of MoveContents()? |
3533 | 0 | int32_t offset = leftBlockChild.Offset(); |
3534 | 0 | EditActionResult retMoveContents = |
3535 | 0 | MoveContents(*rightList, *leftList, &offset); |
3536 | 0 | if (NS_WARN_IF(retMoveContents.Rv() == NS_ERROR_EDITOR_DESTROYED)) { |
3537 | 0 | return ret; |
3538 | 0 | } |
3539 | 0 | NS_WARNING_ASSERTION(retMoveContents.Succeeded(), |
3540 | 0 | "Failed to move contents from the right list to the left list"); |
3541 | 0 | if (retMoveContents.Handled()) { |
3542 | 0 | ret.MarkAsHandled(); |
3543 | 0 | } |
3544 | 0 | // leftBlockChild was moved to rightList. So, it's invalid now. |
3545 | 0 | leftBlockChild.Clear(); |
3546 | 0 | } else { |
3547 | 0 | // Left block is a parent of right block, and the parent of the previous |
3548 | 0 | // visible content. Right block is a child and contains the contents we |
3549 | 0 | // want to move. |
3550 | 0 |
|
3551 | 0 | EditorDOMPoint previousContent; |
3552 | 0 | if (&aLeftNode == leftBlock) { |
3553 | 0 | // We are working with valid HTML, aLeftNode is a block node, and is |
3554 | 0 | // therefore allowed to contain rightBlock. This is the simple case, |
3555 | 0 | // we will simply move the content in rightBlock out of its block. |
3556 | 0 | previousContent = leftBlockChild; |
3557 | 0 | } else { |
3558 | 0 | // We try to work as well as possible with HTML that's already invalid. |
3559 | 0 | // Although "right block" is a block, and a block must not be contained |
3560 | 0 | // in inline elements, reality is that broken documents do exist. The |
3561 | 0 | // DIRECT parent of "left NODE" might be an inline element. Previous |
3562 | 0 | // versions of this code skipped inline parents until the first block |
3563 | 0 | // parent was found (and used "left block" as the destination). |
3564 | 0 | // However, in some situations this strategy moves the content to an |
3565 | 0 | // unexpected position. (see bug 200416) The new idea is to make the |
3566 | 0 | // moving content a sibling, next to the previous visible content. |
3567 | 0 | previousContent.Set(&aLeftNode); |
3568 | 0 |
|
3569 | 0 | // We want to move our content just after the previous visible node. |
3570 | 0 | previousContent.AdvanceOffset(); |
3571 | 0 | } |
3572 | 0 |
|
3573 | 0 | // Because we don't want the moving content to receive the style of the |
3574 | 0 | // previous content, we split the previous content's style. |
3575 | 0 |
|
3576 | 0 | RefPtr<Element> editorRoot = HTMLEditorRef().GetEditorRoot(); |
3577 | 0 | if (!editorRoot || &aLeftNode != editorRoot) { |
3578 | 0 | nsCOMPtr<nsIContent> splittedPreviousContent; |
3579 | 0 | nsCOMPtr<nsINode> previousContentParent = |
3580 | 0 | previousContent.GetContainer(); |
3581 | 0 | int32_t previousContentOffset = previousContent.Offset(); |
3582 | 0 | rv = HTMLEditorRef().SplitStyleAbovePoint( |
3583 | 0 | address_of(previousContentParent), |
3584 | 0 | &previousContentOffset, |
3585 | 0 | nullptr, nullptr, nullptr, |
3586 | 0 | getter_AddRefs(splittedPreviousContent)); |
3587 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3588 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3589 | 0 | } |
3590 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3591 | 0 | return EditActionIgnored(rv); |
3592 | 0 | } |
3593 | 0 | |
3594 | 0 | if (splittedPreviousContent) { |
3595 | 0 | previousContent.Set(splittedPreviousContent); |
3596 | 0 | } else { |
3597 | 0 | previousContent.Set(previousContentParent, previousContentOffset); |
3598 | 0 | } |
3599 | 0 | } |
3600 | 0 |
|
3601 | 0 | if (NS_WARN_IF(!previousContent.IsSet())) { |
3602 | 0 | return EditActionIgnored(NS_ERROR_NULL_POINTER); |
3603 | 0 | } |
3604 | 0 | |
3605 | 0 | ret |= MoveBlock(*previousContent.GetContainerAsElement(), *rightBlock, |
3606 | 0 | previousContent.Offset(), 0); |
3607 | 0 | if (NS_WARN_IF(ret.Failed())) { |
3608 | 0 | return ret; |
3609 | 0 | } |
3610 | 0 | } |
3611 | 0 | if (brNode) { |
3612 | 0 | nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*brNode); |
3613 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3614 | 0 | return ret.SetResult(NS_ERROR_EDITOR_DESTROYED); |
3615 | 0 | } |
3616 | 0 | if (NS_SUCCEEDED(rv)) { |
3617 | 0 | ret.MarkAsHandled(); |
3618 | 0 | } else { |
3619 | 0 | NS_WARNING("Failed to remove the <br> element"); |
3620 | 0 | } |
3621 | 0 | } |
3622 | 0 | return ret; |
3623 | 0 | } |
3624 | 0 | |
3625 | 0 | MOZ_DIAGNOSTIC_ASSERT(!atRightBlockChild.IsSet()); |
3626 | 0 | MOZ_DIAGNOSTIC_ASSERT(!leftBlockChild.IsSet()); |
3627 | 0 |
|
3628 | 0 | // Normal case. Blocks are siblings, or at least close enough. An example |
3629 | 0 | // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>. The |
3630 | 0 | // first li and the p are not true siblings, but we still want to join them |
3631 | 0 | // if you backspace from li into p. |
3632 | 0 |
|
3633 | 0 | // Adjust whitespace at block boundaries |
3634 | 0 | nsresult rv = |
3635 | 0 | WSRunObject::PrepareToJoinBlocks(&HTMLEditorRef(), leftBlock, rightBlock); |
3636 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3637 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3638 | 0 | } |
3639 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3640 | 0 | return EditActionIgnored(rv); |
3641 | 0 | } |
3642 | 0 | // Do br adjustment. |
3643 | 0 | nsCOMPtr<Element> brNode = |
3644 | 0 | CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd); |
3645 | 0 | EditActionResult ret(NS_OK); |
3646 | 0 | if (mergeLists || leftBlock->NodeInfo()->NameAtom() == |
3647 | 0 | rightBlock->NodeInfo()->NameAtom()) { |
3648 | 0 | // Nodes are same type. merge them. |
3649 | 0 | EditorDOMPoint pt; |
3650 | 0 | nsresult rv = |
3651 | 0 | JoinNearestEditableNodesWithTransaction(*leftBlock, *rightBlock, &pt); |
3652 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
3653 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3654 | 0 | } |
3655 | 0 | if (pt.IsSet() && mergeLists) { |
3656 | 0 | CreateElementResult convertListTypeResult = |
3657 | 0 | ConvertListType(*rightBlock, *existingList, *nsGkAtoms::li); |
3658 | 0 | if (NS_WARN_IF(convertListTypeResult.Rv() == NS_ERROR_EDITOR_DESTROYED)) { |
3659 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3660 | 0 | } |
3661 | 0 | } |
3662 | 0 | ret.MarkAsHandled(); |
3663 | 0 | } else { |
3664 | 0 | // Nodes are dissimilar types. |
3665 | 0 | ret |= MoveBlock(*leftBlock, *rightBlock, -1, 0); |
3666 | 0 | if (NS_WARN_IF(ret.Failed())) { |
3667 | 0 | return ret; |
3668 | 0 | } |
3669 | 0 | } |
3670 | 0 | if (brNode) { |
3671 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*brNode); |
3672 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3673 | 0 | return ret.SetResult(NS_ERROR_EDITOR_DESTROYED); |
3674 | 0 | } |
3675 | 0 | // XXX In other top level if blocks, the result of |
3676 | 0 | // DeleteNodeWithTransaction() is ignored. Why does only this result |
3677 | 0 | // is respected? |
3678 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3679 | 0 | return ret.SetResult(rv); |
3680 | 0 | } |
3681 | 0 | ret.MarkAsHandled(); |
3682 | 0 | } |
3683 | 0 | return ret; |
3684 | 0 | } |
3685 | | |
3686 | | EditActionResult |
3687 | | HTMLEditRules::MoveBlock(Element& aLeftBlock, |
3688 | | Element& aRightBlock, |
3689 | | int32_t aLeftOffset, |
3690 | | int32_t aRightOffset) |
3691 | 0 | { |
3692 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
3693 | 0 |
|
3694 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
3695 | 0 | // GetNodesFromPoint is the workhorse that figures out what we wnat to move. |
3696 | 0 | nsresult rv = GetNodesFromPoint(EditorDOMPoint(&aRightBlock, aRightOffset), |
3697 | 0 | EditSubAction::eCreateOrChangeList, |
3698 | 0 | arrayOfNodes, TouchContent::yes); |
3699 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3700 | 0 | return EditActionIgnored(rv); |
3701 | 0 | } |
3702 | 0 | |
3703 | 0 | EditActionResult ret(NS_OK); |
3704 | 0 | for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) { |
3705 | 0 | // get the node to act on |
3706 | 0 | if (IsBlockNode(arrayOfNodes[i])) { |
3707 | 0 | // For block nodes, move their contents only, then delete block. |
3708 | 0 | ret |= |
3709 | 0 | MoveContents(*arrayOfNodes[i]->AsElement(), aLeftBlock, &aLeftOffset); |
3710 | 0 | if (NS_WARN_IF(ret.Failed())) { |
3711 | 0 | return ret; |
3712 | 0 | } |
3713 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*arrayOfNodes[i]); |
3714 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3715 | 0 | return ret.SetResult(NS_ERROR_EDITOR_DESTROYED); |
3716 | 0 | } |
3717 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
3718 | 0 | "Failed to remove a block node"); |
3719 | 0 | ret.MarkAsHandled(); |
3720 | 0 | } else { |
3721 | 0 | // Otherwise move the content as is, checking against the DTD. |
3722 | 0 | ret |= |
3723 | 0 | MoveNodeSmart(*arrayOfNodes[i]->AsContent(), aLeftBlock, &aLeftOffset); |
3724 | 0 | if (NS_WARN_IF(ret.Rv() == NS_ERROR_EDITOR_DESTROYED)) { |
3725 | 0 | return ret; |
3726 | 0 | } |
3727 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
3728 | 0 | "Failed to move current node to the left block"); |
3729 | 0 | } |
3730 | 0 | } |
3731 | 0 |
|
3732 | 0 | // XXX We're only checking return value of the last iteration |
3733 | 0 | if (NS_WARN_IF(ret.Failed())) { |
3734 | 0 | return ret; |
3735 | 0 | } |
3736 | 0 | |
3737 | 0 | return ret; |
3738 | 0 | } |
3739 | | |
3740 | | EditActionResult |
3741 | | HTMLEditRules::MoveNodeSmart(nsIContent& aNode, |
3742 | | Element& aDestElement, |
3743 | | int32_t* aInOutDestOffset) |
3744 | 0 | { |
3745 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
3746 | 0 | MOZ_ASSERT(aInOutDestOffset); |
3747 | 0 |
|
3748 | 0 | // Check if this node can go into the destination node |
3749 | 0 | if (HTMLEditorRef().CanContain(aDestElement, aNode)) { |
3750 | 0 | // If it can, move it there. |
3751 | 0 | if (*aInOutDestOffset == -1) { |
3752 | 0 | nsresult rv = |
3753 | 0 | HTMLEditorRef().MoveNodeToEndWithTransaction(aNode, aDestElement); |
3754 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3755 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3756 | 0 | } |
3757 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3758 | 0 | return EditActionIgnored(rv); |
3759 | 0 | } |
3760 | 0 | } else { |
3761 | 0 | EditorRawDOMPoint pointToInsert(&aDestElement, *aInOutDestOffset); |
3762 | 0 | nsresult rv = |
3763 | 0 | HTMLEditorRef().MoveNodeWithTransaction(aNode, pointToInsert); |
3764 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3765 | 0 | return EditActionIgnored(NS_ERROR_EDITOR_DESTROYED); |
3766 | 0 | } |
3767 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3768 | 0 | return EditActionIgnored(rv); |
3769 | 0 | } |
3770 | 0 | } |
3771 | 0 | if (*aInOutDestOffset != -1) { |
3772 | 0 | (*aInOutDestOffset)++; |
3773 | 0 | } |
3774 | 0 | // XXX Should we check if the node is actually moved in this case? |
3775 | 0 | return EditActionHandled(); |
3776 | 0 | } |
3777 | 0 |
|
3778 | 0 | // If it can't, move its children (if any), and then delete it. |
3779 | 0 | EditActionResult ret(NS_OK); |
3780 | 0 | if (aNode.IsElement()) { |
3781 | 0 | ret = MoveContents(*aNode.AsElement(), aDestElement, aInOutDestOffset); |
3782 | 0 | if (NS_WARN_IF(ret.Failed())) { |
3783 | 0 | return ret; |
3784 | 0 | } |
3785 | 0 | } |
3786 | 0 | |
3787 | 0 | nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(aNode); |
3788 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3789 | 0 | return ret.SetResult(NS_ERROR_EDITOR_DESTROYED); |
3790 | 0 | } |
3791 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3792 | 0 | return ret.SetResult(rv); |
3793 | 0 | } |
3794 | 0 | return ret.MarkAsHandled(); |
3795 | 0 | } |
3796 | | |
3797 | | EditActionResult |
3798 | | HTMLEditRules::MoveContents(Element& aElement, |
3799 | | Element& aDestElement, |
3800 | | int32_t* aInOutDestOffset) |
3801 | 0 | { |
3802 | 0 | MOZ_ASSERT(aInOutDestOffset); |
3803 | 0 |
|
3804 | 0 | if (NS_WARN_IF(&aElement == &aDestElement)) { |
3805 | 0 | return EditActionIgnored(NS_ERROR_ILLEGAL_VALUE); |
3806 | 0 | } |
3807 | 0 | |
3808 | 0 | EditActionResult ret(NS_OK); |
3809 | 0 | while (aElement.GetFirstChild()) { |
3810 | 0 | ret |= |
3811 | 0 | MoveNodeSmart(*aElement.GetFirstChild(), aDestElement, aInOutDestOffset); |
3812 | 0 | if (NS_WARN_IF(ret.Failed())) { |
3813 | 0 | return ret; |
3814 | 0 | } |
3815 | 0 | } |
3816 | 0 | return ret; |
3817 | 0 | } |
3818 | | |
3819 | | nsresult |
3820 | | HTMLEditRules::DeleteElementsExceptTableRelatedElements(nsINode& aNode) |
3821 | 0 | { |
3822 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
3823 | 0 |
|
3824 | 0 | if (!HTMLEditUtils::IsTableElementButNotTable(&aNode)) { |
3825 | 0 | nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(aNode); |
3826 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3827 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3828 | 0 | } |
3829 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3830 | 0 | return rv; |
3831 | 0 | } |
3832 | 0 | return NS_OK; |
3833 | 0 | } |
3834 | 0 | |
3835 | 0 | // XXX For performance, this should just call |
3836 | 0 | // DeleteElementsExceptTableRelatedElements() while there are children |
3837 | 0 | // in aNode. If we need to avoid infinite loop because mutation event |
3838 | 0 | // listeners can add unexpected nodes into aNode, we should just loop |
3839 | 0 | // only original count of the children. |
3840 | 0 | AutoTArray<OwningNonNull<nsIContent>, 10> childList; |
3841 | 0 | for (nsIContent* child = aNode.GetFirstChild(); |
3842 | 0 | child; child = child->GetNextSibling()) { |
3843 | 0 | childList.AppendElement(*child); |
3844 | 0 | } |
3845 | 0 |
|
3846 | 0 | for (const auto& child: childList) { |
3847 | 0 | nsresult rv = DeleteElementsExceptTableRelatedElements(child); |
3848 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3849 | 0 | return rv; |
3850 | 0 | } |
3851 | 0 | } |
3852 | 0 | return NS_OK; |
3853 | 0 | } |
3854 | | |
3855 | | nsresult |
3856 | | HTMLEditRules::DidDeleteSelection() |
3857 | 0 | { |
3858 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
3859 | 0 |
|
3860 | 0 | // find where we are |
3861 | 0 | EditorDOMPoint atStartOfSelection(EditorBase::GetStartPoint(&SelectionRef())); |
3862 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
3863 | 0 | return NS_ERROR_FAILURE; |
3864 | 0 | } |
3865 | 0 | |
3866 | 0 | // find any enclosing mailcite |
3867 | 0 | RefPtr<Element> citeNode = |
3868 | 0 | GetTopEnclosingMailCite(*atStartOfSelection.GetContainer()); |
3869 | 0 | if (citeNode) { |
3870 | 0 | bool isEmpty = true, seenBR = false; |
3871 | 0 | HTMLEditorRef().IsEmptyNodeImpl(citeNode, &isEmpty, true, true, false, |
3872 | 0 | &seenBR); |
3873 | 0 | if (isEmpty) { |
3874 | 0 | EditorDOMPoint atCiteNode(citeNode); |
3875 | 0 | { |
3876 | 0 | AutoEditorDOMPointChildInvalidator lockOffset(atCiteNode); |
3877 | 0 | nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*citeNode); |
3878 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3879 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3880 | 0 | } |
3881 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3882 | 0 | return rv; |
3883 | 0 | } |
3884 | 0 | } |
3885 | 0 | if (atCiteNode.IsSet() && seenBR) { |
3886 | 0 | RefPtr<Element> brElement = |
3887 | 0 | HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(), |
3888 | 0 | atCiteNode); |
3889 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3890 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3891 | 0 | } |
3892 | 0 | if (NS_WARN_IF(!brElement)) { |
3893 | 0 | return NS_ERROR_FAILURE; |
3894 | 0 | } |
3895 | 0 | IgnoredErrorResult error; |
3896 | 0 | SelectionRef().Collapse(EditorRawDOMPoint(brElement), error); |
3897 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
3898 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3899 | 0 | } |
3900 | 0 | NS_WARNING_ASSERTION(!error.Failed(), |
3901 | 0 | "Failed to collapse selection at the new <br> element"); |
3902 | 0 | } |
3903 | 0 | } |
3904 | 0 | } |
3905 | 0 |
|
3906 | 0 | // call through to base class |
3907 | 0 | nsresult rv = TextEditRules::DidDeleteSelection(); |
3908 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3909 | 0 | return rv; |
3910 | 0 | } |
3911 | 0 | return NS_OK; |
3912 | 0 | } |
3913 | | |
3914 | | nsresult |
3915 | | HTMLEditRules::WillMakeList(const nsAString* aListType, |
3916 | | bool aEntireList, |
3917 | | const nsAString* aBulletType, |
3918 | | bool* aCancel, |
3919 | | bool* aHandled, |
3920 | | const nsAString* aItemType) |
3921 | 0 | { |
3922 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
3923 | 0 |
|
3924 | 0 | if (NS_WARN_IF(!aListType) || NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) { |
3925 | 0 | return NS_ERROR_INVALID_ARG; |
3926 | 0 | } |
3927 | 0 | |
3928 | 0 | *aCancel = false; |
3929 | 0 | *aHandled = false; |
3930 | 0 |
|
3931 | 0 | OwningNonNull<nsAtom> listType = NS_Atomize(*aListType); |
3932 | 0 |
|
3933 | 0 | // FYI: Ignore cancel result of WillInsert(). |
3934 | 0 | nsresult rv = WillInsert(); |
3935 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
3936 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3937 | 0 | } |
3938 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
3939 | 0 |
|
3940 | 0 | // deduce what tag to use for list items |
3941 | 0 | RefPtr<nsAtom> itemType; |
3942 | 0 | if (aItemType) { |
3943 | 0 | itemType = NS_Atomize(*aItemType); |
3944 | 0 | } else if (listType == nsGkAtoms::dl) { |
3945 | 0 | itemType = nsGkAtoms::dd; |
3946 | 0 | } else { |
3947 | 0 | itemType = nsGkAtoms::li; |
3948 | 0 | } |
3949 | 0 |
|
3950 | 0 | // convert the selection ranges into "promoted" selection ranges: |
3951 | 0 | // this basically just expands the range to include the immediate |
3952 | 0 | // block parent, and then further expands to include any ancestors |
3953 | 0 | // whose children are all in the range |
3954 | 0 |
|
3955 | 0 | *aHandled = true; |
3956 | 0 |
|
3957 | 0 | rv = NormalizeSelection(); |
3958 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3959 | 0 | return rv; |
3960 | 0 | } |
3961 | 0 | |
3962 | 0 | // MakeList() creates AutoSelectionRestorer. |
3963 | 0 | // Therefore, even if it returns NS_OK, editor might have been destroyed |
3964 | 0 | // at restoring Selection. |
3965 | 0 | rv = MakeList(listType, aEntireList, aBulletType, aCancel, *itemType); |
3966 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) || |
3967 | 0 | NS_WARN_IF(!CanHandleEditAction())) { |
3968 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
3969 | 0 | } |
3970 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3971 | 0 | return rv; |
3972 | 0 | } |
3973 | 0 | return NS_OK; |
3974 | 0 | } |
3975 | | |
3976 | | nsresult |
3977 | | HTMLEditRules::MakeList(nsAtom& aListType, |
3978 | | bool aEntireList, |
3979 | | const nsAString* aBulletType, |
3980 | | bool* aCancel, |
3981 | | nsAtom& aItemType) |
3982 | 0 | { |
3983 | 0 | AutoSelectionRestorer selectionRestorer(&SelectionRef(), &HTMLEditorRef()); |
3984 | 0 |
|
3985 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
3986 | 0 | nsresult rv = |
3987 | 0 | GetListActionNodes(arrayOfNodes, |
3988 | 0 | aEntireList ? EntireList::yes : EntireList::no, |
3989 | 0 | TouchContent::yes); |
3990 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
3991 | 0 | return rv; |
3992 | 0 | } |
3993 | 0 | |
3994 | 0 | // check if all our nodes are <br>s, or empty inlines |
3995 | 0 | bool bOnlyBreaks = true; |
3996 | 0 | for (auto& curNode : arrayOfNodes) { |
3997 | 0 | // if curNode is not a Break or empty inline, we're done |
3998 | 0 | if (!TextEditUtils::IsBreak(curNode) && |
3999 | 0 | !IsEmptyInline(curNode)) { |
4000 | 0 | bOnlyBreaks = false; |
4001 | 0 | break; |
4002 | 0 | } |
4003 | 0 | } |
4004 | 0 |
|
4005 | 0 | // if no nodes, we make empty list. Ditto if the user tried to make a list |
4006 | 0 | // of some # of breaks. |
4007 | 0 | if (arrayOfNodes.IsEmpty() || bOnlyBreaks) { |
4008 | 0 | // if only breaks, delete them |
4009 | 0 | if (bOnlyBreaks) { |
4010 | 0 | for (auto& node : arrayOfNodes) { |
4011 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*node); |
4012 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4013 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4014 | 0 | } |
4015 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4016 | 0 | return rv; |
4017 | 0 | } |
4018 | 0 | } |
4019 | 0 | } |
4020 | 0 |
|
4021 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
4022 | 0 | if (NS_WARN_IF(!firstRange)) { |
4023 | 0 | return NS_ERROR_FAILURE; |
4024 | 0 | } |
4025 | 0 | |
4026 | 0 | EditorDOMPoint atStartOfSelection(firstRange->StartRef()); |
4027 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
4028 | 0 | return NS_ERROR_FAILURE; |
4029 | 0 | } |
4030 | 0 | |
4031 | 0 | // Make sure we can put a list here. |
4032 | 0 | if (!HTMLEditorRef().CanContainTag(*atStartOfSelection.GetContainer(), |
4033 | 0 | aListType)) { |
4034 | 0 | *aCancel = true; |
4035 | 0 | return NS_OK; |
4036 | 0 | } |
4037 | 0 | |
4038 | 0 | SplitNodeResult splitAtSelectionStartResult = |
4039 | 0 | MaybeSplitAncestorsForInsertWithTransaction(aListType, |
4040 | 0 | atStartOfSelection); |
4041 | 0 | if (NS_WARN_IF(splitAtSelectionStartResult.Failed())) { |
4042 | 0 | return splitAtSelectionStartResult.Rv(); |
4043 | 0 | } |
4044 | 0 | RefPtr<Element> theList = |
4045 | 0 | HTMLEditorRef().CreateNodeWithTransaction( |
4046 | 0 | aListType, splitAtSelectionStartResult.SplitPoint()); |
4047 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4048 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4049 | 0 | } |
4050 | 0 | if (NS_WARN_IF(!theList)) { |
4051 | 0 | return NS_ERROR_FAILURE; |
4052 | 0 | } |
4053 | 0 | |
4054 | 0 | EditorRawDOMPoint atFirstListItemToInsertBefore(theList, 0); |
4055 | 0 | RefPtr<Element> theListItem = |
4056 | 0 | HTMLEditorRef().CreateNodeWithTransaction(aItemType, |
4057 | 0 | atFirstListItemToInsertBefore); |
4058 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4059 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4060 | 0 | } |
4061 | 0 | if (NS_WARN_IF(!theListItem)) { |
4062 | 0 | return NS_ERROR_FAILURE; |
4063 | 0 | } |
4064 | 0 | |
4065 | 0 | // remember our new block for postprocessing |
4066 | 0 | mNewBlock = theListItem; |
4067 | 0 | // Put selection in new list item and don't restore the Selection. |
4068 | 0 | selectionRestorer.Abort(); |
4069 | 0 | ErrorResult error; |
4070 | 0 | SelectionRef().Collapse(EditorRawDOMPoint(theListItem, 0), error); |
4071 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4072 | 0 | error.SuppressException(); |
4073 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4074 | 0 | } |
4075 | 0 | if (NS_WARN_IF(!error.Failed())) { |
4076 | 0 | return error.StealNSResult(); |
4077 | 0 | } |
4078 | 0 | return NS_OK; |
4079 | 0 | } |
4080 | 0 | |
4081 | 0 | // if there is only one node in the array, and it is a list, div, or |
4082 | 0 | // blockquote, then look inside of it until we find inner list or content. |
4083 | 0 | |
4084 | 0 | LookInsideDivBQandList(arrayOfNodes); |
4085 | 0 |
|
4086 | 0 | // Ok, now go through all the nodes and put then in the list, |
4087 | 0 | // or whatever is approriate. Wohoo! |
4088 | 0 |
|
4089 | 0 | uint32_t listCount = arrayOfNodes.Length(); |
4090 | 0 | RefPtr<Element> curList, prevListItem; |
4091 | 0 |
|
4092 | 0 | for (uint32_t i = 0; i < listCount; i++) { |
4093 | 0 | // here's where we actually figure out what to do |
4094 | 0 | RefPtr<Element> newBlock; |
4095 | 0 | if (NS_WARN_IF(!arrayOfNodes[i]->IsContent())) { |
4096 | 0 | return NS_ERROR_FAILURE; |
4097 | 0 | } |
4098 | 0 | OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent(); |
4099 | 0 |
|
4100 | 0 | // make sure we don't assemble content that is in different table cells |
4101 | 0 | // into the same list. respect table cell boundaries when listifying. |
4102 | 0 | if (curList && InDifferentTableElements(curList, curNode)) { |
4103 | 0 | curList = nullptr; |
4104 | 0 | } |
4105 | 0 |
|
4106 | 0 | // If curNode is a break, delete it, and quit remembering prev list item. |
4107 | 0 | // If an empty inline container, delete it, but still remember the previous |
4108 | 0 | // item. |
4109 | 0 | if (HTMLEditorRef().IsEditable(curNode) && |
4110 | 0 | (TextEditUtils::IsBreak(curNode) || IsEmptyInline(curNode))) { |
4111 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*curNode); |
4112 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4113 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4114 | 0 | } |
4115 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4116 | 0 | return rv; |
4117 | 0 | } |
4118 | 0 | if (TextEditUtils::IsBreak(curNode)) { |
4119 | 0 | prevListItem = nullptr; |
4120 | 0 | } |
4121 | 0 | continue; |
4122 | 0 | } |
4123 | 0 |
|
4124 | 0 | if (HTMLEditUtils::IsList(curNode)) { |
4125 | 0 | // do we have a curList already? |
4126 | 0 | if (curList && !EditorUtils::IsDescendantOf(*curNode, *curList)) { |
4127 | 0 | // move all of our children into curList. cheezy way to do it: move |
4128 | 0 | // whole list and then RemoveContainerWithTransaction() on the list. |
4129 | 0 | // ConvertListType first: that routine handles converting the list |
4130 | 0 | // item types, if needed. |
4131 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode, *curList); |
4132 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4133 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4134 | 0 | } |
4135 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4136 | 0 | return rv; |
4137 | 0 | } |
4138 | 0 | CreateElementResult convertListTypeResult = |
4139 | 0 | ConvertListType(*curNode->AsElement(), aListType, aItemType); |
4140 | 0 | if (NS_WARN_IF(convertListTypeResult.Failed())) { |
4141 | 0 | return convertListTypeResult.Rv(); |
4142 | 0 | } |
4143 | 0 | rv = HTMLEditorRef().RemoveBlockContainerWithTransaction( |
4144 | 0 | *convertListTypeResult.GetNewNode()); |
4145 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4146 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4147 | 0 | } |
4148 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4149 | 0 | return rv; |
4150 | 0 | } |
4151 | 0 | newBlock = convertListTypeResult.forget(); |
4152 | 0 | } else { |
4153 | 0 | // replace list with new list type |
4154 | 0 | CreateElementResult convertListTypeResult = |
4155 | 0 | ConvertListType(*curNode->AsElement(), aListType, aItemType); |
4156 | 0 | if (NS_WARN_IF(convertListTypeResult.Failed())) { |
4157 | 0 | return convertListTypeResult.Rv(); |
4158 | 0 | } |
4159 | 0 | curList = convertListTypeResult.forget(); |
4160 | 0 | } |
4161 | 0 | prevListItem = nullptr; |
4162 | 0 | continue; |
4163 | 0 | } |
4164 | 0 | |
4165 | 0 | EditorRawDOMPoint atCurNode(curNode); |
4166 | 0 | if (NS_WARN_IF(!atCurNode.IsSet())) { |
4167 | 0 | return NS_ERROR_FAILURE; |
4168 | 0 | } |
4169 | 0 | MOZ_ASSERT(atCurNode.IsSetAndValid()); |
4170 | 0 | if (HTMLEditUtils::IsListItem(curNode)) { |
4171 | 0 | if (!atCurNode.IsContainerHTMLElement(&aListType)) { |
4172 | 0 | // list item is in wrong type of list. if we don't have a curList, |
4173 | 0 | // split the old list and make a new list of correct type. |
4174 | 0 | if (!curList || EditorUtils::IsDescendantOf(*curNode, *curList)) { |
4175 | 0 | if (NS_WARN_IF(!atCurNode.GetContainerAsContent())) { |
4176 | 0 | return NS_ERROR_FAILURE; |
4177 | 0 | } |
4178 | 0 | ErrorResult error; |
4179 | 0 | nsCOMPtr<nsIContent> newLeftNode = |
4180 | 0 | HTMLEditorRef().SplitNodeWithTransaction(atCurNode, error); |
4181 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4182 | 0 | error.SuppressException(); |
4183 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4184 | 0 | } |
4185 | 0 | if (NS_WARN_IF(error.Failed())) { |
4186 | 0 | return error.StealNSResult(); |
4187 | 0 | } |
4188 | 0 | newBlock = newLeftNode ? newLeftNode->AsElement() : nullptr; |
4189 | 0 | EditorRawDOMPoint atParentOfCurNode(atCurNode.GetContainer()); |
4190 | 0 | curList = |
4191 | 0 | HTMLEditorRef().CreateNodeWithTransaction(aListType, |
4192 | 0 | atParentOfCurNode); |
4193 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4194 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4195 | 0 | } |
4196 | 0 | if (NS_WARN_IF(!curList)) { |
4197 | 0 | return NS_ERROR_FAILURE; |
4198 | 0 | } |
4199 | 0 | } |
4200 | 0 | // move list item to new list |
4201 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode, *curList); |
4202 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4203 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4204 | 0 | } |
4205 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4206 | 0 | return rv; |
4207 | 0 | } |
4208 | 0 | // convert list item type if needed |
4209 | 0 | if (!curNode->IsHTMLElement(&aItemType)) { |
4210 | 0 | newBlock = |
4211 | 0 | HTMLEditorRef().ReplaceContainerWithTransaction( |
4212 | 0 | *curNode->AsElement(), aItemType); |
4213 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4214 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4215 | 0 | } |
4216 | 0 | if (NS_WARN_IF(!newBlock)) { |
4217 | 0 | return NS_ERROR_FAILURE; |
4218 | 0 | } |
4219 | 0 | } |
4220 | 0 | } else { |
4221 | 0 | // item is in right type of list. But we might still have to move it. |
4222 | 0 | // and we might need to convert list item types. |
4223 | 0 | if (!curList) { |
4224 | 0 | curList = atCurNode.GetContainerAsElement(); |
4225 | 0 | } else if (atCurNode.GetContainer() != curList) { |
4226 | 0 | // move list item to new list |
4227 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode, *curList); |
4228 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4229 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4230 | 0 | } |
4231 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4232 | 0 | return rv; |
4233 | 0 | } |
4234 | 0 | } |
4235 | 0 | if (!curNode->IsHTMLElement(&aItemType)) { |
4236 | 0 | newBlock = |
4237 | 0 | HTMLEditorRef().ReplaceContainerWithTransaction( |
4238 | 0 | *curNode->AsElement(), aItemType); |
4239 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4240 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4241 | 0 | } |
4242 | 0 | if (NS_WARN_IF(!newBlock)) { |
4243 | 0 | return NS_ERROR_FAILURE; |
4244 | 0 | } |
4245 | 0 | } |
4246 | 0 | } |
4247 | 0 | nsCOMPtr<Element> curElement = do_QueryInterface(curNode); |
4248 | 0 | if (NS_WARN_IF(!curElement)) { |
4249 | 0 | return NS_ERROR_FAILURE; |
4250 | 0 | } |
4251 | 0 | if (aBulletType && !aBulletType->IsEmpty()) { |
4252 | 0 | rv = HTMLEditorRef().SetAttributeWithTransaction(*curElement, |
4253 | 0 | *nsGkAtoms::type, |
4254 | 0 | *aBulletType); |
4255 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4256 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4257 | 0 | } |
4258 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4259 | 0 | return rv; |
4260 | 0 | } |
4261 | 0 | } else { |
4262 | 0 | rv = HTMLEditorRef().RemoveAttributeWithTransaction(*curElement, |
4263 | 0 | *nsGkAtoms::type); |
4264 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4265 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4266 | 0 | } |
4267 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4268 | 0 | return rv; |
4269 | 0 | } |
4270 | 0 | } |
4271 | 0 | continue; |
4272 | 0 | } |
4273 | 0 | |
4274 | 0 | // if we hit a div clear our prevListItem, insert divs contents |
4275 | 0 | // into our node array, and remove the div |
4276 | 0 | if (curNode->IsHTMLElement(nsGkAtoms::div)) { |
4277 | 0 | prevListItem = nullptr; |
4278 | 0 | int32_t j = i + 1; |
4279 | 0 | GetInnerContent(*curNode, arrayOfNodes, &j); |
4280 | 0 | rv = HTMLEditorRef().RemoveContainerWithTransaction( |
4281 | 0 | *curNode->AsElement()); |
4282 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4283 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4284 | 0 | } |
4285 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4286 | 0 | return rv; |
4287 | 0 | } |
4288 | 0 | listCount = arrayOfNodes.Length(); |
4289 | 0 | continue; |
4290 | 0 | } |
4291 | 0 | |
4292 | 0 | // need to make a list to put things in if we haven't already, |
4293 | 0 | if (!curList) { |
4294 | 0 | SplitNodeResult splitCurNodeResult = |
4295 | 0 | MaybeSplitAncestorsForInsertWithTransaction(aListType, atCurNode); |
4296 | 0 | if (NS_WARN_IF(splitCurNodeResult.Failed())) { |
4297 | 0 | return splitCurNodeResult.Rv(); |
4298 | 0 | } |
4299 | 0 | curList = |
4300 | 0 | HTMLEditorRef().CreateNodeWithTransaction( |
4301 | 0 | aListType, splitCurNodeResult.SplitPoint()); |
4302 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4303 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4304 | 0 | } |
4305 | 0 | if (NS_WARN_IF(!curList)) { |
4306 | 0 | return NS_ERROR_FAILURE; |
4307 | 0 | } |
4308 | 0 | // remember our new block for postprocessing |
4309 | 0 | mNewBlock = curList; |
4310 | 0 | // curList is now the correct thing to put curNode in |
4311 | 0 | prevListItem = nullptr; |
4312 | 0 |
|
4313 | 0 | // atCurNode is now referring the right node with mOffset but |
4314 | 0 | // referring the left node with mRef. So, invalidate it now. |
4315 | 0 | atCurNode.Clear(); |
4316 | 0 | } |
4317 | 0 |
|
4318 | 0 | // if curNode isn't a list item, we must wrap it in one |
4319 | 0 | nsCOMPtr<Element> listItem; |
4320 | 0 | if (!HTMLEditUtils::IsListItem(curNode)) { |
4321 | 0 | if (IsInlineNode(curNode) && prevListItem) { |
4322 | 0 | // this is a continuation of some inline nodes that belong together in |
4323 | 0 | // the same list item. use prevListItem |
4324 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode, |
4325 | 0 | *prevListItem); |
4326 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4327 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4328 | 0 | } |
4329 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4330 | 0 | return rv; |
4331 | 0 | } |
4332 | 0 | } else { |
4333 | 0 | // don't wrap li around a paragraph. instead replace paragraph with li |
4334 | 0 | if (curNode->IsHTMLElement(nsGkAtoms::p)) { |
4335 | 0 | listItem = |
4336 | 0 | HTMLEditorRef().ReplaceContainerWithTransaction( |
4337 | 0 | *curNode->AsElement(), aItemType); |
4338 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4339 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4340 | 0 | } |
4341 | 0 | if (NS_WARN_IF(!listItem)) { |
4342 | 0 | return NS_ERROR_FAILURE; |
4343 | 0 | } |
4344 | 0 | } else { |
4345 | 0 | listItem = |
4346 | 0 | HTMLEditorRef().InsertContainerWithTransaction(*curNode, aItemType); |
4347 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4348 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4349 | 0 | } |
4350 | 0 | if (NS_WARN_IF(!listItem)) { |
4351 | 0 | return NS_ERROR_FAILURE; |
4352 | 0 | } |
4353 | 0 | } |
4354 | 0 | if (IsInlineNode(curNode)) { |
4355 | 0 | prevListItem = listItem; |
4356 | 0 | } else { |
4357 | 0 | prevListItem = nullptr; |
4358 | 0 | } |
4359 | 0 | } |
4360 | 0 | } else { |
4361 | 0 | listItem = curNode->AsElement(); |
4362 | 0 | } |
4363 | 0 |
|
4364 | 0 | if (listItem) { |
4365 | 0 | // if we made a new list item, deal with it: tuck the listItem into the |
4366 | 0 | // end of the active list |
4367 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*listItem, *curList); |
4368 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4369 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4370 | 0 | } |
4371 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4372 | 0 | return rv; |
4373 | 0 | } |
4374 | 0 | } |
4375 | 0 | } |
4376 | 0 |
|
4377 | 0 | return NS_OK; |
4378 | 0 | } |
4379 | | |
4380 | | nsresult |
4381 | | HTMLEditRules::WillRemoveList(bool* aCancel, |
4382 | | bool* aHandled) |
4383 | 0 | { |
4384 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
4385 | 0 |
|
4386 | 0 | if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) { |
4387 | 0 | return NS_ERROR_INVALID_ARG; |
4388 | 0 | } |
4389 | 0 | // initialize out param |
4390 | 0 | *aCancel = false; |
4391 | 0 | *aHandled = true; |
4392 | 0 |
|
4393 | 0 | nsresult rv = NormalizeSelection(); |
4394 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4395 | 0 | return rv; |
4396 | 0 | } |
4397 | 0 | |
4398 | 0 | AutoSelectionRestorer selectionRestorer(&SelectionRef(), &HTMLEditorRef()); |
4399 | 0 |
|
4400 | 0 | nsTArray<RefPtr<nsRange>> arrayOfRanges; |
4401 | 0 | GetPromotedRanges(arrayOfRanges, EditSubAction::eCreateOrChangeList); |
4402 | 0 |
|
4403 | 0 | // use these ranges to contruct a list of nodes to act on. |
4404 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
4405 | 0 | rv = GetListActionNodes(arrayOfNodes, EntireList::no, TouchContent::yes); |
4406 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4407 | 0 | return rv; |
4408 | 0 | } |
4409 | 0 | |
4410 | 0 | // Remove all non-editable nodes. Leave them be. |
4411 | 0 | for (int32_t i = arrayOfNodes.Length() - 1; i >= 0; i--) { |
4412 | 0 | OwningNonNull<nsINode> testNode = arrayOfNodes[i]; |
4413 | 0 | if (!HTMLEditorRef().IsEditable(testNode)) { |
4414 | 0 | arrayOfNodes.RemoveElementAt(i); |
4415 | 0 | } |
4416 | 0 | } |
4417 | 0 |
|
4418 | 0 | // Only act on lists or list items in the array |
4419 | 0 | for (auto& curNode : arrayOfNodes) { |
4420 | 0 | // here's where we actually figure out what to do |
4421 | 0 | if (HTMLEditUtils::IsListItem(curNode)) { |
4422 | 0 | // unlist this listitem |
4423 | 0 | bool bOutOfList; |
4424 | 0 | do { |
4425 | 0 | rv = PopListItem(*curNode->AsContent(), &bOutOfList); |
4426 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4427 | 0 | return rv; |
4428 | 0 | } |
4429 | 0 | } while (!bOutOfList); // keep popping it out until it's not in a list anymore |
4430 | 0 | } else if (HTMLEditUtils::IsList(curNode)) { |
4431 | 0 | // node is a list, move list items out |
4432 | 0 | rv = RemoveListStructure(*curNode->AsElement()); |
4433 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4434 | 0 | return rv; |
4435 | 0 | } |
4436 | 0 | } |
4437 | 0 | } |
4438 | 0 | return NS_OK; |
4439 | 0 | } |
4440 | | |
4441 | | nsresult |
4442 | | HTMLEditRules::WillMakeDefListItem(const nsAString *aItemType, |
4443 | | bool aEntireList, |
4444 | | bool* aCancel, |
4445 | | bool* aHandled) |
4446 | 0 | { |
4447 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
4448 | 0 |
|
4449 | 0 | // for now we let WillMakeList handle this |
4450 | 0 | NS_NAMED_LITERAL_STRING(listType, "dl"); |
4451 | 0 | nsresult rv = WillMakeList(&listType.AsString(), aEntireList, |
4452 | 0 | nullptr, aCancel, aHandled, aItemType); |
4453 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4454 | 0 | return rv; |
4455 | 0 | } |
4456 | 0 | return NS_OK; |
4457 | 0 | } |
4458 | | |
4459 | | nsresult |
4460 | | HTMLEditRules::WillMakeBasicBlock(const nsAString& aBlockType, |
4461 | | bool* aCancel, |
4462 | | bool* aHandled) |
4463 | 0 | { |
4464 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
4465 | 0 | MOZ_ASSERT(aCancel && aHandled); |
4466 | 0 |
|
4467 | 0 | OwningNonNull<nsAtom> blockType = NS_Atomize(aBlockType); |
4468 | 0 |
|
4469 | 0 | // FYI: Ignore cancel result of WillInsert(). |
4470 | 0 | nsresult rv = WillInsert(); |
4471 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
4472 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4473 | 0 | } |
4474 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
4475 | 0 |
|
4476 | 0 | *aCancel = false; |
4477 | 0 | *aHandled = true; |
4478 | 0 |
|
4479 | 0 | // MakeBasicBlock() creates AutoSelectionRestorer. |
4480 | 0 | // Therefore, even if it returns NS_OK, editor might have been destroyed |
4481 | 0 | // at restoring Selection. |
4482 | 0 | rv = MakeBasicBlock(blockType); |
4483 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4484 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4485 | 0 | } |
4486 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4487 | 0 | return rv; |
4488 | 0 | } |
4489 | 0 | return NS_OK; |
4490 | 0 | } |
4491 | | |
4492 | | nsresult |
4493 | | HTMLEditRules::MakeBasicBlock(nsAtom& blockType) |
4494 | 0 | { |
4495 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
4496 | 0 |
|
4497 | 0 | nsresult rv = NormalizeSelection(); |
4498 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4499 | 0 | return rv; |
4500 | 0 | } |
4501 | 0 | |
4502 | 0 | AutoSelectionRestorer selectionRestorer(&SelectionRef(), &HTMLEditorRef()); |
4503 | 0 | AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef()); |
4504 | 0 |
|
4505 | 0 | // Contruct a list of nodes to act on. |
4506 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
4507 | 0 | rv = GetNodesFromSelection(EditSubAction::eCreateOrRemoveBlock, arrayOfNodes, |
4508 | 0 | TouchContent::yes); |
4509 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4510 | 0 | return rv; |
4511 | 0 | } |
4512 | 0 | |
4513 | 0 | // If nothing visible in list, make an empty block |
4514 | 0 | if (ListIsEmptyLine(arrayOfNodes)) { |
4515 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
4516 | 0 | if (NS_WARN_IF(!firstRange)) { |
4517 | 0 | return NS_ERROR_FAILURE; |
4518 | 0 | } |
4519 | 0 | |
4520 | 0 | EditorDOMPoint pointToInsertBlock(firstRange->StartRef()); |
4521 | 0 | if (&blockType == nsGkAtoms::normal || |
4522 | 0 | &blockType == nsGkAtoms::_empty) { |
4523 | 0 | // We are removing blocks (going to "body text") |
4524 | 0 | RefPtr<Element> curBlock = |
4525 | 0 | HTMLEditorRef().GetBlock(*pointToInsertBlock.GetContainer()); |
4526 | 0 | if (NS_WARN_IF(!curBlock)) { |
4527 | 0 | return NS_ERROR_FAILURE; |
4528 | 0 | } |
4529 | 0 | if (!HTMLEditUtils::IsFormatNode(curBlock)) { |
4530 | 0 | return NS_OK; |
4531 | 0 | } |
4532 | 0 | |
4533 | 0 | // If the first editable node after selection is a br, consume it. |
4534 | 0 | // Otherwise it gets pushed into a following block after the split, |
4535 | 0 | // which is visually bad. |
4536 | 0 | nsCOMPtr<nsIContent> brContent = |
4537 | 0 | HTMLEditorRef().GetNextEditableHTMLNode(pointToInsertBlock); |
4538 | 0 | if (brContent && brContent->IsHTMLElement(nsGkAtoms::br)) { |
4539 | 0 | AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock); |
4540 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*brContent); |
4541 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4542 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4543 | 0 | } |
4544 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4545 | 0 | return rv; |
4546 | 0 | } |
4547 | 0 | } |
4548 | 0 | // Do the splits! |
4549 | 0 | SplitNodeResult splitNodeResult = |
4550 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
4551 | 0 | *curBlock, pointToInsertBlock, |
4552 | 0 | SplitAtEdges::eDoNotCreateEmptyContainer); |
4553 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4554 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4555 | 0 | } |
4556 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
4557 | 0 | return splitNodeResult.Rv(); |
4558 | 0 | } |
4559 | 0 | EditorRawDOMPoint pointToInsertBrNode(splitNodeResult.SplitPoint()); |
4560 | 0 | // Put a <br> element at the split point |
4561 | 0 | brContent = |
4562 | 0 | HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(), |
4563 | 0 | pointToInsertBrNode); |
4564 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4565 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4566 | 0 | } |
4567 | 0 | if (NS_WARN_IF(!brContent)) { |
4568 | 0 | return NS_ERROR_FAILURE; |
4569 | 0 | } |
4570 | 0 | // Put selection at the split point |
4571 | 0 | EditorRawDOMPoint atBrNode(brContent); |
4572 | 0 | // Don't restore the selection |
4573 | 0 | selectionRestorer.Abort(); |
4574 | 0 | ErrorResult error; |
4575 | 0 | SelectionRef().Collapse(atBrNode, error); |
4576 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4577 | 0 | error.SuppressException(); |
4578 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4579 | 0 | } |
4580 | 0 | if (NS_WARN_IF(error.Failed())) { |
4581 | 0 | return error.StealNSResult(); |
4582 | 0 | } |
4583 | 0 | return NS_OK; |
4584 | 0 | } |
4585 | 0 | |
4586 | 0 | // We are making a block. Consume a br, if needed. |
4587 | 0 | nsCOMPtr<nsIContent> brNode = |
4588 | 0 | HTMLEditorRef().GetNextEditableHTMLNodeInBlock(pointToInsertBlock); |
4589 | 0 | if (brNode && brNode->IsHTMLElement(nsGkAtoms::br)) { |
4590 | 0 | AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock); |
4591 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*brNode); |
4592 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4593 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4594 | 0 | } |
4595 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4596 | 0 | return rv; |
4597 | 0 | } |
4598 | 0 | // We don't need to act on this node any more |
4599 | 0 | arrayOfNodes.RemoveElement(brNode); |
4600 | 0 | } |
4601 | 0 | // Make sure we can put a block here. |
4602 | 0 | SplitNodeResult splitNodeResult = |
4603 | 0 | MaybeSplitAncestorsForInsertWithTransaction(blockType, |
4604 | 0 | pointToInsertBlock); |
4605 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
4606 | 0 | return splitNodeResult.Rv(); |
4607 | 0 | } |
4608 | 0 | RefPtr<Element> block = |
4609 | 0 | HTMLEditorRef().CreateNodeWithTransaction(blockType, |
4610 | 0 | splitNodeResult.SplitPoint()); |
4611 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4612 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4613 | 0 | } |
4614 | 0 | if (NS_WARN_IF(!block)) { |
4615 | 0 | return NS_ERROR_FAILURE; |
4616 | 0 | } |
4617 | 0 | // Remember our new block for postprocessing |
4618 | 0 | mNewBlock = block; |
4619 | 0 | // Delete anything that was in the list of nodes |
4620 | 0 | while (!arrayOfNodes.IsEmpty()) { |
4621 | 0 | OwningNonNull<nsINode> curNode = arrayOfNodes[0]; |
4622 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*curNode); |
4623 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4624 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4625 | 0 | } |
4626 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4627 | 0 | return rv; |
4628 | 0 | } |
4629 | 0 | arrayOfNodes.RemoveElementAt(0); |
4630 | 0 | } |
4631 | 0 | // Don't restore the selection |
4632 | 0 | selectionRestorer.Abort(); |
4633 | 0 | // Put selection in new block |
4634 | 0 | rv = SelectionRef().Collapse(block, 0); |
4635 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4636 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4637 | 0 | } |
4638 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4639 | 0 | return rv; |
4640 | 0 | } |
4641 | 0 | return NS_OK; |
4642 | 0 | } |
4643 | 0 | // Okay, now go through all the nodes and make the right kind of blocks, or |
4644 | 0 | // whatever is approriate. Woohoo! Note: blockquote is handled a little |
4645 | 0 | // differently. |
4646 | 0 | if (&blockType == nsGkAtoms::blockquote) { |
4647 | 0 | rv = MakeBlockquote(arrayOfNodes); |
4648 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4649 | 0 | return rv; |
4650 | 0 | } |
4651 | 0 | } else if (&blockType == nsGkAtoms::normal || |
4652 | 0 | &blockType == nsGkAtoms::_empty) { |
4653 | 0 | rv = RemoveBlockStyle(arrayOfNodes); |
4654 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4655 | 0 | return rv; |
4656 | 0 | } |
4657 | 0 | } else { |
4658 | 0 | rv = ApplyBlockStyle(arrayOfNodes, blockType); |
4659 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4660 | 0 | return rv; |
4661 | 0 | } |
4662 | 0 | } |
4663 | 0 | return NS_OK; |
4664 | 0 | } |
4665 | | |
4666 | | nsresult |
4667 | | HTMLEditRules::DidMakeBasicBlock() |
4668 | 0 | { |
4669 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
4670 | 0 |
|
4671 | 0 | // check for empty block. if so, put a moz br in it. |
4672 | 0 | if (!SelectionRef().IsCollapsed()) { |
4673 | 0 | return NS_OK; |
4674 | 0 | } |
4675 | 0 | |
4676 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
4677 | 0 | if (NS_WARN_IF(!firstRange)) { |
4678 | 0 | return NS_ERROR_FAILURE; |
4679 | 0 | } |
4680 | 0 | const RangeBoundary& atStartOfSelection = firstRange->StartRef(); |
4681 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
4682 | 0 | return NS_ERROR_FAILURE; |
4683 | 0 | } |
4684 | 0 | nsresult rv = InsertMozBRIfNeeded(*atStartOfSelection.Container()); |
4685 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4686 | 0 | return rv; |
4687 | 0 | } |
4688 | 0 | return NS_OK; |
4689 | 0 | } |
4690 | | |
4691 | | nsresult |
4692 | | HTMLEditRules::WillIndent(bool* aCancel, |
4693 | | bool* aHandled) |
4694 | 0 | { |
4695 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
4696 | 0 |
|
4697 | 0 | if (HTMLEditorRef().IsCSSEnabled()) { |
4698 | 0 | nsresult rv = WillCSSIndent(aCancel, aHandled); |
4699 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4700 | 0 | return rv; |
4701 | 0 | } |
4702 | 0 | } else { |
4703 | 0 | nsresult rv = WillHTMLIndent(aCancel, aHandled); |
4704 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4705 | 0 | return rv; |
4706 | 0 | } |
4707 | 0 | } |
4708 | 0 | return NS_OK; |
4709 | 0 | } |
4710 | | |
4711 | | nsresult |
4712 | | HTMLEditRules::WillCSSIndent(bool* aCancel, |
4713 | | bool* aHandled) |
4714 | 0 | { |
4715 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
4716 | 0 |
|
4717 | 0 | if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) { |
4718 | 0 | return NS_ERROR_INVALID_ARG; |
4719 | 0 | } |
4720 | 0 | |
4721 | 0 | // FYI: Ignore cancel result of WillInsert(). |
4722 | 0 | nsresult rv = WillInsert(); |
4723 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
4724 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4725 | 0 | } |
4726 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
4727 | 0 |
|
4728 | 0 | *aCancel = false; |
4729 | 0 | *aHandled = true; |
4730 | 0 |
|
4731 | 0 | rv = NormalizeSelection(); |
4732 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4733 | 0 | return rv; |
4734 | 0 | } |
4735 | 0 | |
4736 | 0 | // IndentAroundSelectionWithCSS() creates AutoSelectionRestorer. |
4737 | 0 | // Therefore, even if it returns NS_OK, editor might have been destroyed |
4738 | 0 | // at restoring Selection. |
4739 | 0 | rv = IndentAroundSelectionWithCSS(); |
4740 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4741 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4742 | 0 | } |
4743 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4744 | 0 | return rv; |
4745 | 0 | } |
4746 | 0 | return NS_OK; |
4747 | 0 | } |
4748 | | |
4749 | | nsresult |
4750 | | HTMLEditRules::IndentAroundSelectionWithCSS() |
4751 | 0 | { |
4752 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
4753 | 0 |
|
4754 | 0 | AutoSelectionRestorer selectionRestorer(&SelectionRef(), &HTMLEditorRef()); |
4755 | 0 | nsTArray<OwningNonNull<nsRange>> arrayOfRanges; |
4756 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
4757 | 0 |
|
4758 | 0 | // short circuit: detect case of collapsed selection inside an <li>. |
4759 | 0 | // just sublist that <li>. This prevents bug 97797. |
4760 | 0 |
|
4761 | 0 | nsCOMPtr<Element> liNode; |
4762 | 0 | if (SelectionRef().IsCollapsed()) { |
4763 | 0 | EditorRawDOMPoint selectionStartPoint( |
4764 | 0 | EditorBase::GetStartPoint(&SelectionRef())); |
4765 | 0 | if (NS_WARN_IF(!selectionStartPoint.IsSet())) { |
4766 | 0 | return NS_ERROR_FAILURE; |
4767 | 0 | } |
4768 | 0 | Element* block = |
4769 | 0 | HTMLEditorRef().GetBlock(*selectionStartPoint.GetContainer()); |
4770 | 0 | if (block && HTMLEditUtils::IsListItem(block)) { |
4771 | 0 | liNode = block; |
4772 | 0 | } |
4773 | 0 | } |
4774 | 0 |
|
4775 | 0 | if (liNode) { |
4776 | 0 | arrayOfNodes.AppendElement(*liNode); |
4777 | 0 | } else { |
4778 | 0 | // convert the selection ranges into "promoted" selection ranges: |
4779 | 0 | // this basically just expands the range to include the immediate |
4780 | 0 | // block parent, and then further expands to include any ancestors |
4781 | 0 | // whose children are all in the range |
4782 | 0 | nsresult rv = |
4783 | 0 | GetNodesFromSelection(EditSubAction::eIndent, arrayOfNodes, |
4784 | 0 | TouchContent::yes); |
4785 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4786 | 0 | return rv; |
4787 | 0 | } |
4788 | 0 | } |
4789 | 0 | |
4790 | 0 | // if nothing visible in list, make an empty block |
4791 | 0 | if (ListIsEmptyLine(arrayOfNodes)) { |
4792 | 0 | // get selection location |
4793 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
4794 | 0 | if (NS_WARN_IF(!firstRange)) { |
4795 | 0 | return NS_ERROR_FAILURE; |
4796 | 0 | } |
4797 | 0 | |
4798 | 0 | EditorDOMPoint atStartOfSelection(firstRange->StartRef()); |
4799 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
4800 | 0 | return NS_ERROR_FAILURE; |
4801 | 0 | } |
4802 | 0 | |
4803 | 0 | // make sure we can put a block here |
4804 | 0 | SplitNodeResult splitNodeResult = |
4805 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div, |
4806 | 0 | atStartOfSelection); |
4807 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
4808 | 0 | return splitNodeResult.Rv(); |
4809 | 0 | } |
4810 | 0 | RefPtr<Element> theBlock = |
4811 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::div, |
4812 | 0 | splitNodeResult.SplitPoint()); |
4813 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4814 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4815 | 0 | } |
4816 | 0 | if (NS_WARN_IF(!theBlock)) { |
4817 | 0 | return NS_ERROR_FAILURE; |
4818 | 0 | } |
4819 | 0 | // remember our new block for postprocessing |
4820 | 0 | mNewBlock = theBlock; |
4821 | 0 | nsresult rv = IncreaseMarginToIndent(*theBlock); |
4822 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4823 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4824 | 0 | } |
4825 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to increase indentation"); |
4826 | 0 | // delete anything that was in the list of nodes |
4827 | 0 | while (!arrayOfNodes.IsEmpty()) { |
4828 | 0 | OwningNonNull<nsINode> curNode = arrayOfNodes[0]; |
4829 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*curNode); |
4830 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4831 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4832 | 0 | } |
4833 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4834 | 0 | return rv; |
4835 | 0 | } |
4836 | 0 | arrayOfNodes.RemoveElementAt(0); |
4837 | 0 | } |
4838 | 0 | // put selection in new block |
4839 | 0 | EditorRawDOMPoint atStartOfTheBlock(theBlock, 0); |
4840 | 0 | // Don't restore the selection |
4841 | 0 | selectionRestorer.Abort(); |
4842 | 0 | ErrorResult error; |
4843 | 0 | SelectionRef().Collapse(atStartOfTheBlock, error); |
4844 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4845 | 0 | error.SuppressException(); |
4846 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4847 | 0 | } |
4848 | 0 | if (NS_WARN_IF(!error.Failed())) { |
4849 | 0 | return error.StealNSResult(); |
4850 | 0 | } |
4851 | 0 | return NS_OK; |
4852 | 0 | } |
4853 | 0 | |
4854 | 0 | // Ok, now go through all the nodes and put them in a blockquote, |
4855 | 0 | // or whatever is appropriate. Wohoo! |
4856 | 0 | nsCOMPtr<Element> curList, curQuote; |
4857 | 0 | nsCOMPtr<nsIContent> sibling; |
4858 | 0 | for (OwningNonNull<nsINode>& curNode : arrayOfNodes) { |
4859 | 0 | // Here's where we actually figure out what to do. |
4860 | 0 | EditorDOMPoint atCurNode(curNode); |
4861 | 0 | if (NS_WARN_IF(!atCurNode.IsSet())) { |
4862 | 0 | continue; |
4863 | 0 | } |
4864 | 0 | |
4865 | 0 | // Ignore all non-editable nodes. Leave them be. |
4866 | 0 | if (!HTMLEditorRef().IsEditable(curNode)) { |
4867 | 0 | continue; |
4868 | 0 | } |
4869 | 0 | |
4870 | 0 | // some logic for putting list items into nested lists... |
4871 | 0 | if (HTMLEditUtils::IsList(atCurNode.GetContainer())) { |
4872 | 0 | // Check for whether we should join a list that follows curNode. |
4873 | 0 | // We do this if the next element is a list, and the list is of the |
4874 | 0 | // same type (li/ol) as curNode was a part it. |
4875 | 0 | sibling = HTMLEditorRef().GetNextHTMLSibling(curNode); |
4876 | 0 | if (sibling && HTMLEditUtils::IsList(sibling) && |
4877 | 0 | atCurNode.GetContainer()->NodeInfo()->NameAtom() == |
4878 | 0 | sibling->NodeInfo()->NameAtom() && |
4879 | 0 | atCurNode.GetContainer()->NodeInfo()->NamespaceID() == |
4880 | 0 | sibling->NodeInfo()->NamespaceID()) { |
4881 | 0 | nsresult rv = |
4882 | 0 | HTMLEditorRef().MoveNodeWithTransaction( |
4883 | 0 | *curNode->AsContent(), |
4884 | 0 | EditorRawDOMPoint(sibling, 0)); |
4885 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4886 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4887 | 0 | } |
4888 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4889 | 0 | return rv; |
4890 | 0 | } |
4891 | 0 | continue; |
4892 | 0 | } |
4893 | 0 | |
4894 | 0 | // Check for whether we should join a list that preceeds curNode. |
4895 | 0 | // We do this if the previous element is a list, and the list is of |
4896 | 0 | // the same type (li/ol) as curNode was a part of. |
4897 | 0 | sibling = HTMLEditorRef().GetPriorHTMLSibling(curNode); |
4898 | 0 | if (sibling && HTMLEditUtils::IsList(sibling) && |
4899 | 0 | atCurNode.GetContainer()->NodeInfo()->NameAtom() == |
4900 | 0 | sibling->NodeInfo()->NameAtom() && |
4901 | 0 | atCurNode.GetContainer()->NodeInfo()->NamespaceID() == |
4902 | 0 | sibling->NodeInfo()->NamespaceID()) { |
4903 | 0 | nsresult rv = |
4904 | 0 | HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
4905 | 0 | *sibling); |
4906 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4907 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4908 | 0 | } |
4909 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4910 | 0 | return rv; |
4911 | 0 | } |
4912 | 0 | continue; |
4913 | 0 | } |
4914 | 0 | |
4915 | 0 | // check to see if curList is still appropriate. Which it is if |
4916 | 0 | // curNode is still right after it in the same list. |
4917 | 0 | sibling = nullptr; |
4918 | 0 | if (curList) { |
4919 | 0 | sibling = HTMLEditorRef().GetPriorHTMLSibling(curNode); |
4920 | 0 | } |
4921 | 0 |
|
4922 | 0 | if (!curList || (sibling && sibling != curList)) { |
4923 | 0 | nsAtom* containerName = |
4924 | 0 | atCurNode.GetContainer()->NodeInfo()->NameAtom(); |
4925 | 0 | // Create a new nested list of correct type. |
4926 | 0 | SplitNodeResult splitNodeResult = |
4927 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*containerName, |
4928 | 0 | atCurNode); |
4929 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
4930 | 0 | return splitNodeResult.Rv(); |
4931 | 0 | } |
4932 | 0 | curList = |
4933 | 0 | HTMLEditorRef().CreateNodeWithTransaction( |
4934 | 0 | *containerName, splitNodeResult.SplitPoint()); |
4935 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4936 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4937 | 0 | } |
4938 | 0 | if (NS_WARN_IF(!curList)) { |
4939 | 0 | return NS_ERROR_FAILURE; |
4940 | 0 | } |
4941 | 0 | // curList is now the correct thing to put curNode in |
4942 | 0 | // remember our new block for postprocessing |
4943 | 0 | mNewBlock = curList; |
4944 | 0 | } |
4945 | 0 | // tuck the node into the end of the active list |
4946 | 0 | nsresult rv = |
4947 | 0 | HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
4948 | 0 | *curList); |
4949 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4950 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4951 | 0 | } |
4952 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
4953 | 0 | return rv; |
4954 | 0 | } |
4955 | 0 | continue; |
4956 | 0 | } |
4957 | 0 | |
4958 | 0 | // Not a list item. |
4959 | 0 | |
4960 | 0 | if (IsBlockNode(*curNode)) { |
4961 | 0 | nsresult rv = IncreaseMarginToIndent(*curNode->AsElement()); |
4962 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
4963 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4964 | 0 | } |
4965 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to inrease indentation"); |
4966 | 0 | curQuote = nullptr; |
4967 | 0 | continue; |
4968 | 0 | } |
4969 | 0 | |
4970 | 0 | if (!curQuote) { |
4971 | 0 | // First, check that our element can contain a div. |
4972 | 0 | if (!HTMLEditorRef().CanContainTag(*atCurNode.GetContainer(), |
4973 | 0 | *nsGkAtoms::div)) { |
4974 | 0 | return NS_OK; // cancelled |
4975 | 0 | } |
4976 | 0 | |
4977 | 0 | SplitNodeResult splitNodeResult = |
4978 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div, atCurNode); |
4979 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
4980 | 0 | return splitNodeResult.Rv(); |
4981 | 0 | } |
4982 | 0 | curQuote = |
4983 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::div, |
4984 | 0 | splitNodeResult.SplitPoint()); |
4985 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
4986 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4987 | 0 | } |
4988 | 0 | if (NS_WARN_IF(!curQuote)) { |
4989 | 0 | return NS_ERROR_FAILURE; |
4990 | 0 | } |
4991 | 0 | nsresult rv = IncreaseMarginToIndent(*curQuote); |
4992 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
4993 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
4994 | 0 | } |
4995 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to increase indentation"); |
4996 | 0 | // remember our new block for postprocessing |
4997 | 0 | mNewBlock = curQuote; |
4998 | 0 | // curQuote is now the correct thing to put curNode in |
4999 | 0 | } |
5000 | 0 |
|
5001 | 0 | // tuck the node into the end of the active blockquote |
5002 | 0 | nsresult rv = |
5003 | 0 | HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
5004 | 0 | *curQuote); |
5005 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5006 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5007 | 0 | } |
5008 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5009 | 0 | return rv; |
5010 | 0 | } |
5011 | 0 | } |
5012 | 0 | return NS_OK; |
5013 | 0 | } |
5014 | | |
5015 | | nsresult |
5016 | | HTMLEditRules::WillHTMLIndent(bool* aCancel, |
5017 | | bool* aHandled) |
5018 | 0 | { |
5019 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
5020 | 0 |
|
5021 | 0 | if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) { |
5022 | 0 | return NS_ERROR_INVALID_ARG; |
5023 | 0 | } |
5024 | 0 | |
5025 | 0 | // FYI: Ignore cancel result of WillInsert(). |
5026 | 0 | nsresult rv = WillInsert(); |
5027 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
5028 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5029 | 0 | } |
5030 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
5031 | 0 |
|
5032 | 0 | *aCancel = false; |
5033 | 0 | *aHandled = true; |
5034 | 0 |
|
5035 | 0 | rv = NormalizeSelection(); |
5036 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5037 | 0 | return rv; |
5038 | 0 | } |
5039 | 0 | |
5040 | 0 | // IndentAroundSelectionWithHTML() creates AutoSelectionRestorer. |
5041 | 0 | // Therefore, even if it returns NS_OK, editor might have been destroyed |
5042 | 0 | // at restoring Selection. |
5043 | 0 | rv = IndentAroundSelectionWithHTML(); |
5044 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5045 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5046 | 0 | } |
5047 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5048 | 0 | return rv; |
5049 | 0 | } |
5050 | 0 | return NS_OK; |
5051 | 0 | } |
5052 | | |
5053 | | nsresult |
5054 | | HTMLEditRules::IndentAroundSelectionWithHTML() |
5055 | 0 | { |
5056 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
5057 | 0 |
|
5058 | 0 | AutoSelectionRestorer selectionRestorer(&SelectionRef(), &HTMLEditorRef()); |
5059 | 0 |
|
5060 | 0 | // convert the selection ranges into "promoted" selection ranges: |
5061 | 0 | // this basically just expands the range to include the immediate |
5062 | 0 | // block parent, and then further expands to include any ancestors |
5063 | 0 | // whose children are all in the range |
5064 | 0 |
|
5065 | 0 | nsTArray<RefPtr<nsRange>> arrayOfRanges; |
5066 | 0 | GetPromotedRanges(arrayOfRanges, EditSubAction::eIndent); |
5067 | 0 |
|
5068 | 0 | // use these ranges to contruct a list of nodes to act on. |
5069 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
5070 | 0 | nsresult rv = |
5071 | 0 | GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditSubAction::eIndent, |
5072 | 0 | TouchContent::yes); |
5073 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5074 | 0 | return rv; |
5075 | 0 | } |
5076 | 0 | |
5077 | 0 | // if nothing visible in list, make an empty block |
5078 | 0 | if (ListIsEmptyLine(arrayOfNodes)) { |
5079 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
5080 | 0 | if (NS_WARN_IF(!firstRange)) { |
5081 | 0 | return NS_ERROR_FAILURE; |
5082 | 0 | } |
5083 | 0 | |
5084 | 0 | EditorDOMPoint atStartOfSelection(firstRange->StartRef()); |
5085 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
5086 | 0 | return NS_ERROR_FAILURE; |
5087 | 0 | } |
5088 | 0 | |
5089 | 0 | // Make sure we can put a block here. |
5090 | 0 | SplitNodeResult splitNodeResult = |
5091 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::blockquote, |
5092 | 0 | atStartOfSelection); |
5093 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
5094 | 0 | return splitNodeResult.Rv(); |
5095 | 0 | } |
5096 | 0 | RefPtr<Element> theBlock = |
5097 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::blockquote, |
5098 | 0 | splitNodeResult.SplitPoint()); |
5099 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5100 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5101 | 0 | } |
5102 | 0 | if (NS_WARN_IF(!theBlock)) { |
5103 | 0 | return NS_ERROR_FAILURE; |
5104 | 0 | } |
5105 | 0 | // remember our new block for postprocessing |
5106 | 0 | mNewBlock = theBlock; |
5107 | 0 | // delete anything that was in the list of nodes |
5108 | 0 | while (!arrayOfNodes.IsEmpty()) { |
5109 | 0 | OwningNonNull<nsINode> curNode = arrayOfNodes[0]; |
5110 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*curNode); |
5111 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5112 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5113 | 0 | } |
5114 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5115 | 0 | return rv; |
5116 | 0 | } |
5117 | 0 | arrayOfNodes.RemoveElementAt(0); |
5118 | 0 | } |
5119 | 0 | EditorRawDOMPoint atStartOfTheBlock(theBlock, 0); |
5120 | 0 | // Don't restore the selection |
5121 | 0 | selectionRestorer.Abort(); |
5122 | 0 | ErrorResult error; |
5123 | 0 | SelectionRef().Collapse(atStartOfTheBlock, error); |
5124 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5125 | 0 | error.SuppressException(); |
5126 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5127 | 0 | } |
5128 | 0 | if (NS_WARN_IF(!error.Failed())) { |
5129 | 0 | return error.StealNSResult(); |
5130 | 0 | } |
5131 | 0 | return NS_OK; |
5132 | 0 | } |
5133 | 0 | |
5134 | 0 | // Ok, now go through all the nodes and put them in a blockquote, |
5135 | 0 | // or whatever is appropriate. Wohoo! |
5136 | 0 | nsCOMPtr<nsIContent> sibling; |
5137 | 0 | nsCOMPtr<Element> curList, curQuote, indentedLI; |
5138 | 0 | for (OwningNonNull<nsINode>& curNode: arrayOfNodes) { |
5139 | 0 | // Here's where we actually figure out what to do. |
5140 | 0 | EditorDOMPoint atCurNode(curNode); |
5141 | 0 | if (NS_WARN_IF(!atCurNode.IsSet())) { |
5142 | 0 | continue; |
5143 | 0 | } |
5144 | 0 | |
5145 | 0 | // Ignore all non-editable nodes. Leave them be. |
5146 | 0 | if (!HTMLEditorRef().IsEditable(curNode)) { |
5147 | 0 | continue; |
5148 | 0 | } |
5149 | 0 | |
5150 | 0 | // some logic for putting list items into nested lists... |
5151 | 0 | if (HTMLEditUtils::IsList(atCurNode.GetContainer())) { |
5152 | 0 | // Check for whether we should join a list that follows curNode. |
5153 | 0 | // We do this if the next element is a list, and the list is of the |
5154 | 0 | // same type (li/ol) as curNode was a part it. |
5155 | 0 | sibling = HTMLEditorRef().GetNextHTMLSibling(curNode); |
5156 | 0 | if (sibling && HTMLEditUtils::IsList(sibling) && |
5157 | 0 | atCurNode.GetContainer()->NodeInfo()->NameAtom() == |
5158 | 0 | sibling->NodeInfo()->NameAtom() && |
5159 | 0 | atCurNode.GetContainer()->NodeInfo()->NamespaceID() == |
5160 | 0 | sibling->NodeInfo()->NamespaceID()) { |
5161 | 0 | rv = HTMLEditorRef().MoveNodeWithTransaction( |
5162 | 0 | *curNode->AsContent(), |
5163 | 0 | EditorRawDOMPoint(sibling, 0)); |
5164 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5165 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5166 | 0 | } |
5167 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5168 | 0 | return rv; |
5169 | 0 | } |
5170 | 0 | continue; |
5171 | 0 | } |
5172 | 0 | |
5173 | 0 | // Check for whether we should join a list that preceeds curNode. |
5174 | 0 | // We do this if the previous element is a list, and the list is of |
5175 | 0 | // the same type (li/ol) as curNode was a part of. |
5176 | 0 | sibling = HTMLEditorRef().GetPriorHTMLSibling(curNode); |
5177 | 0 | if (sibling && HTMLEditUtils::IsList(sibling) && |
5178 | 0 | atCurNode.GetContainer()->NodeInfo()->NameAtom() == |
5179 | 0 | sibling->NodeInfo()->NameAtom() && |
5180 | 0 | atCurNode.GetContainer()->NodeInfo()->NamespaceID() == |
5181 | 0 | sibling->NodeInfo()->NamespaceID()) { |
5182 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
5183 | 0 | *sibling); |
5184 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5185 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5186 | 0 | } |
5187 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5188 | 0 | return rv; |
5189 | 0 | } |
5190 | 0 | continue; |
5191 | 0 | } |
5192 | 0 | |
5193 | 0 | // check to see if curList is still appropriate. Which it is if |
5194 | 0 | // curNode is still right after it in the same list. |
5195 | 0 | sibling = nullptr; |
5196 | 0 | if (curList) { |
5197 | 0 | sibling = HTMLEditorRef().GetPriorHTMLSibling(curNode); |
5198 | 0 | } |
5199 | 0 |
|
5200 | 0 | if (!curList || (sibling && sibling != curList)) { |
5201 | 0 | nsAtom* containerName = |
5202 | 0 | atCurNode.GetContainer()->NodeInfo()->NameAtom(); |
5203 | 0 | // Create a new nested list of correct type. |
5204 | 0 | SplitNodeResult splitNodeResult = |
5205 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*containerName, |
5206 | 0 | atCurNode); |
5207 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
5208 | 0 | return splitNodeResult.Rv(); |
5209 | 0 | } |
5210 | 0 | curList = |
5211 | 0 | HTMLEditorRef().CreateNodeWithTransaction( |
5212 | 0 | *containerName, splitNodeResult.SplitPoint()); |
5213 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5214 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5215 | 0 | } |
5216 | 0 | if (NS_WARN_IF(!curList)) { |
5217 | 0 | return NS_ERROR_FAILURE; |
5218 | 0 | } |
5219 | 0 | // curList is now the correct thing to put curNode in |
5220 | 0 | // remember our new block for postprocessing |
5221 | 0 | mNewBlock = curList; |
5222 | 0 | } |
5223 | 0 | // tuck the node into the end of the active list |
5224 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
5225 | 0 | *curList); |
5226 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5227 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5228 | 0 | } |
5229 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5230 | 0 | return rv; |
5231 | 0 | } |
5232 | 0 | // forget curQuote, if any |
5233 | 0 | curQuote = nullptr; |
5234 | 0 |
|
5235 | 0 | continue; |
5236 | 0 | } |
5237 | 0 | |
5238 | 0 | // Not a list item, use blockquote? |
5239 | 0 | |
5240 | 0 | // if we are inside a list item, we don't want to blockquote, we want |
5241 | 0 | // to sublist the list item. We may have several nodes listed in the |
5242 | 0 | // array of nodes to act on, that are in the same list item. Since |
5243 | 0 | // we only want to indent that li once, we must keep track of the most |
5244 | 0 | // recent indented list item, and not indent it if we find another node |
5245 | 0 | // to act on that is still inside the same li. |
5246 | 0 | RefPtr<Element> listItem = IsInListItem(curNode); |
5247 | 0 | if (listItem) { |
5248 | 0 | if (indentedLI == listItem) { |
5249 | 0 | // already indented this list item |
5250 | 0 | continue; |
5251 | 0 | } |
5252 | 0 | // check to see if curList is still appropriate. Which it is if |
5253 | 0 | // curNode is still right after it in the same list. |
5254 | 0 | if (curList) { |
5255 | 0 | sibling = HTMLEditorRef().GetPriorHTMLSibling(listItem); |
5256 | 0 | } |
5257 | 0 |
|
5258 | 0 | if (!curList || (sibling && sibling != curList)) { |
5259 | 0 | EditorDOMPoint atListItem(listItem); |
5260 | 0 | if (NS_WARN_IF(!listItem)) { |
5261 | 0 | return NS_ERROR_FAILURE; |
5262 | 0 | } |
5263 | 0 | nsAtom* containerName = |
5264 | 0 | atListItem.GetContainer()->NodeInfo()->NameAtom(); |
5265 | 0 | // Create a new nested list of correct type. |
5266 | 0 | SplitNodeResult splitNodeResult = |
5267 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*containerName, |
5268 | 0 | atListItem); |
5269 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
5270 | 0 | return splitNodeResult.Rv(); |
5271 | 0 | } |
5272 | 0 | curList = |
5273 | 0 | HTMLEditorRef().CreateNodeWithTransaction( |
5274 | 0 | *containerName, splitNodeResult.SplitPoint()); |
5275 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5276 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5277 | 0 | } |
5278 | 0 | if (NS_WARN_IF(!curList)) { |
5279 | 0 | return NS_ERROR_FAILURE; |
5280 | 0 | } |
5281 | 0 | } |
5282 | 0 | |
5283 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*listItem, *curList); |
5284 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5285 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5286 | 0 | } |
5287 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5288 | 0 | return rv; |
5289 | 0 | } |
5290 | 0 | |
5291 | 0 | // remember we indented this li |
5292 | 0 | indentedLI = listItem; |
5293 | 0 |
|
5294 | 0 | continue; |
5295 | 0 | } |
5296 | 0 | |
5297 | 0 | // need to make a blockquote to put things in if we haven't already, |
5298 | 0 | // or if this node doesn't go in blockquote we used earlier. |
5299 | 0 | // One reason it might not go in prio blockquote is if we are now |
5300 | 0 | // in a different table cell. |
5301 | 0 | if (curQuote && InDifferentTableElements(curQuote, curNode)) { |
5302 | 0 | curQuote = nullptr; |
5303 | 0 | } |
5304 | 0 |
|
5305 | 0 | if (!curQuote) { |
5306 | 0 | // First, check that our element can contain a blockquote. |
5307 | 0 | if (!HTMLEditorRef().CanContainTag(*atCurNode.GetContainer(), |
5308 | 0 | *nsGkAtoms::blockquote)) { |
5309 | 0 | return NS_OK; // cancelled |
5310 | 0 | } |
5311 | 0 | |
5312 | 0 | SplitNodeResult splitNodeResult = |
5313 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::blockquote, |
5314 | 0 | atCurNode); |
5315 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
5316 | 0 | return splitNodeResult.Rv(); |
5317 | 0 | } |
5318 | 0 | curQuote = |
5319 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::blockquote, |
5320 | 0 | splitNodeResult.SplitPoint()); |
5321 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5322 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5323 | 0 | } |
5324 | 0 | if (NS_WARN_IF(!curQuote)) { |
5325 | 0 | return NS_ERROR_FAILURE; |
5326 | 0 | } |
5327 | 0 | // remember our new block for postprocessing |
5328 | 0 | mNewBlock = curQuote; |
5329 | 0 | // curQuote is now the correct thing to put curNode in |
5330 | 0 | } |
5331 | 0 |
|
5332 | 0 | // tuck the node into the end of the active blockquote |
5333 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
5334 | 0 | *curQuote); |
5335 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5336 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5337 | 0 | } |
5338 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5339 | 0 | return rv; |
5340 | 0 | } |
5341 | 0 | // forget curList, if any |
5342 | 0 | curList = nullptr; |
5343 | 0 | } |
5344 | 0 | return NS_OK; |
5345 | 0 | } |
5346 | | |
5347 | | |
5348 | | nsresult |
5349 | | HTMLEditRules::WillOutdent(bool* aCancel, |
5350 | | bool* aHandled) |
5351 | 0 | { |
5352 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
5353 | 0 | MOZ_ASSERT(aCancel && aHandled); |
5354 | 0 | *aCancel = false; |
5355 | 0 | *aHandled = true; |
5356 | 0 |
|
5357 | 0 | nsresult rv = NormalizeSelection(); |
5358 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5359 | 0 | return rv; |
5360 | 0 | } |
5361 | 0 | |
5362 | 0 | // OutdentAroundSelection() creates AutoSelectionRestorer. Therefore, |
5363 | 0 | // even if it returns NS_OK, the editor might have been destroyed at |
5364 | 0 | // restoring Selection. |
5365 | 0 | SplitRangeOffFromNodeResult outdentResult = OutdentAroundSelection(); |
5366 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5367 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5368 | 0 | } |
5369 | 0 | if (NS_WARN_IF(outdentResult.Failed())) { |
5370 | 0 | return outdentResult.Rv(); |
5371 | 0 | } |
5372 | 0 | |
5373 | 0 | // Make sure selection didn't stick to last piece of content in old bq (only |
5374 | 0 | // a problem for collapsed selections) |
5375 | 0 | if (!outdentResult.GetLeftContent() && !outdentResult.GetRightContent()) { |
5376 | 0 | return NS_OK; |
5377 | 0 | } |
5378 | 0 | |
5379 | 0 | if (!SelectionRef().IsCollapsed()) { |
5380 | 0 | return NS_OK; |
5381 | 0 | } |
5382 | 0 | |
5383 | 0 | // Push selection past end of left element of last split indented element. |
5384 | 0 | if (outdentResult.GetLeftContent()) { |
5385 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
5386 | 0 | if (NS_WARN_IF(!firstRange)) { |
5387 | 0 | return NS_OK; |
5388 | 0 | } |
5389 | 0 | const RangeBoundary& atStartOfSelection = firstRange->StartRef(); |
5390 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
5391 | 0 | return NS_ERROR_FAILURE; |
5392 | 0 | } |
5393 | 0 | if (atStartOfSelection.Container() == outdentResult.GetLeftContent() || |
5394 | 0 | EditorUtils::IsDescendantOf(*atStartOfSelection.Container(), |
5395 | 0 | *outdentResult.GetLeftContent())) { |
5396 | 0 | // Selection is inside the left node - push it past it. |
5397 | 0 | EditorRawDOMPoint afterRememberedLeftBQ(outdentResult.GetLeftContent()); |
5398 | 0 | afterRememberedLeftBQ.AdvanceOffset(); |
5399 | 0 | IgnoredErrorResult error; |
5400 | 0 | SelectionRef().Collapse(afterRememberedLeftBQ, error); |
5401 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5402 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5403 | 0 | } |
5404 | 0 | NS_WARNING_ASSERTION(!error.Failed(), |
5405 | 0 | "Failed to collapse selection after the left <blockquote>"); |
5406 | 0 | } |
5407 | 0 | } |
5408 | 0 | // And pull selection before beginning of right element of last split |
5409 | 0 | // indented element. |
5410 | 0 | if (outdentResult.GetRightContent()) { |
5411 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
5412 | 0 | const RangeBoundary& atStartOfSelection = firstRange->StartRef(); |
5413 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
5414 | 0 | return NS_ERROR_FAILURE; |
5415 | 0 | } |
5416 | 0 | if (atStartOfSelection.Container() == outdentResult.GetRightContent() || |
5417 | 0 | EditorUtils::IsDescendantOf(*atStartOfSelection.Container(), |
5418 | 0 | *outdentResult.GetRightContent())) { |
5419 | 0 | // Selection is inside the right element - push it before it. |
5420 | 0 | EditorRawDOMPoint atRememberedRightBQ(outdentResult.GetRightContent()); |
5421 | 0 | IgnoredErrorResult error; |
5422 | 0 | SelectionRef().Collapse(atRememberedRightBQ, error); |
5423 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5424 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5425 | 0 | } |
5426 | 0 | NS_WARNING_ASSERTION(!error.Failed(), |
5427 | 0 | "Failed to collapse selection after the right <blockquote>"); |
5428 | 0 | } |
5429 | 0 | } |
5430 | 0 | return NS_OK; |
5431 | 0 | } |
5432 | | |
5433 | | SplitRangeOffFromNodeResult |
5434 | | HTMLEditRules::OutdentAroundSelection() |
5435 | 0 | { |
5436 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
5437 | 0 |
|
5438 | 0 | AutoSelectionRestorer selectionRestorer(&SelectionRef(), &HTMLEditorRef()); |
5439 | 0 |
|
5440 | 0 | bool useCSS = HTMLEditorRef().IsCSSEnabled(); |
5441 | 0 |
|
5442 | 0 | // Convert the selection ranges into "promoted" selection ranges: this |
5443 | 0 | // basically just expands the range to include the immediate block parent, |
5444 | 0 | // and then further expands to include any ancestors whose children are all |
5445 | 0 | // in the range |
5446 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
5447 | 0 | nsresult rv = |
5448 | 0 | GetNodesFromSelection(EditSubAction::eOutdent, arrayOfNodes, |
5449 | 0 | TouchContent::yes); |
5450 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5451 | 0 | return SplitRangeOffFromNodeResult(rv); |
5452 | 0 | } |
5453 | 0 | |
5454 | 0 | // Okay, now go through all the nodes and remove a level of blockquoting, |
5455 | 0 | // or whatever is appropriate. Wohoo! |
5456 | 0 | |
5457 | 0 | nsCOMPtr<nsIContent> leftContentOfLastOutdented; |
5458 | 0 | nsCOMPtr<nsIContent> middleContentOfLastOutdented; |
5459 | 0 | nsCOMPtr<nsIContent> rightContentOfLastOutdented; |
5460 | 0 | nsCOMPtr<Element> curBlockQuote; |
5461 | 0 | nsCOMPtr<nsIContent> firstBQChild, lastBQChild; |
5462 | 0 | bool curBlockQuoteIsIndentedWithCSS = false; |
5463 | 0 | for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) { |
5464 | 0 | if (!arrayOfNodes[i]->IsContent()) { |
5465 | 0 | continue; |
5466 | 0 | } |
5467 | 0 | OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent(); |
5468 | 0 |
|
5469 | 0 | // Here's where we actually figure out what to do |
5470 | 0 | int32_t offset; |
5471 | 0 | nsCOMPtr<nsINode> curParent = |
5472 | 0 | EditorBase::GetNodeLocation(curNode, &offset); |
5473 | 0 | if (!curParent) { |
5474 | 0 | continue; |
5475 | 0 | } |
5476 | 0 | |
5477 | 0 | // Is it a blockquote? |
5478 | 0 | if (curNode->IsHTMLElement(nsGkAtoms::blockquote)) { |
5479 | 0 | // If it is a blockquote, remove it. So we need to finish up dealng |
5480 | 0 | // with any curBlockQuote first. |
5481 | 0 | if (curBlockQuote) { |
5482 | 0 | SplitRangeOffFromNodeResult outdentResult = |
5483 | 0 | OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild, |
5484 | 0 | curBlockQuoteIsIndentedWithCSS); |
5485 | 0 | if (NS_WARN_IF(outdentResult.Failed())) { |
5486 | 0 | return outdentResult; |
5487 | 0 | } |
5488 | 0 | leftContentOfLastOutdented = outdentResult.GetLeftContent(); |
5489 | 0 | middleContentOfLastOutdented = outdentResult.GetMiddleContent(); |
5490 | 0 | rightContentOfLastOutdented = outdentResult.GetRightContent(); |
5491 | 0 | curBlockQuote = nullptr; |
5492 | 0 | firstBQChild = nullptr; |
5493 | 0 | lastBQChild = nullptr; |
5494 | 0 | curBlockQuoteIsIndentedWithCSS = false; |
5495 | 0 | } |
5496 | 0 | rv = HTMLEditorRef().RemoveBlockContainerWithTransaction( |
5497 | 0 | *curNode->AsElement()); |
5498 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5499 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5500 | 0 | } |
5501 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5502 | 0 | return SplitRangeOffFromNodeResult(rv); |
5503 | 0 | } |
5504 | 0 | continue; |
5505 | 0 | } |
5506 | 0 | |
5507 | 0 | // Is it a block with a 'margin' property? |
5508 | 0 | if (useCSS && IsBlockNode(curNode)) { |
5509 | 0 | nsAtom& marginProperty = MarginPropertyAtomForIndent(curNode); |
5510 | 0 | nsAutoString value; |
5511 | 0 | CSSEditUtils::GetSpecifiedProperty(curNode, marginProperty, value); |
5512 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5513 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5514 | 0 | } |
5515 | 0 | float f; |
5516 | 0 | RefPtr<nsAtom> unit; |
5517 | 0 | CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit)); |
5518 | 0 | if (f > 0) { |
5519 | 0 | nsresult rv = DecreaseMarginToOutdent(*curNode->AsElement()); |
5520 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
5521 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5522 | 0 | } |
5523 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
5524 | 0 | "Failed to decrease indentation"); |
5525 | 0 | continue; |
5526 | 0 | } |
5527 | 0 | } |
5528 | 0 |
|
5529 | 0 | // Is it a list item? |
5530 | 0 | if (HTMLEditUtils::IsListItem(curNode)) { |
5531 | 0 | // If it is a list item, that means we are not outdenting whole list. |
5532 | 0 | // So we need to finish up dealing with any curBlockQuote, and then pop |
5533 | 0 | // this list item. |
5534 | 0 | if (curBlockQuote) { |
5535 | 0 | SplitRangeOffFromNodeResult outdentResult = |
5536 | 0 | OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild, |
5537 | 0 | curBlockQuoteIsIndentedWithCSS); |
5538 | 0 | if (NS_WARN_IF(outdentResult.Failed())) { |
5539 | 0 | return outdentResult; |
5540 | 0 | } |
5541 | 0 | leftContentOfLastOutdented = outdentResult.GetLeftContent(); |
5542 | 0 | middleContentOfLastOutdented = outdentResult.GetMiddleContent(); |
5543 | 0 | rightContentOfLastOutdented = outdentResult.GetRightContent(); |
5544 | 0 | curBlockQuote = nullptr; |
5545 | 0 | firstBQChild = nullptr; |
5546 | 0 | lastBQChild = nullptr; |
5547 | 0 | curBlockQuoteIsIndentedWithCSS = false; |
5548 | 0 | } |
5549 | 0 | rv = PopListItem(*curNode->AsContent()); |
5550 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5551 | 0 | return SplitRangeOffFromNodeResult(rv); |
5552 | 0 | } |
5553 | 0 | continue; |
5554 | 0 | } |
5555 | 0 | |
5556 | 0 | // Do we have a blockquote that we are already committed to removing? |
5557 | 0 | if (curBlockQuote) { |
5558 | 0 | // If so, is this node a descendant? |
5559 | 0 | if (EditorUtils::IsDescendantOf(*curNode, *curBlockQuote)) { |
5560 | 0 | lastBQChild = curNode; |
5561 | 0 | // Then we don't need to do anything different for this node |
5562 | 0 | continue; |
5563 | 0 | } |
5564 | 0 | // Otherwise, we have progressed beyond end of curBlockQuote, so |
5565 | 0 | // let's handle it now. We need to remove the portion of |
5566 | 0 | // curBlockQuote that contains [firstBQChild - lastBQChild]. |
5567 | 0 | SplitRangeOffFromNodeResult outdentResult = |
5568 | 0 | OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild, |
5569 | 0 | curBlockQuoteIsIndentedWithCSS); |
5570 | 0 | if (NS_WARN_IF(outdentResult.Failed())) { |
5571 | 0 | return outdentResult; |
5572 | 0 | } |
5573 | 0 | leftContentOfLastOutdented = outdentResult.GetLeftContent(); |
5574 | 0 | middleContentOfLastOutdented = outdentResult.GetMiddleContent(); |
5575 | 0 | rightContentOfLastOutdented = outdentResult.GetRightContent(); |
5576 | 0 | curBlockQuote = nullptr; |
5577 | 0 | firstBQChild = nullptr; |
5578 | 0 | lastBQChild = nullptr; |
5579 | 0 | curBlockQuoteIsIndentedWithCSS = false; |
5580 | 0 | // Fall out and handle curNode |
5581 | 0 | } |
5582 | 0 |
|
5583 | 0 | // Are we inside a blockquote? |
5584 | 0 | OwningNonNull<nsINode> n = curNode; |
5585 | 0 | curBlockQuoteIsIndentedWithCSS = false; |
5586 | 0 | // Keep looking up the hierarchy as long as we don't hit the body or the |
5587 | 0 | // active editing host or a table element (other than an entire table) |
5588 | 0 | while (!n->IsHTMLElement(nsGkAtoms::body) && |
5589 | 0 | HTMLEditorRef().IsDescendantOfEditorRoot(n) && |
5590 | 0 | (n->IsHTMLElement(nsGkAtoms::table) || |
5591 | 0 | !HTMLEditUtils::IsTableElement(n))) { |
5592 | 0 | if (!n->GetParentNode()) { |
5593 | 0 | break; |
5594 | 0 | } |
5595 | 0 | n = *n->GetParentNode(); |
5596 | 0 | if (n->IsHTMLElement(nsGkAtoms::blockquote)) { |
5597 | 0 | // If so, remember it and the first node we are taking out of it. |
5598 | 0 | curBlockQuote = n->AsElement(); |
5599 | 0 | firstBQChild = curNode; |
5600 | 0 | lastBQChild = curNode; |
5601 | 0 | break; |
5602 | 0 | } |
5603 | 0 | |
5604 | 0 | if (!useCSS) { |
5605 | 0 | continue; |
5606 | 0 | } |
5607 | 0 | |
5608 | 0 | nsAtom& marginProperty = MarginPropertyAtomForIndent(curNode); |
5609 | 0 | nsAutoString value; |
5610 | 0 | CSSEditUtils::GetSpecifiedProperty(*n, marginProperty, value); |
5611 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5612 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5613 | 0 | } |
5614 | 0 | float f; |
5615 | 0 | RefPtr<nsAtom> unit; |
5616 | 0 | CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit)); |
5617 | 0 | if (f > 0 && !(HTMLEditUtils::IsList(curParent) && |
5618 | 0 | HTMLEditUtils::IsList(curNode))) { |
5619 | 0 | curBlockQuote = n->AsElement(); |
5620 | 0 | firstBQChild = curNode; |
5621 | 0 | lastBQChild = curNode; |
5622 | 0 | curBlockQuoteIsIndentedWithCSS = true; |
5623 | 0 | break; |
5624 | 0 | } |
5625 | 0 | } |
5626 | 0 |
|
5627 | 0 | if (curBlockQuote) { |
5628 | 0 | continue; |
5629 | 0 | } |
5630 | 0 | |
5631 | 0 | // Couldn't find enclosing blockquote. |
5632 | 0 | if (HTMLEditUtils::IsList(curParent)) { |
5633 | 0 | // Move node out of list |
5634 | 0 | if (HTMLEditUtils::IsList(curNode)) { |
5635 | 0 | // Just unwrap this sublist |
5636 | 0 | rv = HTMLEditorRef().RemoveBlockContainerWithTransaction( |
5637 | 0 | *curNode->AsElement()); |
5638 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5639 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5640 | 0 | } |
5641 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5642 | 0 | return SplitRangeOffFromNodeResult(rv); |
5643 | 0 | } |
5644 | 0 | } |
5645 | 0 | continue; |
5646 | 0 | } |
5647 | 0 | |
5648 | 0 | if (HTMLEditUtils::IsList(curNode)) { |
5649 | 0 | // node is a list, but parent is non-list: move list items out |
5650 | 0 | nsCOMPtr<nsIContent> child = curNode->GetLastChild(); |
5651 | 0 | while (child) { |
5652 | 0 | if (HTMLEditUtils::IsListItem(child)) { |
5653 | 0 | rv = PopListItem(*child); |
5654 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5655 | 0 | return SplitRangeOffFromNodeResult(rv); |
5656 | 0 | } |
5657 | 0 | } else if (HTMLEditUtils::IsList(child)) { |
5658 | 0 | // We have an embedded list, so move it out from under the parent |
5659 | 0 | // list. Be sure to put it after the parent list because this |
5660 | 0 | // loop iterates backwards through the parent's list of children. |
5661 | 0 | EditorRawDOMPoint afterCurrentList(curParent, offset + 1); |
5662 | 0 | rv = HTMLEditorRef().MoveNodeWithTransaction(*child, |
5663 | 0 | afterCurrentList); |
5664 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5665 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5666 | 0 | } |
5667 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5668 | 0 | return SplitRangeOffFromNodeResult(rv); |
5669 | 0 | } |
5670 | 0 | } else { |
5671 | 0 | // Delete any non-list items for now |
5672 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*child); |
5673 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5674 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5675 | 0 | } |
5676 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5677 | 0 | return SplitRangeOffFromNodeResult(rv); |
5678 | 0 | } |
5679 | 0 | } |
5680 | 0 | child = curNode->GetLastChild(); |
5681 | 0 | } |
5682 | 0 | // Delete the now-empty list |
5683 | 0 | rv = HTMLEditorRef().RemoveBlockContainerWithTransaction( |
5684 | 0 | *curNode->AsElement()); |
5685 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5686 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5687 | 0 | } |
5688 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5689 | 0 | return SplitRangeOffFromNodeResult(rv); |
5690 | 0 | } |
5691 | 0 | continue; |
5692 | 0 | } |
5693 | 0 | |
5694 | 0 | if (useCSS) { |
5695 | 0 | nsCOMPtr<Element> element; |
5696 | 0 | if (curNode->GetAsText()) { |
5697 | 0 | // We want to outdent the parent of text nodes |
5698 | 0 | element = curNode->GetParentElement(); |
5699 | 0 | } else if (curNode->IsElement()) { |
5700 | 0 | element = curNode->AsElement(); |
5701 | 0 | } |
5702 | 0 | if (element) { |
5703 | 0 | nsresult rv = DecreaseMarginToOutdent(*element); |
5704 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
5705 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5706 | 0 | } |
5707 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
5708 | 0 | "Failed to decrease indentation"); |
5709 | 0 | } |
5710 | 0 | continue; |
5711 | 0 | } |
5712 | 0 | } |
5713 | 0 |
|
5714 | 0 | if (!curBlockQuote) { |
5715 | 0 | return SplitRangeOffFromNodeResult(leftContentOfLastOutdented, |
5716 | 0 | middleContentOfLastOutdented, |
5717 | 0 | rightContentOfLastOutdented); |
5718 | 0 | } |
5719 | 0 | |
5720 | 0 | // We have a <blockquote> we haven't finished handling. |
5721 | 0 | SplitRangeOffFromNodeResult outdentResult = |
5722 | 0 | OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild, |
5723 | 0 | curBlockQuoteIsIndentedWithCSS); |
5724 | 0 | if (NS_WARN_IF(outdentResult.Failed())) { |
5725 | 0 | return outdentResult; |
5726 | 0 | } |
5727 | 0 | return outdentResult; |
5728 | 0 | } |
5729 | | |
5730 | | SplitRangeOffFromNodeResult |
5731 | | HTMLEditRules::SplitRangeOffFromBlockAndRemoveMiddleContainer( |
5732 | | Element& aBlockElement, |
5733 | | nsIContent& aStartOfRange, |
5734 | | nsIContent& aEndOfRange) |
5735 | 0 | { |
5736 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
5737 | 0 |
|
5738 | 0 | SplitRangeOffFromNodeResult splitResult = |
5739 | 0 | SplitRangeOffFromBlock(aBlockElement, aStartOfRange, aEndOfRange); |
5740 | 0 | if (NS_WARN_IF(splitResult.Rv() == NS_ERROR_EDITOR_DESTROYED)) { |
5741 | 0 | return splitResult; |
5742 | 0 | } |
5743 | 0 | NS_WARNING_ASSERTION(splitResult.Succeeded(), |
5744 | 0 | "Failed to split the range off from the block element"); |
5745 | 0 | nsresult rv = |
5746 | 0 | HTMLEditorRef().RemoveBlockContainerWithTransaction(aBlockElement); |
5747 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5748 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5749 | 0 | } |
5750 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5751 | 0 | return SplitRangeOffFromNodeResult(rv); |
5752 | 0 | } |
5753 | 0 | return SplitRangeOffFromNodeResult(splitResult.GetLeftContent(), |
5754 | 0 | nullptr, |
5755 | 0 | splitResult.GetRightContent()); |
5756 | 0 | } |
5757 | | |
5758 | | SplitRangeOffFromNodeResult |
5759 | | HTMLEditRules::SplitRangeOffFromBlock(Element& aBlockElement, |
5760 | | nsIContent& aStartOfMiddleElement, |
5761 | | nsIContent& aEndOfMiddleElement) |
5762 | 0 | { |
5763 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
5764 | 0 |
|
5765 | 0 | // aStartOfMiddleElement and aEndOfMiddleElement must be exclusive |
5766 | 0 | // descendants of aBlockElement. |
5767 | 0 | MOZ_ASSERT(EditorUtils::IsDescendantOf(aStartOfMiddleElement, aBlockElement)); |
5768 | 0 | MOZ_ASSERT(EditorUtils::IsDescendantOf(aEndOfMiddleElement, aBlockElement)); |
5769 | 0 |
|
5770 | 0 | // Split at the start. |
5771 | 0 | SplitNodeResult splitAtStartResult = |
5772 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
5773 | 0 | aBlockElement, EditorRawDOMPoint(&aStartOfMiddleElement), |
5774 | 0 | SplitAtEdges::eDoNotCreateEmptyContainer); |
5775 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5776 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5777 | 0 | } |
5778 | 0 | NS_WARNING_ASSERTION(splitAtStartResult.Succeeded(), |
5779 | 0 | "Failed to split aBlockElement at start"); |
5780 | 0 |
|
5781 | 0 | // Split at after the end |
5782 | 0 | EditorRawDOMPoint atAfterEnd(&aEndOfMiddleElement); |
5783 | 0 | DebugOnly<bool> advanced = atAfterEnd.AdvanceOffset(); |
5784 | 0 | NS_WARNING_ASSERTION(advanced, |
5785 | 0 | "Failed to advance offset after the end node"); |
5786 | 0 | SplitNodeResult splitAtEndResult = |
5787 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
5788 | 0 | aBlockElement, atAfterEnd, |
5789 | 0 | SplitAtEdges::eDoNotCreateEmptyContainer); |
5790 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5791 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5792 | 0 | } |
5793 | 0 | NS_WARNING_ASSERTION(splitAtEndResult.Succeeded(), |
5794 | 0 | "Failed to split aBlockElement at after end"); |
5795 | 0 |
|
5796 | 0 | return SplitRangeOffFromNodeResult(splitAtStartResult, splitAtEndResult); |
5797 | 0 | } |
5798 | | |
5799 | | SplitRangeOffFromNodeResult |
5800 | | HTMLEditRules::OutdentPartOfBlock(Element& aBlockElement, |
5801 | | nsIContent& aStartOfOutdent, |
5802 | | nsIContent& aEndOfOutdent, |
5803 | | bool aIsBlockIndentedWithCSS) |
5804 | 0 | { |
5805 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
5806 | 0 |
|
5807 | 0 | SplitRangeOffFromNodeResult splitResult = |
5808 | 0 | SplitRangeOffFromBlock(aBlockElement, aStartOfOutdent, aEndOfOutdent); |
5809 | 0 | if (NS_WARN_IF(splitResult.Rv() == NS_ERROR_EDITOR_DESTROYED)) { |
5810 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5811 | 0 | } |
5812 | 0 | |
5813 | 0 | if (NS_WARN_IF(!splitResult.GetMiddleContentAsElement())) { |
5814 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_FAILURE); |
5815 | 0 | } |
5816 | 0 | |
5817 | 0 | if (!aIsBlockIndentedWithCSS) { |
5818 | 0 | nsresult rv = |
5819 | 0 | HTMLEditorRef().RemoveBlockContainerWithTransaction( |
5820 | 0 | *splitResult.GetMiddleContentAsElement()); |
5821 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5822 | 0 | return SplitRangeOffFromNodeResult(NS_ERROR_EDITOR_DESTROYED); |
5823 | 0 | } |
5824 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5825 | 0 | return SplitRangeOffFromNodeResult(rv); |
5826 | 0 | } |
5827 | 0 | return SplitRangeOffFromNodeResult(splitResult.GetLeftContent(), |
5828 | 0 | nullptr, |
5829 | 0 | splitResult.GetRightContent()); |
5830 | 0 | } |
5831 | 0 | |
5832 | 0 | if (splitResult.GetMiddleContentAsElement()) { |
5833 | 0 | nsresult rv = |
5834 | 0 | DecreaseMarginToOutdent(*splitResult.GetMiddleContentAsElement()); |
5835 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5836 | 0 | return SplitRangeOffFromNodeResult(rv); |
5837 | 0 | } |
5838 | 0 | return splitResult; |
5839 | 0 | } |
5840 | 0 | |
5841 | 0 | return splitResult; |
5842 | 0 | } |
5843 | | |
5844 | | CreateElementResult |
5845 | | HTMLEditRules::ConvertListType(Element& aListElement, |
5846 | | nsAtom& aNewListTag, |
5847 | | nsAtom& aNewListItemTag) |
5848 | 0 | { |
5849 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
5850 | 0 |
|
5851 | 0 | nsCOMPtr<nsINode> child = aListElement.GetFirstChild(); |
5852 | 0 | while (child) { |
5853 | 0 | if (child->IsElement()) { |
5854 | 0 | Element* element = child->AsElement(); |
5855 | 0 | if (HTMLEditUtils::IsListItem(element) && |
5856 | 0 | !element->IsHTMLElement(&aNewListItemTag)) { |
5857 | 0 | child = |
5858 | 0 | HTMLEditorRef().ReplaceContainerWithTransaction(*element, |
5859 | 0 | aNewListItemTag); |
5860 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5861 | 0 | return CreateElementResult(NS_ERROR_EDITOR_DESTROYED); |
5862 | 0 | } |
5863 | 0 | if (NS_WARN_IF(!child)) { |
5864 | 0 | return CreateElementResult(NS_ERROR_FAILURE); |
5865 | 0 | } |
5866 | 0 | } else if (HTMLEditUtils::IsList(element) && |
5867 | 0 | !element->IsHTMLElement(&aNewListTag)) { |
5868 | 0 | // XXX List elements shouldn't have other list elements as their |
5869 | 0 | // child. Why do we handle such invalid tree? |
5870 | 0 | CreateElementResult convertListTypeResult = |
5871 | 0 | ConvertListType(*child->AsElement(), aNewListTag, aNewListItemTag); |
5872 | 0 | if (NS_WARN_IF(convertListTypeResult.Failed())) { |
5873 | 0 | return convertListTypeResult; |
5874 | 0 | } |
5875 | 0 | child = convertListTypeResult.forget(); |
5876 | 0 | } |
5877 | 0 | } |
5878 | 0 | child = child->GetNextSibling(); |
5879 | 0 | } |
5880 | 0 |
|
5881 | 0 | if (aListElement.IsHTMLElement(&aNewListTag)) { |
5882 | 0 | return CreateElementResult(&aListElement); |
5883 | 0 | } |
5884 | 0 | |
5885 | 0 | RefPtr<Element> listElement = |
5886 | 0 | HTMLEditorRef().ReplaceContainerWithTransaction(aListElement, aNewListTag); |
5887 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5888 | 0 | return CreateElementResult(NS_ERROR_EDITOR_DESTROYED); |
5889 | 0 | } |
5890 | 0 | NS_WARNING_ASSERTION(listElement != nullptr, "Failed to create list element"); |
5891 | 0 | return CreateElementResult(listElement.forget()); |
5892 | 0 | } |
5893 | | |
5894 | | nsresult |
5895 | | HTMLEditRules::CreateStyleForInsertText(nsIDocument& aDocument) |
5896 | 0 | { |
5897 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
5898 | 0 | MOZ_ASSERT(HTMLEditorRef().mTypeInState); |
5899 | 0 |
|
5900 | 0 | bool weDidSomething = false; |
5901 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
5902 | 0 | if (NS_WARN_IF(!firstRange)) { |
5903 | 0 | return NS_ERROR_FAILURE; |
5904 | 0 | } |
5905 | 0 | nsCOMPtr<nsINode> node = firstRange->GetStartContainer(); |
5906 | 0 | int32_t offset = firstRange->StartOffset(); |
5907 | 0 |
|
5908 | 0 | RefPtr<Element> rootElement = aDocument.GetRootElement(); |
5909 | 0 | if (NS_WARN_IF(!rootElement)) { |
5910 | 0 | return NS_ERROR_FAILURE; |
5911 | 0 | } |
5912 | 0 | |
5913 | 0 | // process clearing any styles first |
5914 | 0 | UniquePtr<PropItem> item = HTMLEditorRef().mTypeInState->TakeClearProperty(); |
5915 | 0 |
|
5916 | 0 | { |
5917 | 0 | // Transactions may set selection, but we will set selection if necessary. |
5918 | 0 | AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef()); |
5919 | 0 |
|
5920 | 0 | while (item && node != rootElement) { |
5921 | 0 | // XXX If we redesign ClearStyle(), we can use EditorDOMPoint in this |
5922 | 0 | // method. |
5923 | 0 | nsresult rv = |
5924 | 0 | HTMLEditorRef().ClearStyle(address_of(node), &offset, |
5925 | 0 | item->tag, item->attr); |
5926 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5927 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5928 | 0 | } |
5929 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5930 | 0 | return rv; |
5931 | 0 | } |
5932 | 0 | item = HTMLEditorRef().mTypeInState->TakeClearProperty(); |
5933 | 0 | weDidSomething = true; |
5934 | 0 | } |
5935 | 0 | } |
5936 | 0 |
|
5937 | 0 | // then process setting any styles |
5938 | 0 | int32_t relFontSize = HTMLEditorRef().mTypeInState->TakeRelativeFontSize(); |
5939 | 0 | item = HTMLEditorRef().mTypeInState->TakeSetProperty(); |
5940 | 0 |
|
5941 | 0 | if (item || relFontSize) { |
5942 | 0 | // we have at least one style to add; make a new text node to insert style |
5943 | 0 | // nodes above. |
5944 | 0 | if (RefPtr<Text> text = node->GetAsText()) { |
5945 | 0 | // if we are in a text node, split it |
5946 | 0 | SplitNodeResult splitTextNodeResult = |
5947 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
5948 | 0 | *text, EditorRawDOMPoint(text, offset), |
5949 | 0 | SplitAtEdges::eAllowToCreateEmptyContainer); |
5950 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5951 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5952 | 0 | } |
5953 | 0 | if (NS_WARN_IF(splitTextNodeResult.Failed())) { |
5954 | 0 | return splitTextNodeResult.Rv(); |
5955 | 0 | } |
5956 | 0 | EditorRawDOMPoint splitPoint(splitTextNodeResult.SplitPoint()); |
5957 | 0 | node = splitPoint.GetContainer(); |
5958 | 0 | offset = splitPoint.Offset(); |
5959 | 0 | } |
5960 | 0 | if (!HTMLEditorRef().IsContainer(node)) { |
5961 | 0 | return NS_OK; |
5962 | 0 | } |
5963 | 0 | OwningNonNull<Text> newNode = |
5964 | 0 | EditorBase::CreateTextNode(aDocument, EmptyString()); |
5965 | 0 | nsresult rv = |
5966 | 0 | HTMLEditorRef().InsertNodeWithTransaction( |
5967 | 0 | *newNode, EditorRawDOMPoint(node, offset)); |
5968 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5969 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5970 | 0 | } |
5971 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5972 | 0 | return rv; |
5973 | 0 | } |
5974 | 0 | node = newNode; |
5975 | 0 | offset = 0; |
5976 | 0 | weDidSomething = true; |
5977 | 0 |
|
5978 | 0 | if (relFontSize) { |
5979 | 0 | // dir indicated bigger versus smaller. 1 = bigger, -1 = smaller |
5980 | 0 | HTMLEditor::FontSize dir = relFontSize > 0 ? |
5981 | 0 | HTMLEditor::FontSize::incr : HTMLEditor::FontSize::decr; |
5982 | 0 | for (int32_t j = 0; j < DeprecatedAbs(relFontSize); j++) { |
5983 | 0 | rv = HTMLEditorRef().RelativeFontChangeOnTextNode(dir, newNode, 0, -1); |
5984 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5985 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5986 | 0 | } |
5987 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
5988 | 0 | return rv; |
5989 | 0 | } |
5990 | 0 | } |
5991 | 0 | } |
5992 | 0 |
|
5993 | 0 | while (item) { |
5994 | 0 | rv = HTMLEditorRef().SetInlinePropertyOnNode(*node->AsContent(), |
5995 | 0 | *item->tag, item->attr, |
5996 | 0 | item->value); |
5997 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
5998 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
5999 | 0 | } |
6000 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6001 | 0 | return rv; |
6002 | 0 | } |
6003 | 0 | item = HTMLEditorRef().mTypeInState->TakeSetProperty(); |
6004 | 0 | } |
6005 | 0 | } |
6006 | 0 |
|
6007 | 0 | if (!weDidSomething) { |
6008 | 0 | return NS_OK; |
6009 | 0 | } |
6010 | 0 | |
6011 | 0 | nsresult rv = SelectionRef().Collapse(node, offset); |
6012 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6013 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6014 | 0 | } |
6015 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6016 | 0 | return rv; |
6017 | 0 | } |
6018 | 0 | return NS_OK; |
6019 | 0 | } |
6020 | | |
6021 | | bool |
6022 | | HTMLEditRules::IsEmptyBlockElement(Element& aElement, |
6023 | | IgnoreSingleBR aIgnoreSingleBR) |
6024 | 0 | { |
6025 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
6026 | 0 |
|
6027 | 0 | if (NS_WARN_IF(!IsBlockNode(aElement))) { |
6028 | 0 | return false; |
6029 | 0 | } |
6030 | 0 | bool isEmpty = true; |
6031 | 0 | nsresult rv = |
6032 | 0 | HTMLEditorRef().IsEmptyNode(&aElement, &isEmpty, |
6033 | 0 | aIgnoreSingleBR == IgnoreSingleBR::eYes); |
6034 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6035 | 0 | return false; |
6036 | 0 | } |
6037 | 0 | return isEmpty; |
6038 | 0 | } |
6039 | | |
6040 | | nsresult |
6041 | | HTMLEditRules::WillAlign(const nsAString& aAlignType, |
6042 | | bool* aCancel, |
6043 | | bool* aHandled) |
6044 | 0 | { |
6045 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
6046 | 0 | MOZ_ASSERT(aCancel && aHandled); |
6047 | 0 |
|
6048 | 0 | *aCancel = false; |
6049 | 0 | *aHandled = false; |
6050 | 0 |
|
6051 | 0 | // FYI: Ignore cancel result of WillInsert(). |
6052 | 0 | nsresult rv = WillInsert(); |
6053 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
6054 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6055 | 0 | } |
6056 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
6057 | 0 |
|
6058 | 0 | rv = NormalizeSelection(); |
6059 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6060 | 0 | return rv; |
6061 | 0 | } |
6062 | 0 | |
6063 | 0 | *aHandled = true; |
6064 | 0 | rv = AlignContentsAtSelection(aAlignType); |
6065 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) || |
6066 | 0 | NS_WARN_IF(!CanHandleEditAction())) { |
6067 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6068 | 0 | } |
6069 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6070 | 0 | return rv; |
6071 | 0 | } |
6072 | 0 | return NS_OK; |
6073 | 0 | } |
6074 | | |
6075 | | nsresult |
6076 | | HTMLEditRules::AlignContentsAtSelection(const nsAString& aAlignType) |
6077 | 0 | { |
6078 | 0 | AutoSelectionRestorer selectionRestorer(&SelectionRef(), &HTMLEditorRef()); |
6079 | 0 |
|
6080 | 0 | // Convert the selection ranges into "promoted" selection ranges: This |
6081 | 0 | // basically just expands the range to include the immediate block parent, |
6082 | 0 | // and then further expands to include any ancestors whose children are all |
6083 | 0 | // in the range |
6084 | 0 | nsTArray<OwningNonNull<nsINode>> nodeArray; |
6085 | 0 | nsresult rv = |
6086 | 0 | GetNodesFromSelection(EditSubAction::eSetOrClearAlignment, nodeArray, |
6087 | 0 | TouchContent::yes); |
6088 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6089 | 0 | return rv; |
6090 | 0 | } |
6091 | 0 | |
6092 | 0 | // If we don't have any nodes, or we have only a single br, then we are |
6093 | 0 | // creating an empty alignment div. We have to do some different things for |
6094 | 0 | // these. |
6095 | 0 | bool emptyDiv = nodeArray.IsEmpty(); |
6096 | 0 | if (nodeArray.Length() == 1) { |
6097 | 0 | OwningNonNull<nsINode> node = nodeArray[0]; |
6098 | 0 |
|
6099 | 0 | if (HTMLEditUtils::SupportsAlignAttr(*node)) { |
6100 | 0 | // The node is a table element, an hr, a paragraph, a div or a section |
6101 | 0 | // header; in HTML 4, it can directly carry the ALIGN attribute and we |
6102 | 0 | // don't need to make a div! If we are in CSS mode, all the work is done |
6103 | 0 | // in AlignBlock |
6104 | 0 | rv = AlignBlock(*node->AsElement(), aAlignType, |
6105 | 0 | ResetAlignOf::OnlyDescendants); |
6106 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6107 | 0 | return rv; |
6108 | 0 | } |
6109 | 0 | return NS_OK; |
6110 | 0 | } |
6111 | 0 | |
6112 | 0 | if (TextEditUtils::IsBreak(node)) { |
6113 | 0 | // The special case emptyDiv code (below) that consumes BRs can cause |
6114 | 0 | // tables to split if the start node of the selection is not in a table |
6115 | 0 | // cell or caption, for example parent is a <tr>. Avoid this unnecessary |
6116 | 0 | // splitting if possible by leaving emptyDiv FALSE so that we fall |
6117 | 0 | // through to the normal case alignment code. |
6118 | 0 | // |
6119 | 0 | // XXX: It seems a little error prone for the emptyDiv special case code |
6120 | 0 | // to assume that the start node of the selection is the parent of the |
6121 | 0 | // single node in the nodeArray, as the paragraph above points out. Do we |
6122 | 0 | // rely on the selection start node because of the fact that nodeArray |
6123 | 0 | // can be empty? We should probably revisit this issue. - kin |
6124 | 0 |
|
6125 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
6126 | 0 | if (NS_WARN_IF(!firstRange)) { |
6127 | 0 | return NS_ERROR_FAILURE; |
6128 | 0 | } |
6129 | 0 | const RangeBoundary& atStartOfSelection = firstRange->StartRef(); |
6130 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
6131 | 0 | return NS_ERROR_FAILURE; |
6132 | 0 | } |
6133 | 0 | nsINode* parent = atStartOfSelection.Container(); |
6134 | 0 | emptyDiv = !HTMLEditUtils::IsTableElement(parent) || |
6135 | 0 | HTMLEditUtils::IsTableCellOrCaption(*parent); |
6136 | 0 | } |
6137 | 0 | } |
6138 | 0 | if (emptyDiv) { |
6139 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
6140 | 0 | if (NS_WARN_IF(!firstRange)) { |
6141 | 0 | return NS_ERROR_FAILURE; |
6142 | 0 | } |
6143 | 0 | |
6144 | 0 | EditorDOMPoint atStartOfSelection(firstRange->StartRef()); |
6145 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
6146 | 0 | return NS_ERROR_FAILURE; |
6147 | 0 | } |
6148 | 0 | |
6149 | 0 | SplitNodeResult splitNodeResult = |
6150 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div, |
6151 | 0 | atStartOfSelection); |
6152 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
6153 | 0 | return splitNodeResult.Rv(); |
6154 | 0 | } |
6155 | 0 | |
6156 | 0 | // Consume a trailing br, if any. This is to keep an alignment from |
6157 | 0 | // creating extra lines, if possible. |
6158 | 0 | nsCOMPtr<nsIContent> brContent = |
6159 | 0 | HTMLEditorRef().GetNextEditableHTMLNodeInBlock( |
6160 | 0 | splitNodeResult.SplitPoint()); |
6161 | 0 | EditorDOMPoint pointToInsertDiv(splitNodeResult.SplitPoint()); |
6162 | 0 | if (brContent && TextEditUtils::IsBreak(brContent)) { |
6163 | 0 | // Making use of html structure... if next node after where we are |
6164 | 0 | // putting our div is not a block, then the br we found is in same block |
6165 | 0 | // we are, so it's safe to consume it. |
6166 | 0 | nsCOMPtr<nsIContent> sibling; |
6167 | 0 | if (pointToInsertDiv.GetChild()) { |
6168 | 0 | sibling = |
6169 | 0 | HTMLEditorRef().GetNextHTMLSibling(pointToInsertDiv.GetChild()); |
6170 | 0 | } |
6171 | 0 | if (sibling && !IsBlockNode(*sibling)) { |
6172 | 0 | AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertDiv); |
6173 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*brContent); |
6174 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6175 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6176 | 0 | } |
6177 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6178 | 0 | return rv; |
6179 | 0 | } |
6180 | 0 | } |
6181 | 0 | } |
6182 | 0 | RefPtr<Element> div = |
6183 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::div, |
6184 | 0 | pointToInsertDiv); |
6185 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6186 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6187 | 0 | } |
6188 | 0 | if (NS_WARN_IF(!div)) { |
6189 | 0 | return NS_ERROR_FAILURE; |
6190 | 0 | } |
6191 | 0 | // Remember our new block for postprocessing |
6192 | 0 | mNewBlock = div; |
6193 | 0 | // Set up the alignment on the div, using HTML or CSS |
6194 | 0 | rv = AlignBlock(*div, aAlignType, ResetAlignOf::OnlyDescendants); |
6195 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6196 | 0 | return rv; |
6197 | 0 | } |
6198 | 0 | // Put in a moz-br so that it won't get deleted |
6199 | 0 | CreateElementResult createMozBrResult = |
6200 | 0 | CreateMozBR(EditorRawDOMPoint(div, 0)); |
6201 | 0 | if (NS_WARN_IF(createMozBrResult.Failed())) { |
6202 | 0 | return createMozBrResult.Rv(); |
6203 | 0 | } |
6204 | 0 | EditorRawDOMPoint atStartOfDiv(div, 0); |
6205 | 0 | // Don't restore the selection |
6206 | 0 | selectionRestorer.Abort(); |
6207 | 0 | ErrorResult error; |
6208 | 0 | SelectionRef().Collapse(atStartOfDiv, error); |
6209 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6210 | 0 | error.SuppressException(); |
6211 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6212 | 0 | } |
6213 | 0 | if (NS_WARN_IF(error.Failed())) { |
6214 | 0 | return error.StealNSResult(); |
6215 | 0 | } |
6216 | 0 | return NS_OK; |
6217 | 0 | } |
6218 | 0 | |
6219 | 0 | // Next we detect all the transitions in the array, where a transition |
6220 | 0 | // means that adjacent nodes in the array don't have the same parent. |
6221 | 0 | |
6222 | 0 | nsTArray<bool> transitionList; |
6223 | 0 | MakeTransitionList(nodeArray, transitionList); |
6224 | 0 |
|
6225 | 0 | // Okay, now go through all the nodes and give them an align attrib or put |
6226 | 0 | // them in a div, or whatever is appropriate. Woohoo! |
6227 | 0 |
|
6228 | 0 | nsCOMPtr<Element> curDiv; |
6229 | 0 | bool useCSS = HTMLEditorRef().IsCSSEnabled(); |
6230 | 0 | int32_t indexOfTransitionList = -1; |
6231 | 0 | for (OwningNonNull<nsINode>& curNode : nodeArray) { |
6232 | 0 | ++indexOfTransitionList; |
6233 | 0 |
|
6234 | 0 | // Ignore all non-editable nodes. Leave them be. |
6235 | 0 | if (!HTMLEditorRef().IsEditable(curNode)) { |
6236 | 0 | continue; |
6237 | 0 | } |
6238 | 0 | |
6239 | 0 | // The node is a table element, an hr, a paragraph, a div or a section |
6240 | 0 | // header; in HTML 4, it can directly carry the ALIGN attribute and we |
6241 | 0 | // don't need to nest it, just set the alignment. In CSS, assign the |
6242 | 0 | // corresponding CSS styles in AlignBlock |
6243 | 0 | if (HTMLEditUtils::SupportsAlignAttr(*curNode)) { |
6244 | 0 | rv = AlignBlock(*curNode->AsElement(), aAlignType, |
6245 | 0 | ResetAlignOf::ElementAndDescendants); |
6246 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6247 | 0 | return rv; |
6248 | 0 | } |
6249 | 0 | // Clear out curDiv so that we don't put nodes after this one into it |
6250 | 0 | curDiv = nullptr; |
6251 | 0 | continue; |
6252 | 0 | } |
6253 | 0 | |
6254 | 0 | EditorDOMPoint atCurNode(curNode); |
6255 | 0 | if (NS_WARN_IF(!atCurNode.IsSet())) { |
6256 | 0 | continue; |
6257 | 0 | } |
6258 | 0 | |
6259 | 0 | // Skip insignificant formatting text nodes to prevent unnecessary |
6260 | 0 | // structure splitting! |
6261 | 0 | bool isEmptyTextNode = false; |
6262 | 0 | if (curNode->GetAsText() && |
6263 | 0 | ((HTMLEditUtils::IsTableElement(atCurNode.GetContainer()) && |
6264 | 0 | !HTMLEditUtils::IsTableCellOrCaption(*atCurNode.GetContainer())) || |
6265 | 0 | HTMLEditUtils::IsList(atCurNode.GetContainer()) || |
6266 | 0 | (NS_SUCCEEDED(HTMLEditorRef().IsEmptyNode(curNode, |
6267 | 0 | &isEmptyTextNode)) && |
6268 | 0 | isEmptyTextNode))) { |
6269 | 0 | continue; |
6270 | 0 | } |
6271 | 0 | |
6272 | 0 | // If it's a list item, or a list inside a list, forget any "current" div, |
6273 | 0 | // and instead put divs inside the appropriate block (td, li, etc.) |
6274 | 0 | if (HTMLEditUtils::IsListItem(curNode) || |
6275 | 0 | HTMLEditUtils::IsList(curNode)) { |
6276 | 0 | AutoEditorDOMPointOffsetInvalidator lockChild(atCurNode); |
6277 | 0 | rv = RemoveAlignment(*curNode, aAlignType, true); |
6278 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6279 | 0 | return rv; |
6280 | 0 | } |
6281 | 0 | if (useCSS) { |
6282 | 0 | HTMLEditorRef().mCSSEditUtils->SetCSSEquivalentToHTMLStyle( |
6283 | 0 | curNode->AsElement(), nullptr, |
6284 | 0 | nsGkAtoms::align, &aAlignType, false); |
6285 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6286 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6287 | 0 | } |
6288 | 0 | curDiv = nullptr; |
6289 | 0 | continue; |
6290 | 0 | } |
6291 | 0 | if (HTMLEditUtils::IsList(atCurNode.GetContainer())) { |
6292 | 0 | // If we don't use CSS, add a content to list element: they have to |
6293 | 0 | // be inside another list, i.e., >= second level of nesting. |
6294 | 0 | // XXX AlignInnerBlocks() handles list item elements and table cells. |
6295 | 0 | // Is it intentional to change alignment of nested other type |
6296 | 0 | // descendants too? |
6297 | 0 | rv = AlignInnerBlocks(*curNode, aAlignType); |
6298 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6299 | 0 | return rv; |
6300 | 0 | } |
6301 | 0 | curDiv = nullptr; |
6302 | 0 | continue; |
6303 | 0 | } |
6304 | 0 | // Clear out curDiv so that we don't put nodes after this one into it |
6305 | 0 | } |
6306 | 0 |
|
6307 | 0 | // Need to make a div to put things in if we haven't already, or if this |
6308 | 0 | // node doesn't go in div we used earlier. |
6309 | 0 | if (!curDiv || transitionList[indexOfTransitionList]) { |
6310 | 0 | // First, check that our element can contain a div. |
6311 | 0 | if (!HTMLEditorRef().CanContainTag(*atCurNode.GetContainer(), |
6312 | 0 | *nsGkAtoms::div)) { |
6313 | 0 | // Cancelled |
6314 | 0 | return NS_OK; |
6315 | 0 | } |
6316 | 0 | |
6317 | 0 | SplitNodeResult splitNodeResult = |
6318 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div, atCurNode); |
6319 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
6320 | 0 | return splitNodeResult.Rv(); |
6321 | 0 | } |
6322 | 0 | curDiv = |
6323 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::div, |
6324 | 0 | splitNodeResult.SplitPoint()); |
6325 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6326 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6327 | 0 | } |
6328 | 0 | if (NS_WARN_IF(!curDiv)) { |
6329 | 0 | return NS_ERROR_FAILURE; |
6330 | 0 | } |
6331 | 0 | // Remember our new block for postprocessing |
6332 | 0 | mNewBlock = curDiv; |
6333 | 0 | // Set up the alignment on the div |
6334 | 0 | rv = AlignBlock(*curDiv, aAlignType, ResetAlignOf::OnlyDescendants); |
6335 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
6336 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6337 | 0 | } |
6338 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to align the <div>"); |
6339 | 0 | } |
6340 | 0 |
|
6341 | 0 | // Tuck the node into the end of the active div |
6342 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
6343 | 0 | *curDiv); |
6344 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6345 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6346 | 0 | } |
6347 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6348 | 0 | return rv; |
6349 | 0 | } |
6350 | 0 | } |
6351 | 0 |
|
6352 | 0 | return NS_OK; |
6353 | 0 | } |
6354 | | |
6355 | | nsresult |
6356 | | HTMLEditRules::AlignInnerBlocks(nsINode& aNode, |
6357 | | const nsAString& aAlignType) |
6358 | 0 | { |
6359 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
6360 | 0 |
|
6361 | 0 | // Gather list of table cells or list items |
6362 | 0 | nsTArray<OwningNonNull<nsINode>> nodeArray; |
6363 | 0 | TableCellAndListItemFunctor functor; |
6364 | 0 | DOMIterator iter(aNode); |
6365 | 0 | iter.AppendList(functor, nodeArray); |
6366 | 0 |
|
6367 | 0 | // Now that we have the list, align their contents as requested |
6368 | 0 | for (auto& node : nodeArray) { |
6369 | 0 | nsresult rv = AlignBlockContents(*node, aAlignType); |
6370 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6371 | 0 | return rv; |
6372 | 0 | } |
6373 | 0 | } |
6374 | 0 |
|
6375 | 0 | return NS_OK; |
6376 | 0 | } |
6377 | | |
6378 | | |
6379 | | nsresult |
6380 | | HTMLEditRules::AlignBlockContents(nsINode& aNode, |
6381 | | const nsAString& aAlignType) |
6382 | 0 | { |
6383 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
6384 | 0 |
|
6385 | 0 | nsCOMPtr<nsIContent> firstChild = |
6386 | 0 | HTMLEditorRef().GetFirstEditableChild(aNode); |
6387 | 0 | if (!firstChild) { |
6388 | 0 | // this cell has no content, nothing to align |
6389 | 0 | return NS_OK; |
6390 | 0 | } |
6391 | 0 | |
6392 | 0 | nsCOMPtr<nsIContent> lastChild = HTMLEditorRef().GetLastEditableChild(aNode); |
6393 | 0 | if (firstChild == lastChild && firstChild->IsHTMLElement(nsGkAtoms::div)) { |
6394 | 0 | // the cell already has a div containing all of its content: just |
6395 | 0 | // act on this div. |
6396 | 0 | nsresult rv = |
6397 | 0 | HTMLEditorRef().SetAttributeOrEquivalent(firstChild->AsElement(), |
6398 | 0 | nsGkAtoms::align, |
6399 | 0 | aAlignType, false); |
6400 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6401 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6402 | 0 | } |
6403 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6404 | 0 | return rv; |
6405 | 0 | } |
6406 | 0 | return NS_OK; |
6407 | 0 | } |
6408 | 0 | |
6409 | 0 | // else we need to put in a div, set the alignment, and toss in all the |
6410 | 0 | // children |
6411 | 0 | EditorRawDOMPoint atStartOfNode(&aNode, 0); |
6412 | 0 | RefPtr<Element> divElem = |
6413 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::div, atStartOfNode); |
6414 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6415 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6416 | 0 | } |
6417 | 0 | if (NS_WARN_IF(!divElem)) { |
6418 | 0 | return NS_ERROR_FAILURE; |
6419 | 0 | } |
6420 | 0 | // set up the alignment on the div |
6421 | 0 | nsresult rv = |
6422 | 0 | HTMLEditorRef().SetAttributeOrEquivalent(divElem, nsGkAtoms::align, |
6423 | 0 | aAlignType, false); |
6424 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6425 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6426 | 0 | } |
6427 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6428 | 0 | return rv; |
6429 | 0 | } |
6430 | 0 | // tuck the children into the end of the active div |
6431 | 0 | while (lastChild && (lastChild != divElem)) { |
6432 | 0 | nsresult rv = |
6433 | 0 | HTMLEditorRef().MoveNodeWithTransaction(*lastChild, |
6434 | 0 | EditorRawDOMPoint(divElem, 0)); |
6435 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6436 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6437 | 0 | } |
6438 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6439 | 0 | return rv; |
6440 | 0 | } |
6441 | 0 | lastChild = HTMLEditorRef().GetLastEditableChild(aNode); |
6442 | 0 | } |
6443 | 0 | return NS_OK; |
6444 | 0 | } |
6445 | | |
6446 | | nsresult |
6447 | | HTMLEditRules::MaybeDeleteTopMostEmptyAncestor(nsINode& aStartNode, |
6448 | | Element& aEditingHostElement, |
6449 | | nsIEditor::EDirection aAction, |
6450 | | bool* aHandled) |
6451 | 0 | { |
6452 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
6453 | 0 |
|
6454 | 0 | // If the editing host is an inline element, bail out early. |
6455 | 0 | if (IsInlineNode(aEditingHostElement)) { |
6456 | 0 | return NS_OK; |
6457 | 0 | } |
6458 | 0 | |
6459 | 0 | // If we are inside an empty block, delete it. Note: do NOT delete table |
6460 | 0 | // elements this way. |
6461 | 0 | RefPtr<Element> block = HTMLEditorRef().GetBlock(aStartNode); |
6462 | 0 | RefPtr<Element> emptyBlock; |
6463 | 0 | if (block && block != &aEditingHostElement) { |
6464 | 0 | // Efficiency hack, avoiding IsEmptyNode() call when in body |
6465 | 0 | bool isEmptyNode = false; |
6466 | 0 | nsresult rv = |
6467 | 0 | HTMLEditorRef().IsEmptyNode(block, &isEmptyNode, true, false); |
6468 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6469 | 0 | return rv; |
6470 | 0 | } |
6471 | 0 | while (block && isEmptyNode && !HTMLEditUtils::IsTableElement(block) && |
6472 | 0 | block != &aEditingHostElement) { |
6473 | 0 | emptyBlock = block; |
6474 | 0 | block = HTMLEditorRef().GetBlockNodeParent(emptyBlock); |
6475 | 0 | if (block) { |
6476 | 0 | rv = HTMLEditorRef().IsEmptyNode(block, &isEmptyNode, true, false); |
6477 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6478 | 0 | return rv; |
6479 | 0 | } |
6480 | 0 | } |
6481 | 0 | } |
6482 | 0 | } |
6483 | 0 |
|
6484 | 0 | if (!emptyBlock || !emptyBlock->IsEditable()) { |
6485 | 0 | return NS_OK; |
6486 | 0 | } |
6487 | 0 | |
6488 | 0 | nsCOMPtr<nsINode> blockParent = emptyBlock->GetParentNode(); |
6489 | 0 | if (NS_WARN_IF(!blockParent)) { |
6490 | 0 | return NS_ERROR_FAILURE; |
6491 | 0 | } |
6492 | 0 | |
6493 | 0 | if (HTMLEditUtils::IsListItem(emptyBlock)) { |
6494 | 0 | // If the found empty block is a list item element and its grand parent |
6495 | 0 | // (i.e., parent of list element) is NOT a list element, insert <br> |
6496 | 0 | // element before the list element which has the empty list item. |
6497 | 0 | // XXX Typically, list element shouldn't have another list element. |
6498 | 0 | // So, what's the purpose of this block? |
6499 | 0 | if (HTMLEditorRef().IsFirstEditableChild(emptyBlock)) { |
6500 | 0 | EditorDOMPoint atBlockParent(blockParent); |
6501 | 0 | if (NS_WARN_IF(!atBlockParent.IsSet())) { |
6502 | 0 | return NS_ERROR_FAILURE; |
6503 | 0 | } |
6504 | 0 | // If the grand parent IS a list element, we'll adjust Selection in |
6505 | 0 | // AfterEdit(). |
6506 | 0 | if (!HTMLEditUtils::IsList(atBlockParent.GetContainer())) { |
6507 | 0 | RefPtr<Element> brElement = |
6508 | 0 | HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(), |
6509 | 0 | atBlockParent); |
6510 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6511 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6512 | 0 | } |
6513 | 0 | if (NS_WARN_IF(!brElement)) { |
6514 | 0 | return NS_ERROR_FAILURE; |
6515 | 0 | } |
6516 | 0 | ErrorResult error; |
6517 | 0 | SelectionRef().Collapse(EditorRawDOMPoint(brElement), error); |
6518 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6519 | 0 | error.SuppressException(); |
6520 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6521 | 0 | } |
6522 | 0 | if (NS_WARN_IF(error.Failed())) { |
6523 | 0 | return error.StealNSResult(); |
6524 | 0 | } |
6525 | 0 | } |
6526 | 0 | } |
6527 | 0 | } else { |
6528 | 0 | switch (aAction) { |
6529 | 0 | case nsIEditor::eNext: |
6530 | 0 | case nsIEditor::eNextWord: |
6531 | 0 | case nsIEditor::eToEndOfLine: { |
6532 | 0 | // Collapse Selection to next node of after empty block element |
6533 | 0 | // if there is. Otherwise, to just after the empty block. |
6534 | 0 | EditorRawDOMPoint afterEmptyBlock(emptyBlock); |
6535 | 0 | bool advancedFromEmptyBlock = afterEmptyBlock.AdvanceOffset(); |
6536 | 0 | NS_WARNING_ASSERTION(advancedFromEmptyBlock, |
6537 | 0 | "Failed to set selection to the after the empty block"); |
6538 | 0 | nsCOMPtr<nsIContent> nextNode = |
6539 | 0 | HTMLEditorRef().GetNextNode(afterEmptyBlock); |
6540 | 0 | if (nextNode) { |
6541 | 0 | EditorDOMPoint pt = GetGoodSelPointForNode(*nextNode, aAction); |
6542 | 0 | ErrorResult error; |
6543 | 0 | SelectionRef().Collapse(pt, error); |
6544 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6545 | 0 | error.SuppressException(); |
6546 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6547 | 0 | } |
6548 | 0 | if (NS_WARN_IF(error.Failed())) { |
6549 | 0 | return error.StealNSResult(); |
6550 | 0 | } |
6551 | 0 | break; |
6552 | 0 | } |
6553 | 0 | if (NS_WARN_IF(!advancedFromEmptyBlock)) { |
6554 | 0 | return NS_ERROR_FAILURE; |
6555 | 0 | } |
6556 | 0 | ErrorResult error; |
6557 | 0 | SelectionRef().Collapse(afterEmptyBlock, error); |
6558 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6559 | 0 | error.SuppressException(); |
6560 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6561 | 0 | } |
6562 | 0 | if (NS_WARN_IF(error.Failed())) { |
6563 | 0 | return error.StealNSResult(); |
6564 | 0 | } |
6565 | 0 | break; |
6566 | 0 | } |
6567 | 0 | case nsIEditor::ePrevious: |
6568 | 0 | case nsIEditor::ePreviousWord: |
6569 | 0 | case nsIEditor::eToBeginningOfLine: { |
6570 | 0 | // Collapse Selection to previous editable node of the empty block |
6571 | 0 | // if there is. Otherwise, to after the empty block. |
6572 | 0 | EditorRawDOMPoint atEmptyBlock(emptyBlock); |
6573 | 0 | nsCOMPtr<nsIContent> priorNode = |
6574 | 0 | HTMLEditorRef().GetPreviousEditableNode(atEmptyBlock); |
6575 | 0 | if (priorNode) { |
6576 | 0 | EditorDOMPoint pt = GetGoodSelPointForNode(*priorNode, aAction); |
6577 | 0 | ErrorResult error; |
6578 | 0 | SelectionRef().Collapse(pt, error); |
6579 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6580 | 0 | error.SuppressException(); |
6581 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6582 | 0 | } |
6583 | 0 | if (NS_WARN_IF(error.Failed())) { |
6584 | 0 | return error.StealNSResult(); |
6585 | 0 | } |
6586 | 0 | break; |
6587 | 0 | } |
6588 | 0 | EditorRawDOMPoint afterEmptyBlock(emptyBlock); |
6589 | 0 | if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) { |
6590 | 0 | return NS_ERROR_FAILURE; |
6591 | 0 | } |
6592 | 0 | ErrorResult error; |
6593 | 0 | SelectionRef().Collapse(afterEmptyBlock, error); |
6594 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6595 | 0 | error.SuppressException(); |
6596 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6597 | 0 | } |
6598 | 0 | if (NS_WARN_IF(error.Failed())) { |
6599 | 0 | return error.StealNSResult(); |
6600 | 0 | } |
6601 | 0 | break; |
6602 | 0 | } |
6603 | 0 | case nsIEditor::eNone: |
6604 | 0 | break; |
6605 | 0 | default: |
6606 | 0 | MOZ_CRASH("CheckForEmptyBlock doesn't support this action yet"); |
6607 | 0 | } |
6608 | 0 | } |
6609 | 0 | *aHandled = true; |
6610 | 0 | nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*emptyBlock); |
6611 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6612 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6613 | 0 | } |
6614 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6615 | 0 | return rv; |
6616 | 0 | } |
6617 | 0 | return NS_OK; |
6618 | 0 | } |
6619 | | |
6620 | | Element* |
6621 | | HTMLEditRules::CheckForInvisibleBR(Element& aBlock, |
6622 | | BRLocation aWhere, |
6623 | | int32_t aOffset) |
6624 | 0 | { |
6625 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
6626 | 0 |
|
6627 | 0 | nsCOMPtr<nsINode> testNode; |
6628 | 0 | int32_t testOffset = 0; |
6629 | 0 |
|
6630 | 0 | if (aWhere == BRLocation::blockEnd) { |
6631 | 0 | // No block crossing |
6632 | 0 | nsCOMPtr<nsIContent> rightmostNode = |
6633 | 0 | HTMLEditorRef().GetRightmostChild(&aBlock, true); |
6634 | 0 |
|
6635 | 0 | if (!rightmostNode) { |
6636 | 0 | return nullptr; |
6637 | 0 | } |
6638 | 0 | |
6639 | 0 | testNode = rightmostNode->GetParentNode(); |
6640 | 0 | // Since rightmostNode is always the last child, its index is equal to the |
6641 | 0 | // child count, so instead of ComputeIndexOf() we use the faster |
6642 | 0 | // GetChildCount(), and assert the equivalence below. |
6643 | 0 | testOffset = testNode->GetChildCount(); |
6644 | 0 |
|
6645 | 0 | // Use offset + 1, so last node is included in our evaluation |
6646 | 0 | MOZ_ASSERT(testNode->ComputeIndexOf(rightmostNode) + 1 == testOffset); |
6647 | 0 | } else if (aOffset) { |
6648 | 0 | testNode = &aBlock; |
6649 | 0 | // We'll check everything to the left of the input position |
6650 | 0 | testOffset = aOffset; |
6651 | 0 | } else { |
6652 | 0 | return nullptr; |
6653 | 0 | } |
6654 | 0 | |
6655 | 0 | WSRunObject wsTester(&HTMLEditorRef(), testNode, testOffset); |
6656 | 0 | if (WSType::br == wsTester.mStartReason) { |
6657 | 0 | return wsTester.mStartReasonNode->AsElement(); |
6658 | 0 | } |
6659 | 0 | |
6660 | 0 | return nullptr; |
6661 | 0 | } |
6662 | | |
6663 | | void |
6664 | | HTMLEditRules::GetInnerContent( |
6665 | | nsINode& aNode, |
6666 | | nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes, |
6667 | | int32_t* aIndex, |
6668 | | Lists aLists, |
6669 | | Tables aTables) |
6670 | 0 | { |
6671 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
6672 | 0 | MOZ_ASSERT(aIndex); |
6673 | 0 |
|
6674 | 0 | for (nsCOMPtr<nsIContent> node = HTMLEditorRef().GetFirstEditableChild(aNode); |
6675 | 0 | node; node = node->GetNextSibling()) { |
6676 | 0 | if ((aLists == Lists::yes && (HTMLEditUtils::IsList(node) || |
6677 | 0 | HTMLEditUtils::IsListItem(node))) || |
6678 | 0 | (aTables == Tables::yes && HTMLEditUtils::IsTableElement(node))) { |
6679 | 0 | GetInnerContent(*node, aOutArrayOfNodes, aIndex, aLists, aTables); |
6680 | 0 | } else { |
6681 | 0 | aOutArrayOfNodes.InsertElementAt(*aIndex, *node); |
6682 | 0 | (*aIndex)++; |
6683 | 0 | } |
6684 | 0 | } |
6685 | 0 | } |
6686 | | |
6687 | | nsresult |
6688 | | HTMLEditRules::ExpandSelectionForDeletion() |
6689 | 0 | { |
6690 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
6691 | 0 |
|
6692 | 0 | // Don't need to touch collapsed selections |
6693 | 0 | if (SelectionRef().IsCollapsed()) { |
6694 | 0 | return NS_OK; |
6695 | 0 | } |
6696 | 0 | |
6697 | 0 | // We don't need to mess with cell selections, and we assume multirange |
6698 | 0 | // selections are those. |
6699 | 0 | if (SelectionRef().RangeCount() != 1) { |
6700 | 0 | return NS_OK; |
6701 | 0 | } |
6702 | 0 | |
6703 | 0 | // Find current sel start and end |
6704 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
6705 | 0 | if (NS_WARN_IF(!firstRange)) { |
6706 | 0 | return NS_ERROR_FAILURE; |
6707 | 0 | } |
6708 | 0 | |
6709 | 0 | nsCOMPtr<nsINode> selStartNode = firstRange->GetStartContainer(); |
6710 | 0 | int32_t selStartOffset = firstRange->StartOffset(); |
6711 | 0 | nsCOMPtr<nsINode> selEndNode = firstRange->GetEndContainer(); |
6712 | 0 | int32_t selEndOffset = firstRange->EndOffset(); |
6713 | 0 |
|
6714 | 0 | // Find current selection common block parent |
6715 | 0 | RefPtr<Element> selCommon = |
6716 | 0 | HTMLEditor::GetBlock(*firstRange->GetCommonAncestor()); |
6717 | 0 | if (NS_WARN_IF(!selCommon)) { |
6718 | 0 | return NS_ERROR_FAILURE; |
6719 | 0 | } |
6720 | 0 | |
6721 | 0 | // Set up for loops and cache our root element |
6722 | 0 | nsCOMPtr<nsINode> firstBRParent; |
6723 | 0 | int32_t firstBROffset = 0; |
6724 | 0 | WSType wsType; |
6725 | 0 | RefPtr<Element> root = HTMLEditorRef().GetActiveEditingHost(); |
6726 | 0 | if (NS_WARN_IF(!root)) { |
6727 | 0 | return NS_ERROR_FAILURE; |
6728 | 0 | } |
6729 | 0 | |
6730 | 0 | // Find previous visible things before start of selection |
6731 | 0 | if (selStartNode != selCommon && selStartNode != root) { |
6732 | 0 | while (true) { |
6733 | 0 | WSRunObject wsObj(&HTMLEditorRef(), selStartNode, selStartOffset); |
6734 | 0 | wsObj.PriorVisibleNode(EditorRawDOMPoint(selStartNode, selStartOffset), |
6735 | 0 | &wsType); |
6736 | 0 | if (wsType != WSType::thisBlock) { |
6737 | 0 | break; |
6738 | 0 | } |
6739 | 0 | // We want to keep looking up. But stop if we are crossing table |
6740 | 0 | // element boundaries, or if we hit the root. |
6741 | 0 | if (HTMLEditUtils::IsTableElement(wsObj.mStartReasonNode) || |
6742 | 0 | selCommon == wsObj.mStartReasonNode || |
6743 | 0 | root == wsObj.mStartReasonNode) { |
6744 | 0 | break; |
6745 | 0 | } |
6746 | 0 | selStartNode = wsObj.mStartReasonNode->GetParentNode(); |
6747 | 0 | selStartOffset = selStartNode ? |
6748 | 0 | selStartNode->ComputeIndexOf(wsObj.mStartReasonNode) : -1; |
6749 | 0 | } |
6750 | 0 | } |
6751 | 0 |
|
6752 | 0 | // Find next visible things after end of selection |
6753 | 0 | if (selEndNode != selCommon && selEndNode != root) { |
6754 | 0 | for (;;) { |
6755 | 0 | WSRunObject wsObj(&HTMLEditorRef(), selEndNode, selEndOffset); |
6756 | 0 | wsObj.NextVisibleNode(EditorRawDOMPoint(selEndNode, selEndOffset), |
6757 | 0 | &wsType); |
6758 | 0 | if (wsType == WSType::br) { |
6759 | 0 | if (HTMLEditorRef().IsVisibleBRElement(wsObj.mEndReasonNode)) { |
6760 | 0 | break; |
6761 | 0 | } |
6762 | 0 | if (!firstBRParent) { |
6763 | 0 | firstBRParent = selEndNode; |
6764 | 0 | firstBROffset = selEndOffset; |
6765 | 0 | } |
6766 | 0 | selEndNode = wsObj.mEndReasonNode->GetParentNode(); |
6767 | 0 | selEndOffset = selEndNode |
6768 | 0 | ? selEndNode->ComputeIndexOf(wsObj.mEndReasonNode) + 1 : 0; |
6769 | 0 | } else if (wsType == WSType::thisBlock) { |
6770 | 0 | // We want to keep looking up. But stop if we are crossing table |
6771 | 0 | // element boundaries, or if we hit the root. |
6772 | 0 | if (HTMLEditUtils::IsTableElement(wsObj.mEndReasonNode) || |
6773 | 0 | selCommon == wsObj.mEndReasonNode || |
6774 | 0 | root == wsObj.mEndReasonNode) { |
6775 | 0 | break; |
6776 | 0 | } |
6777 | 0 | selEndNode = wsObj.mEndReasonNode->GetParentNode(); |
6778 | 0 | selEndOffset = 1 + selEndNode->ComputeIndexOf(wsObj.mEndReasonNode); |
6779 | 0 | } else { |
6780 | 0 | break; |
6781 | 0 | } |
6782 | 0 | } |
6783 | 0 | } |
6784 | 0 | // Now set the selection to the new range |
6785 | 0 | DebugOnly<nsresult> rv = |
6786 | 0 | SelectionRef().Collapse(selStartNode, selStartOffset); |
6787 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6788 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6789 | 0 | } |
6790 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to collapse selection"); |
6791 | 0 |
|
6792 | 0 | // Expand selection endpoint only if we didn't pass a <br>, or if we really |
6793 | 0 | // needed to pass that <br> (i.e., its block is now totally selected). |
6794 | 0 | bool doEndExpansion = true; |
6795 | 0 | if (firstBRParent) { |
6796 | 0 | // Find block node containing <br>. |
6797 | 0 | nsCOMPtr<Element> brBlock = HTMLEditor::GetBlock(*firstBRParent); |
6798 | 0 | bool nodeBefore = false, nodeAfter = false; |
6799 | 0 |
|
6800 | 0 | // Create a range that represents expanded selection |
6801 | 0 | RefPtr<nsRange> range = new nsRange(selStartNode); |
6802 | 0 | nsresult rv = range->SetStartAndEnd(selStartNode, selStartOffset, |
6803 | 0 | selEndNode, selEndOffset); |
6804 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6805 | 0 | return rv; |
6806 | 0 | } |
6807 | 0 | |
6808 | 0 | // Check if block is entirely inside range |
6809 | 0 | if (brBlock) { |
6810 | 0 | nsRange::CompareNodeToRange(brBlock, range, &nodeBefore, &nodeAfter); |
6811 | 0 | } |
6812 | 0 |
|
6813 | 0 | // If block isn't contained, forgo grabbing the <br> in expanded selection. |
6814 | 0 | if (nodeBefore || nodeAfter) { |
6815 | 0 | doEndExpansion = false; |
6816 | 0 | } |
6817 | 0 | } |
6818 | 0 | if (doEndExpansion) { |
6819 | 0 | nsresult rv = SelectionRef().Extend(selEndNode, selEndOffset); |
6820 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6821 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6822 | 0 | } |
6823 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6824 | 0 | return rv; |
6825 | 0 | } |
6826 | 0 | } else { |
6827 | 0 | // Only expand to just before <br>. |
6828 | 0 | nsresult rv = SelectionRef().Extend(firstBRParent, firstBROffset); |
6829 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6830 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
6831 | 0 | } |
6832 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
6833 | 0 | return rv; |
6834 | 0 | } |
6835 | 0 | } |
6836 | 0 | |
6837 | 0 | return NS_OK; |
6838 | 0 | } |
6839 | | |
6840 | | nsresult |
6841 | | HTMLEditRules::NormalizeSelection() |
6842 | 0 | { |
6843 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
6844 | 0 |
|
6845 | 0 | // NormalizeSelection() tweaks non-collapsed selections to be more "natural". |
6846 | 0 | // Idea here is to adjust selection endpoint so that they do not cross breaks |
6847 | 0 | // or block boundaries unless something editable beyond that boundary is also |
6848 | 0 | // selected. This adjustment makes it much easier for the various block |
6849 | 0 | // operations to determine what nodes to act on. |
6850 | 0 |
|
6851 | 0 | // don't need to touch collapsed selections |
6852 | 0 | if (SelectionRef().IsCollapsed()) { |
6853 | 0 | return NS_OK; |
6854 | 0 | } |
6855 | 0 | |
6856 | 0 | // We don't need to mess with cell selections, and we assume multirange |
6857 | 0 | // selections are those. |
6858 | 0 | // XXX Why? Even in <input>, user can select 2 or more ranges. |
6859 | 0 | if (SelectionRef().RangeCount() != 1) { |
6860 | 0 | return NS_OK; |
6861 | 0 | } |
6862 | 0 | |
6863 | 0 | RefPtr<nsRange> range = SelectionRef().GetRangeAt(0); |
6864 | 0 | if (NS_WARN_IF(!range)) { |
6865 | 0 | return NS_ERROR_FAILURE; |
6866 | 0 | } |
6867 | 0 | |
6868 | 0 | nsCOMPtr<nsINode> startNode = range->GetStartContainer(); |
6869 | 0 | if (NS_WARN_IF(!startNode)) { |
6870 | 0 | return NS_ERROR_FAILURE; |
6871 | 0 | } |
6872 | 0 | nsCOMPtr<nsINode> endNode = range->GetEndContainer(); |
6873 | 0 | if (NS_WARN_IF(!endNode)) { |
6874 | 0 | return NS_ERROR_FAILURE; |
6875 | 0 | } |
6876 | 0 | nsIContent* startChild = range->GetChildAtStartOffset(); |
6877 | 0 | nsIContent* endChild = range->GetChildAtEndOffset(); |
6878 | 0 | uint32_t startOffset = range->StartOffset(); |
6879 | 0 | uint32_t endOffset = range->EndOffset(); |
6880 | 0 |
|
6881 | 0 | // adjusted values default to original values |
6882 | 0 | nsCOMPtr<nsINode> newStartNode = startNode; |
6883 | 0 | uint32_t newStartOffset = startOffset; |
6884 | 0 | nsCOMPtr<nsINode> newEndNode = endNode; |
6885 | 0 | uint32_t newEndOffset = endOffset; |
6886 | 0 |
|
6887 | 0 | // some locals we need for whitespace code |
6888 | 0 | WSType wsType; |
6889 | 0 |
|
6890 | 0 | // let the whitespace code do the heavy lifting |
6891 | 0 | WSRunObject wsEndObj(&HTMLEditorRef(), endNode, |
6892 | 0 | static_cast<int32_t>(endOffset)); |
6893 | 0 | // Is there any intervening visible whitespace? If so we can't push |
6894 | 0 | // selection past that, it would visibly change meaning of users selection. |
6895 | 0 | wsEndObj.PriorVisibleNode(EditorRawDOMPoint(endNode, endOffset), &wsType); |
6896 | 0 | if (wsType != WSType::text && wsType != WSType::normalWS) { |
6897 | 0 | // eThisBlock and eOtherBlock conveniently distinguish cases |
6898 | 0 | // of going "down" into a block and "up" out of a block. |
6899 | 0 | if (wsEndObj.mStartReason == WSType::otherBlock) { |
6900 | 0 | // endpoint is just after the close of a block. |
6901 | 0 | nsINode* child = |
6902 | 0 | HTMLEditorRef().GetRightmostChild(wsEndObj.mStartReasonNode, true); |
6903 | 0 | if (child) { |
6904 | 0 | int32_t offset = -1; |
6905 | 0 | newEndNode = EditorBase::GetNodeLocation(child, &offset); |
6906 | 0 | // offset *after* child |
6907 | 0 | newEndOffset = static_cast<uint32_t>(offset + 1); |
6908 | 0 | } |
6909 | 0 | // else block is empty - we can leave selection alone here, i think. |
6910 | 0 | } else if (wsEndObj.mStartReason == WSType::thisBlock) { |
6911 | 0 | // endpoint is just after start of this block |
6912 | 0 | EditorRawDOMPoint atEnd(endNode, endChild, endOffset); |
6913 | 0 | nsINode* child = HTMLEditorRef().GetPreviousEditableHTMLNode(atEnd); |
6914 | 0 | if (child) { |
6915 | 0 | int32_t offset = -1; |
6916 | 0 | newEndNode = EditorBase::GetNodeLocation(child, &offset); |
6917 | 0 | // offset *after* child |
6918 | 0 | newEndOffset = static_cast<uint32_t>(offset + 1); |
6919 | 0 | } |
6920 | 0 | // else block is empty - we can leave selection alone here, i think. |
6921 | 0 | } else if (wsEndObj.mStartReason == WSType::br) { |
6922 | 0 | // endpoint is just after break. lets adjust it to before it. |
6923 | 0 | int32_t offset = -1; |
6924 | 0 | newEndNode = |
6925 | 0 | EditorBase::GetNodeLocation(wsEndObj.mStartReasonNode, &offset); |
6926 | 0 | newEndOffset = static_cast<uint32_t>(offset);; |
6927 | 0 | } |
6928 | 0 | } |
6929 | 0 |
|
6930 | 0 |
|
6931 | 0 | // similar dealio for start of range |
6932 | 0 | WSRunObject wsStartObj(&HTMLEditorRef(), startNode, |
6933 | 0 | static_cast<int32_t>(startOffset)); |
6934 | 0 | // Is there any intervening visible whitespace? If so we can't push |
6935 | 0 | // selection past that, it would visibly change meaning of users selection. |
6936 | 0 | wsStartObj.NextVisibleNode(EditorRawDOMPoint(startNode, startOffset), |
6937 | 0 | &wsType); |
6938 | 0 | if (wsType != WSType::text && wsType != WSType::normalWS) { |
6939 | 0 | // eThisBlock and eOtherBlock conveniently distinguish cases |
6940 | 0 | // of going "down" into a block and "up" out of a block. |
6941 | 0 | if (wsStartObj.mEndReason == WSType::otherBlock) { |
6942 | 0 | // startpoint is just before the start of a block. |
6943 | 0 | nsINode* child = |
6944 | 0 | HTMLEditorRef().GetLeftmostChild(wsStartObj.mEndReasonNode, true); |
6945 | 0 | if (child) { |
6946 | 0 | int32_t offset = -1; |
6947 | 0 | newStartNode = EditorBase::GetNodeLocation(child, &offset); |
6948 | 0 | newStartOffset = static_cast<uint32_t>(offset); |
6949 | 0 | } |
6950 | 0 | // else block is empty - we can leave selection alone here, i think. |
6951 | 0 | } else if (wsStartObj.mEndReason == WSType::thisBlock) { |
6952 | 0 | // startpoint is just before end of this block |
6953 | 0 | nsINode* child = |
6954 | 0 | HTMLEditorRef().GetNextEditableHTMLNode( |
6955 | 0 | EditorRawDOMPoint(startNode, |
6956 | 0 | startChild, startOffset)); |
6957 | 0 | if (child) { |
6958 | 0 | int32_t offset = -1; |
6959 | 0 | newStartNode = EditorBase::GetNodeLocation(child, &offset); |
6960 | 0 | newStartOffset = static_cast<uint32_t>(offset); |
6961 | 0 | } |
6962 | 0 | // else block is empty - we can leave selection alone here, i think. |
6963 | 0 | } else if (wsStartObj.mEndReason == WSType::br) { |
6964 | 0 | // startpoint is just before a break. lets adjust it to after it. |
6965 | 0 | int32_t offset = -1; |
6966 | 0 | newStartNode = |
6967 | 0 | EditorBase::GetNodeLocation(wsStartObj.mEndReasonNode, &offset); |
6968 | 0 | // offset *after* break |
6969 | 0 | newStartOffset = static_cast<uint32_t>(offset + 1); |
6970 | 0 | } |
6971 | 0 | } |
6972 | 0 |
|
6973 | 0 | // There is a demented possiblity we have to check for. We might have a very |
6974 | 0 | // strange selection that is not collapsed and yet does not contain any |
6975 | 0 | // editable content, and satisfies some of the above conditions that cause |
6976 | 0 | // tweaking. In this case we don't want to tweak the selection into a block |
6977 | 0 | // it was never in, etc. There are a variety of strategies one might use to |
6978 | 0 | // try to detect these cases, but I think the most straightforward is to see |
6979 | 0 | // if the adjusted locations "cross" the old values: i.e., new end before old |
6980 | 0 | // start, or new start after old end. If so then just leave things alone. |
6981 | 0 |
|
6982 | 0 | int16_t comp; |
6983 | 0 | comp = nsContentUtils::ComparePoints(startNode, startOffset, |
6984 | 0 | newEndNode, newEndOffset); |
6985 | 0 | if (comp == 1) { |
6986 | 0 | return NS_OK; // New end before old start. |
6987 | 0 | } |
6988 | 0 | comp = nsContentUtils::ComparePoints(newStartNode, newStartOffset, |
6989 | 0 | endNode, endOffset); |
6990 | 0 | if (comp == 1) { |
6991 | 0 | return NS_OK; // New start after old end. |
6992 | 0 | } |
6993 | 0 | |
6994 | 0 | // otherwise set selection to new values. |
6995 | 0 | // XXX Why don't we use SetBaseAndExtent()? |
6996 | 0 | DebugOnly<nsresult> rv = |
6997 | 0 | SelectionRef().Collapse(newStartNode, newStartOffset); |
6998 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
6999 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
7000 | 0 | } |
7001 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
7002 | 0 | "Failed to collapse selection"); |
7003 | 0 | rv = SelectionRef().Extend(newEndNode, newEndOffset); |
7004 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
7005 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
7006 | 0 | } |
7007 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
7008 | 0 | "Failed to extend selection"); |
7009 | 0 | return NS_OK; |
7010 | 0 | } |
7011 | | |
7012 | | EditorDOMPoint |
7013 | | HTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, |
7014 | | nsINode& aNode, |
7015 | | int32_t aOffset, |
7016 | | EditSubAction aEditSubAction) |
7017 | 0 | { |
7018 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7019 | 0 |
|
7020 | 0 | // we do one thing for text actions, something else entirely for other |
7021 | 0 | // actions |
7022 | 0 | if (aEditSubAction == EditSubAction::eInsertText || |
7023 | 0 | aEditSubAction == EditSubAction::eInsertTextComingFromIME || |
7024 | 0 | aEditSubAction == EditSubAction::eInsertParagraphSeparator || |
7025 | 0 | aEditSubAction == EditSubAction::eDeleteText) { |
7026 | 0 | bool isSpace, isNBSP; |
7027 | 0 | nsCOMPtr<nsIContent> content = |
7028 | 0 | aNode.IsContent() ? aNode.AsContent() : nullptr; |
7029 | 0 | nsCOMPtr<nsIContent> temp; |
7030 | 0 | int32_t newOffset = aOffset; |
7031 | 0 | // for text actions, we want to look backwards (or forwards, as |
7032 | 0 | // appropriate) for additional whitespace or nbsp's. We may have to act on |
7033 | 0 | // these later even though they are outside of the initial selection. Even |
7034 | 0 | // if they are in another node! |
7035 | 0 | while (content) { |
7036 | 0 | int32_t offset; |
7037 | 0 | if (aWhere == kStart) { |
7038 | 0 | HTMLEditorRef().IsPrevCharInNodeWhitespace(content, newOffset, |
7039 | 0 | &isSpace, &isNBSP, |
7040 | 0 | getter_AddRefs(temp), |
7041 | 0 | &offset); |
7042 | 0 | } else { |
7043 | 0 | HTMLEditorRef().IsNextCharInNodeWhitespace(content, newOffset, |
7044 | 0 | &isSpace, &isNBSP, |
7045 | 0 | getter_AddRefs(temp), |
7046 | 0 | &offset); |
7047 | 0 | } |
7048 | 0 | if (isSpace || isNBSP) { |
7049 | 0 | content = temp; |
7050 | 0 | newOffset = offset; |
7051 | 0 | } else { |
7052 | 0 | break; |
7053 | 0 | } |
7054 | 0 | } |
7055 | 0 |
|
7056 | 0 | return EditorDOMPoint(content, newOffset); |
7057 | 0 | } |
7058 | 0 |
|
7059 | 0 | EditorDOMPoint point(&aNode, aOffset); |
7060 | 0 |
|
7061 | 0 | // else not a text section. In this case we want to see if we should grab |
7062 | 0 | // any adjacent inline nodes and/or parents and other ancestors |
7063 | 0 | if (aWhere == kStart) { |
7064 | 0 | // some special casing for text nodes |
7065 | 0 | if (point.IsInTextNode()) { |
7066 | 0 | if (!point.GetContainer()->GetParentNode()) { |
7067 | 0 | // Okay, can't promote any further |
7068 | 0 | return point; |
7069 | 0 | } |
7070 | 0 | point.Set(point.GetContainer()); |
7071 | 0 | } |
7072 | 0 |
|
7073 | 0 | // look back through any further inline nodes that aren't across a <br> |
7074 | 0 | // from us, and that are enclosed in the same block. |
7075 | 0 | nsCOMPtr<nsINode> priorNode = |
7076 | 0 | HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point); |
7077 | 0 |
|
7078 | 0 | while (priorNode && priorNode->GetParentNode() && |
7079 | 0 | !HTMLEditorRef().IsVisibleBRElement(priorNode) && |
7080 | 0 | !IsBlockNode(*priorNode)) { |
7081 | 0 | point.Set(priorNode); |
7082 | 0 | priorNode = HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point); |
7083 | 0 | } |
7084 | 0 |
|
7085 | 0 | // finding the real start for this point. look up the tree for as long as |
7086 | 0 | // we are the first node in the container, and as long as we haven't hit |
7087 | 0 | // the body node. |
7088 | 0 | nsCOMPtr<nsIContent> nearNode = |
7089 | 0 | HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point); |
7090 | 0 | while (!nearNode && |
7091 | 0 | !point.IsContainerHTMLElement(nsGkAtoms::body) && |
7092 | 0 | point.GetContainer()->GetParentNode()) { |
7093 | 0 | // some cutoffs are here: we don't need to also include them in the |
7094 | 0 | // aWhere == kEnd case. as long as they are in one or the other it will |
7095 | 0 | // work. special case for outdent: don't keep looking up if we have |
7096 | 0 | // found a blockquote element to act on |
7097 | 0 | if (aEditSubAction == EditSubAction::eOutdent && |
7098 | 0 | point.IsContainerHTMLElement(nsGkAtoms::blockquote)) { |
7099 | 0 | break; |
7100 | 0 | } |
7101 | 0 | |
7102 | 0 | // Don't walk past the editable section. Note that we need to check |
7103 | 0 | // before walking up to a parent because we need to return the parent |
7104 | 0 | // object, so the parent itself might not be in the editable area, but |
7105 | 0 | // it's OK if we're not performing a block-level action. |
7106 | 0 | bool blockLevelAction = |
7107 | 0 | aEditSubAction == EditSubAction::eIndent || |
7108 | 0 | aEditSubAction == EditSubAction::eOutdent || |
7109 | 0 | aEditSubAction == EditSubAction::eSetOrClearAlignment || |
7110 | 0 | aEditSubAction == EditSubAction::eCreateOrRemoveBlock; |
7111 | 0 | if (!HTMLEditorRef().IsDescendantOfEditorRoot( |
7112 | 0 | point.GetContainer()->GetParentNode()) && |
7113 | 0 | (blockLevelAction || |
7114 | 0 | !HTMLEditorRef().IsDescendantOfEditorRoot(point.GetContainer()))) { |
7115 | 0 | break; |
7116 | 0 | } |
7117 | 0 | |
7118 | 0 | point.Set(point.GetContainer()); |
7119 | 0 | nearNode = HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point); |
7120 | 0 | } |
7121 | 0 | return point; |
7122 | 0 | } |
7123 | 0 |
|
7124 | 0 | // aWhere == kEnd |
7125 | 0 | // some special casing for text nodes |
7126 | 0 | if (point.IsInTextNode()) { |
7127 | 0 | if (!point.GetContainer()->GetParentNode()) { |
7128 | 0 | // Okay, can't promote any further |
7129 | 0 | return point; |
7130 | 0 | } |
7131 | 0 | // want to be after the text node |
7132 | 0 | point.Set(point.GetContainer()); |
7133 | 0 | DebugOnly<bool> advanced = point.AdvanceOffset(); |
7134 | 0 | NS_WARNING_ASSERTION(advanced, |
7135 | 0 | "Failed to advance offset to after the text node"); |
7136 | 0 | } |
7137 | 0 |
|
7138 | 0 | // look ahead through any further inline nodes that aren't across a <br> from |
7139 | 0 | // us, and that are enclosed in the same block. |
7140 | 0 | // XXX Currently, we stop block-extending when finding visible <br> element. |
7141 | 0 | // This might be different from "block-extend" of execCommand spec. |
7142 | 0 | // However, the spec is really unclear. |
7143 | 0 | // XXX Probably, scanning only editable nodes is wrong for |
7144 | 0 | // EditSubAction::eCreateOrRemoveBlock because it might be better to wrap |
7145 | 0 | // existing inline elements even if it's non-editable. For example, |
7146 | 0 | // following examples with insertParagraph causes different result: |
7147 | 0 | // * <div contenteditable>foo[]<b contenteditable="false">bar</b></div> |
7148 | 0 | // * <div contenteditable>foo[]<b>bar</b></div> |
7149 | 0 | // * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div> |
7150 | 0 | // Only in the first case, after the caret position isn't wrapped with |
7151 | 0 | // new <div> element. |
7152 | 0 | nsCOMPtr<nsIContent> nextNode = |
7153 | 0 | HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point); |
7154 | 0 |
|
7155 | 0 | while (nextNode && !IsBlockNode(*nextNode) && nextNode->GetParentNode()) { |
7156 | 0 | point.Set(nextNode); |
7157 | 0 | if (NS_WARN_IF(!point.AdvanceOffset())) { |
7158 | 0 | break; |
7159 | 0 | } |
7160 | 0 | if (HTMLEditorRef().IsVisibleBRElement(nextNode)) { |
7161 | 0 | break; |
7162 | 0 | } |
7163 | 0 | |
7164 | 0 | // Check for newlines in pre-formatted text nodes. |
7165 | 0 | if (EditorBase::IsPreformatted(nextNode) && |
7166 | 0 | EditorBase::IsTextNode(nextNode)) { |
7167 | 0 | nsAutoString tempString; |
7168 | 0 | nextNode->GetAsText()->GetData(tempString); |
7169 | 0 | int32_t newlinePos = tempString.FindChar(nsCRT::LF); |
7170 | 0 | if (newlinePos >= 0) { |
7171 | 0 | if (static_cast<uint32_t>(newlinePos) + 1 == tempString.Length()) { |
7172 | 0 | // No need for special processing if the newline is at the end. |
7173 | 0 | break; |
7174 | 0 | } |
7175 | 0 | return EditorDOMPoint(nextNode, newlinePos + 1); |
7176 | 0 | } |
7177 | 0 | } |
7178 | 0 | nextNode = HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point); |
7179 | 0 | } |
7180 | 0 |
|
7181 | 0 | // finding the real end for this point. look up the tree for as long as we |
7182 | 0 | // are the last node in the container, and as long as we haven't hit the body |
7183 | 0 | // node. |
7184 | 0 | nsCOMPtr<nsIContent> nearNode = |
7185 | 0 | HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point); |
7186 | 0 | while (!nearNode && |
7187 | 0 | !point.IsContainerHTMLElement(nsGkAtoms::body) && |
7188 | 0 | point.GetContainer()->GetParentNode()) { |
7189 | 0 | // Don't walk past the editable section. Note that we need to check before |
7190 | 0 | // walking up to a parent because we need to return the parent object, so |
7191 | 0 | // the parent itself might not be in the editable area, but it's OK. |
7192 | 0 | if (!HTMLEditorRef().IsDescendantOfEditorRoot(point.GetContainer()) && |
7193 | 0 | !HTMLEditorRef().IsDescendantOfEditorRoot( |
7194 | 0 | point.GetContainer()->GetParentNode())) { |
7195 | 0 | break; |
7196 | 0 | } |
7197 | 0 | |
7198 | 0 | point.Set(point.GetContainer()); |
7199 | 0 | if (NS_WARN_IF(!point.AdvanceOffset())) { |
7200 | 0 | break; |
7201 | 0 | } |
7202 | 0 | nearNode = HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point); |
7203 | 0 | } |
7204 | 0 | return point; |
7205 | 0 | } |
7206 | | |
7207 | | void |
7208 | | HTMLEditRules::GetPromotedRanges(nsTArray<RefPtr<nsRange>>& outArrayOfRanges, |
7209 | | EditSubAction aEditSubAction) |
7210 | 0 | { |
7211 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7212 | 0 |
|
7213 | 0 | uint32_t rangeCount = SelectionRef().RangeCount(); |
7214 | 0 | for (uint32_t i = 0; i < rangeCount; i++) { |
7215 | 0 | RefPtr<nsRange> selectionRange = SelectionRef().GetRangeAt(i); |
7216 | 0 | MOZ_ASSERT(selectionRange); |
7217 | 0 |
|
7218 | 0 | // Clone range so we don't muck with actual selection ranges |
7219 | 0 | RefPtr<nsRange> opRange = selectionRange->CloneRange(); |
7220 | 0 |
|
7221 | 0 | // Make a new adjusted range to represent the appropriate block content. |
7222 | 0 | // The basic idea is to push out the range endpoints to truly enclose the |
7223 | 0 | // blocks that we will affect. This call alters opRange. |
7224 | 0 | PromoteRange(*opRange, aEditSubAction); |
7225 | 0 |
|
7226 | 0 | // Stuff new opRange into array |
7227 | 0 | outArrayOfRanges.AppendElement(opRange); |
7228 | 0 | } |
7229 | 0 | } |
7230 | | |
7231 | | void |
7232 | | HTMLEditRules::PromoteRange(nsRange& aRange, |
7233 | | EditSubAction aEditSubAction) |
7234 | 0 | { |
7235 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7236 | 0 | MOZ_ASSERT(!aRange.IsInSelection()); |
7237 | 0 |
|
7238 | 0 | if (!aRange.IsPositioned()) { |
7239 | 0 | return; |
7240 | 0 | } |
7241 | 0 | |
7242 | 0 | nsCOMPtr<nsINode> startNode = aRange.GetStartContainer(); |
7243 | 0 | nsCOMPtr<nsINode> endNode = aRange.GetEndContainer(); |
7244 | 0 | int32_t startOffset = aRange.StartOffset(); |
7245 | 0 | int32_t endOffset = aRange.EndOffset(); |
7246 | 0 |
|
7247 | 0 | // MOOSE major hack: |
7248 | 0 | // GetPromotedPoint doesn't really do the right thing for collapsed ranges |
7249 | 0 | // inside block elements that contain nothing but a solo <br>. It's easier |
7250 | 0 | // to put a workaround here than to revamp GetPromotedPoint. :-( |
7251 | 0 | if (startNode == endNode && startOffset == endOffset) { |
7252 | 0 | RefPtr<Element> block = HTMLEditorRef().GetBlock(*startNode); |
7253 | 0 | if (block) { |
7254 | 0 | bool bIsEmptyNode = false; |
7255 | 0 | nsIContent* host = HTMLEditorRef().GetActiveEditingHost(); |
7256 | 0 | if (NS_WARN_IF(!host)) { |
7257 | 0 | return; |
7258 | 0 | } |
7259 | 0 | // Make sure we don't go higher than our root element in the content tree |
7260 | 0 | if (!nsContentUtils::ContentIsDescendantOf(host, block)) { |
7261 | 0 | HTMLEditorRef().IsEmptyNode(block, &bIsEmptyNode, true, false); |
7262 | 0 | } |
7263 | 0 | if (bIsEmptyNode) { |
7264 | 0 | startNode = block; |
7265 | 0 | endNode = block; |
7266 | 0 | startOffset = 0; |
7267 | 0 | endOffset = block->Length(); |
7268 | 0 | } |
7269 | 0 | } |
7270 | 0 | } |
7271 | 0 |
|
7272 | 0 | if (aEditSubAction == EditSubAction::eInsertText || |
7273 | 0 | aEditSubAction == EditSubAction::eInsertTextComingFromIME || |
7274 | 0 | aEditSubAction == EditSubAction::eInsertParagraphSeparator || |
7275 | 0 | aEditSubAction == EditSubAction::eDeleteText) { |
7276 | 0 | if (!startNode->IsContent() || |
7277 | 0 | !endNode->IsContent()) { |
7278 | 0 | // GetPromotedPoint cannot promote node when action type is text |
7279 | 0 | // operation and selected node isn't content node. |
7280 | 0 | return; |
7281 | 0 | } |
7282 | 0 | } |
7283 | 0 | |
7284 | 0 | // Make a new adjusted range to represent the appropriate block content. |
7285 | 0 | // This is tricky. The basic idea is to push out the range endpoints to |
7286 | 0 | // truly enclose the blocks that we will affect. |
7287 | 0 | |
7288 | 0 | // Make sure that the new range ends up to be in the editable section. |
7289 | 0 | // XXX Looks like that this check wastes the time. Perhaps, we should |
7290 | 0 | // implement a method which checks both two DOM points in the editor |
7291 | 0 | // root. |
7292 | 0 | EditorDOMPoint startPoint = |
7293 | 0 | GetPromotedPoint(kStart, *startNode, startOffset, aEditSubAction); |
7294 | 0 | if (!HTMLEditorRef().IsDescendantOfEditorRoot( |
7295 | 0 | EditorBase::GetNodeAtRangeOffsetPoint(startPoint))) { |
7296 | 0 | return; |
7297 | 0 | } |
7298 | 0 | EditorDOMPoint endPoint = |
7299 | 0 | GetPromotedPoint(kEnd, *endNode, endOffset, aEditSubAction); |
7300 | 0 | EditorRawDOMPoint lastRawPoint(endPoint); |
7301 | 0 | lastRawPoint.RewindOffset(); |
7302 | 0 | if (!HTMLEditorRef().IsDescendantOfEditorRoot( |
7303 | 0 | EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) { |
7304 | 0 | return; |
7305 | 0 | } |
7306 | 0 | |
7307 | 0 | DebugOnly<nsresult> rv = aRange.SetStartAndEnd(startPoint, endPoint); |
7308 | 0 | MOZ_ASSERT(NS_SUCCEEDED(rv)); |
7309 | 0 | } |
7310 | | |
7311 | | class UniqueFunctor final : public BoolDomIterFunctor |
7312 | | { |
7313 | | public: |
7314 | | explicit UniqueFunctor(nsTArray<OwningNonNull<nsINode>>& aArray) |
7315 | | : mArray(aArray) |
7316 | 0 | { |
7317 | 0 | } |
7318 | | |
7319 | | // Used to build list of all nodes iterator covers. |
7320 | | virtual bool operator()(nsINode* aNode) const override |
7321 | 0 | { |
7322 | 0 | return !mArray.Contains(aNode); |
7323 | 0 | } |
7324 | | |
7325 | | private: |
7326 | | nsTArray<OwningNonNull<nsINode>>& mArray; |
7327 | | }; |
7328 | | |
7329 | | nsresult |
7330 | | HTMLEditRules::GetNodesForOperation( |
7331 | | nsTArray<RefPtr<nsRange>>& aArrayOfRanges, |
7332 | | nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes, |
7333 | | EditSubAction aEditSubAction, |
7334 | | TouchContent aTouchContent) |
7335 | 0 | { |
7336 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7337 | 0 |
|
7338 | 0 | if (aTouchContent == TouchContent::yes) { |
7339 | 0 | // Split text nodes. This is necessary, since GetPromotedPoint() may return a |
7340 | 0 | // range ending in a text node in case where part of a pre-formatted |
7341 | 0 | // elements needs to be moved. |
7342 | 0 | for (RefPtr<nsRange>& range : aArrayOfRanges) { |
7343 | 0 | EditorDOMPoint atEnd(range->EndRef()); |
7344 | 0 | if (NS_WARN_IF(!atEnd.IsSet()) || !atEnd.IsInTextNode()) { |
7345 | 0 | continue; |
7346 | 0 | } |
7347 | 0 | |
7348 | 0 | if (!atEnd.IsStartOfContainer() && !atEnd.IsEndOfContainer()) { |
7349 | 0 | // Split the text node. |
7350 | 0 | ErrorResult error; |
7351 | 0 | nsCOMPtr<nsIContent> newLeftNode = |
7352 | 0 | HTMLEditorRef().SplitNodeWithTransaction(atEnd, error); |
7353 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
7354 | 0 | error.SuppressException(); |
7355 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
7356 | 0 | } |
7357 | 0 | if (NS_WARN_IF(error.Failed())) { |
7358 | 0 | return error.StealNSResult(); |
7359 | 0 | } |
7360 | 0 | |
7361 | 0 | // Correct the range. |
7362 | 0 | // The new end parent becomes the parent node of the text. |
7363 | 0 | EditorRawDOMPoint atContainerOfSplitNode(atEnd.GetContainer()); |
7364 | 0 | MOZ_ASSERT(!range->IsInSelection()); |
7365 | 0 | range->SetEnd(atContainerOfSplitNode, error); |
7366 | 0 | if (NS_WARN_IF(error.Failed())) { |
7367 | 0 | error.SuppressException(); |
7368 | 0 | } |
7369 | 0 | } |
7370 | 0 | } |
7371 | 0 | } |
7372 | 0 |
|
7373 | 0 | // Bust up any inlines that cross our range endpoints, but only if we are |
7374 | 0 | // allowed to touch content. |
7375 | 0 | // XXX Why don't we merge this block with the previous block? |
7376 | 0 | if (aTouchContent == TouchContent::yes) { |
7377 | 0 | nsTArray<OwningNonNull<RangeItem>> rangeItemArray; |
7378 | 0 | rangeItemArray.AppendElements(aArrayOfRanges.Length()); |
7379 | 0 |
|
7380 | 0 | // First register ranges for special editor gravity |
7381 | 0 | for (auto& rangeItem : rangeItemArray) { |
7382 | 0 | rangeItem = new RangeItem(); |
7383 | 0 | rangeItem->StoreRange(aArrayOfRanges[0]); |
7384 | 0 | HTMLEditorRef().mRangeUpdater.RegisterRangeItem(rangeItem); |
7385 | 0 | aArrayOfRanges.RemoveElementAt(0); |
7386 | 0 | } |
7387 | 0 | // Now bust up inlines. |
7388 | 0 | for (auto& item : Reversed(rangeItemArray)) { |
7389 | 0 | nsresult rv = BustUpInlinesAtRangeEndpoints(*item); |
7390 | 0 | if (NS_FAILED(rv)) { |
7391 | 0 | break; |
7392 | 0 | } |
7393 | 0 | } |
7394 | 0 | // Then unregister the ranges |
7395 | 0 | for (auto& item : rangeItemArray) { |
7396 | 0 | HTMLEditorRef().mRangeUpdater.DropRangeItem(item); |
7397 | 0 | RefPtr<nsRange> range = item->GetRange(); |
7398 | 0 | if (range) { |
7399 | 0 | aArrayOfRanges.AppendElement(range); |
7400 | 0 | } |
7401 | 0 | } |
7402 | 0 | } |
7403 | 0 | // Gather up a list of all the nodes |
7404 | 0 | for (auto& range : aArrayOfRanges) { |
7405 | 0 | DOMSubtreeIterator iter; |
7406 | 0 | nsresult rv = iter.Init(*range); |
7407 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
7408 | 0 | return rv; |
7409 | 0 | } |
7410 | 0 | if (aOutArrayOfNodes.IsEmpty()) { |
7411 | 0 | iter.AppendList(TrivialFunctor(), aOutArrayOfNodes); |
7412 | 0 | } else { |
7413 | 0 | // We don't want duplicates in aOutArrayOfNodes, so we use an |
7414 | 0 | // iterator/functor that only return nodes that are not already in |
7415 | 0 | // aOutArrayOfNodes. |
7416 | 0 | nsTArray<OwningNonNull<nsINode>> nodes; |
7417 | 0 | iter.AppendList(UniqueFunctor(aOutArrayOfNodes), nodes); |
7418 | 0 | aOutArrayOfNodes.AppendElements(nodes); |
7419 | 0 | } |
7420 | 0 | } |
7421 | 0 |
|
7422 | 0 | // Certain operations should not act on li's and td's, but rather inside |
7423 | 0 | // them. Alter the list as needed. |
7424 | 0 | if (aEditSubAction == EditSubAction::eCreateOrRemoveBlock) { |
7425 | 0 | for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) { |
7426 | 0 | OwningNonNull<nsINode> node = aOutArrayOfNodes[i]; |
7427 | 0 | if (HTMLEditUtils::IsListItem(node)) { |
7428 | 0 | int32_t j = i; |
7429 | 0 | aOutArrayOfNodes.RemoveElementAt(i); |
7430 | 0 | GetInnerContent(*node, aOutArrayOfNodes, &j); |
7431 | 0 | } |
7432 | 0 | } |
7433 | 0 | // Empty text node shouldn't be selected if unnecessary |
7434 | 0 | for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) { |
7435 | 0 | if (Text* text = aOutArrayOfNodes[i]->GetAsText()) { |
7436 | 0 | // Don't select empty text except to empty block |
7437 | 0 | if (!HTMLEditorRef().IsVisibleTextNode(*text)) { |
7438 | 0 | aOutArrayOfNodes.RemoveElementAt(i); |
7439 | 0 | } |
7440 | 0 | } |
7441 | 0 | } |
7442 | 0 | } |
7443 | 0 | // Indent/outdent already do something special for list items, but we still |
7444 | 0 | // need to make sure we don't act on table elements |
7445 | 0 | else if (aEditSubAction == EditSubAction::eOutdent || |
7446 | 0 | aEditSubAction == EditSubAction::eIndent || |
7447 | 0 | aEditSubAction == EditSubAction::eSetPositionToAbsolute) { |
7448 | 0 | for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) { |
7449 | 0 | OwningNonNull<nsINode> node = aOutArrayOfNodes[i]; |
7450 | 0 | if (HTMLEditUtils::IsTableElementButNotTable(node)) { |
7451 | 0 | int32_t j = i; |
7452 | 0 | aOutArrayOfNodes.RemoveElementAt(i); |
7453 | 0 | GetInnerContent(*node, aOutArrayOfNodes, &j); |
7454 | 0 | } |
7455 | 0 | } |
7456 | 0 | } |
7457 | 0 | // Outdent should look inside of divs. |
7458 | 0 | if (aEditSubAction == EditSubAction::eOutdent && |
7459 | 0 | !HTMLEditorRef().IsCSSEnabled()) { |
7460 | 0 | for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) { |
7461 | 0 | OwningNonNull<nsINode> node = aOutArrayOfNodes[i]; |
7462 | 0 | if (node->IsHTMLElement(nsGkAtoms::div)) { |
7463 | 0 | int32_t j = i; |
7464 | 0 | aOutArrayOfNodes.RemoveElementAt(i); |
7465 | 0 | GetInnerContent(*node, aOutArrayOfNodes, &j, Lists::no, Tables::no); |
7466 | 0 | } |
7467 | 0 | } |
7468 | 0 | } |
7469 | 0 |
|
7470 | 0 |
|
7471 | 0 | // Post-process the list to break up inline containers that contain br's, but |
7472 | 0 | // only for operations that might care, like making lists or paragraphs |
7473 | 0 | if (aEditSubAction == EditSubAction::eCreateOrRemoveBlock || |
7474 | 0 | aEditSubAction == EditSubAction::eCreateOrChangeList || |
7475 | 0 | aEditSubAction == EditSubAction::eSetOrClearAlignment || |
7476 | 0 | aEditSubAction == EditSubAction::eSetPositionToAbsolute || |
7477 | 0 | aEditSubAction == EditSubAction::eIndent || |
7478 | 0 | aEditSubAction == EditSubAction::eOutdent) { |
7479 | 0 | for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) { |
7480 | 0 | OwningNonNull<nsINode> node = aOutArrayOfNodes[i]; |
7481 | 0 | // XXX Why do we run this loop even when aTouchContent is "no"? |
7482 | 0 | if (aTouchContent == TouchContent::yes && IsInlineNode(node) && |
7483 | 0 | HTMLEditorRef().IsContainer(node) && !EditorBase::IsTextNode(node)) { |
7484 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfInlines; |
7485 | 0 | nsresult rv = BustUpInlinesAtBRs(*node->AsContent(), arrayOfInlines); |
7486 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
7487 | 0 | return rv; |
7488 | 0 | } |
7489 | 0 | |
7490 | 0 | // Put these nodes in aOutArrayOfNodes, replacing the current node |
7491 | 0 | aOutArrayOfNodes.RemoveElementAt(i); |
7492 | 0 | aOutArrayOfNodes.InsertElementsAt(i, arrayOfInlines); |
7493 | 0 | } |
7494 | 0 | } |
7495 | 0 | } |
7496 | 0 | return NS_OK; |
7497 | 0 | } |
7498 | | |
7499 | | void |
7500 | | HTMLEditRules::GetChildNodesForOperation( |
7501 | | nsINode& aNode, |
7502 | | nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes) |
7503 | 0 | { |
7504 | 0 | for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild(); |
7505 | 0 | child; child = child->GetNextSibling()) { |
7506 | 0 | outArrayOfNodes.AppendElement(*child); |
7507 | 0 | } |
7508 | 0 | } |
7509 | | |
7510 | | nsresult |
7511 | | HTMLEditRules::GetListActionNodes( |
7512 | | nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes, |
7513 | | EntireList aEntireList, |
7514 | | TouchContent aTouchContent) |
7515 | 0 | { |
7516 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7517 | 0 |
|
7518 | 0 | // Added this in so that ui code can ask to change an entire list, even if |
7519 | 0 | // selection is only in part of it. used by list item dialog. |
7520 | 0 | if (aEntireList == EntireList::yes) { |
7521 | 0 | uint32_t rangeCount = SelectionRef().RangeCount(); |
7522 | 0 | for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { |
7523 | 0 | RefPtr<nsRange> range = SelectionRef().GetRangeAt(rangeIdx); |
7524 | 0 | for (nsCOMPtr<nsINode> parent = range->GetCommonAncestor(); |
7525 | 0 | parent; parent = parent->GetParentNode()) { |
7526 | 0 | if (HTMLEditUtils::IsList(parent)) { |
7527 | 0 | aOutArrayOfNodes.AppendElement(*parent); |
7528 | 0 | break; |
7529 | 0 | } |
7530 | 0 | } |
7531 | 0 | } |
7532 | 0 | // If we didn't find any nodes this way, then try the normal way. Perhaps |
7533 | 0 | // the selection spans multiple lists but with no common list parent. |
7534 | 0 | if (!aOutArrayOfNodes.IsEmpty()) { |
7535 | 0 | return NS_OK; |
7536 | 0 | } |
7537 | 0 | } |
7538 | 0 | |
7539 | 0 | { |
7540 | 0 | // We don't like other people messing with our selection! |
7541 | 0 | AutoTransactionsConserveSelection dontChangeMySelection(HTMLEditorRef()); |
7542 | 0 |
|
7543 | 0 | // contruct a list of nodes to act on. |
7544 | 0 | nsresult rv = GetNodesFromSelection(EditSubAction::eCreateOrChangeList, |
7545 | 0 | aOutArrayOfNodes, aTouchContent); |
7546 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
7547 | 0 | return rv; |
7548 | 0 | } |
7549 | 0 | } |
7550 | 0 | |
7551 | 0 | // Pre-process our list of nodes |
7552 | 0 | for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) { |
7553 | 0 | OwningNonNull<nsINode> testNode = aOutArrayOfNodes[i]; |
7554 | 0 |
|
7555 | 0 | // Remove all non-editable nodes. Leave them be. |
7556 | 0 | if (!HTMLEditorRef().IsEditable(testNode)) { |
7557 | 0 | aOutArrayOfNodes.RemoveElementAt(i); |
7558 | 0 | continue; |
7559 | 0 | } |
7560 | 0 | |
7561 | 0 | // Scan for table elements and divs. If we find table elements other than |
7562 | 0 | // table, replace it with a list of any editable non-table content. |
7563 | 0 | if (HTMLEditUtils::IsTableElementButNotTable(testNode)) { |
7564 | 0 | int32_t j = i; |
7565 | 0 | aOutArrayOfNodes.RemoveElementAt(i); |
7566 | 0 | GetInnerContent(*testNode, aOutArrayOfNodes, &j, Lists::no); |
7567 | 0 | } |
7568 | 0 | } |
7569 | 0 |
|
7570 | 0 | // If there is only one node in the array, and it is a list, div, or |
7571 | 0 | // blockquote, then look inside of it until we find inner list or content. |
7572 | 0 | LookInsideDivBQandList(aOutArrayOfNodes); |
7573 | 0 |
|
7574 | 0 | return NS_OK; |
7575 | 0 | } |
7576 | | |
7577 | | void |
7578 | | HTMLEditRules::LookInsideDivBQandList( |
7579 | | nsTArray<OwningNonNull<nsINode>>& aNodeArray) |
7580 | 0 | { |
7581 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7582 | 0 |
|
7583 | 0 | // If there is only one node in the array, and it is a list, div, or |
7584 | 0 | // blockquote, then look inside of it until we find inner list or content. |
7585 | 0 | if (aNodeArray.Length() != 1) { |
7586 | 0 | return; |
7587 | 0 | } |
7588 | 0 | |
7589 | 0 | OwningNonNull<nsINode> curNode = aNodeArray[0]; |
7590 | 0 |
|
7591 | 0 | while (curNode->IsHTMLElement(nsGkAtoms::div) || |
7592 | 0 | HTMLEditUtils::IsList(curNode) || |
7593 | 0 | curNode->IsHTMLElement(nsGkAtoms::blockquote)) { |
7594 | 0 | // Dive as long as there's only one child, and it's a list, div, blockquote |
7595 | 0 | uint32_t numChildren = HTMLEditorRef().CountEditableChildren(curNode); |
7596 | 0 | if (numChildren != 1) { |
7597 | 0 | break; |
7598 | 0 | } |
7599 | 0 | |
7600 | 0 | // Keep diving! XXX One would expect to dive into the one editable node. |
7601 | 0 | nsCOMPtr<nsIContent> child = curNode->GetFirstChild(); |
7602 | 0 | if (!child->IsHTMLElement(nsGkAtoms::div) && |
7603 | 0 | !HTMLEditUtils::IsList(child) && |
7604 | 0 | !child->IsHTMLElement(nsGkAtoms::blockquote)) { |
7605 | 0 | break; |
7606 | 0 | } |
7607 | 0 | |
7608 | 0 | // check editability XXX floppy moose |
7609 | 0 | curNode = child; |
7610 | 0 | } |
7611 | 0 |
|
7612 | 0 | // We've found innermost list/blockquote/div: replace the one node in the |
7613 | 0 | // array with these nodes |
7614 | 0 | aNodeArray.RemoveElementAt(0); |
7615 | 0 | if (curNode->IsAnyOfHTMLElements(nsGkAtoms::div, |
7616 | 0 | nsGkAtoms::blockquote)) { |
7617 | 0 | int32_t j = 0; |
7618 | 0 | GetInnerContent(*curNode, aNodeArray, &j, Lists::no, Tables::no); |
7619 | 0 | return; |
7620 | 0 | } |
7621 | 0 | |
7622 | 0 | aNodeArray.AppendElement(*curNode); |
7623 | 0 | } |
7624 | | |
7625 | | void |
7626 | | HTMLEditRules::GetDefinitionListItemTypes(dom::Element* aElement, |
7627 | | bool* aDT, |
7628 | | bool* aDD) |
7629 | 0 | { |
7630 | 0 | MOZ_ASSERT(aElement); |
7631 | 0 | MOZ_ASSERT(aElement->IsHTMLElement(nsGkAtoms::dl)); |
7632 | 0 | MOZ_ASSERT(aDT); |
7633 | 0 | MOZ_ASSERT(aDD); |
7634 | 0 |
|
7635 | 0 | *aDT = *aDD = false; |
7636 | 0 | for (nsIContent* child = aElement->GetFirstChild(); |
7637 | 0 | child; |
7638 | 0 | child = child->GetNextSibling()) { |
7639 | 0 | if (child->IsHTMLElement(nsGkAtoms::dt)) { |
7640 | 0 | *aDT = true; |
7641 | 0 | } else if (child->IsHTMLElement(nsGkAtoms::dd)) { |
7642 | 0 | *aDD = true; |
7643 | 0 | } |
7644 | 0 | } |
7645 | 0 | } |
7646 | | |
7647 | | nsresult |
7648 | | HTMLEditRules::GetParagraphFormatNodes( |
7649 | | nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes) |
7650 | 0 | { |
7651 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7652 | 0 |
|
7653 | 0 | // Contruct a list of nodes to act on. |
7654 | 0 | nsresult rv = |
7655 | 0 | GetNodesFromSelection(EditSubAction::eCreateOrRemoveBlock, |
7656 | 0 | outArrayOfNodes, TouchContent::no); |
7657 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
7658 | 0 | return rv; |
7659 | 0 | } |
7660 | 0 | |
7661 | 0 | // Pre-process our list of nodes |
7662 | 0 | for (int32_t i = outArrayOfNodes.Length() - 1; i >= 0; i--) { |
7663 | 0 | OwningNonNull<nsINode> testNode = outArrayOfNodes[i]; |
7664 | 0 |
|
7665 | 0 | // Remove all non-editable nodes. Leave them be. |
7666 | 0 | if (!HTMLEditorRef().IsEditable(testNode)) { |
7667 | 0 | outArrayOfNodes.RemoveElementAt(i); |
7668 | 0 | continue; |
7669 | 0 | } |
7670 | 0 | |
7671 | 0 | // Scan for table elements. If we find table elements other than table, |
7672 | 0 | // replace it with a list of any editable non-table content. Ditto for |
7673 | 0 | // list elements. |
7674 | 0 | if (HTMLEditUtils::IsTableElement(testNode) || |
7675 | 0 | HTMLEditUtils::IsList(testNode) || |
7676 | 0 | HTMLEditUtils::IsListItem(testNode)) { |
7677 | 0 | int32_t j = i; |
7678 | 0 | outArrayOfNodes.RemoveElementAt(i); |
7679 | 0 | GetInnerContent(testNode, outArrayOfNodes, &j); |
7680 | 0 | } |
7681 | 0 | } |
7682 | 0 | return NS_OK; |
7683 | 0 | } |
7684 | | |
7685 | | nsresult |
7686 | | HTMLEditRules::BustUpInlinesAtRangeEndpoints(RangeItem& aRangeItem) |
7687 | 0 | { |
7688 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7689 | 0 |
|
7690 | 0 | bool isCollapsed = aRangeItem.mStartContainer == aRangeItem.mEndContainer && |
7691 | 0 | aRangeItem.mStartOffset == aRangeItem.mEndOffset; |
7692 | 0 |
|
7693 | 0 | nsCOMPtr<nsIContent> endInline = |
7694 | 0 | GetHighestInlineParent(*aRangeItem.mEndContainer); |
7695 | 0 |
|
7696 | 0 | // XXX Oh, then, if the range is collapsed, we don't need to call |
7697 | 0 | // GetHighestInlineParent(), isn't it? |
7698 | 0 | if (endInline && !isCollapsed) { |
7699 | 0 | SplitNodeResult splitEndInlineResult = |
7700 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
7701 | 0 | *endInline, |
7702 | 0 | EditorRawDOMPoint(aRangeItem.mEndContainer, |
7703 | 0 | aRangeItem.mEndOffset), |
7704 | 0 | SplitAtEdges::eDoNotCreateEmptyContainer); |
7705 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
7706 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
7707 | 0 | } |
7708 | 0 | if (NS_WARN_IF(splitEndInlineResult.Failed())) { |
7709 | 0 | return splitEndInlineResult.Rv(); |
7710 | 0 | } |
7711 | 0 | EditorRawDOMPoint splitPointAtEnd(splitEndInlineResult.SplitPoint()); |
7712 | 0 | if (NS_WARN_IF(!splitPointAtEnd.IsSet())) { |
7713 | 0 | return NS_ERROR_FAILURE; |
7714 | 0 | } |
7715 | 0 | aRangeItem.mEndContainer = splitPointAtEnd.GetContainer(); |
7716 | 0 | aRangeItem.mEndOffset = splitPointAtEnd.Offset(); |
7717 | 0 | } |
7718 | 0 |
|
7719 | 0 | nsCOMPtr<nsIContent> startInline = |
7720 | 0 | GetHighestInlineParent(*aRangeItem.mStartContainer); |
7721 | 0 |
|
7722 | 0 | if (startInline) { |
7723 | 0 | SplitNodeResult splitStartInlineResult = |
7724 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
7725 | 0 | *startInline, |
7726 | 0 | EditorRawDOMPoint(aRangeItem.mStartContainer, |
7727 | 0 | aRangeItem.mStartOffset), |
7728 | 0 | SplitAtEdges::eDoNotCreateEmptyContainer); |
7729 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
7730 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
7731 | 0 | } |
7732 | 0 | if (NS_WARN_IF(splitStartInlineResult.Failed())) { |
7733 | 0 | return splitStartInlineResult.Rv(); |
7734 | 0 | } |
7735 | 0 | // XXX If we split only here because of collapsed range, we're modifying |
7736 | 0 | // only start point of aRangeItem. Shouldn't we modify end point here |
7737 | 0 | // if it's collapsed? |
7738 | 0 | EditorRawDOMPoint splitPointAtStart(splitStartInlineResult.SplitPoint()); |
7739 | 0 | if (NS_WARN_IF(!splitPointAtStart.IsSet())) { |
7740 | 0 | return NS_ERROR_FAILURE; |
7741 | 0 | } |
7742 | 0 | aRangeItem.mStartContainer = splitPointAtStart.GetContainer(); |
7743 | 0 | aRangeItem.mStartOffset = splitPointAtStart.Offset(); |
7744 | 0 | } |
7745 | 0 |
|
7746 | 0 | return NS_OK; |
7747 | 0 | } |
7748 | | |
7749 | | nsresult |
7750 | | HTMLEditRules::BustUpInlinesAtBRs( |
7751 | | nsIContent& aNode, |
7752 | | nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes) |
7753 | 0 | { |
7754 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7755 | 0 |
|
7756 | 0 | // First build up a list of all the break nodes inside the inline container. |
7757 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfBreaks; |
7758 | 0 | BRNodeFunctor functor; |
7759 | 0 | DOMIterator iter(aNode); |
7760 | 0 | iter.AppendList(functor, arrayOfBreaks); |
7761 | 0 |
|
7762 | 0 | // If there aren't any breaks, just put inNode itself in the array |
7763 | 0 | if (arrayOfBreaks.IsEmpty()) { |
7764 | 0 | aOutArrayOfNodes.AppendElement(aNode); |
7765 | 0 | return NS_OK; |
7766 | 0 | } |
7767 | 0 | |
7768 | 0 | // Else we need to bust up aNode along all the breaks |
7769 | 0 | nsCOMPtr<nsIContent> nextNode = &aNode; |
7770 | 0 | for (OwningNonNull<nsINode>& brNode : arrayOfBreaks) { |
7771 | 0 | EditorRawDOMPoint atBrNode(brNode); |
7772 | 0 | if (NS_WARN_IF(!atBrNode.IsSet())) { |
7773 | 0 | return NS_ERROR_FAILURE; |
7774 | 0 | } |
7775 | 0 | SplitNodeResult splitNodeResult = |
7776 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
7777 | 0 | *nextNode, atBrNode, |
7778 | 0 | SplitAtEdges::eAllowToCreateEmptyContainer); |
7779 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
7780 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
7781 | 0 | } |
7782 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
7783 | 0 | return splitNodeResult.Rv(); |
7784 | 0 | } |
7785 | 0 | |
7786 | 0 | // Put previous node at the split point. |
7787 | 0 | if (splitNodeResult.GetPreviousNode()) { |
7788 | 0 | // Might not be a left node. A break might have been at the very |
7789 | 0 | // beginning of inline container, in which case |
7790 | 0 | // SplitNodeDeepWithTransaction() would not actually split anything. |
7791 | 0 | aOutArrayOfNodes.AppendElement(*splitNodeResult.GetPreviousNode()); |
7792 | 0 | } |
7793 | 0 |
|
7794 | 0 | // Move break outside of container and also put in node list |
7795 | 0 | EditorRawDOMPoint atNextNode(splitNodeResult.GetNextNode()); |
7796 | 0 | nsresult rv = |
7797 | 0 | HTMLEditorRef().MoveNodeWithTransaction(*brNode->AsContent(), atNextNode); |
7798 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
7799 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
7800 | 0 | } |
7801 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
7802 | 0 | return rv; |
7803 | 0 | } |
7804 | 0 | aOutArrayOfNodes.AppendElement(*brNode); |
7805 | 0 |
|
7806 | 0 | nextNode = splitNodeResult.GetNextNode(); |
7807 | 0 | } |
7808 | 0 |
|
7809 | 0 | // Now tack on remaining next node. |
7810 | 0 | aOutArrayOfNodes.AppendElement(*nextNode); |
7811 | 0 |
|
7812 | 0 | return NS_OK; |
7813 | 0 | } |
7814 | | |
7815 | | nsIContent* |
7816 | | HTMLEditRules::GetHighestInlineParent(nsINode& aNode) |
7817 | 0 | { |
7818 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7819 | 0 |
|
7820 | 0 | if (!aNode.IsContent() || IsBlockNode(aNode)) { |
7821 | 0 | return nullptr; |
7822 | 0 | } |
7823 | 0 | |
7824 | 0 | Element* host = HTMLEditorRef().GetActiveEditingHost(); |
7825 | 0 | if (NS_WARN_IF(!host)) { |
7826 | 0 | return nullptr; |
7827 | 0 | } |
7828 | 0 | |
7829 | 0 | // If aNode is the editing host itself, there is no modifiable inline parent. |
7830 | 0 | if (&aNode == host) { |
7831 | 0 | return nullptr; |
7832 | 0 | } |
7833 | 0 | |
7834 | 0 | // If aNode is outside of the <body> element, we don't support to edit |
7835 | 0 | // such elements for now. |
7836 | 0 | // XXX This should be MOZ_ASSERT after fixing bug 1413131 for avoiding |
7837 | 0 | // calling this expensive method. |
7838 | 0 | if (NS_WARN_IF(!EditorUtils::IsDescendantOf(aNode, *host))) { |
7839 | 0 | return nullptr; |
7840 | 0 | } |
7841 | 0 | |
7842 | 0 | // Looks for the highest inline parent in the editing host. |
7843 | 0 | nsIContent* content = aNode.AsContent(); |
7844 | 0 | for (nsIContent* parent = content->GetParent(); |
7845 | 0 | parent && parent != host && IsInlineNode(*parent); |
7846 | 0 | parent = parent->GetParent()) { |
7847 | 0 | content = parent; |
7848 | 0 | } |
7849 | 0 | return content; |
7850 | 0 | } |
7851 | | |
7852 | | nsresult |
7853 | | HTMLEditRules::GetNodesFromPoint( |
7854 | | const EditorDOMPoint& aPoint, |
7855 | | EditSubAction aEditSubAction, |
7856 | | nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes, |
7857 | | TouchContent aTouchContent) |
7858 | 0 | { |
7859 | 0 | if (NS_WARN_IF(!aPoint.IsSet())) { |
7860 | 0 | return NS_ERROR_INVALID_ARG; |
7861 | 0 | } |
7862 | 0 | RefPtr<nsRange> range = new nsRange(aPoint.GetContainer()); |
7863 | 0 | IgnoredErrorResult ignoredError; |
7864 | 0 | range->SetStart(aPoint, ignoredError); |
7865 | 0 | // error will assert on failure, because we are not cleaning it up, |
7866 | 0 | // but we're asserting in that case anyway. |
7867 | 0 | MOZ_ASSERT(!ignoredError.Failed()); |
7868 | 0 |
|
7869 | 0 | // Expand the range to include adjacent inlines |
7870 | 0 | PromoteRange(*range, aEditSubAction); |
7871 | 0 |
|
7872 | 0 | // Make array of ranges |
7873 | 0 | nsTArray<RefPtr<nsRange>> arrayOfRanges; |
7874 | 0 |
|
7875 | 0 | // Stuff new opRange into array |
7876 | 0 | arrayOfRanges.AppendElement(range); |
7877 | 0 |
|
7878 | 0 | // Use these ranges to contruct a list of nodes to act on |
7879 | 0 | nsresult rv = |
7880 | 0 | GetNodesForOperation(arrayOfRanges, outArrayOfNodes, aEditSubAction, |
7881 | 0 | aTouchContent); |
7882 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
7883 | 0 | return rv; |
7884 | 0 | } |
7885 | 0 | |
7886 | 0 | return NS_OK; |
7887 | 0 | } |
7888 | | |
7889 | | nsresult |
7890 | | HTMLEditRules::GetNodesFromSelection( |
7891 | | EditSubAction aEditSubAction, |
7892 | | nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes, |
7893 | | TouchContent aTouchContent) |
7894 | 0 | { |
7895 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7896 | 0 |
|
7897 | 0 | // Promote selection ranges |
7898 | 0 | nsTArray<RefPtr<nsRange>> arrayOfRanges; |
7899 | 0 | GetPromotedRanges(arrayOfRanges, aEditSubAction); |
7900 | 0 |
|
7901 | 0 | // Use these ranges to contruct a list of nodes to act on. |
7902 | 0 | nsresult rv = GetNodesForOperation(arrayOfRanges, outArrayOfNodes, |
7903 | 0 | aEditSubAction, aTouchContent); |
7904 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
7905 | 0 | return rv; |
7906 | 0 | } |
7907 | 0 | |
7908 | 0 | return NS_OK; |
7909 | 0 | } |
7910 | | |
7911 | | void |
7912 | | HTMLEditRules::MakeTransitionList(nsTArray<OwningNonNull<nsINode>>& aNodeArray, |
7913 | | nsTArray<bool>& aTransitionArray) |
7914 | 0 | { |
7915 | 0 | nsCOMPtr<nsINode> prevParent; |
7916 | 0 |
|
7917 | 0 | aTransitionArray.EnsureLengthAtLeast(aNodeArray.Length()); |
7918 | 0 | for (uint32_t i = 0; i < aNodeArray.Length(); i++) { |
7919 | 0 | if (aNodeArray[i]->GetParentNode() != prevParent) { |
7920 | 0 | // Different parents: transition point |
7921 | 0 | aTransitionArray[i] = true; |
7922 | 0 | } else { |
7923 | 0 | // Same parents: these nodes grew up together |
7924 | 0 | aTransitionArray[i] = false; |
7925 | 0 | } |
7926 | 0 | prevParent = aNodeArray[i]->GetParentNode(); |
7927 | 0 | } |
7928 | 0 | } |
7929 | | |
7930 | | Element* |
7931 | | HTMLEditRules::IsInListItem(nsINode* aNode) |
7932 | 0 | { |
7933 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7934 | 0 |
|
7935 | 0 | NS_ENSURE_TRUE(aNode, nullptr); |
7936 | 0 | if (HTMLEditUtils::IsListItem(aNode)) { |
7937 | 0 | return aNode->AsElement(); |
7938 | 0 | } |
7939 | 0 | |
7940 | 0 | Element* parent = aNode->GetParentElement(); |
7941 | 0 | while (parent && |
7942 | 0 | HTMLEditorRef().IsDescendantOfEditorRoot(parent) && |
7943 | 0 | !HTMLEditUtils::IsTableElement(parent)) { |
7944 | 0 | if (HTMLEditUtils::IsListItem(parent)) { |
7945 | 0 | return parent; |
7946 | 0 | } |
7947 | 0 | parent = parent->GetParentElement(); |
7948 | 0 | } |
7949 | 0 | return nullptr; |
7950 | 0 | } |
7951 | | |
7952 | | nsAtom& |
7953 | | HTMLEditRules::DefaultParagraphSeparator() |
7954 | 0 | { |
7955 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7956 | 0 | return ParagraphSeparatorElement( |
7957 | 0 | HTMLEditorRef().GetDefaultParagraphSeparator()); |
7958 | 0 | } |
7959 | | |
7960 | | nsresult |
7961 | | HTMLEditRules::ReturnInHeader(Element& aHeader, |
7962 | | nsINode& aNode, |
7963 | | int32_t aOffset) |
7964 | 0 | { |
7965 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
7966 | 0 |
|
7967 | 0 | // Remember where the header is |
7968 | 0 | nsCOMPtr<nsINode> headerParent = aHeader.GetParentNode(); |
7969 | 0 | int32_t offset = headerParent ? headerParent->ComputeIndexOf(&aHeader) : -1; |
7970 | 0 |
|
7971 | 0 | // Get ws code to adjust any ws |
7972 | 0 | nsCOMPtr<nsINode> node = &aNode; |
7973 | 0 | nsresult rv = WSRunObject::PrepareToSplitAcrossBlocks(&HTMLEditorRef(), |
7974 | 0 | address_of(node), |
7975 | 0 | &aOffset); |
7976 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
7977 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
7978 | 0 | } |
7979 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
7980 | 0 | return rv; |
7981 | 0 | } |
7982 | 0 | if (NS_WARN_IF(!node->IsContent())) { |
7983 | 0 | return NS_ERROR_FAILURE; |
7984 | 0 | } |
7985 | 0 | |
7986 | 0 | // Split the header |
7987 | 0 | SplitNodeResult splitHeaderResult = |
7988 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
7989 | 0 | aHeader, EditorRawDOMPoint(node, aOffset), |
7990 | 0 | SplitAtEdges::eAllowToCreateEmptyContainer); |
7991 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
7992 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
7993 | 0 | } |
7994 | 0 | NS_WARNING_ASSERTION(splitHeaderResult.Succeeded(), |
7995 | 0 | "Failed to split aHeader"); |
7996 | 0 |
|
7997 | 0 | // If the previous heading of split point is empty, put a mozbr into it. |
7998 | 0 | nsCOMPtr<nsIContent> prevItem = HTMLEditorRef().GetPriorHTMLSibling(&aHeader); |
7999 | 0 | if (prevItem) { |
8000 | 0 | MOZ_DIAGNOSTIC_ASSERT( |
8001 | 0 | HTMLEditUtils::IsHeader(*prevItem)); |
8002 | 0 | bool isEmptyNode; |
8003 | 0 | rv = HTMLEditorRef().IsEmptyNode(prevItem, &isEmptyNode); |
8004 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8005 | 0 | return rv; |
8006 | 0 | } |
8007 | 0 | if (isEmptyNode) { |
8008 | 0 | CreateElementResult createMozBrResult = |
8009 | 0 | CreateMozBR(EditorRawDOMPoint(prevItem, 0)); |
8010 | 0 | if (NS_WARN_IF(createMozBrResult.Failed())) { |
8011 | 0 | return createMozBrResult.Rv(); |
8012 | 0 | } |
8013 | 0 | } |
8014 | 0 | } |
8015 | 0 | |
8016 | 0 | // If the new (righthand) header node is empty, delete it |
8017 | 0 | if (IsEmptyBlockElement(aHeader, IgnoreSingleBR::eYes)) { |
8018 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(aHeader); |
8019 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8020 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8021 | 0 | } |
8022 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8023 | 0 | return rv; |
8024 | 0 | } |
8025 | 0 | // Layout tells the caret to blink in a weird place if we don't place a |
8026 | 0 | // break after the header. |
8027 | 0 | nsCOMPtr<nsIContent> sibling; |
8028 | 0 | if (aHeader.GetNextSibling()) { |
8029 | 0 | sibling = HTMLEditorRef().GetNextHTMLSibling(aHeader.GetNextSibling()); |
8030 | 0 | } |
8031 | 0 | if (!sibling || !sibling->IsHTMLElement(nsGkAtoms::br)) { |
8032 | 0 | ClearCachedStyles(); |
8033 | 0 | HTMLEditorRef().mTypeInState->ClearAllProps(); |
8034 | 0 |
|
8035 | 0 | // Create a paragraph |
8036 | 0 | nsAtom& paraAtom = DefaultParagraphSeparator(); |
8037 | 0 | // We want a wrapper element even if we separate with <br> |
8038 | 0 | EditorRawDOMPoint nextToHeader(headerParent, offset + 1); |
8039 | 0 | RefPtr<Element> pNode = |
8040 | 0 | HTMLEditorRef().CreateNodeWithTransaction(¶Atom == nsGkAtoms::br ? |
8041 | 0 | *nsGkAtoms::p : paraAtom, |
8042 | 0 | nextToHeader); |
8043 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8044 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8045 | 0 | } |
8046 | 0 | if (NS_WARN_IF(!pNode)) { |
8047 | 0 | return NS_ERROR_FAILURE; |
8048 | 0 | } |
8049 | 0 | |
8050 | 0 | // Append a <br> to it |
8051 | 0 | RefPtr<Element> brElement = |
8052 | 0 | HTMLEditorRef().InsertBrElementWithTransaction( |
8053 | 0 | SelectionRef(), EditorRawDOMPoint(pNode, 0)); |
8054 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8055 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8056 | 0 | } |
8057 | 0 | if (NS_WARN_IF(!brElement)) { |
8058 | 0 | return NS_ERROR_FAILURE; |
8059 | 0 | } |
8060 | 0 | |
8061 | 0 | // Set selection to before the break |
8062 | 0 | ErrorResult error; |
8063 | 0 | SelectionRef().Collapse(EditorRawDOMPoint(pNode, 0), error); |
8064 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8065 | 0 | error.SuppressException(); |
8066 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8067 | 0 | } |
8068 | 0 | if (NS_WARN_IF(error.Failed())) { |
8069 | 0 | return error.StealNSResult(); |
8070 | 0 | } |
8071 | 0 | } else { |
8072 | 0 | EditorRawDOMPoint afterSibling(sibling); |
8073 | 0 | if (NS_WARN_IF(!afterSibling.AdvanceOffset())) { |
8074 | 0 | return NS_ERROR_FAILURE; |
8075 | 0 | } |
8076 | 0 | // Put selection after break |
8077 | 0 | ErrorResult error; |
8078 | 0 | SelectionRef().Collapse(afterSibling, error); |
8079 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8080 | 0 | error.SuppressException(); |
8081 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8082 | 0 | } |
8083 | 0 | if (NS_WARN_IF(error.Failed())) { |
8084 | 0 | return error.StealNSResult(); |
8085 | 0 | } |
8086 | 0 | } |
8087 | 0 | } else { |
8088 | 0 | // Put selection at front of righthand heading |
8089 | 0 | ErrorResult error; |
8090 | 0 | SelectionRef().Collapse(RawRangeBoundary(&aHeader, 0), error); |
8091 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8092 | 0 | error.SuppressException(); |
8093 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8094 | 0 | } |
8095 | 0 | if (NS_WARN_IF(error.Failed())) { |
8096 | 0 | return error.StealNSResult(); |
8097 | 0 | } |
8098 | 0 | } |
8099 | 0 | return NS_OK; |
8100 | 0 | } |
8101 | | |
8102 | | EditActionResult |
8103 | | HTMLEditRules::ReturnInParagraph(Element& aParentDivOrP) |
8104 | 0 | { |
8105 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
8106 | 0 |
|
8107 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
8108 | 0 | if (NS_WARN_IF(!firstRange)) { |
8109 | 0 | return EditActionResult(NS_ERROR_FAILURE); |
8110 | 0 | } |
8111 | 0 | |
8112 | 0 | EditorDOMPoint atStartOfSelection(firstRange->StartRef()); |
8113 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
8114 | 0 | return EditActionResult(NS_ERROR_FAILURE); |
8115 | 0 | } |
8116 | 0 | MOZ_ASSERT(atStartOfSelection.IsSetAndValid()); |
8117 | 0 |
|
8118 | 0 | // We shouldn't create new anchor element which has non-empty href unless |
8119 | 0 | // splitting middle of it because we assume that users don't want to create |
8120 | 0 | // *same* anchor element across two or more paragraphs in most cases. |
8121 | 0 | // So, adjust selection start if it's edge of anchor element(s). |
8122 | 0 | // XXX We don't support whitespace collapsing in these cases since it needs |
8123 | 0 | // some additional work with WSRunObject but it's not usual case. |
8124 | 0 | // E.g., |<a href="foo"><b>foo []</b> </a>| |
8125 | 0 | if (atStartOfSelection.IsStartOfContainer()) { |
8126 | 0 | for (nsIContent* container = atStartOfSelection.GetContainerAsContent(); |
8127 | 0 | container && container != &aParentDivOrP; |
8128 | 0 | container = container->GetParent()) { |
8129 | 0 | if (HTMLEditUtils::IsLink(container)) { |
8130 | 0 | // Found link should be only in right node. So, we shouldn't split it. |
8131 | 0 | atStartOfSelection.Set(container); |
8132 | 0 | // Even if we found an anchor element, don't break because DOM API |
8133 | 0 | // allows to nest anchor elements. |
8134 | 0 | } |
8135 | 0 | // If the container is middle of its parent, stop adjusting split point. |
8136 | 0 | if (container->GetPreviousSibling()) { |
8137 | 0 | // XXX Should we check if previous sibling is visible content? |
8138 | 0 | // E.g., should we ignore comment node, invisible <br> element? |
8139 | 0 | break; |
8140 | 0 | } |
8141 | 0 | } |
8142 | 0 | } |
8143 | 0 | // We also need to check if selection is at invisible <br> element at end |
8144 | 0 | // of an <a href="foo"> element because editor inserts a <br> element when |
8145 | 0 | // user types Enter key after a whitespace which is at middle of |
8146 | 0 | // <a href="foo"> element and when setting selection at end of the element, |
8147 | 0 | // selection becomes referring the <br> element. We may need to change this |
8148 | 0 | // behavior later if it'd be standardized. |
8149 | 0 | else if (atStartOfSelection.IsEndOfContainer() || |
8150 | 0 | atStartOfSelection.IsBRElementAtEndOfContainer()) { |
8151 | 0 | // If there are 2 <br> elements, the first <br> element is visible. E.g., |
8152 | 0 | // |<a href="foo"><b>boo[]<br></b><br></a>|, we should split the <a> |
8153 | 0 | // element. Otherwise, E.g., |<a href="foo"><b>boo[]<br></b></a>|, |
8154 | 0 | // we should not split the <a> element and ignore inline elements in it. |
8155 | 0 | bool foundBRElement = atStartOfSelection.IsBRElementAtEndOfContainer(); |
8156 | 0 | for (nsIContent* container = atStartOfSelection.GetContainerAsContent(); |
8157 | 0 | container && container != &aParentDivOrP; |
8158 | 0 | container = container->GetParent()) { |
8159 | 0 | if (HTMLEditUtils::IsLink(container)) { |
8160 | 0 | // Found link should be only in left node. So, we shouldn't split it. |
8161 | 0 | atStartOfSelection.SetAfter(container); |
8162 | 0 | // Even if we found an anchor element, don't break because DOM API |
8163 | 0 | // allows to nest anchor elements. |
8164 | 0 | } |
8165 | 0 | // If the container is middle of its parent, stop adjusting split point. |
8166 | 0 | if (nsIContent* nextSibling = container->GetNextSibling()) { |
8167 | 0 | if (foundBRElement) { |
8168 | 0 | // If we've already found a <br> element, we assume found node is |
8169 | 0 | // visible <br> or something other node. |
8170 | 0 | // XXX Should we check if non-text data node like comment? |
8171 | 0 | break; |
8172 | 0 | } |
8173 | 0 | |
8174 | 0 | // XXX Should we check if non-text data node like comment? |
8175 | 0 | if (!nextSibling->IsHTMLElement(nsGkAtoms::br)) { |
8176 | 0 | break; |
8177 | 0 | } |
8178 | 0 | foundBRElement = true; |
8179 | 0 | } |
8180 | 0 | } |
8181 | 0 | } |
8182 | 0 |
|
8183 | 0 | bool doesCRCreateNewP = |
8184 | 0 | HTMLEditorRef().GetReturnInParagraphCreatesNewParagraph(); |
8185 | 0 |
|
8186 | 0 | bool splitAfterNewBR = false; |
8187 | 0 | nsCOMPtr<nsIContent> brContent; |
8188 | 0 |
|
8189 | 0 | EditorDOMPoint pointToSplitParentDivOrP(atStartOfSelection); |
8190 | 0 |
|
8191 | 0 | EditorRawDOMPoint pointToInsertBR; |
8192 | 0 | if (doesCRCreateNewP && |
8193 | 0 | atStartOfSelection.GetContainer() == &aParentDivOrP) { |
8194 | 0 | // We are at the edges of the block, so, we don't need to create new <br>. |
8195 | 0 | brContent = nullptr; |
8196 | 0 | } else if (atStartOfSelection.IsInTextNode()) { |
8197 | 0 | // at beginning of text node? |
8198 | 0 | if (atStartOfSelection.IsStartOfContainer()) { |
8199 | 0 | // is there a BR prior to it? |
8200 | 0 | brContent = |
8201 | 0 | HTMLEditorRef().GetPriorHTMLSibling(atStartOfSelection.GetContainer()); |
8202 | 0 | if (!brContent || |
8203 | 0 | !HTMLEditorRef().IsVisibleBRElement(brContent) || |
8204 | 0 | TextEditUtils::HasMozAttr(brContent)) { |
8205 | 0 | pointToInsertBR.Set(atStartOfSelection.GetContainer()); |
8206 | 0 | brContent = nullptr; |
8207 | 0 | } |
8208 | 0 | } else if (atStartOfSelection.IsEndOfContainer()) { |
8209 | 0 | // we're at the end of text node... |
8210 | 0 | // is there a BR after to it? |
8211 | 0 | brContent = |
8212 | 0 | HTMLEditorRef().GetNextHTMLSibling(atStartOfSelection.GetContainer()); |
8213 | 0 | if (!brContent || |
8214 | 0 | !HTMLEditorRef().IsVisibleBRElement(brContent) || |
8215 | 0 | TextEditUtils::HasMozAttr(brContent)) { |
8216 | 0 | pointToInsertBR.Set(atStartOfSelection.GetContainer()); |
8217 | 0 | DebugOnly<bool> advanced = pointToInsertBR.AdvanceOffset(); |
8218 | 0 | NS_WARNING_ASSERTION(advanced, |
8219 | 0 | "Failed to advance offset to after the container of selection start"); |
8220 | 0 | brContent = nullptr; |
8221 | 0 | } |
8222 | 0 | } else { |
8223 | 0 | if (doesCRCreateNewP) { |
8224 | 0 | ErrorResult error; |
8225 | 0 | nsCOMPtr<nsIContent> newLeftDivOrP = |
8226 | 0 | HTMLEditorRef().SplitNodeWithTransaction(pointToSplitParentDivOrP, |
8227 | 0 | error); |
8228 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8229 | 0 | error.SuppressException(); |
8230 | 0 | return EditActionResult(NS_ERROR_EDITOR_DESTROYED); |
8231 | 0 | } |
8232 | 0 | if (NS_WARN_IF(error.Failed())) { |
8233 | 0 | return EditActionResult(error.StealNSResult()); |
8234 | 0 | } |
8235 | 0 | pointToSplitParentDivOrP.SetToEndOf(newLeftDivOrP); |
8236 | 0 | } |
8237 | 0 |
|
8238 | 0 | // We need to put new <br> after the left node if given node was split |
8239 | 0 | // above. |
8240 | 0 | pointToInsertBR.Set(pointToSplitParentDivOrP.GetContainer()); |
8241 | 0 | DebugOnly<bool> advanced = pointToInsertBR.AdvanceOffset(); |
8242 | 0 | NS_WARNING_ASSERTION(advanced, |
8243 | 0 | "Failed to advance offset to after the container of selection start"); |
8244 | 0 | } |
8245 | 0 | } else { |
8246 | 0 | // not in a text node. |
8247 | 0 | // is there a BR prior to it? |
8248 | 0 | nsCOMPtr<nsIContent> nearNode; |
8249 | 0 | nearNode = HTMLEditorRef().GetPreviousEditableHTMLNode(atStartOfSelection); |
8250 | 0 | if (!nearNode || !HTMLEditorRef().IsVisibleBRElement(nearNode) || |
8251 | 0 | TextEditUtils::HasMozAttr(nearNode)) { |
8252 | 0 | // is there a BR after it? |
8253 | 0 | nearNode = HTMLEditorRef().GetNextEditableHTMLNode(atStartOfSelection); |
8254 | 0 | if (!nearNode || !HTMLEditorRef().IsVisibleBRElement(nearNode) || |
8255 | 0 | TextEditUtils::HasMozAttr(nearNode)) { |
8256 | 0 | pointToInsertBR = atStartOfSelection; |
8257 | 0 | splitAfterNewBR = true; |
8258 | 0 | } |
8259 | 0 | } |
8260 | 0 | if (!pointToInsertBR.IsSet() && TextEditUtils::IsBreak(nearNode)) { |
8261 | 0 | brContent = nearNode; |
8262 | 0 | } |
8263 | 0 | } |
8264 | 0 | if (pointToInsertBR.IsSet()) { |
8265 | 0 | // if CR does not create a new P, default to BR creation |
8266 | 0 | if (NS_WARN_IF(!doesCRCreateNewP)) { |
8267 | 0 | return EditActionResult(NS_OK); |
8268 | 0 | } |
8269 | 0 | |
8270 | 0 | brContent = |
8271 | 0 | HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(), |
8272 | 0 | pointToInsertBR); |
8273 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8274 | 0 | return EditActionResult(NS_ERROR_EDITOR_DESTROYED); |
8275 | 0 | } |
8276 | 0 | NS_WARNING_ASSERTION(brContent, "Failed to create a <br> element"); |
8277 | 0 | if (splitAfterNewBR) { |
8278 | 0 | // We split the parent after the br we've just inserted. |
8279 | 0 | pointToSplitParentDivOrP.Set(brContent); |
8280 | 0 | DebugOnly<bool> advanced = pointToSplitParentDivOrP.AdvanceOffset(); |
8281 | 0 | NS_WARNING_ASSERTION(advanced, |
8282 | 0 | "Failed to advance offset after the new <br>"); |
8283 | 0 | } |
8284 | 0 | } |
8285 | 0 | EditActionResult result( |
8286 | 0 | SplitParagraph(aParentDivOrP, pointToSplitParentDivOrP, brContent)); |
8287 | 0 | result.MarkAsHandled(); |
8288 | 0 | if (NS_WARN_IF(result.Failed())) { |
8289 | 0 | return result; |
8290 | 0 | } |
8291 | 0 | return result; |
8292 | 0 | } |
8293 | | |
8294 | | template<typename PT, typename CT> |
8295 | | nsresult |
8296 | | HTMLEditRules::SplitParagraph( |
8297 | | Element& aParentDivOrP, |
8298 | | const EditorDOMPointBase<PT, CT>& aStartOfRightNode, |
8299 | | nsIContent* aNextBRNode) |
8300 | 0 | { |
8301 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
8302 | 0 |
|
8303 | 0 | // split para |
8304 | 0 | // get ws code to adjust any ws |
8305 | 0 | nsCOMPtr<nsINode> selNode = aStartOfRightNode.GetContainer(); |
8306 | 0 | int32_t selOffset = aStartOfRightNode.Offset(); |
8307 | 0 | nsresult rv = |
8308 | 0 | WSRunObject::PrepareToSplitAcrossBlocks(&HTMLEditorRef(), |
8309 | 0 | address_of(selNode), &selOffset); |
8310 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8311 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8312 | 0 | } |
8313 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8314 | 0 | return rv; |
8315 | 0 | } |
8316 | 0 | if (NS_WARN_IF(!selNode->IsContent())) { |
8317 | 0 | return NS_ERROR_FAILURE; |
8318 | 0 | } |
8319 | 0 | |
8320 | 0 | // Split the paragraph. |
8321 | 0 | SplitNodeResult splitDivOrPResult = |
8322 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
8323 | 0 | aParentDivOrP, |
8324 | 0 | EditorRawDOMPoint(selNode, selOffset), |
8325 | 0 | SplitAtEdges::eAllowToCreateEmptyContainer); |
8326 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8327 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8328 | 0 | } |
8329 | 0 | if (NS_WARN_IF(splitDivOrPResult.Failed())) { |
8330 | 0 | return splitDivOrPResult.Rv(); |
8331 | 0 | } |
8332 | 0 | if (NS_WARN_IF(!splitDivOrPResult.DidSplit())) { |
8333 | 0 | return NS_ERROR_FAILURE; |
8334 | 0 | } |
8335 | 0 | |
8336 | 0 | // Get rid of the break, if it is visible (otherwise it may be needed to |
8337 | 0 | // prevent an empty p). |
8338 | 0 | if (aNextBRNode && HTMLEditorRef().IsVisibleBRElement(aNextBRNode)) { |
8339 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*aNextBRNode); |
8340 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8341 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8342 | 0 | } |
8343 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8344 | 0 | return rv; |
8345 | 0 | } |
8346 | 0 | } |
8347 | 0 | |
8348 | 0 | // Remove ID attribute on the paragraph from the existing right node. |
8349 | 0 | rv = HTMLEditorRef().RemoveAttributeWithTransaction(aParentDivOrP, |
8350 | 0 | *nsGkAtoms::id); |
8351 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8352 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8353 | 0 | } |
8354 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8355 | 0 | return rv; |
8356 | 0 | } |
8357 | 0 | |
8358 | 0 | // We need to ensure to both paragraphs visible even if they are empty. |
8359 | 0 | // However, moz-<br> element isn't useful in this case because moz-<br> |
8360 | 0 | // elements will be ignored by PlaintextSerializer. Additionally, |
8361 | 0 | // moz-<br> will be exposed as <br> with Element.innerHTML. Therefore, |
8362 | 0 | // we can use normal <br> elements for placeholder in this case. |
8363 | 0 | // Note that Chromium also behaves so. |
8364 | 0 | rv = InsertBRIfNeeded(*splitDivOrPResult.GetPreviousNode()); |
8365 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8366 | 0 | return rv; |
8367 | 0 | } |
8368 | 0 | rv = InsertBRIfNeeded(*splitDivOrPResult.GetNextNode()); |
8369 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8370 | 0 | return rv; |
8371 | 0 | } |
8372 | 0 | |
8373 | 0 | // selection to beginning of right hand para; |
8374 | 0 | // look inside any containers that are up front. |
8375 | 0 | nsIContent* child = HTMLEditorRef().GetLeftmostChild(&aParentDivOrP, true); |
8376 | 0 | if (EditorBase::IsTextNode(child) || HTMLEditorRef().IsContainer(child)) { |
8377 | 0 | EditorRawDOMPoint atStartOfChild(child, 0); |
8378 | 0 | IgnoredErrorResult ignoredError; |
8379 | 0 | SelectionRef().Collapse(atStartOfChild, ignoredError); |
8380 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8381 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8382 | 0 | } |
8383 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
8384 | 0 | "Failed to collapse selection at the end of the child"); |
8385 | 0 | } else { |
8386 | 0 | EditorRawDOMPoint atChild(child); |
8387 | 0 | IgnoredErrorResult ignoredError; |
8388 | 0 | SelectionRef().Collapse(atChild, ignoredError); |
8389 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8390 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8391 | 0 | } |
8392 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
8393 | 0 | "Failed to collapse selection at the child"); |
8394 | 0 | } |
8395 | 0 | return NS_OK; |
8396 | 0 | } |
8397 | | |
8398 | | nsresult |
8399 | | HTMLEditRules::ReturnInListItem(Element& aListItem, |
8400 | | nsINode& aNode, |
8401 | | int32_t aOffset) |
8402 | 0 | { |
8403 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
8404 | 0 | MOZ_ASSERT(HTMLEditUtils::IsListItem(&aListItem)); |
8405 | 0 |
|
8406 | 0 | // Get the item parent and the active editing host. |
8407 | 0 | RefPtr<Element> host = HTMLEditorRef().GetActiveEditingHost(); |
8408 | 0 |
|
8409 | 0 | // If we are in an empty item, then we want to pop up out of the list, but |
8410 | 0 | // only if prefs say it's okay and if the parent isn't the active editing |
8411 | 0 | // host. |
8412 | 0 | if (mReturnInEmptyLIKillsList && |
8413 | 0 | host != aListItem.GetParentElement() && |
8414 | 0 | IsEmptyBlockElement(aListItem, IgnoreSingleBR::eYes)) { |
8415 | 0 | nsCOMPtr<nsIContent> leftListNode = aListItem.GetParent(); |
8416 | 0 | // Are we the last list item in the list? |
8417 | 0 | if (!HTMLEditorRef().IsLastEditableChild(&aListItem)) { |
8418 | 0 | // We need to split the list! |
8419 | 0 | EditorRawDOMPoint atListItem(&aListItem); |
8420 | 0 | ErrorResult error; |
8421 | 0 | leftListNode = |
8422 | 0 | HTMLEditorRef().SplitNodeWithTransaction(atListItem, error); |
8423 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8424 | 0 | error.SuppressException(); |
8425 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8426 | 0 | } |
8427 | 0 | if (NS_WARN_IF(error.Failed())) { |
8428 | 0 | return error.StealNSResult(); |
8429 | 0 | } |
8430 | 0 | } |
8431 | 0 | |
8432 | 0 | // Are we in a sublist? |
8433 | 0 | EditorRawDOMPoint atNextSiblingOfLeftList(leftListNode); |
8434 | 0 | DebugOnly<bool> advanced = atNextSiblingOfLeftList.AdvanceOffset(); |
8435 | 0 | NS_WARNING_ASSERTION(advanced, |
8436 | 0 | "Failed to advance offset after the right list node"); |
8437 | 0 | if (HTMLEditUtils::IsList(atNextSiblingOfLeftList.GetContainer())) { |
8438 | 0 | // If so, move item out of this list and into the grandparent list |
8439 | 0 | nsresult rv = |
8440 | 0 | HTMLEditorRef().MoveNodeWithTransaction(aListItem, |
8441 | 0 | atNextSiblingOfLeftList); |
8442 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8443 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8444 | 0 | } |
8445 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8446 | 0 | return rv; |
8447 | 0 | } |
8448 | 0 | ErrorResult error; |
8449 | 0 | SelectionRef().Collapse(RawRangeBoundary(&aListItem, 0), error); |
8450 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8451 | 0 | error.SuppressException(); |
8452 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8453 | 0 | } |
8454 | 0 | if (NS_WARN_IF(error.Failed())) { |
8455 | 0 | return error.StealNSResult(); |
8456 | 0 | } |
8457 | 0 | } else { |
8458 | 0 | // Otherwise kill this item |
8459 | 0 | nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(aListItem); |
8460 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8461 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8462 | 0 | } |
8463 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8464 | 0 | return rv; |
8465 | 0 | } |
8466 | 0 | |
8467 | 0 | // Time to insert a paragraph |
8468 | 0 | nsAtom& paraAtom = DefaultParagraphSeparator(); |
8469 | 0 | // We want a wrapper even if we separate with <br> |
8470 | 0 | RefPtr<Element> pNode = |
8471 | 0 | HTMLEditorRef().CreateNodeWithTransaction(¶Atom == nsGkAtoms::br ? |
8472 | 0 | *nsGkAtoms::p : paraAtom, |
8473 | 0 | atNextSiblingOfLeftList); |
8474 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8475 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8476 | 0 | } |
8477 | 0 | if (NS_WARN_IF(!pNode)) { |
8478 | 0 | return NS_ERROR_FAILURE; |
8479 | 0 | } |
8480 | 0 | |
8481 | 0 | // Append a <br> to it |
8482 | 0 | RefPtr<Element> brElement = |
8483 | 0 | HTMLEditorRef().InsertBrElementWithTransaction( |
8484 | 0 | SelectionRef(), EditorRawDOMPoint(pNode, 0)); |
8485 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8486 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8487 | 0 | } |
8488 | 0 | if (NS_WARN_IF(!brElement)) { |
8489 | 0 | return NS_ERROR_FAILURE; |
8490 | 0 | } |
8491 | 0 | |
8492 | 0 | // Set selection to before the break |
8493 | 0 | ErrorResult error; |
8494 | 0 | SelectionRef().Collapse(EditorRawDOMPoint(pNode, 0), error); |
8495 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8496 | 0 | error.SuppressException(); |
8497 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8498 | 0 | } |
8499 | 0 | if (NS_WARN_IF(error.Failed())) { |
8500 | 0 | return error.StealNSResult(); |
8501 | 0 | } |
8502 | 0 | } |
8503 | 0 | return NS_OK; |
8504 | 0 | } |
8505 | 0 | |
8506 | 0 | // Else we want a new list item at the same list level. Get ws code to |
8507 | 0 | // adjust any ws. |
8508 | 0 | nsCOMPtr<nsINode> selNode = &aNode; |
8509 | 0 | nsresult rv = |
8510 | 0 | WSRunObject::PrepareToSplitAcrossBlocks(&HTMLEditorRef(), |
8511 | 0 | address_of(selNode), &aOffset); |
8512 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8513 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8514 | 0 | } |
8515 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8516 | 0 | return rv; |
8517 | 0 | } |
8518 | 0 | if (NS_WARN_IF(!selNode->IsContent())) { |
8519 | 0 | return NS_ERROR_FAILURE; |
8520 | 0 | } |
8521 | 0 | |
8522 | 0 | // Now split the list item. |
8523 | 0 | SplitNodeResult splitListItemResult = |
8524 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
8525 | 0 | aListItem, EditorRawDOMPoint(selNode, aOffset), |
8526 | 0 | SplitAtEdges::eAllowToCreateEmptyContainer); |
8527 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8528 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8529 | 0 | } |
8530 | 0 | NS_WARNING_ASSERTION(splitListItemResult.Succeeded(), |
8531 | 0 | "Failed to split the list item"); |
8532 | 0 |
|
8533 | 0 | // Hack: until I can change the damaged doc range code back to being |
8534 | 0 | // extra-inclusive, I have to manually detect certain list items that may be |
8535 | 0 | // left empty. |
8536 | 0 | nsCOMPtr<nsIContent> prevItem = |
8537 | 0 | HTMLEditorRef().GetPriorHTMLSibling(&aListItem); |
8538 | 0 | if (prevItem && HTMLEditUtils::IsListItem(prevItem)) { |
8539 | 0 | bool isEmptyNode; |
8540 | 0 | rv = HTMLEditorRef().IsEmptyNode(prevItem, &isEmptyNode); |
8541 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8542 | 0 | return rv; |
8543 | 0 | } |
8544 | 0 | if (isEmptyNode) { |
8545 | 0 | CreateElementResult createMozBrResult = |
8546 | 0 | CreateMozBR(EditorRawDOMPoint(prevItem, 0)); |
8547 | 0 | if (NS_WARN_IF(createMozBrResult.Failed())) { |
8548 | 0 | return createMozBrResult.Rv(); |
8549 | 0 | } |
8550 | 0 | } else { |
8551 | 0 | rv = HTMLEditorRef().IsEmptyNode(&aListItem, &isEmptyNode, true); |
8552 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8553 | 0 | return rv; |
8554 | 0 | } |
8555 | 0 | if (isEmptyNode) { |
8556 | 0 | RefPtr<nsAtom> nodeAtom = aListItem.NodeInfo()->NameAtom(); |
8557 | 0 | if (nodeAtom == nsGkAtoms::dd || nodeAtom == nsGkAtoms::dt) { |
8558 | 0 | nsCOMPtr<nsINode> list = aListItem.GetParentNode(); |
8559 | 0 | int32_t itemOffset = list ? list->ComputeIndexOf(&aListItem) : -1; |
8560 | 0 |
|
8561 | 0 | nsAtom* listAtom = nodeAtom == nsGkAtoms::dt ? nsGkAtoms::dd |
8562 | 0 | : nsGkAtoms::dt; |
8563 | 0 | MOZ_DIAGNOSTIC_ASSERT(itemOffset != -1); |
8564 | 0 | EditorRawDOMPoint atNextListItem(list, aListItem.GetNextSibling(), |
8565 | 0 | itemOffset + 1); |
8566 | 0 | RefPtr<Element> newListItem = |
8567 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*listAtom, |
8568 | 0 | atNextListItem); |
8569 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8570 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8571 | 0 | } |
8572 | 0 | if (NS_WARN_IF(!newListItem)) { |
8573 | 0 | return NS_ERROR_FAILURE; |
8574 | 0 | } |
8575 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(aListItem); |
8576 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8577 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8578 | 0 | } |
8579 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8580 | 0 | return rv; |
8581 | 0 | } |
8582 | 0 | ErrorResult error; |
8583 | 0 | SelectionRef().Collapse(EditorRawDOMPoint(newListItem, 0), error); |
8584 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8585 | 0 | error.SuppressException(); |
8586 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8587 | 0 | } |
8588 | 0 | if (NS_WARN_IF(error.Failed())) { |
8589 | 0 | return error.StealNSResult(); |
8590 | 0 | } |
8591 | 0 | return NS_OK; |
8592 | 0 | } |
8593 | 0 | |
8594 | 0 | RefPtr<Element> brElement; |
8595 | 0 | nsresult rv = |
8596 | 0 | HTMLEditorRef().CopyLastEditableChildStylesWithTransaction( |
8597 | 0 | *prevItem->AsElement(), aListItem, |
8598 | 0 | address_of(brElement)); |
8599 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8600 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8601 | 0 | } |
8602 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8603 | 0 | return NS_ERROR_FAILURE; |
8604 | 0 | } |
8605 | 0 | if (brElement) { |
8606 | 0 | EditorRawDOMPoint atBrNode(brElement); |
8607 | 0 | if (NS_WARN_IF(!atBrNode.IsSetAndValid())) { |
8608 | 0 | return NS_ERROR_FAILURE; |
8609 | 0 | } |
8610 | 0 | ErrorResult error; |
8611 | 0 | SelectionRef().Collapse(atBrNode, error); |
8612 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8613 | 0 | error.SuppressException(); |
8614 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8615 | 0 | } |
8616 | 0 | if (NS_WARN_IF(error.Failed())) { |
8617 | 0 | return error.StealNSResult(); |
8618 | 0 | } |
8619 | 0 | return NS_OK; |
8620 | 0 | } |
8621 | 0 | } else { |
8622 | 0 | WSRunObject wsObj(&HTMLEditorRef(), &aListItem, 0); |
8623 | 0 | nsCOMPtr<nsINode> visNode; |
8624 | 0 | int32_t visOffset = 0; |
8625 | 0 | WSType wsType; |
8626 | 0 | wsObj.NextVisibleNode(EditorRawDOMPoint(&aListItem, 0), |
8627 | 0 | address_of(visNode), &visOffset, &wsType); |
8628 | 0 | if (wsType == WSType::special || wsType == WSType::br || |
8629 | 0 | visNode->IsHTMLElement(nsGkAtoms::hr)) { |
8630 | 0 | EditorRawDOMPoint atVisNode(visNode); |
8631 | 0 | if (NS_WARN_IF(!atVisNode.IsSetAndValid())) { |
8632 | 0 | return NS_ERROR_FAILURE; |
8633 | 0 | } |
8634 | 0 | ErrorResult error; |
8635 | 0 | SelectionRef().Collapse(atVisNode, error); |
8636 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8637 | 0 | error.SuppressException(); |
8638 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8639 | 0 | } |
8640 | 0 | if (NS_WARN_IF(error.Failed())) { |
8641 | 0 | return error.StealNSResult(); |
8642 | 0 | } |
8643 | 0 | return NS_OK; |
8644 | 0 | } |
8645 | 0 | |
8646 | 0 | rv = SelectionRef().Collapse(visNode, visOffset); |
8647 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8648 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8649 | 0 | } |
8650 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8651 | 0 | return rv; |
8652 | 0 | } |
8653 | 0 | return NS_OK; |
8654 | 0 | } |
8655 | 0 | } |
8656 | 0 | } |
8657 | 0 |
|
8658 | 0 | ErrorResult error; |
8659 | 0 | SelectionRef().Collapse(EditorRawDOMPoint(&aListItem, 0), error); |
8660 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8661 | 0 | error.SuppressException(); |
8662 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8663 | 0 | } |
8664 | 0 | if (NS_WARN_IF(error.Failed())) { |
8665 | 0 | return error.StealNSResult(); |
8666 | 0 | } |
8667 | 0 | return NS_OK; |
8668 | 0 | } |
8669 | | |
8670 | | nsresult |
8671 | | HTMLEditRules::MakeBlockquote(nsTArray<OwningNonNull<nsINode>>& aNodeArray) |
8672 | 0 | { |
8673 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
8674 | 0 |
|
8675 | 0 | // The idea here is to put the nodes into a minimal number of blockquotes. |
8676 | 0 | // When the user blockquotes something, they expect one blockquote. That may |
8677 | 0 | // not be possible (for instance, if they have two table cells selected, you |
8678 | 0 | // need two blockquotes inside the cells). |
8679 | 0 | RefPtr<Element> curBlock; |
8680 | 0 | nsCOMPtr<nsINode> prevParent; |
8681 | 0 |
|
8682 | 0 | for (auto& curNode : aNodeArray) { |
8683 | 0 | // Get the node to act on, and its location |
8684 | 0 | if (NS_WARN_IF(!curNode->IsContent())) { |
8685 | 0 | return NS_ERROR_FAILURE; |
8686 | 0 | } |
8687 | 0 | |
8688 | 0 | // If the node is a table element or list item, dive inside |
8689 | 0 | if (HTMLEditUtils::IsTableElementButNotTable(curNode) || |
8690 | 0 | HTMLEditUtils::IsListItem(curNode)) { |
8691 | 0 | // Forget any previous block |
8692 | 0 | curBlock = nullptr; |
8693 | 0 | // Recursion time |
8694 | 0 | nsTArray<OwningNonNull<nsINode>> childArray; |
8695 | 0 | GetChildNodesForOperation(*curNode, childArray); |
8696 | 0 | nsresult rv = MakeBlockquote(childArray); |
8697 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8698 | 0 | return rv; |
8699 | 0 | } |
8700 | 0 | } |
8701 | 0 | |
8702 | 0 | // If the node has different parent than previous node, further nodes in a |
8703 | 0 | // new parent |
8704 | 0 | if (prevParent) { |
8705 | 0 | if (prevParent != curNode->GetParentNode()) { |
8706 | 0 | // Forget any previous blockquote node we were using |
8707 | 0 | curBlock = nullptr; |
8708 | 0 | prevParent = curNode->GetParentNode(); |
8709 | 0 | } |
8710 | 0 | } else { |
8711 | 0 | prevParent = curNode->GetParentNode(); |
8712 | 0 | } |
8713 | 0 |
|
8714 | 0 | // If no curBlock, make one |
8715 | 0 | if (!curBlock) { |
8716 | 0 | EditorDOMPoint atCurNode(curNode); |
8717 | 0 | SplitNodeResult splitNodeResult = |
8718 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::blockquote, |
8719 | 0 | atCurNode); |
8720 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
8721 | 0 | return splitNodeResult.Rv(); |
8722 | 0 | } |
8723 | 0 | curBlock = |
8724 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::blockquote, |
8725 | 0 | splitNodeResult.SplitPoint()); |
8726 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8727 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8728 | 0 | } |
8729 | 0 | if (NS_WARN_IF(!curBlock)) { |
8730 | 0 | return NS_ERROR_FAILURE; |
8731 | 0 | } |
8732 | 0 | // remember our new block for postprocessing |
8733 | 0 | mNewBlock = curBlock; |
8734 | 0 | // note: doesn't matter if we set mNewBlock multiple times. |
8735 | 0 | } |
8736 | 0 |
|
8737 | 0 | nsresult rv = |
8738 | 0 | HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
8739 | 0 | *curBlock); |
8740 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8741 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8742 | 0 | } |
8743 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8744 | 0 | return rv; |
8745 | 0 | } |
8746 | 0 | } |
8747 | 0 | return NS_OK; |
8748 | 0 | } |
8749 | | |
8750 | | nsresult |
8751 | | HTMLEditRules::RemoveBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray) |
8752 | 0 | { |
8753 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
8754 | 0 |
|
8755 | 0 | // Intent of this routine is to be used for converting to/from headers, |
8756 | 0 | // paragraphs, pre, and address. Those blocks that pretty much just contain |
8757 | 0 | // inline things... |
8758 | 0 | nsCOMPtr<Element> curBlock; |
8759 | 0 | nsCOMPtr<nsIContent> firstNode, lastNode; |
8760 | 0 | for (auto& curNode : aNodeArray) { |
8761 | 0 | // If curNode is an <address>, <p>, <hn>, or <pre>, remove it. |
8762 | 0 | if (HTMLEditUtils::IsFormatNode(curNode)) { |
8763 | 0 | // Process any partial progress saved |
8764 | 0 | if (curBlock) { |
8765 | 0 | SplitRangeOffFromNodeResult removeMiddleContainerResult = |
8766 | 0 | SplitRangeOffFromBlockAndRemoveMiddleContainer(*curBlock, |
8767 | 0 | *firstNode, *lastNode); |
8768 | 0 | if (NS_WARN_IF(removeMiddleContainerResult.Failed())) { |
8769 | 0 | return removeMiddleContainerResult.Rv(); |
8770 | 0 | } |
8771 | 0 | firstNode = lastNode = curBlock = nullptr; |
8772 | 0 | } |
8773 | 0 | if (!HTMLEditorRef().IsEditable(curNode)) { |
8774 | 0 | continue; |
8775 | 0 | } |
8776 | 0 | // Remove current block |
8777 | 0 | nsresult rv = |
8778 | 0 | HTMLEditorRef().RemoveBlockContainerWithTransaction( |
8779 | 0 | *curNode->AsElement()); |
8780 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8781 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8782 | 0 | } |
8783 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8784 | 0 | return rv; |
8785 | 0 | } |
8786 | 0 | continue; |
8787 | 0 | } |
8788 | 0 | |
8789 | 0 | // XXX How about, <th>, <thead>, <tfoot>, <dt>, <dl>? |
8790 | 0 | if (curNode->IsAnyOfHTMLElements(nsGkAtoms::table, |
8791 | 0 | nsGkAtoms::tr, |
8792 | 0 | nsGkAtoms::tbody, |
8793 | 0 | nsGkAtoms::td, |
8794 | 0 | nsGkAtoms::li, |
8795 | 0 | nsGkAtoms::blockquote, |
8796 | 0 | nsGkAtoms::div) || |
8797 | 0 | HTMLEditUtils::IsList(curNode)) { |
8798 | 0 | // Process any partial progress saved |
8799 | 0 | if (curBlock) { |
8800 | 0 | SplitRangeOffFromNodeResult removeMiddleContainerResult = |
8801 | 0 | SplitRangeOffFromBlockAndRemoveMiddleContainer(*curBlock, |
8802 | 0 | *firstNode, *lastNode); |
8803 | 0 | if (NS_WARN_IF(removeMiddleContainerResult.Failed())) { |
8804 | 0 | return removeMiddleContainerResult.Rv(); |
8805 | 0 | } |
8806 | 0 | firstNode = lastNode = curBlock = nullptr; |
8807 | 0 | } |
8808 | 0 | if (!HTMLEditorRef().IsEditable(curNode)) { |
8809 | 0 | continue; |
8810 | 0 | } |
8811 | 0 | // Recursion time |
8812 | 0 | nsTArray<OwningNonNull<nsINode>> childArray; |
8813 | 0 | GetChildNodesForOperation(*curNode, childArray); |
8814 | 0 | nsresult rv = RemoveBlockStyle(childArray); |
8815 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8816 | 0 | return rv; |
8817 | 0 | } |
8818 | 0 | continue; |
8819 | 0 | } |
8820 | 0 | |
8821 | 0 | if (IsInlineNode(curNode)) { |
8822 | 0 | if (curBlock) { |
8823 | 0 | // If so, is this node a descendant? |
8824 | 0 | if (EditorUtils::IsDescendantOf(*curNode, *curBlock)) { |
8825 | 0 | // Then we don't need to do anything different for this node |
8826 | 0 | lastNode = curNode->AsContent(); |
8827 | 0 | continue; |
8828 | 0 | } |
8829 | 0 | // Otherwise, we have progressed beyond end of curBlock, so let's |
8830 | 0 | // handle it now. We need to remove the portion of curBlock that |
8831 | 0 | // contains [firstNode - lastNode]. |
8832 | 0 | SplitRangeOffFromNodeResult removeMiddleContainerResult = |
8833 | 0 | SplitRangeOffFromBlockAndRemoveMiddleContainer(*curBlock, |
8834 | 0 | *firstNode, *lastNode); |
8835 | 0 | if (NS_WARN_IF(removeMiddleContainerResult.Failed())) { |
8836 | 0 | return removeMiddleContainerResult.Rv(); |
8837 | 0 | } |
8838 | 0 | firstNode = lastNode = curBlock = nullptr; |
8839 | 0 | // Fall out and handle curNode |
8840 | 0 | } |
8841 | 0 | curBlock = HTMLEditorRef().GetBlockNodeParent(curNode); |
8842 | 0 | if (!curBlock || !HTMLEditUtils::IsFormatNode(curBlock) || |
8843 | 0 | !HTMLEditorRef().IsEditable(curBlock)) { |
8844 | 0 | // Not a block kind that we care about. |
8845 | 0 | curBlock = nullptr; |
8846 | 0 | } else { |
8847 | 0 | firstNode = lastNode = curNode->AsContent(); |
8848 | 0 | } |
8849 | 0 | continue; |
8850 | 0 | } |
8851 | 0 |
|
8852 | 0 | if (curBlock) { |
8853 | 0 | // Some node that is already sans block style. Skip over it and process |
8854 | 0 | // any partial progress saved. |
8855 | 0 | SplitRangeOffFromNodeResult removeMiddleContainerResult = |
8856 | 0 | SplitRangeOffFromBlockAndRemoveMiddleContainer(*curBlock, |
8857 | 0 | *firstNode, *lastNode); |
8858 | 0 | if (NS_WARN_IF(removeMiddleContainerResult.Failed())) { |
8859 | 0 | return removeMiddleContainerResult.Rv(); |
8860 | 0 | } |
8861 | 0 | firstNode = lastNode = curBlock = nullptr; |
8862 | 0 | continue; |
8863 | 0 | } |
8864 | 0 | } |
8865 | 0 | // Process any partial progress saved |
8866 | 0 | if (curBlock) { |
8867 | 0 | SplitRangeOffFromNodeResult removeMiddleContainerResult = |
8868 | 0 | SplitRangeOffFromBlockAndRemoveMiddleContainer(*curBlock, |
8869 | 0 | *firstNode, *lastNode); |
8870 | 0 | if (NS_WARN_IF(removeMiddleContainerResult.Failed())) { |
8871 | 0 | return removeMiddleContainerResult.Rv(); |
8872 | 0 | } |
8873 | 0 | firstNode = lastNode = curBlock = nullptr; |
8874 | 0 | } |
8875 | 0 | return NS_OK; |
8876 | 0 | } |
8877 | | |
8878 | | nsresult |
8879 | | HTMLEditRules::ApplyBlockStyle(nsTArray<OwningNonNull<nsINode>>& aNodeArray, |
8880 | | nsAtom& aBlockTag) |
8881 | 0 | { |
8882 | 0 | // Intent of this routine is to be used for converting to/from headers, |
8883 | 0 | // paragraphs, pre, and address. Those blocks that pretty much just contain |
8884 | 0 | // inline things... |
8885 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
8886 | 0 |
|
8887 | 0 | nsCOMPtr<Element> newBlock; |
8888 | 0 |
|
8889 | 0 | nsCOMPtr<Element> curBlock; |
8890 | 0 | for (auto& curNode : aNodeArray) { |
8891 | 0 | EditorDOMPoint atCurNode(curNode); |
8892 | 0 |
|
8893 | 0 | // Is it already the right kind of block, or an uneditable block? |
8894 | 0 | if (curNode->IsHTMLElement(&aBlockTag) || |
8895 | 0 | (!HTMLEditorRef().IsEditable(curNode) && IsBlockNode(curNode))) { |
8896 | 0 | // Forget any previous block used for previous inline nodes |
8897 | 0 | curBlock = nullptr; |
8898 | 0 | // Do nothing to this block |
8899 | 0 | continue; |
8900 | 0 | } |
8901 | 0 | |
8902 | 0 | // If curNode is a address, p, header, address, or pre, replace it with a |
8903 | 0 | // new block of correct type. |
8904 | 0 | // XXX: pre can't hold everything the others can |
8905 | 0 | if (HTMLEditUtils::IsMozDiv(curNode) || |
8906 | 0 | HTMLEditUtils::IsFormatNode(curNode)) { |
8907 | 0 | // Forget any previous block used for previous inline nodes |
8908 | 0 | curBlock = nullptr; |
8909 | 0 | newBlock = |
8910 | 0 | HTMLEditorRef().ReplaceContainerAndCloneAttributesWithTransaction( |
8911 | 0 | *curNode->AsElement(), aBlockTag); |
8912 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8913 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8914 | 0 | } |
8915 | 0 | if (NS_WARN_IF(!newBlock)) { |
8916 | 0 | return NS_ERROR_FAILURE; |
8917 | 0 | } |
8918 | 0 | continue; |
8919 | 0 | } |
8920 | 0 | |
8921 | 0 | if (HTMLEditUtils::IsTable(curNode) || |
8922 | 0 | HTMLEditUtils::IsList(curNode) || |
8923 | 0 | curNode->IsAnyOfHTMLElements(nsGkAtoms::tbody, |
8924 | 0 | nsGkAtoms::tr, |
8925 | 0 | nsGkAtoms::td, |
8926 | 0 | nsGkAtoms::li, |
8927 | 0 | nsGkAtoms::blockquote, |
8928 | 0 | nsGkAtoms::div)) { |
8929 | 0 | // Forget any previous block used for previous inline nodes |
8930 | 0 | curBlock = nullptr; |
8931 | 0 | // Recursion time |
8932 | 0 | nsTArray<OwningNonNull<nsINode>> childArray; |
8933 | 0 | GetChildNodesForOperation(*curNode, childArray); |
8934 | 0 | if (!childArray.IsEmpty()) { |
8935 | 0 | nsresult rv = ApplyBlockStyle(childArray, aBlockTag); |
8936 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8937 | 0 | return rv; |
8938 | 0 | } |
8939 | 0 | continue; |
8940 | 0 | } |
8941 | 0 | |
8942 | 0 | // Make sure we can put a block here |
8943 | 0 | SplitNodeResult splitNodeResult = |
8944 | 0 | MaybeSplitAncestorsForInsertWithTransaction(aBlockTag, atCurNode); |
8945 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
8946 | 0 | return splitNodeResult.Rv(); |
8947 | 0 | } |
8948 | 0 | RefPtr<Element> theBlock = |
8949 | 0 | HTMLEditorRef().CreateNodeWithTransaction(aBlockTag, |
8950 | 0 | splitNodeResult.SplitPoint()); |
8951 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8952 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8953 | 0 | } |
8954 | 0 | if (NS_WARN_IF(!theBlock)) { |
8955 | 0 | return NS_ERROR_FAILURE; |
8956 | 0 | } |
8957 | 0 | // Remember our new block for postprocessing |
8958 | 0 | mNewBlock = theBlock; |
8959 | 0 | continue; |
8960 | 0 | } |
8961 | 0 | |
8962 | 0 | if (curNode->IsHTMLElement(nsGkAtoms::br)) { |
8963 | 0 | // If the node is a break, we honor it by putting further nodes in a new |
8964 | 0 | // parent |
8965 | 0 | if (curBlock) { |
8966 | 0 | // Forget any previous block used for previous inline nodes |
8967 | 0 | curBlock = nullptr; |
8968 | 0 | nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*curNode); |
8969 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8970 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8971 | 0 | } |
8972 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
8973 | 0 | return rv; |
8974 | 0 | } |
8975 | 0 | continue; |
8976 | 0 | } |
8977 | 0 | |
8978 | 0 | // The break is the first (or even only) node we encountered. Create a |
8979 | 0 | // block for it. |
8980 | 0 | SplitNodeResult splitNodeResult = |
8981 | 0 | MaybeSplitAncestorsForInsertWithTransaction(aBlockTag, atCurNode); |
8982 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
8983 | 0 | return splitNodeResult.Rv(); |
8984 | 0 | } |
8985 | 0 | curBlock = |
8986 | 0 | HTMLEditorRef().CreateNodeWithTransaction(aBlockTag, |
8987 | 0 | splitNodeResult.SplitPoint()); |
8988 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
8989 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
8990 | 0 | } |
8991 | 0 | if (NS_WARN_IF(!curBlock)) { |
8992 | 0 | return NS_ERROR_FAILURE; |
8993 | 0 | } |
8994 | 0 | // Remember our new block for postprocessing |
8995 | 0 | mNewBlock = curBlock; |
8996 | 0 | // Note: doesn't matter if we set mNewBlock multiple times. |
8997 | 0 | nsresult rv = |
8998 | 0 | HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
8999 | 0 | *curBlock); |
9000 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9001 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9002 | 0 | } |
9003 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9004 | 0 | return rv; |
9005 | 0 | } |
9006 | 0 | continue; |
9007 | 0 | } |
9008 | 0 | |
9009 | 0 | if (IsInlineNode(curNode)) { |
9010 | 0 | // If curNode is inline, pull it into curBlock. Note: it's assumed that |
9011 | 0 | // consecutive inline nodes in aNodeArray are actually members of the |
9012 | 0 | // same block parent. This happens to be true now as a side effect of |
9013 | 0 | // how aNodeArray is contructed, but some additional logic should be |
9014 | 0 | // added here if that should change |
9015 | 0 | // |
9016 | 0 | // If curNode is a non editable, drop it if we are going to <pre>. |
9017 | 0 | if (&aBlockTag == nsGkAtoms::pre && |
9018 | 0 | !HTMLEditorRef().IsEditable(curNode)) { |
9019 | 0 | // Do nothing to this block |
9020 | 0 | continue; |
9021 | 0 | } |
9022 | 0 | |
9023 | 0 | // If no curBlock, make one |
9024 | 0 | if (!curBlock) { |
9025 | 0 | AutoEditorDOMPointOffsetInvalidator lockChild(atCurNode); |
9026 | 0 |
|
9027 | 0 | SplitNodeResult splitNodeResult = |
9028 | 0 | MaybeSplitAncestorsForInsertWithTransaction(aBlockTag, atCurNode); |
9029 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
9030 | 0 | return splitNodeResult.Rv(); |
9031 | 0 | } |
9032 | 0 | curBlock = |
9033 | 0 | HTMLEditorRef().CreateNodeWithTransaction( |
9034 | 0 | aBlockTag, splitNodeResult.SplitPoint()); |
9035 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9036 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9037 | 0 | } |
9038 | 0 | if (NS_WARN_IF(!curBlock)) { |
9039 | 0 | return NS_ERROR_FAILURE; |
9040 | 0 | } |
9041 | 0 | // Remember our new block for postprocessing |
9042 | 0 | mNewBlock = curBlock; |
9043 | 0 | // Note: doesn't matter if we set mNewBlock multiple times. |
9044 | 0 | } |
9045 | 0 |
|
9046 | 0 | if (NS_WARN_IF(!atCurNode.IsSet())) { |
9047 | 0 | // This is possible due to mutation events, let's not assert |
9048 | 0 | return NS_ERROR_UNEXPECTED; |
9049 | 0 | } |
9050 | 0 | |
9051 | 0 | // XXX If curNode is a br, replace it with a return if going to <pre> |
9052 | 0 | |
9053 | 0 | // This is a continuation of some inline nodes that belong together in |
9054 | 0 | // the same block item. Use curBlock. |
9055 | 0 | nsresult rv = |
9056 | 0 | HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
9057 | 0 | *curBlock); |
9058 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9059 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9060 | 0 | } |
9061 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9062 | 0 | return rv; |
9063 | 0 | } |
9064 | 0 | } |
9065 | 0 | } |
9066 | 0 | return NS_OK; |
9067 | 0 | } |
9068 | | |
9069 | | template<typename PT, typename CT> |
9070 | | SplitNodeResult |
9071 | | HTMLEditRules::MaybeSplitAncestorsForInsertWithTransaction( |
9072 | | nsAtom& aTag, |
9073 | | const EditorDOMPointBase<PT, CT>& aStartOfDeepestRightNode) |
9074 | 0 | { |
9075 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9076 | 0 |
|
9077 | 0 | if (NS_WARN_IF(!aStartOfDeepestRightNode.IsSet())) { |
9078 | 0 | return SplitNodeResult(NS_ERROR_INVALID_ARG); |
9079 | 0 | } |
9080 | 0 | MOZ_ASSERT(aStartOfDeepestRightNode.IsSetAndValid()); |
9081 | 0 |
|
9082 | 0 | RefPtr<Element> host = HTMLEditorRef().GetActiveEditingHost(); |
9083 | 0 | if (NS_WARN_IF(!host)) { |
9084 | 0 | return SplitNodeResult(NS_ERROR_FAILURE); |
9085 | 0 | } |
9086 | 0 | |
9087 | 0 | // The point must be descendant of editing host. |
9088 | 0 | if (NS_WARN_IF(aStartOfDeepestRightNode.GetContainer() != host && |
9089 | 0 | !EditorUtils::IsDescendantOf( |
9090 | 0 | *aStartOfDeepestRightNode.GetContainer(), *host))) { |
9091 | 0 | return SplitNodeResult(NS_ERROR_INVALID_ARG); |
9092 | 0 | } |
9093 | 0 | |
9094 | 0 | // Look for a node that can legally contain the tag. |
9095 | 0 | EditorRawDOMPoint pointToInsert(aStartOfDeepestRightNode); |
9096 | 0 | for (; pointToInsert.IsSet(); |
9097 | 0 | pointToInsert.Set(pointToInsert.GetContainer())) { |
9098 | 0 | // We cannot split active editing host and its ancestor. So, there is |
9099 | 0 | // no element to contain the specified element. |
9100 | 0 | if (NS_WARN_IF(pointToInsert.GetChild() == host)) { |
9101 | 0 | return SplitNodeResult(NS_ERROR_FAILURE); |
9102 | 0 | } |
9103 | 0 | |
9104 | 0 | if (HTMLEditorRef().CanContainTag(*pointToInsert.GetContainer(), aTag)) { |
9105 | 0 | // Found an ancestor node which can contain the element. |
9106 | 0 | break; |
9107 | 0 | } |
9108 | 0 | } |
9109 | 0 |
|
9110 | 0 | MOZ_DIAGNOSTIC_ASSERT(pointToInsert.IsSet()); |
9111 | 0 |
|
9112 | 0 | // If the point itself can contain the tag, we don't need to split any |
9113 | 0 | // ancestor nodes. In this case, we should return the given split point |
9114 | 0 | // as is. |
9115 | 0 | if (pointToInsert.GetContainer() == aStartOfDeepestRightNode.GetContainer()) { |
9116 | 0 | return SplitNodeResult(aStartOfDeepestRightNode); |
9117 | 0 | } |
9118 | 0 | |
9119 | 0 | SplitNodeResult splitNodeResult = |
9120 | 0 | HTMLEditorRef().SplitNodeDeepWithTransaction( |
9121 | 0 | *pointToInsert.GetChild(), |
9122 | 0 | aStartOfDeepestRightNode, |
9123 | 0 | SplitAtEdges::eAllowToCreateEmptyContainer); |
9124 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9125 | 0 | return SplitNodeResult(NS_ERROR_EDITOR_DESTROYED); |
9126 | 0 | } |
9127 | 0 | NS_WARNING_ASSERTION(splitNodeResult.Succeeded(), |
9128 | 0 | "Failed to split the node for insert the element"); |
9129 | 0 | return splitNodeResult; |
9130 | 0 | } Unexecuted instantiation: mozilla::SplitNodeResult mozilla::HTMLEditRules::MaybeSplitAncestorsForInsertWithTransaction<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent> >(nsAtom&, mozilla::EditorDOMPointBase<nsCOMPtr<nsINode>, nsCOMPtr<nsIContent> > const&) Unexecuted instantiation: mozilla::SplitNodeResult mozilla::HTMLEditRules::MaybeSplitAncestorsForInsertWithTransaction<nsINode*, nsIContent*>(nsAtom&, mozilla::EditorDOMPointBase<nsINode*, nsIContent*> const&) |
9131 | | |
9132 | | nsresult |
9133 | | HTMLEditRules::JoinNearestEditableNodesWithTransaction( |
9134 | | nsIContent& aNodeLeft, |
9135 | | nsIContent& aNodeRight, |
9136 | | EditorDOMPoint* aNewFirstChildOfRightNode) |
9137 | 0 | { |
9138 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9139 | 0 | MOZ_ASSERT(aNewFirstChildOfRightNode); |
9140 | 0 |
|
9141 | 0 | // Caller responsible for left and right node being the same type |
9142 | 0 | nsCOMPtr<nsINode> parent = aNodeLeft.GetParentNode(); |
9143 | 0 | if (NS_WARN_IF(!parent)) { |
9144 | 0 | return NS_ERROR_FAILURE; |
9145 | 0 | } |
9146 | 0 | nsCOMPtr<nsINode> rightParent = aNodeRight.GetParentNode(); |
9147 | 0 |
|
9148 | 0 | // If they don't have the same parent, first move the right node to after the |
9149 | 0 | // left one |
9150 | 0 | if (parent != rightParent) { |
9151 | 0 | int32_t parOffset = parent->ComputeIndexOf(&aNodeLeft); |
9152 | 0 | nsresult rv = |
9153 | 0 | HTMLEditorRef().MoveNodeWithTransaction( |
9154 | 0 | aNodeRight, EditorRawDOMPoint(parent, parOffset)); |
9155 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9156 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9157 | 0 | } |
9158 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9159 | 0 | return rv; |
9160 | 0 | } |
9161 | 0 | } |
9162 | 0 | |
9163 | 0 | EditorDOMPoint ret(&aNodeRight, aNodeLeft.Length()); |
9164 | 0 |
|
9165 | 0 | // Separate join rules for differing blocks |
9166 | 0 | if (HTMLEditUtils::IsList(&aNodeLeft) || aNodeLeft.GetAsText()) { |
9167 | 0 | // For lists, merge shallow (wouldn't want to combine list items) |
9168 | 0 | nsresult rv = |
9169 | 0 | HTMLEditorRef().JoinNodesWithTransaction(aNodeLeft, aNodeRight); |
9170 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9171 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9172 | 0 | } |
9173 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9174 | 0 | return rv; |
9175 | 0 | } |
9176 | 0 | *aNewFirstChildOfRightNode = std::move(ret); |
9177 | 0 | return NS_OK; |
9178 | 0 | } |
9179 | 0 | |
9180 | 0 | // Remember the last left child, and first right child |
9181 | 0 | nsCOMPtr<nsIContent> lastLeft = |
9182 | 0 | HTMLEditorRef().GetLastEditableChild(aNodeLeft); |
9183 | 0 | if (NS_WARN_IF(!lastLeft)) { |
9184 | 0 | return NS_ERROR_FAILURE; |
9185 | 0 | } |
9186 | 0 | |
9187 | 0 | nsCOMPtr<nsIContent> firstRight = |
9188 | 0 | HTMLEditorRef().GetFirstEditableChild(aNodeRight); |
9189 | 0 | if (NS_WARN_IF(!firstRight)) { |
9190 | 0 | return NS_ERROR_FAILURE; |
9191 | 0 | } |
9192 | 0 | |
9193 | 0 | // For list items, divs, etc., merge smart |
9194 | 0 | nsresult rv = HTMLEditorRef().JoinNodesWithTransaction(aNodeLeft, aNodeRight); |
9195 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9196 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9197 | 0 | } |
9198 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9199 | 0 | return rv; |
9200 | 0 | } |
9201 | 0 | |
9202 | 0 | if (lastLeft && firstRight && |
9203 | 0 | HTMLEditorRef().AreNodesSameType(*lastLeft, *firstRight) && |
9204 | 0 | (lastLeft->GetAsText() || |
9205 | 0 | (lastLeft->IsElement() && firstRight->IsElement() && |
9206 | 0 | CSSEditUtils::ElementsSameStyle(lastLeft->AsElement(), |
9207 | 0 | firstRight->AsElement())))) { |
9208 | 0 | nsresult rv = |
9209 | 0 | JoinNearestEditableNodesWithTransaction(*lastLeft, *firstRight, |
9210 | 0 | aNewFirstChildOfRightNode); |
9211 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9212 | 0 | return rv; |
9213 | 0 | } |
9214 | 0 | return NS_OK; |
9215 | 0 | } |
9216 | 0 | *aNewFirstChildOfRightNode = std::move(ret); |
9217 | 0 | return NS_OK; |
9218 | 0 | } |
9219 | | |
9220 | | Element* |
9221 | | HTMLEditRules::GetTopEnclosingMailCite(nsINode& aNode) |
9222 | 0 | { |
9223 | 0 | nsCOMPtr<Element> ret; |
9224 | 0 |
|
9225 | 0 | for (nsCOMPtr<nsINode> node = &aNode; node; node = node->GetParentNode()) { |
9226 | 0 | if ((IsPlaintextEditor() && node->IsHTMLElement(nsGkAtoms::pre)) || |
9227 | 0 | HTMLEditUtils::IsMailCite(node)) { |
9228 | 0 | ret = node->AsElement(); |
9229 | 0 | } |
9230 | 0 | if (node->IsHTMLElement(nsGkAtoms::body)) { |
9231 | 0 | break; |
9232 | 0 | } |
9233 | 0 | } |
9234 | 0 |
|
9235 | 0 | return ret; |
9236 | 0 | } |
9237 | | |
9238 | | nsresult |
9239 | | HTMLEditRules::CacheInlineStyles(nsINode* aNode) |
9240 | 0 | { |
9241 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9242 | 0 |
|
9243 | 0 | if (NS_WARN_IF(!aNode)) { |
9244 | 0 | return NS_ERROR_INVALID_ARG; |
9245 | 0 | } |
9246 | 0 | |
9247 | 0 | nsresult rv = GetInlineStyles(aNode, mCachedStyles); |
9248 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9249 | 0 | return rv; |
9250 | 0 | } |
9251 | 0 | return NS_OK; |
9252 | 0 | } |
9253 | | |
9254 | | nsresult |
9255 | | HTMLEditRules::GetInlineStyles(nsINode* aNode, |
9256 | | StyleCache aStyleCache[SIZE_STYLE_TABLE]) |
9257 | 0 | { |
9258 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9259 | 0 | MOZ_ASSERT(aNode); |
9260 | 0 |
|
9261 | 0 | bool useCSS = HTMLEditorRef().IsCSSEnabled(); |
9262 | 0 |
|
9263 | 0 | for (size_t j = 0; j < SIZE_STYLE_TABLE; ++j) { |
9264 | 0 | // If type-in state is set, don't intervene |
9265 | 0 | bool typeInSet, unused; |
9266 | 0 | HTMLEditorRef().mTypeInState->GetTypingState( |
9267 | 0 | typeInSet, unused, aStyleCache[j].tag, |
9268 | 0 | aStyleCache[j].attr, nullptr); |
9269 | 0 | if (typeInSet) { |
9270 | 0 | continue; |
9271 | 0 | } |
9272 | 0 | |
9273 | 0 | bool isSet = false; |
9274 | 0 | nsAutoString outValue; |
9275 | 0 | // Don't use CSS for <font size>, we don't support it usefully (bug 780035) |
9276 | 0 | if (!useCSS || (aStyleCache[j].tag == nsGkAtoms::font && |
9277 | 0 | aStyleCache[j].attr == nsGkAtoms::size)) { |
9278 | 0 | isSet = |
9279 | 0 | HTMLEditorRef().IsTextPropertySetByContent(aNode, aStyleCache[j].tag, |
9280 | 0 | aStyleCache[j].attr, |
9281 | 0 | nullptr, &outValue); |
9282 | 0 | } else { |
9283 | 0 | isSet = CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet( |
9284 | 0 | aNode, aStyleCache[j].tag, aStyleCache[j].attr, outValue, |
9285 | 0 | CSSEditUtils::eComputed); |
9286 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9287 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9288 | 0 | } |
9289 | 0 | } |
9290 | 0 | if (isSet) { |
9291 | 0 | aStyleCache[j].mPresent = true; |
9292 | 0 | aStyleCache[j].value.Assign(outValue); |
9293 | 0 | } |
9294 | 0 | } |
9295 | 0 | return NS_OK; |
9296 | 0 | } |
9297 | | |
9298 | | nsresult |
9299 | | HTMLEditRules::ReapplyCachedStyles() |
9300 | 0 | { |
9301 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9302 | 0 |
|
9303 | 0 | // The idea here is to examine our cached list of styles and see if any have |
9304 | 0 | // been removed. If so, add typeinstate for them, so that they will be |
9305 | 0 | // reinserted when new content is added. |
9306 | 0 |
|
9307 | 0 | // remember if we are in css mode |
9308 | 0 | bool useCSS = HTMLEditorRef().IsCSSEnabled(); |
9309 | 0 |
|
9310 | 0 | if (!SelectionRef().RangeCount()) { |
9311 | 0 | // Nothing to do |
9312 | 0 | return NS_OK; |
9313 | 0 | } |
9314 | 0 | const RangeBoundary& atStartOfSelection = |
9315 | 0 | SelectionRef().GetRangeAt(0)->StartRef(); |
9316 | 0 | nsCOMPtr<nsIContent> selNode = |
9317 | 0 | atStartOfSelection.Container() && |
9318 | 0 | atStartOfSelection.Container()->IsContent() ? |
9319 | 0 | atStartOfSelection.Container()->AsContent() : nullptr; |
9320 | 0 | if (!selNode) { |
9321 | 0 | // Nothing to do |
9322 | 0 | return NS_OK; |
9323 | 0 | } |
9324 | 0 | |
9325 | 0 | StyleCache styleAtInsertionPoint[SIZE_STYLE_TABLE]; |
9326 | 0 | InitStyleCacheArray(styleAtInsertionPoint); |
9327 | 0 | nsresult rv = GetInlineStyles(selNode, styleAtInsertionPoint); |
9328 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9329 | 0 | return rv == NS_ERROR_EDITOR_DESTROYED ? NS_ERROR_EDITOR_DESTROYED : NS_OK; |
9330 | 0 | } |
9331 | 0 |
|
9332 | 0 | for (size_t i = 0; i < SIZE_STYLE_TABLE; ++i) { |
9333 | 0 | if (mCachedStyles[i].mPresent) { |
9334 | 0 | bool bFirst, bAny, bAll; |
9335 | 0 | bFirst = bAny = bAll = false; |
9336 | 0 |
|
9337 | 0 | nsAutoString curValue; |
9338 | 0 | if (useCSS) { |
9339 | 0 | // check computed style first in css case |
9340 | 0 | bAny = CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet( |
9341 | 0 | selNode, mCachedStyles[i].tag, mCachedStyles[i].attr, curValue, |
9342 | 0 | CSSEditUtils::eComputed); |
9343 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9344 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9345 | 0 | } |
9346 | 0 | } |
9347 | 0 | if (!bAny) { |
9348 | 0 | // then check typeinstate and html style |
9349 | 0 | nsresult rv = |
9350 | 0 | HTMLEditorRef().GetInlinePropertyBase(*mCachedStyles[i].tag, |
9351 | 0 | mCachedStyles[i].attr, |
9352 | 0 | &(mCachedStyles[i].value), |
9353 | 0 | &bFirst, &bAny, &bAll, |
9354 | 0 | &curValue); |
9355 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9356 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9357 | 0 | } |
9358 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9359 | 0 | return rv; |
9360 | 0 | } |
9361 | 0 | } |
9362 | 0 | // This style has disappeared through deletion. Let's add the styles to |
9363 | 0 | // mTypeInState when same style isn't applied to the node already. |
9364 | 0 | if ((!bAny || IsStyleCachePreservingSubAction(mTopLevelEditSubAction)) && |
9365 | 0 | (!styleAtInsertionPoint[i].mPresent || |
9366 | 0 | styleAtInsertionPoint[i].value != mCachedStyles[i].value)) { |
9367 | 0 | HTMLEditorRef().mTypeInState->SetProp(mCachedStyles[i].tag, |
9368 | 0 | mCachedStyles[i].attr, |
9369 | 0 | mCachedStyles[i].value); |
9370 | 0 | } |
9371 | 0 | } |
9372 | 0 | } |
9373 | 0 |
|
9374 | 0 | return NS_OK; |
9375 | 0 | } |
9376 | | |
9377 | | void |
9378 | | HTMLEditRules::ClearCachedStyles() |
9379 | 0 | { |
9380 | 0 | // clear the mPresent bits in mCachedStyles array |
9381 | 0 | for (size_t j = 0; j < SIZE_STYLE_TABLE; j++) { |
9382 | 0 | mCachedStyles[j].mPresent = false; |
9383 | 0 | mCachedStyles[j].value.Truncate(); |
9384 | 0 | } |
9385 | 0 | } |
9386 | | |
9387 | | nsresult |
9388 | | HTMLEditRules::InsertBRElementToEmptyListItemsAndTableCellsInChangedRange() |
9389 | 0 | { |
9390 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9391 | 0 |
|
9392 | 0 | // Gather list of empty nodes |
9393 | 0 | nsTArray<OwningNonNull<nsINode>> nodeArray; |
9394 | 0 | EmptyEditableFunctor functor(&HTMLEditorRef()); |
9395 | 0 | DOMIterator iter; |
9396 | 0 | if (NS_WARN_IF(NS_FAILED(iter.Init(*mDocChangeRange)))) { |
9397 | 0 | return NS_ERROR_FAILURE; |
9398 | 0 | } |
9399 | 0 | iter.AppendList(functor, nodeArray); |
9400 | 0 |
|
9401 | 0 | // Put moz-br's into these empty li's and td's |
9402 | 0 | for (auto& node : nodeArray) { |
9403 | 0 | // Need to put br at END of node. It may have empty containers in it and |
9404 | 0 | // still pass the "IsEmptyNode" test, and we want the br's to be after |
9405 | 0 | // them. Also, we want the br to be after the selection if the selection |
9406 | 0 | // is in this node. |
9407 | 0 | EditorRawDOMPoint endOfNode; |
9408 | 0 | endOfNode.SetToEndOf(node); |
9409 | 0 | // XXX This method should return nsreuslt due to may be destroyed by this |
9410 | 0 | // CreateMozBr() call. |
9411 | 0 | CreateElementResult createMozBrResult = CreateMozBR(endOfNode); |
9412 | 0 | if (NS_WARN_IF(createMozBrResult.Failed())) { |
9413 | 0 | return createMozBrResult.Rv(); |
9414 | 0 | } |
9415 | 0 | } |
9416 | 0 | return NS_OK; |
9417 | 0 | } |
9418 | | |
9419 | | nsresult |
9420 | | HTMLEditRules::AdjustWhitespace() |
9421 | 0 | { |
9422 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9423 | 0 |
|
9424 | 0 | EditorRawDOMPoint selectionStartPoint( |
9425 | 0 | EditorBase::GetStartPoint(&SelectionRef())); |
9426 | 0 | if (NS_WARN_IF(!selectionStartPoint.IsSet())) { |
9427 | 0 | return NS_ERROR_FAILURE; |
9428 | 0 | } |
9429 | 0 | |
9430 | 0 | // Ask whitespace object to tweak nbsp's |
9431 | 0 | nsresult rv = |
9432 | 0 | WSRunObject(&HTMLEditorRef(), selectionStartPoint).AdjustWhitespace(); |
9433 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9434 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9435 | 0 | } |
9436 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9437 | 0 | return rv; |
9438 | 0 | } |
9439 | 0 | return NS_OK; |
9440 | 0 | } |
9441 | | |
9442 | | nsresult |
9443 | | HTMLEditRules::PinSelectionToNewBlock() |
9444 | 0 | { |
9445 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9446 | 0 |
|
9447 | 0 | if (!SelectionRef().IsCollapsed()) { |
9448 | 0 | return NS_OK; |
9449 | 0 | } |
9450 | 0 | |
9451 | 0 | if (NS_WARN_IF(!mNewBlock)) { |
9452 | 0 | return NS_ERROR_NULL_POINTER; |
9453 | 0 | } |
9454 | 0 | |
9455 | 0 | EditorRawDOMPoint selectionStartPoint( |
9456 | 0 | EditorBase::GetStartPoint(&SelectionRef())); |
9457 | 0 | if (NS_WARN_IF(!selectionStartPoint.IsSet())) { |
9458 | 0 | return NS_ERROR_FAILURE; |
9459 | 0 | } |
9460 | 0 | |
9461 | 0 | // Use ranges and nsRange::CompareNodeToRange() to compare selection start |
9462 | 0 | // to new block. |
9463 | 0 | // XXX It's too expensive to use nsRange and set it only for comparing a |
9464 | 0 | // DOM point with a node. |
9465 | 0 | RefPtr<nsRange> range = new nsRange(selectionStartPoint.GetContainer()); |
9466 | 0 | nsresult rv = range->CollapseTo(selectionStartPoint); |
9467 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9468 | 0 | return rv; |
9469 | 0 | } |
9470 | 0 | |
9471 | 0 | bool nodeBefore, nodeAfter; |
9472 | 0 | rv = nsRange::CompareNodeToRange(mNewBlock, range, &nodeBefore, &nodeAfter); |
9473 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9474 | 0 | return rv; |
9475 | 0 | } |
9476 | 0 | |
9477 | 0 | if (nodeBefore && nodeAfter) { |
9478 | 0 | return NS_OK; // selection is inside block |
9479 | 0 | } |
9480 | 0 | |
9481 | 0 | if (nodeBefore) { |
9482 | 0 | // selection is after block. put at end of block. |
9483 | 0 | nsCOMPtr<nsINode> tmp = HTMLEditorRef().GetLastEditableChild(*mNewBlock); |
9484 | 0 | if (!tmp) { |
9485 | 0 | tmp = mNewBlock; |
9486 | 0 | } |
9487 | 0 | EditorRawDOMPoint endPoint; |
9488 | 0 | if (EditorBase::IsTextNode(tmp) || |
9489 | 0 | HTMLEditorRef().IsContainer(tmp)) { |
9490 | 0 | endPoint.SetToEndOf(tmp); |
9491 | 0 | } else { |
9492 | 0 | endPoint.Set(tmp); |
9493 | 0 | if (NS_WARN_IF(!endPoint.AdvanceOffset())) { |
9494 | 0 | return NS_ERROR_FAILURE; |
9495 | 0 | } |
9496 | 0 | } |
9497 | 0 | ErrorResult error; |
9498 | 0 | SelectionRef().Collapse(endPoint, error); |
9499 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9500 | 0 | error.SuppressException(); |
9501 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9502 | 0 | } |
9503 | 0 | if (NS_WARN_IF(error.Failed())) { |
9504 | 0 | return error.StealNSResult(); |
9505 | 0 | } |
9506 | 0 | return NS_OK; |
9507 | 0 | } |
9508 | 0 | |
9509 | 0 | // selection is before block. put at start of block. |
9510 | 0 | nsCOMPtr<nsINode> tmp = HTMLEditorRef().GetFirstEditableChild(*mNewBlock); |
9511 | 0 | if (!tmp) { |
9512 | 0 | tmp = mNewBlock; |
9513 | 0 | } |
9514 | 0 | EditorRawDOMPoint atStartOfBlock; |
9515 | 0 | if (EditorBase::IsTextNode(tmp) || |
9516 | 0 | HTMLEditorRef().IsContainer(tmp)) { |
9517 | 0 | atStartOfBlock.Set(tmp); |
9518 | 0 | } else { |
9519 | 0 | atStartOfBlock.Set(tmp, 0); |
9520 | 0 | } |
9521 | 0 | ErrorResult error; |
9522 | 0 | SelectionRef().Collapse(atStartOfBlock, error); |
9523 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9524 | 0 | error.SuppressException(); |
9525 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9526 | 0 | } |
9527 | 0 | if (NS_WARN_IF(error.Failed())) { |
9528 | 0 | return error.StealNSResult(); |
9529 | 0 | } |
9530 | 0 | return NS_OK; |
9531 | 0 | } |
9532 | | |
9533 | | void |
9534 | | HTMLEditRules::CheckInterlinePosition() |
9535 | 0 | { |
9536 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9537 | 0 |
|
9538 | 0 | // If the selection isn't collapsed, do nothing. |
9539 | 0 | if (!SelectionRef().IsCollapsed()) { |
9540 | 0 | return; |
9541 | 0 | } |
9542 | 0 | |
9543 | 0 | // Get the (collapsed) selection location |
9544 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
9545 | 0 | if (NS_WARN_IF(!firstRange)) { |
9546 | 0 | return; |
9547 | 0 | } |
9548 | 0 | |
9549 | 0 | EditorDOMPoint atStartOfSelection(firstRange->StartRef()); |
9550 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
9551 | 0 | return; |
9552 | 0 | } |
9553 | 0 | MOZ_ASSERT(atStartOfSelection.IsSetAndValid()); |
9554 | 0 |
|
9555 | 0 | // First, let's check to see if we are after a <br>. We take care of this |
9556 | 0 | // special-case first so that we don't accidentally fall through into one of |
9557 | 0 | // the other conditionals. |
9558 | 0 | nsCOMPtr<nsIContent> node = |
9559 | 0 | HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(atStartOfSelection); |
9560 | 0 | if (node && node->IsHTMLElement(nsGkAtoms::br)) { |
9561 | 0 | IgnoredErrorResult ignoredError; |
9562 | 0 | SelectionRef().SetInterlinePosition(true, ignoredError); |
9563 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
9564 | 0 | "Failed to set interline position"); |
9565 | 0 | return; |
9566 | 0 | } |
9567 | 0 |
|
9568 | 0 | // Are we after a block? If so try set caret to following content |
9569 | 0 | if (atStartOfSelection.GetChild()) { |
9570 | 0 | node = HTMLEditorRef().GetPriorHTMLSibling(atStartOfSelection.GetChild()); |
9571 | 0 | } else { |
9572 | 0 | node = nullptr; |
9573 | 0 | } |
9574 | 0 | if (node && IsBlockNode(*node)) { |
9575 | 0 | IgnoredErrorResult ignoredError; |
9576 | 0 | SelectionRef().SetInterlinePosition(true, ignoredError); |
9577 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
9578 | 0 | "Failed to set interline position"); |
9579 | 0 | return; |
9580 | 0 | } |
9581 | 0 |
|
9582 | 0 | // Are we before a block? If so try set caret to prior content |
9583 | 0 | if (atStartOfSelection.GetChild()) { |
9584 | 0 | node = HTMLEditorRef().GetNextHTMLSibling(atStartOfSelection.GetChild()); |
9585 | 0 | } else { |
9586 | 0 | node = nullptr; |
9587 | 0 | } |
9588 | 0 | if (node && IsBlockNode(*node)) { |
9589 | 0 | IgnoredErrorResult ignoredError; |
9590 | 0 | SelectionRef().SetInterlinePosition(false, ignoredError); |
9591 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
9592 | 0 | "Failed to unset interline position"); |
9593 | 0 | } |
9594 | 0 | } |
9595 | | |
9596 | | nsresult |
9597 | | HTMLEditRules::AdjustSelection(nsIEditor::EDirection aAction) |
9598 | 0 | { |
9599 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9600 | 0 |
|
9601 | 0 | // if the selection isn't collapsed, do nothing. |
9602 | 0 | // moose: one thing to do instead is check for the case of |
9603 | 0 | // only a single break selected, and collapse it. Good thing? Beats me. |
9604 | 0 | if (!SelectionRef().IsCollapsed()) { |
9605 | 0 | return NS_OK; |
9606 | 0 | } |
9607 | 0 | |
9608 | 0 | // get the (collapsed) selection location |
9609 | 0 | EditorDOMPoint point(EditorBase::GetStartPoint(&SelectionRef())); |
9610 | 0 | if (NS_WARN_IF(!point.IsSet())) { |
9611 | 0 | return NS_ERROR_FAILURE; |
9612 | 0 | } |
9613 | 0 | |
9614 | 0 | // are we in an editable node? |
9615 | 0 | while (!HTMLEditorRef().IsEditable(point.GetContainer())) { |
9616 | 0 | // scan up the tree until we find an editable place to be |
9617 | 0 | point.Set(point.GetContainer()); |
9618 | 0 | if (NS_WARN_IF(!point.IsSet())) { |
9619 | 0 | return NS_ERROR_FAILURE; |
9620 | 0 | } |
9621 | 0 | } |
9622 | 0 |
|
9623 | 0 | // make sure we aren't in an empty block - user will see no cursor. If this |
9624 | 0 | // is happening, put a <br> in the block if allowed. |
9625 | 0 | RefPtr<Element> theblock = HTMLEditorRef().GetBlock(*point.GetContainer()); |
9626 | 0 |
|
9627 | 0 | if (theblock && HTMLEditorRef().IsEditable(theblock)) { |
9628 | 0 | bool isEmptyNode; |
9629 | 0 | nsresult rv = |
9630 | 0 | HTMLEditorRef().IsEmptyNode(theblock, &isEmptyNode, false, false); |
9631 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9632 | 0 | return rv; |
9633 | 0 | } |
9634 | 0 | // check if br can go into the destination node |
9635 | 0 | if (isEmptyNode && |
9636 | 0 | HTMLEditorRef().CanContainTag(*point.GetContainer(), *nsGkAtoms::br)) { |
9637 | 0 | Element* rootElement = HTMLEditorRef().GetRoot(); |
9638 | 0 | if (NS_WARN_IF(!rootElement)) { |
9639 | 0 | return NS_ERROR_FAILURE; |
9640 | 0 | } |
9641 | 0 | if (point.GetContainer() == rootElement) { |
9642 | 0 | // Our root node is completely empty. Don't add a <br> here. |
9643 | 0 | // AfterEditInner() will add one for us when it calls |
9644 | 0 | // CreateBogusNodeIfNeeded()! |
9645 | 0 | return NS_OK; |
9646 | 0 | } |
9647 | 0 | |
9648 | 0 | // we know we can skip the rest of this routine given the cirumstance |
9649 | 0 | CreateElementResult createMozBrResult = CreateMozBR(point); |
9650 | 0 | if (NS_WARN_IF(createMozBrResult.Failed())) { |
9651 | 0 | return createMozBrResult.Rv(); |
9652 | 0 | } |
9653 | 0 | return NS_OK; |
9654 | 0 | } |
9655 | 0 | } |
9656 | 0 |
|
9657 | 0 | // are we in a text node? |
9658 | 0 | if (point.IsInTextNode()) { |
9659 | 0 | return NS_OK; // we LIKE it when we are in a text node. that RULZ |
9660 | 0 | } |
9661 | 0 | |
9662 | 0 | // do we need to insert a special mozBR? We do if we are: |
9663 | 0 | // 1) prior node is in same block where selection is AND |
9664 | 0 | // 2) prior node is a br AND |
9665 | 0 | // 3) that br is not visible |
9666 | 0 | |
9667 | 0 | nsCOMPtr<nsIContent> nearNode = |
9668 | 0 | HTMLEditorRef().GetPreviousEditableHTMLNode(point); |
9669 | 0 | if (nearNode) { |
9670 | 0 | // is nearNode also a descendant of same block? |
9671 | 0 | RefPtr<Element> block = HTMLEditorRef().GetBlock(*point.GetContainer()); |
9672 | 0 | RefPtr<Element> nearBlock = HTMLEditorRef().GetBlockNodeParent(nearNode); |
9673 | 0 | if (block && block == nearBlock) { |
9674 | 0 | if (nearNode && TextEditUtils::IsBreak(nearNode)) { |
9675 | 0 | if (!HTMLEditorRef().IsVisibleBRElement(nearNode)) { |
9676 | 0 | // need to insert special moz BR. Why? Because if we don't |
9677 | 0 | // the user will see no new line for the break. Also, things |
9678 | 0 | // like table cells won't grow in height. |
9679 | 0 | CreateElementResult createMozBrResult = CreateMozBR(point); |
9680 | 0 | if (NS_WARN_IF(createMozBrResult.Failed())) { |
9681 | 0 | return createMozBrResult.Rv(); |
9682 | 0 | } |
9683 | 0 | point.Set(createMozBrResult.GetNewNode()); |
9684 | 0 | // selection stays *before* moz-br, sticking to it |
9685 | 0 | ErrorResult error; |
9686 | 0 | SelectionRef().SetInterlinePosition(true, error); |
9687 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9688 | 0 | error.SuppressException(); |
9689 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9690 | 0 | } |
9691 | 0 | NS_WARNING_ASSERTION(!error.Failed(), |
9692 | 0 | "Failed to set interline position"); |
9693 | 0 | error = NS_OK; |
9694 | 0 | SelectionRef().Collapse(point, error); |
9695 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9696 | 0 | error.SuppressException(); |
9697 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9698 | 0 | } |
9699 | 0 | if (NS_WARN_IF(error.Failed())) { |
9700 | 0 | return error.StealNSResult(); |
9701 | 0 | } |
9702 | 0 | } else { |
9703 | 0 | nsCOMPtr<nsIContent> nextNode = |
9704 | 0 | HTMLEditorRef().GetNextEditableHTMLNodeInBlock(*nearNode); |
9705 | 0 | if (nextNode && TextEditUtils::IsMozBR(nextNode)) { |
9706 | 0 | // selection between br and mozbr. make it stick to mozbr |
9707 | 0 | // so that it will be on blank line. |
9708 | 0 | IgnoredErrorResult ignoredError; |
9709 | 0 | SelectionRef().SetInterlinePosition(true, ignoredError); |
9710 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
9711 | 0 | "Failed to set interline position"); |
9712 | 0 | } |
9713 | 0 | } |
9714 | 0 | } |
9715 | 0 | } |
9716 | 0 | } |
9717 | 0 |
|
9718 | 0 | // we aren't in a textnode: are we adjacent to text or a break or an image? |
9719 | 0 | nearNode = HTMLEditorRef().GetPreviousEditableHTMLNodeInBlock(point); |
9720 | 0 | if (nearNode && (TextEditUtils::IsBreak(nearNode) || |
9721 | 0 | EditorBase::IsTextNode(nearNode) || |
9722 | 0 | HTMLEditUtils::IsImage(nearNode) || |
9723 | 0 | nearNode->IsHTMLElement(nsGkAtoms::hr))) { |
9724 | 0 | // this is a good place for the caret to be |
9725 | 0 | return NS_OK; |
9726 | 0 | } |
9727 | 0 | nearNode = HTMLEditorRef().GetNextEditableHTMLNodeInBlock(point); |
9728 | 0 | if (nearNode && (TextEditUtils::IsBreak(nearNode) || |
9729 | 0 | EditorBase::IsTextNode(nearNode) || |
9730 | 0 | nearNode->IsAnyOfHTMLElements(nsGkAtoms::img, |
9731 | 0 | nsGkAtoms::hr))) { |
9732 | 0 | return NS_OK; // this is a good place for the caret to be |
9733 | 0 | } |
9734 | 0 | |
9735 | 0 | // look for a nearby text node. |
9736 | 0 | // prefer the correct direction. |
9737 | 0 | nearNode = FindNearEditableNode(point, aAction); |
9738 | 0 | if (!nearNode) { |
9739 | 0 | return NS_OK; |
9740 | 0 | } |
9741 | 0 | |
9742 | 0 | EditorDOMPoint pt = GetGoodSelPointForNode(*nearNode, aAction); |
9743 | 0 | ErrorResult error; |
9744 | 0 | SelectionRef().Collapse(pt, error); |
9745 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9746 | 0 | error.SuppressException(); |
9747 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9748 | 0 | } |
9749 | 0 | if (NS_WARN_IF(error.Failed())) { |
9750 | 0 | return error.StealNSResult(); |
9751 | 0 | } |
9752 | 0 | return NS_OK; |
9753 | 0 | } |
9754 | | |
9755 | | template<typename PT, typename CT> |
9756 | | nsIContent* |
9757 | | HTMLEditRules::FindNearEditableNode(const EditorDOMPointBase<PT, CT>& aPoint, |
9758 | | nsIEditor::EDirection aDirection) |
9759 | 0 | { |
9760 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9761 | 0 |
|
9762 | 0 | if (NS_WARN_IF(!aPoint.IsSet())) { |
9763 | 0 | return nullptr; |
9764 | 0 | } |
9765 | 0 | MOZ_ASSERT(aPoint.IsSetAndValid()); |
9766 | 0 |
|
9767 | 0 | nsIContent* nearNode = nullptr; |
9768 | 0 | if (aDirection == nsIEditor::ePrevious) { |
9769 | 0 | nearNode = HTMLEditorRef().GetPreviousEditableHTMLNode(aPoint); |
9770 | 0 | if (!nearNode) { |
9771 | 0 | return nullptr; // Not illegal. |
9772 | 0 | } |
9773 | 0 | } else { |
9774 | 0 | nearNode = HTMLEditorRef().GetNextEditableHTMLNode(aPoint); |
9775 | 0 | if (NS_WARN_IF(!nearNode)) { |
9776 | 0 | // Perhaps, illegal because the node pointed by aPoint isn't editable |
9777 | 0 | // and nobody of previous nodes is editable. |
9778 | 0 | return nullptr; |
9779 | 0 | } |
9780 | 0 | } |
9781 | 0 | |
9782 | 0 | // scan in the right direction until we find an eligible text node, |
9783 | 0 | // but don't cross any breaks, images, or table elements. |
9784 | 0 | // XXX This comment sounds odd. |nearNode| may have already crossed breaks |
9785 | 0 | // and/or images. |
9786 | 0 | while (nearNode && !(EditorBase::IsTextNode(nearNode) || |
9787 | 0 | TextEditUtils::IsBreak(nearNode) || |
9788 | 0 | HTMLEditUtils::IsImage(nearNode))) { |
9789 | 0 | if (aDirection == nsIEditor::ePrevious) { |
9790 | 0 | nearNode = HTMLEditorRef().GetPreviousEditableHTMLNode(*nearNode); |
9791 | 0 | if (NS_WARN_IF(!nearNode)) { |
9792 | 0 | return nullptr; |
9793 | 0 | } |
9794 | 0 | } else { |
9795 | 0 | nearNode = HTMLEditorRef().GetNextEditableHTMLNode(*nearNode); |
9796 | 0 | if (NS_WARN_IF(!nearNode)) { |
9797 | 0 | return nullptr; |
9798 | 0 | } |
9799 | 0 | } |
9800 | 0 | } |
9801 | 0 |
|
9802 | 0 | // don't cross any table elements |
9803 | 0 | if (InDifferentTableElements(nearNode, aPoint.GetContainer())) { |
9804 | 0 | return nullptr; |
9805 | 0 | } |
9806 | 0 | |
9807 | 0 | // otherwise, ok, we have found a good spot to put the selection |
9808 | 0 | return nearNode; |
9809 | 0 | } |
9810 | | |
9811 | | bool |
9812 | | HTMLEditRules::InDifferentTableElements(nsINode* aNode1, |
9813 | | nsINode* aNode2) |
9814 | 0 | { |
9815 | 0 | MOZ_ASSERT(aNode1 && aNode2); |
9816 | 0 |
|
9817 | 0 | while (aNode1 && !HTMLEditUtils::IsTableElement(aNode1)) { |
9818 | 0 | aNode1 = aNode1->GetParentNode(); |
9819 | 0 | } |
9820 | 0 |
|
9821 | 0 | while (aNode2 && !HTMLEditUtils::IsTableElement(aNode2)) { |
9822 | 0 | aNode2 = aNode2->GetParentNode(); |
9823 | 0 | } |
9824 | 0 |
|
9825 | 0 | return aNode1 != aNode2; |
9826 | 0 | } |
9827 | | |
9828 | | |
9829 | | nsresult |
9830 | | HTMLEditRules::RemoveEmptyNodesInChangedRange() |
9831 | 0 | { |
9832 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9833 | 0 |
|
9834 | 0 | // Some general notes on the algorithm used here: the goal is to examine all |
9835 | 0 | // the nodes in mDocChangeRange, and remove the empty ones. We do this by |
9836 | 0 | // using a content iterator to traverse all the nodes in the range, and |
9837 | 0 | // placing the empty nodes into an array. After finishing the iteration, we |
9838 | 0 | // delete the empty nodes in the array. (They cannot be deleted as we find |
9839 | 0 | // them because that would invalidate the iterator.) |
9840 | 0 | // |
9841 | 0 | // Since checking to see if a node is empty can be costly for nodes with many |
9842 | 0 | // descendants, there are some optimizations made. I rely on the fact that |
9843 | 0 | // the iterator is post-order: it will visit children of a node before |
9844 | 0 | // visiting the parent node. So if I find that a child node is not empty, I |
9845 | 0 | // know that its parent is not empty without even checking. So I put the |
9846 | 0 | // parent on a "skipList" which is just a voidArray of nodes I can skip the |
9847 | 0 | // empty check on. If I encounter a node on the skiplist, i skip the |
9848 | 0 | // processing for that node and replace its slot in the skiplist with that |
9849 | 0 | // node's parent. |
9850 | 0 | // |
9851 | 0 | // An interesting idea is to go ahead and regard parent nodes that are NOT on |
9852 | 0 | // the skiplist as being empty (without even doing the IsEmptyNode check) on |
9853 | 0 | // the theory that if they weren't empty, we would have encountered a |
9854 | 0 | // non-empty child earlier and thus put this parent node on the skiplist. |
9855 | 0 | // |
9856 | 0 | // Unfortunately I can't use that strategy here, because the range may |
9857 | 0 | // include some children of a node while excluding others. Thus I could find |
9858 | 0 | // all the _examined_ children empty, but still not have an empty parent. |
9859 | 0 |
|
9860 | 0 | // need an iterator |
9861 | 0 | nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator(); |
9862 | 0 |
|
9863 | 0 | nsresult rv = iter->Init(mDocChangeRange); |
9864 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9865 | 0 | return rv; |
9866 | 0 | } |
9867 | 0 | |
9868 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfEmptyNodes, arrayOfEmptyCites, skipList; |
9869 | 0 |
|
9870 | 0 | // Check for empty nodes |
9871 | 0 | while (!iter->IsDone()) { |
9872 | 0 | OwningNonNull<nsINode> node = *iter->GetCurrentNode(); |
9873 | 0 |
|
9874 | 0 | nsCOMPtr<nsINode> parent = node->GetParentNode(); |
9875 | 0 |
|
9876 | 0 | size_t idx = skipList.IndexOf(node); |
9877 | 0 | if (idx != skipList.NoIndex) { |
9878 | 0 | // This node is on our skip list. Skip processing for this node, and |
9879 | 0 | // replace its value in the skip list with the value of its parent |
9880 | 0 | if (parent) { |
9881 | 0 | skipList[idx] = parent; |
9882 | 0 | } |
9883 | 0 | } else { |
9884 | 0 | bool isCandidate = false; |
9885 | 0 | bool isEmptyNode = false; |
9886 | 0 | bool isMailCite = false; |
9887 | 0 |
|
9888 | 0 | if (node->IsElement()) { |
9889 | 0 | if (node->IsHTMLElement(nsGkAtoms::body)) { |
9890 | 0 | // Don't delete the body |
9891 | 0 | } else if ((isMailCite = HTMLEditUtils::IsMailCite(node)) || |
9892 | 0 | node->IsHTMLElement(nsGkAtoms::a) || |
9893 | 0 | HTMLEditUtils::IsInlineStyle(node) || |
9894 | 0 | HTMLEditUtils::IsList(node) || |
9895 | 0 | node->IsHTMLElement(nsGkAtoms::div)) { |
9896 | 0 | // Only consider certain nodes to be empty for purposes of removal |
9897 | 0 | isCandidate = true; |
9898 | 0 | } else if (HTMLEditUtils::IsFormatNode(node) || |
9899 | 0 | HTMLEditUtils::IsListItem(node) || |
9900 | 0 | node->IsHTMLElement(nsGkAtoms::blockquote)) { |
9901 | 0 | // These node types are candidates if selection is not in them. If |
9902 | 0 | // it is one of these, don't delete if selection inside. This is so |
9903 | 0 | // we can create empty headings, etc., for the user to type into. |
9904 | 0 | bool isSelectionEndInNode; |
9905 | 0 | rv = SelectionEndpointInNode(node, &isSelectionEndInNode); |
9906 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9907 | 0 | return rv; |
9908 | 0 | } |
9909 | 0 | if (!isSelectionEndInNode) { |
9910 | 0 | isCandidate = true; |
9911 | 0 | } |
9912 | 0 | } |
9913 | 0 | } |
9914 | 0 |
|
9915 | 0 | if (isCandidate) { |
9916 | 0 | // We delete mailcites even if they have a solo br in them. Other |
9917 | 0 | // nodes we require to be empty. |
9918 | 0 | rv = HTMLEditorRef().IsEmptyNode(node, &isEmptyNode, |
9919 | 0 | isMailCite, true); |
9920 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9921 | 0 | return rv; |
9922 | 0 | } |
9923 | 0 | if (isEmptyNode) { |
9924 | 0 | if (isMailCite) { |
9925 | 0 | // mailcites go on a separate list from other empty nodes |
9926 | 0 | arrayOfEmptyCites.AppendElement(*node); |
9927 | 0 | } else { |
9928 | 0 | arrayOfEmptyNodes.AppendElement(*node); |
9929 | 0 | } |
9930 | 0 | } |
9931 | 0 | } |
9932 | 0 |
|
9933 | 0 | if (!isEmptyNode && parent) { |
9934 | 0 | // put parent on skip list |
9935 | 0 | skipList.AppendElement(*parent); |
9936 | 0 | } |
9937 | 0 | } |
9938 | 0 |
|
9939 | 0 | iter->Next(); |
9940 | 0 | } |
9941 | 0 |
|
9942 | 0 | // now delete the empty nodes |
9943 | 0 | for (OwningNonNull<nsINode>& delNode : arrayOfEmptyNodes) { |
9944 | 0 | if (HTMLEditorRef().IsModifiableNode(delNode)) { |
9945 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*delNode); |
9946 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9947 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9948 | 0 | } |
9949 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9950 | 0 | return rv; |
9951 | 0 | } |
9952 | 0 | } |
9953 | 0 | } |
9954 | 0 |
|
9955 | 0 | // Now delete the empty mailcites. This is a separate step because we want |
9956 | 0 | // to pull out any br's and preserve them. |
9957 | 0 | for (OwningNonNull<nsINode>& delNode : arrayOfEmptyCites) { |
9958 | 0 | bool isEmptyNode; |
9959 | 0 | rv = HTMLEditorRef().IsEmptyNode(delNode, &isEmptyNode, false, true); |
9960 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9961 | 0 | return rv; |
9962 | 0 | } |
9963 | 0 | if (!isEmptyNode) { |
9964 | 0 | // We are deleting a cite that has just a br. We want to delete cite, |
9965 | 0 | // but preserve br. |
9966 | 0 | RefPtr<Element> brElement = |
9967 | 0 | HTMLEditorRef().InsertBrElementWithTransaction( |
9968 | 0 | SelectionRef(), EditorRawDOMPoint(delNode)); |
9969 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9970 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9971 | 0 | } |
9972 | 0 | if (NS_WARN_IF(!brElement)) { |
9973 | 0 | return NS_ERROR_FAILURE; |
9974 | 0 | } |
9975 | 0 | } |
9976 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*delNode); |
9977 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
9978 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
9979 | 0 | } |
9980 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
9981 | 0 | return rv; |
9982 | 0 | } |
9983 | 0 | } |
9984 | 0 |
|
9985 | 0 | return NS_OK; |
9986 | 0 | } |
9987 | | |
9988 | | nsresult |
9989 | | HTMLEditRules::SelectionEndpointInNode(nsINode* aNode, |
9990 | | bool* aResult) |
9991 | 0 | { |
9992 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
9993 | 0 |
|
9994 | 0 | NS_ENSURE_TRUE(aNode && aResult, NS_ERROR_NULL_POINTER); |
9995 | 0 |
|
9996 | 0 | *aResult = false; |
9997 | 0 |
|
9998 | 0 | uint32_t rangeCount = SelectionRef().RangeCount(); |
9999 | 0 | for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) { |
10000 | 0 | RefPtr<nsRange> range = SelectionRef().GetRangeAt(rangeIdx); |
10001 | 0 | nsINode* startContainer = range->GetStartContainer(); |
10002 | 0 | if (startContainer) { |
10003 | 0 | if (aNode == startContainer) { |
10004 | 0 | *aResult = true; |
10005 | 0 | return NS_OK; |
10006 | 0 | } |
10007 | 0 | if (EditorUtils::IsDescendantOf(*startContainer, *aNode)) { |
10008 | 0 | *aResult = true; |
10009 | 0 | return NS_OK; |
10010 | 0 | } |
10011 | 0 | } |
10012 | 0 | nsINode* endContainer = range->GetEndContainer(); |
10013 | 0 | if (startContainer == endContainer) { |
10014 | 0 | continue; |
10015 | 0 | } |
10016 | 0 | if (endContainer) { |
10017 | 0 | if (aNode == endContainer) { |
10018 | 0 | *aResult = true; |
10019 | 0 | return NS_OK; |
10020 | 0 | } |
10021 | 0 | if (EditorUtils::IsDescendantOf(*endContainer, *aNode)) { |
10022 | 0 | *aResult = true; |
10023 | 0 | return NS_OK; |
10024 | 0 | } |
10025 | 0 | } |
10026 | 0 | } |
10027 | 0 | return NS_OK; |
10028 | 0 | } |
10029 | | |
10030 | | bool |
10031 | | HTMLEditRules::IsEmptyInline(nsINode& aNode) |
10032 | 0 | { |
10033 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10034 | 0 |
|
10035 | 0 | if (IsInlineNode(aNode) && HTMLEditorRef().IsContainer(&aNode)) { |
10036 | 0 | bool isEmpty = true; |
10037 | 0 | HTMLEditorRef().IsEmptyNode(&aNode, &isEmpty); |
10038 | 0 | return isEmpty; |
10039 | 0 | } |
10040 | 0 | return false; |
10041 | 0 | } |
10042 | | |
10043 | | |
10044 | | bool |
10045 | | HTMLEditRules::ListIsEmptyLine(nsTArray<OwningNonNull<nsINode>>& aArrayOfNodes) |
10046 | 0 | { |
10047 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10048 | 0 |
|
10049 | 0 | // We have a list of nodes which we are candidates for being moved into a new |
10050 | 0 | // block. Determine if it's anything more than a blank line. Look for |
10051 | 0 | // editable content above and beyond one single BR. |
10052 | 0 | if (NS_WARN_IF(!aArrayOfNodes.Length())) { |
10053 | 0 | return true; |
10054 | 0 | } |
10055 | 0 | |
10056 | 0 | int32_t brCount = 0; |
10057 | 0 | for (auto& node : aArrayOfNodes) { |
10058 | 0 | if (!HTMLEditorRef().IsEditable(node)) { |
10059 | 0 | continue; |
10060 | 0 | } |
10061 | 0 | if (TextEditUtils::IsBreak(node)) { |
10062 | 0 | // First break doesn't count |
10063 | 0 | if (brCount) { |
10064 | 0 | return false; |
10065 | 0 | } |
10066 | 0 | brCount++; |
10067 | 0 | } else if (IsEmptyInline(node)) { |
10068 | 0 | // Empty inline, keep looking |
10069 | 0 | } else { |
10070 | 0 | return false; |
10071 | 0 | } |
10072 | 0 | } |
10073 | 0 | return true; |
10074 | 0 | } |
10075 | | |
10076 | | |
10077 | | nsresult |
10078 | | HTMLEditRules::PopListItem(nsIContent& aListItem, |
10079 | | bool* aOutOfList) |
10080 | 0 | { |
10081 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10082 | 0 |
|
10083 | 0 | if (aOutOfList) { |
10084 | 0 | *aOutOfList = false; |
10085 | 0 | } |
10086 | 0 |
|
10087 | 0 | if (NS_WARN_IF(!aListItem.GetParent()) || |
10088 | 0 | NS_WARN_IF(!aListItem.GetParent()->GetParentNode()) || |
10089 | 0 | !HTMLEditUtils::IsListItem(&aListItem)) { |
10090 | 0 | return NS_ERROR_FAILURE; |
10091 | 0 | } |
10092 | 0 | |
10093 | 0 | // if it's first or last list item, don't need to split the list |
10094 | 0 | // otherwise we do. |
10095 | 0 | bool isFirstListItem = HTMLEditorRef().IsFirstEditableChild(&aListItem); |
10096 | 0 | bool isLastListItem = HTMLEditorRef().IsLastEditableChild(&aListItem); |
10097 | 0 |
|
10098 | 0 | nsCOMPtr<nsIContent> leftListNode = aListItem.GetParent(); |
10099 | 0 |
|
10100 | 0 | // If it's at middle of parent list element, split the parent list element. |
10101 | 0 | // Then, aListItem becomes the first list item of the right list element. |
10102 | 0 | nsCOMPtr<nsIContent> listItem(&aListItem); |
10103 | 0 | if (!isFirstListItem && !isLastListItem) { |
10104 | 0 | EditorDOMPoint atListItem(listItem); |
10105 | 0 | if (NS_WARN_IF(!atListItem.IsSet())) { |
10106 | 0 | return NS_ERROR_INVALID_ARG; |
10107 | 0 | } |
10108 | 0 | MOZ_ASSERT(atListItem.IsSetAndValid()); |
10109 | 0 | ErrorResult error; |
10110 | 0 | leftListNode = HTMLEditorRef().SplitNodeWithTransaction(atListItem, error); |
10111 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10112 | 0 | error.SuppressException(); |
10113 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10114 | 0 | } |
10115 | 0 | if (NS_WARN_IF(error.Failed())) { |
10116 | 0 | return error.StealNSResult(); |
10117 | 0 | } |
10118 | 0 | } |
10119 | 0 | |
10120 | 0 | // In most cases, insert the list item into the new left list node.. |
10121 | 0 | EditorDOMPoint pointToInsertListItem(leftListNode); |
10122 | 0 | if (NS_WARN_IF(!pointToInsertListItem.IsSet())) { |
10123 | 0 | return NS_ERROR_FAILURE; |
10124 | 0 | } |
10125 | 0 | MOZ_ASSERT(pointToInsertListItem.IsSetAndValid()); |
10126 | 0 |
|
10127 | 0 | // But when the list item was the first child of the right list, it should |
10128 | 0 | // be inserted between the both list elements. This allows user to hit |
10129 | 0 | // Enter twice at a list item breaks the parent list node. |
10130 | 0 | if (!isFirstListItem) { |
10131 | 0 | DebugOnly<bool> advanced = pointToInsertListItem.AdvanceOffset(); |
10132 | 0 | NS_WARNING_ASSERTION(advanced, |
10133 | 0 | "Failed to advance offset to right list node"); |
10134 | 0 | } |
10135 | 0 |
|
10136 | 0 | nsresult rv = |
10137 | 0 | HTMLEditorRef().MoveNodeWithTransaction(*listItem, pointToInsertListItem); |
10138 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10139 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10140 | 0 | } |
10141 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10142 | 0 | return rv; |
10143 | 0 | } |
10144 | 0 | |
10145 | 0 | // unwrap list item contents if they are no longer in a list |
10146 | 0 | // XXX If the parent list element is a child of another list element |
10147 | 0 | // (although invalid tree), the list item element won't be unwrapped. |
10148 | 0 | // That makes the parent ancestor element tree valid, but might be |
10149 | 0 | // unexpected result. |
10150 | 0 | // XXX If aListItem is <dl> or <dd> and current parent is <ul> or <ol>, |
10151 | 0 | // the list items won't be unwrapped. If aListItem is <li> and its |
10152 | 0 | // current parent is <dl>, there is same issue. |
10153 | 0 | if (!HTMLEditUtils::IsList(pointToInsertListItem.GetContainer()) && |
10154 | 0 | HTMLEditUtils::IsListItem(listItem)) { |
10155 | 0 | rv = HTMLEditorRef().RemoveBlockContainerWithTransaction( |
10156 | 0 | *listItem->AsElement()); |
10157 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10158 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10159 | 0 | } |
10160 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10161 | 0 | return rv; |
10162 | 0 | } |
10163 | 0 | if (aOutOfList) { |
10164 | 0 | *aOutOfList = true; |
10165 | 0 | } |
10166 | 0 | } |
10167 | 0 | return NS_OK; |
10168 | 0 | } |
10169 | | |
10170 | | nsresult |
10171 | | HTMLEditRules::RemoveListStructure(Element& aListElement) |
10172 | 0 | { |
10173 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10174 | 0 | MOZ_ASSERT(HTMLEditUtils::IsList(&aListElement)); |
10175 | 0 |
|
10176 | 0 | while (aListElement.GetFirstChild()) { |
10177 | 0 | OwningNonNull<nsIContent> child = *aListElement.GetFirstChild(); |
10178 | 0 |
|
10179 | 0 | if (HTMLEditUtils::IsListItem(child)) { |
10180 | 0 | bool isOutOfList; |
10181 | 0 | // Keep popping it out until it's not in a list anymore |
10182 | 0 | // XXX Using PopuListItem() is too expensive for this purpose. Looks |
10183 | 0 | // like the reason why this method uses it is, only this loop |
10184 | 0 | // wants to work with first child of aList. However, what it |
10185 | 0 | // actually does is removing <li> as container. So, just using |
10186 | 0 | // RemoveBlockContainerWithTransaction() is reasonable. |
10187 | 0 | // XXX This loop means that if aListElement is is a child of another |
10188 | 0 | // list element (although it's invalid tree), this moves the |
10189 | 0 | // list item to outside of aListElement's parent. Is that really |
10190 | 0 | // intentional behavior? |
10191 | 0 | do { |
10192 | 0 | nsresult rv = PopListItem(child, &isOutOfList); |
10193 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10194 | 0 | return rv; |
10195 | 0 | } |
10196 | 0 | } while (!isOutOfList); |
10197 | 0 | continue; |
10198 | 0 | } |
10199 | 0 | |
10200 | 0 | if (HTMLEditUtils::IsList(child)) { |
10201 | 0 | nsresult rv = RemoveListStructure(*child->AsElement()); |
10202 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10203 | 0 | return rv; |
10204 | 0 | } |
10205 | 0 | continue; |
10206 | 0 | } |
10207 | 0 | |
10208 | 0 | // Delete any non-list items for now |
10209 | 0 | // XXX This is not HTML5 aware. HTML5 allows all list elements to have |
10210 | 0 | // <script> and <template> and <dl> element to have <div> to group |
10211 | 0 | // some <dt> and <dd> elements. So, this may break valid children. |
10212 | 0 | nsresult rv = HTMLEditorRef().DeleteNodeWithTransaction(*child); |
10213 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10214 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10215 | 0 | } |
10216 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10217 | 0 | return rv; |
10218 | 0 | } |
10219 | 0 | } |
10220 | 0 |
|
10221 | 0 | // Delete the now-empty list |
10222 | 0 | nsresult rv = |
10223 | 0 | HTMLEditorRef().RemoveBlockContainerWithTransaction(aListElement); |
10224 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10225 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10226 | 0 | } |
10227 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10228 | 0 | return rv; |
10229 | 0 | } |
10230 | 0 | return NS_OK; |
10231 | 0 | } |
10232 | | |
10233 | | nsresult |
10234 | | HTMLEditRules::ConfirmSelectionInBody() |
10235 | 0 | { |
10236 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10237 | 0 |
|
10238 | 0 | Element* rootElement = HTMLEditorRef().GetRoot(); |
10239 | 0 | if (NS_WARN_IF(!rootElement)) { |
10240 | 0 | return NS_ERROR_UNEXPECTED; |
10241 | 0 | } |
10242 | 0 | |
10243 | 0 | EditorRawDOMPoint selectionStartPoint( |
10244 | 0 | EditorBase::GetStartPoint(&SelectionRef())); |
10245 | 0 | if (NS_WARN_IF(!selectionStartPoint.IsSet())) { |
10246 | 0 | return NS_ERROR_FAILURE; |
10247 | 0 | } |
10248 | 0 | |
10249 | 0 | // Check that selection start container is inside the <body> element. |
10250 | 0 | //XXXsmaug this code is insane. |
10251 | 0 | nsINode* temp = selectionStartPoint.GetContainer(); |
10252 | 0 | while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) { |
10253 | 0 | temp = temp->GetParentOrHostNode(); |
10254 | 0 | } |
10255 | 0 |
|
10256 | 0 | // If we aren't in the <body> element, force the issue. |
10257 | 0 | if (!temp) { |
10258 | 0 | IgnoredErrorResult ignoredError; |
10259 | 0 | SelectionRef().Collapse(RawRangeBoundary(rootElement, 0), ignoredError); |
10260 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10261 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10262 | 0 | } |
10263 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
10264 | 0 | "Failed to collapse selection at start of the root element"); |
10265 | 0 | return NS_OK; |
10266 | 0 | } |
10267 | 0 | |
10268 | 0 | EditorRawDOMPoint selectionEndPoint(EditorBase::GetEndPoint(&SelectionRef())); |
10269 | 0 | if (NS_WARN_IF(!selectionEndPoint.IsSet())) { |
10270 | 0 | return NS_ERROR_FAILURE; |
10271 | 0 | } |
10272 | 0 | |
10273 | 0 | // check that selNode is inside body |
10274 | 0 | //XXXsmaug this code is insane. |
10275 | 0 | temp = selectionEndPoint.GetContainer(); |
10276 | 0 | while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) { |
10277 | 0 | temp = temp->GetParentOrHostNode(); |
10278 | 0 | } |
10279 | 0 |
|
10280 | 0 | // If we aren't in the <body> element, force the issue. |
10281 | 0 | if (!temp) { |
10282 | 0 | IgnoredErrorResult ignoredError; |
10283 | 0 | SelectionRef().Collapse(RawRangeBoundary(rootElement, 0), ignoredError); |
10284 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10285 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10286 | 0 | } |
10287 | 0 | NS_WARNING_ASSERTION(!ignoredError.Failed(), |
10288 | 0 | "Failed to collapse selection at start of the root element"); |
10289 | 0 | } |
10290 | 0 |
|
10291 | 0 | return NS_OK; |
10292 | 0 | } |
10293 | | |
10294 | | nsresult |
10295 | | HTMLEditRules::UpdateDocChangeRange(nsRange* aRange) |
10296 | 0 | { |
10297 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10298 | 0 |
|
10299 | 0 | // first make sure aRange is in the document. It might not be if |
10300 | 0 | // portions of our editting action involved manipulating nodes |
10301 | 0 | // prior to placing them in the document (e.g., populating a list item |
10302 | 0 | // before placing it in its list) |
10303 | 0 | const RangeBoundary& atStart = aRange->StartRef(); |
10304 | 0 | if (NS_WARN_IF(!atStart.IsSet())) { |
10305 | 0 | return NS_ERROR_FAILURE; |
10306 | 0 | } |
10307 | 0 | if (!HTMLEditorRef().IsDescendantOfRoot(atStart.Container())) { |
10308 | 0 | // just return - we don't need to adjust mDocChangeRange in this case |
10309 | 0 | return NS_OK; |
10310 | 0 | } |
10311 | 0 | |
10312 | 0 | if (!mDocChangeRange) { |
10313 | 0 | // clone aRange. |
10314 | 0 | mDocChangeRange = aRange->CloneRange(); |
10315 | 0 | } else { |
10316 | 0 | // compare starts of ranges |
10317 | 0 | ErrorResult error; |
10318 | 0 | int16_t result = |
10319 | 0 | mDocChangeRange->CompareBoundaryPoints(Range_Binding::START_TO_START, |
10320 | 0 | *aRange, error); |
10321 | 0 | if (error.ErrorCodeIs(NS_ERROR_NOT_INITIALIZED)) { |
10322 | 0 | // This will happen is mDocChangeRange is non-null, but the range is |
10323 | 0 | // uninitialized. In this case we'll set the start to aRange start. |
10324 | 0 | // The same test won't be needed further down since after we've set |
10325 | 0 | // the start the range will be collapsed to that point. |
10326 | 0 | result = 1; |
10327 | 0 | error.SuppressException(); |
10328 | 0 | } |
10329 | 0 | if (NS_WARN_IF(error.Failed())) { |
10330 | 0 | return error.StealNSResult(); |
10331 | 0 | } |
10332 | 0 | |
10333 | 0 | // Positive result means mDocChangeRange start is after aRange start. |
10334 | 0 | if (result > 0) { |
10335 | 0 | mDocChangeRange->SetStart(atStart.AsRaw(), error); |
10336 | 0 | if (NS_WARN_IF(error.Failed())) { |
10337 | 0 | return error.StealNSResult(); |
10338 | 0 | } |
10339 | 0 | } |
10340 | 0 | |
10341 | 0 | // compare ends of ranges |
10342 | 0 | result = |
10343 | 0 | mDocChangeRange->CompareBoundaryPoints(Range_Binding::END_TO_END, |
10344 | 0 | *aRange, error); |
10345 | 0 | if (NS_WARN_IF(error.Failed())) { |
10346 | 0 | return error.StealNSResult(); |
10347 | 0 | } |
10348 | 0 | |
10349 | 0 | // Negative result means mDocChangeRange end is before aRange end. |
10350 | 0 | if (result < 0) { |
10351 | 0 | const RangeBoundary& atEnd = aRange->EndRef(); |
10352 | 0 | if (NS_WARN_IF(!atEnd.IsSet())) { |
10353 | 0 | return NS_ERROR_FAILURE; |
10354 | 0 | } |
10355 | 0 | mDocChangeRange->SetEnd(atEnd.AsRaw(), error); |
10356 | 0 | if (NS_WARN_IF(error.Failed())) { |
10357 | 0 | return error.StealNSResult(); |
10358 | 0 | } |
10359 | 0 | } |
10360 | 0 | } |
10361 | 0 | return NS_OK; |
10362 | 0 | } |
10363 | | |
10364 | | nsresult |
10365 | | HTMLEditRules::InsertBRIfNeededInternal(nsINode& aNode, |
10366 | | bool aInsertMozBR) |
10367 | 0 | { |
10368 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10369 | 0 |
|
10370 | 0 | if (!IsBlockNode(aNode)) { |
10371 | 0 | return NS_OK; |
10372 | 0 | } |
10373 | 0 | |
10374 | 0 | bool isEmpty; |
10375 | 0 | nsresult rv = HTMLEditorRef().IsEmptyNode(&aNode, &isEmpty); |
10376 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10377 | 0 | return rv; |
10378 | 0 | } |
10379 | 0 | if (!isEmpty) { |
10380 | 0 | return NS_OK; |
10381 | 0 | } |
10382 | 0 | |
10383 | 0 | CreateElementResult createBrResult = |
10384 | 0 | !aInsertMozBR ? CreateBR(EditorRawDOMPoint(&aNode, 0)) : |
10385 | 0 | CreateMozBR(EditorRawDOMPoint(&aNode, 0)); |
10386 | 0 | if (NS_WARN_IF(createBrResult.Failed())) { |
10387 | 0 | return createBrResult.Rv(); |
10388 | 0 | } |
10389 | 0 | return NS_OK; |
10390 | 0 | } |
10391 | | |
10392 | | void |
10393 | | HTMLEditRules::DidCreateNode(Selection& aSelection, |
10394 | | Element& aNewElement) |
10395 | 0 | { |
10396 | 0 | if (!mListenerEnabled) { |
10397 | 0 | return; |
10398 | 0 | } |
10399 | 0 | |
10400 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10401 | 0 | return; |
10402 | 0 | } |
10403 | 0 | |
10404 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, aSelection); |
10405 | 0 |
|
10406 | 0 | // assumption that Join keeps the righthand node |
10407 | 0 | IgnoredErrorResult ignoredError; |
10408 | 0 | mUtilRange->SelectNode(aNewElement, ignoredError); |
10409 | 0 | if (NS_WARN_IF(ignoredError.Failed())) { |
10410 | 0 | return; |
10411 | 0 | } |
10412 | 0 | UpdateDocChangeRange(mUtilRange); |
10413 | 0 | } |
10414 | | |
10415 | | void |
10416 | | HTMLEditRules::DidInsertNode(Selection& aSelection, |
10417 | | nsIContent& aContent) |
10418 | 0 | { |
10419 | 0 | if (!mListenerEnabled) { |
10420 | 0 | return; |
10421 | 0 | } |
10422 | 0 | |
10423 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10424 | 0 | return; |
10425 | 0 | } |
10426 | 0 | |
10427 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, aSelection); |
10428 | 0 |
|
10429 | 0 | IgnoredErrorResult ignoredError; |
10430 | 0 | mUtilRange->SelectNode(aContent, ignoredError); |
10431 | 0 | if (NS_WARN_IF(ignoredError.Failed())) { |
10432 | 0 | return; |
10433 | 0 | } |
10434 | 0 | UpdateDocChangeRange(mUtilRange); |
10435 | 0 | } |
10436 | | |
10437 | | void |
10438 | | HTMLEditRules::WillDeleteNode(Selection& aSelection, |
10439 | | nsINode& aChild) |
10440 | 0 | { |
10441 | 0 | if (!mListenerEnabled) { |
10442 | 0 | return; |
10443 | 0 | } |
10444 | 0 | |
10445 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10446 | 0 | return; |
10447 | 0 | } |
10448 | 0 | |
10449 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, aSelection); |
10450 | 0 |
|
10451 | 0 | IgnoredErrorResult ignoredError; |
10452 | 0 | mUtilRange->SelectNode(aChild, ignoredError); |
10453 | 0 | if (NS_WARN_IF(ignoredError.Failed())) { |
10454 | 0 | return; |
10455 | 0 | } |
10456 | 0 | UpdateDocChangeRange(mUtilRange); |
10457 | 0 | } |
10458 | | |
10459 | | void |
10460 | | HTMLEditRules::DidSplitNode(Selection& aSelection, |
10461 | | nsINode& aExistingRightNode, |
10462 | | nsINode& aNewLeftNode) |
10463 | 0 | { |
10464 | 0 | if (!mListenerEnabled) { |
10465 | 0 | return; |
10466 | 0 | } |
10467 | 0 | |
10468 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10469 | 0 | return; |
10470 | 0 | } |
10471 | 0 | |
10472 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, aSelection); |
10473 | 0 |
|
10474 | 0 | nsresult rv = mUtilRange->SetStartAndEnd(&aNewLeftNode, 0, |
10475 | 0 | &aExistingRightNode, 0); |
10476 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10477 | 0 | return; |
10478 | 0 | } |
10479 | 0 | UpdateDocChangeRange(mUtilRange); |
10480 | 0 | } |
10481 | | |
10482 | | void |
10483 | | HTMLEditRules::WillJoinNodes(nsINode& aLeftNode, |
10484 | | nsINode& aRightNode) |
10485 | 0 | { |
10486 | 0 | if (!mListenerEnabled) { |
10487 | 0 | return; |
10488 | 0 | } |
10489 | 0 | |
10490 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10491 | 0 | return; |
10492 | 0 | } |
10493 | 0 | |
10494 | 0 | // remember split point |
10495 | 0 | mJoinOffset = aLeftNode.Length(); |
10496 | 0 | } |
10497 | | |
10498 | | void |
10499 | | HTMLEditRules::DidJoinNodes(Selection& aSelection, |
10500 | | nsINode& aLeftNode, |
10501 | | nsINode& aRightNode) |
10502 | 0 | { |
10503 | 0 | if (!mListenerEnabled) { |
10504 | 0 | return; |
10505 | 0 | } |
10506 | 0 | |
10507 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10508 | 0 | return; |
10509 | 0 | } |
10510 | 0 | |
10511 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, aSelection); |
10512 | 0 |
|
10513 | 0 | // assumption that Join keeps the righthand node |
10514 | 0 | nsresult rv = mUtilRange->CollapseTo(&aRightNode, mJoinOffset); |
10515 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10516 | 0 | return; |
10517 | 0 | } |
10518 | 0 | UpdateDocChangeRange(mUtilRange); |
10519 | 0 | } |
10520 | | |
10521 | | void |
10522 | | HTMLEditRules::DidInsertText(Selection& aSelection, |
10523 | | nsINode& aTextNode, |
10524 | | int32_t aOffset, |
10525 | | const nsAString& aString) |
10526 | 0 | { |
10527 | 0 | if (!mListenerEnabled) { |
10528 | 0 | return; |
10529 | 0 | } |
10530 | 0 | |
10531 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10532 | 0 | return; |
10533 | 0 | } |
10534 | 0 | |
10535 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, aSelection); |
10536 | 0 |
|
10537 | 0 | int32_t length = aString.Length(); |
10538 | 0 | nsresult rv = mUtilRange->SetStartAndEnd(&aTextNode, aOffset, |
10539 | 0 | &aTextNode, aOffset + length); |
10540 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10541 | 0 | return; |
10542 | 0 | } |
10543 | 0 | UpdateDocChangeRange(mUtilRange); |
10544 | 0 | } |
10545 | | |
10546 | | void |
10547 | | HTMLEditRules::DidDeleteText(Selection& aSelection, |
10548 | | nsINode& aTextNode, |
10549 | | int32_t aOffset, |
10550 | | int32_t aLength) |
10551 | 0 | { |
10552 | 0 | if (!mListenerEnabled) { |
10553 | 0 | return; |
10554 | 0 | } |
10555 | 0 | |
10556 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10557 | 0 | return; |
10558 | 0 | } |
10559 | 0 | |
10560 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, aSelection); |
10561 | 0 |
|
10562 | 0 | nsresult rv = mUtilRange->CollapseTo(&aTextNode, aOffset); |
10563 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10564 | 0 | return; |
10565 | 0 | } |
10566 | 0 | UpdateDocChangeRange(mUtilRange); |
10567 | 0 | } |
10568 | | |
10569 | | void |
10570 | | HTMLEditRules::WillDeleteSelection(Selection& aSelection) |
10571 | 0 | { |
10572 | 0 | if (!mListenerEnabled) { |
10573 | 0 | return; |
10574 | 0 | } |
10575 | 0 | |
10576 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10577 | 0 | return; |
10578 | 0 | } |
10579 | 0 | |
10580 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, aSelection); |
10581 | 0 |
|
10582 | 0 | EditorRawDOMPoint startPoint = EditorBase::GetStartPoint(&SelectionRef()); |
10583 | 0 | if (NS_WARN_IF(!startPoint.IsSet())) { |
10584 | 0 | return; |
10585 | 0 | } |
10586 | 0 | EditorRawDOMPoint endPoint = EditorBase::GetEndPoint(&SelectionRef()); |
10587 | 0 | if (NS_WARN_IF(!endPoint.IsSet())) { |
10588 | 0 | return; |
10589 | 0 | } |
10590 | 0 | nsresult rv = mUtilRange->SetStartAndEnd(startPoint, endPoint); |
10591 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10592 | 0 | return; |
10593 | 0 | } |
10594 | 0 | UpdateDocChangeRange(mUtilRange); |
10595 | 0 | } |
10596 | | |
10597 | | nsresult |
10598 | | HTMLEditRules::RemoveAlignment(nsINode& aNode, |
10599 | | const nsAString& aAlignType, |
10600 | | bool aDescendantsOnly) |
10601 | 0 | { |
10602 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10603 | 0 |
|
10604 | 0 | if (EditorBase::IsTextNode(&aNode) || HTMLEditUtils::IsTable(&aNode)) { |
10605 | 0 | return NS_OK; |
10606 | 0 | } |
10607 | 0 | |
10608 | 0 | nsCOMPtr<nsINode> child, tmp; |
10609 | 0 | if (aDescendantsOnly) { |
10610 | 0 | child = aNode.GetFirstChild(); |
10611 | 0 | } else { |
10612 | 0 | child = &aNode; |
10613 | 0 | } |
10614 | 0 |
|
10615 | 0 | bool useCSS = HTMLEditorRef().IsCSSEnabled(); |
10616 | 0 |
|
10617 | 0 |
|
10618 | 0 | // Let's remove all alignment hints in the children of aNode; it can |
10619 | 0 | // be an ALIGN attribute (in case we just remove it) or a CENTER |
10620 | 0 | // element (here we have to remove the container and keep its |
10621 | 0 | // children). We break on tables and don't look at their children. |
10622 | 0 | while (child) { |
10623 | 0 | if (aDescendantsOnly) { |
10624 | 0 | // get the next sibling right now because we could have to remove child |
10625 | 0 | tmp = child->GetNextSibling(); |
10626 | 0 | } else { |
10627 | 0 | tmp = nullptr; |
10628 | 0 | } |
10629 | 0 |
|
10630 | 0 | if (child->IsHTMLElement(nsGkAtoms::center)) { |
10631 | 0 | // the current node is a CENTER element |
10632 | 0 | // first remove children's alignment |
10633 | 0 | nsresult rv = RemoveAlignment(*child, aAlignType, true); |
10634 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10635 | 0 | return rv; |
10636 | 0 | } |
10637 | 0 | |
10638 | 0 | // we may have to insert BRs in first and last position of element's children |
10639 | 0 | // if the nodes before/after are not blocks and not BRs |
10640 | 0 | rv = MakeSureElemStartsAndEndsOnCR(*child); |
10641 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10642 | 0 | return rv; |
10643 | 0 | } |
10644 | 0 | |
10645 | 0 | // now remove the CENTER container |
10646 | 0 | rv = HTMLEditorRef().RemoveContainerWithTransaction(*child->AsElement()); |
10647 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10648 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10649 | 0 | } |
10650 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10651 | 0 | return rv; |
10652 | 0 | } |
10653 | 0 | } else if (IsBlockNode(*child) || child->IsHTMLElement(nsGkAtoms::hr)) { |
10654 | 0 | // the current node is a block element |
10655 | 0 | if (HTMLEditUtils::SupportsAlignAttr(*child)) { |
10656 | 0 | // remove the ALIGN attribute if this element can have it |
10657 | 0 | nsresult rv = |
10658 | 0 | HTMLEditorRef().RemoveAttributeWithTransaction(*child->AsElement(), |
10659 | 0 | *nsGkAtoms::align); |
10660 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10661 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10662 | 0 | } |
10663 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10664 | 0 | return rv; |
10665 | 0 | } |
10666 | 0 | } |
10667 | 0 | if (useCSS) { |
10668 | 0 | if (child->IsAnyOfHTMLElements(nsGkAtoms::table, nsGkAtoms::hr)) { |
10669 | 0 | nsresult rv = |
10670 | 0 | HTMLEditorRef().SetAttributeOrEquivalent(child->AsElement(), |
10671 | 0 | nsGkAtoms::align, |
10672 | 0 | aAlignType, false); |
10673 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10674 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10675 | 0 | } |
10676 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10677 | 0 | return rv; |
10678 | 0 | } |
10679 | 0 | } else { |
10680 | 0 | nsAutoString dummyCssValue; |
10681 | 0 | nsresult rv = HTMLEditorRef().mCSSEditUtils->RemoveCSSInlineStyle( |
10682 | 0 | *child, |
10683 | 0 | nsGkAtoms::textAlign, |
10684 | 0 | dummyCssValue); |
10685 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10686 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10687 | 0 | } |
10688 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10689 | 0 | return rv; |
10690 | 0 | } |
10691 | 0 | } |
10692 | 0 | } |
10693 | 0 | if (!child->IsHTMLElement(nsGkAtoms::table)) { |
10694 | 0 | // unless this is a table, look at children |
10695 | 0 | nsresult rv = RemoveAlignment(*child, aAlignType, true); |
10696 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10697 | 0 | return rv; |
10698 | 0 | } |
10699 | 0 | } |
10700 | 0 | } |
10701 | 0 | child = tmp; |
10702 | 0 | } |
10703 | 0 | return NS_OK; |
10704 | 0 | } |
10705 | | |
10706 | | nsresult |
10707 | | HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsINode& aNode, |
10708 | | bool aStarts) |
10709 | 0 | { |
10710 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10711 | 0 |
|
10712 | 0 | nsINode* child = aStarts ? HTMLEditorRef().GetFirstEditableChild(aNode) : |
10713 | 0 | HTMLEditorRef().GetLastEditableChild(aNode); |
10714 | 0 | if (NS_WARN_IF(!child)) { |
10715 | 0 | return NS_OK; |
10716 | 0 | } |
10717 | 0 | |
10718 | 0 | bool foundCR = false; |
10719 | 0 | if (IsBlockNode(*child) || child->IsHTMLElement(nsGkAtoms::br)) { |
10720 | 0 | foundCR = true; |
10721 | 0 | } else { |
10722 | 0 | nsINode* sibling = |
10723 | 0 | aStarts ? HTMLEditorRef().GetPriorHTMLSibling(&aNode) : |
10724 | 0 | HTMLEditorRef().GetNextHTMLSibling(&aNode); |
10725 | 0 | if (sibling) { |
10726 | 0 | if (IsBlockNode(*sibling) || sibling->IsHTMLElement(nsGkAtoms::br)) { |
10727 | 0 | foundCR = true; |
10728 | 0 | } |
10729 | 0 | } else { |
10730 | 0 | foundCR = true; |
10731 | 0 | } |
10732 | 0 | } |
10733 | 0 | if (!foundCR) { |
10734 | 0 | EditorRawDOMPoint pointToInsert; |
10735 | 0 | if (!aStarts) { |
10736 | 0 | pointToInsert.SetToEndOf(&aNode); |
10737 | 0 | } else { |
10738 | 0 | pointToInsert.Set(&aNode, 0); |
10739 | 0 | } |
10740 | 0 | RefPtr<Element> brElement = |
10741 | 0 | HTMLEditorRef().InsertBrElementWithTransaction(SelectionRef(), |
10742 | 0 | pointToInsert); |
10743 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10744 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10745 | 0 | } |
10746 | 0 | if (NS_WARN_IF(!brElement)) { |
10747 | 0 | return NS_ERROR_FAILURE; |
10748 | 0 | } |
10749 | 0 | } |
10750 | 0 | return NS_OK; |
10751 | 0 | } |
10752 | | |
10753 | | nsresult |
10754 | | HTMLEditRules::MakeSureElemStartsAndEndsOnCR(nsINode& aNode) |
10755 | 0 | { |
10756 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10757 | 0 |
|
10758 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10759 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10760 | 0 | } |
10761 | 0 | |
10762 | 0 | Selection* selection = mHTMLEditor->GetSelection(); |
10763 | 0 | if (NS_WARN_IF(!selection)) { |
10764 | 0 | return NS_ERROR_FAILURE; |
10765 | 0 | } |
10766 | 0 | |
10767 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *selection); |
10768 | 0 |
|
10769 | 0 | nsresult rv = MakeSureElemStartsOrEndsOnCR(aNode, false); |
10770 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10771 | 0 | return rv; |
10772 | 0 | } |
10773 | 0 | rv = MakeSureElemStartsOrEndsOnCR(aNode, true); |
10774 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10775 | 0 | return rv; |
10776 | 0 | } |
10777 | 0 | return NS_OK; |
10778 | 0 | } |
10779 | | |
10780 | | nsresult |
10781 | | HTMLEditRules::AlignBlock(Element& aElement, |
10782 | | const nsAString& aAlignType, |
10783 | | ResetAlignOf aResetAlignOf) |
10784 | 0 | { |
10785 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10786 | 0 |
|
10787 | 0 | if (!IsBlockNode(aElement) && !aElement.IsHTMLElement(nsGkAtoms::hr)) { |
10788 | 0 | // We deal only with blocks; early way out |
10789 | 0 | return NS_OK; |
10790 | 0 | } |
10791 | 0 | |
10792 | 0 | nsresult rv = RemoveAlignment(aElement, aAlignType, |
10793 | 0 | aResetAlignOf == ResetAlignOf::OnlyDescendants); |
10794 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10795 | 0 | return rv; |
10796 | 0 | } |
10797 | 0 | if (HTMLEditorRef().IsCSSEnabled()) { |
10798 | 0 | // Let's use CSS alignment; we use margin-left and margin-right for tables |
10799 | 0 | // and text-align for other block-level elements |
10800 | 0 | nsresult rv = |
10801 | 0 | HTMLEditorRef().SetAttributeOrEquivalent(&aElement, nsGkAtoms::align, |
10802 | 0 | aAlignType, false); |
10803 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10804 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10805 | 0 | } |
10806 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10807 | 0 | return rv; |
10808 | 0 | } |
10809 | 0 | return NS_OK; |
10810 | 0 | } |
10811 | 0 | |
10812 | 0 | // HTML case; this code is supposed to be called ONLY if the element |
10813 | 0 | // supports the align attribute but we'll never know... |
10814 | 0 | if (NS_WARN_IF(!HTMLEditUtils::SupportsAlignAttr(aElement))) { |
10815 | 0 | // XXX error? |
10816 | 0 | return NS_OK; |
10817 | 0 | } |
10818 | 0 | |
10819 | 0 | rv = HTMLEditorRef().SetAttributeOrEquivalent(&aElement, nsGkAtoms::align, |
10820 | 0 | aAlignType, false); |
10821 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10822 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10823 | 0 | } |
10824 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10825 | 0 | return rv; |
10826 | 0 | } |
10827 | 0 | return NS_OK; |
10828 | 0 | } |
10829 | | |
10830 | | nsresult |
10831 | | HTMLEditRules::ChangeMarginStart(Element& aElement, |
10832 | | bool aIncrease) |
10833 | 0 | { |
10834 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10835 | 0 |
|
10836 | 0 | nsAtom& marginProperty = MarginPropertyAtomForIndent(aElement); |
10837 | 0 | nsAutoString value; |
10838 | 0 | CSSEditUtils::GetSpecifiedProperty(aElement, marginProperty, value); |
10839 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10840 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10841 | 0 | } |
10842 | 0 | float f; |
10843 | 0 | RefPtr<nsAtom> unit; |
10844 | 0 | CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit)); |
10845 | 0 | if (!f) { |
10846 | 0 | nsAutoString defaultLengthUnit; |
10847 | 0 | CSSEditUtils::GetDefaultLengthUnit(defaultLengthUnit); |
10848 | 0 | unit = NS_Atomize(defaultLengthUnit); |
10849 | 0 | } |
10850 | 0 | int8_t multiplier = aIncrease ? +1 : -1; |
10851 | 0 | if (nsGkAtoms::in == unit) { |
10852 | 0 | f += NS_EDITOR_INDENT_INCREMENT_IN * multiplier; |
10853 | 0 | } else if (nsGkAtoms::cm == unit) { |
10854 | 0 | f += NS_EDITOR_INDENT_INCREMENT_CM * multiplier; |
10855 | 0 | } else if (nsGkAtoms::mm == unit) { |
10856 | 0 | f += NS_EDITOR_INDENT_INCREMENT_MM * multiplier; |
10857 | 0 | } else if (nsGkAtoms::pt == unit) { |
10858 | 0 | f += NS_EDITOR_INDENT_INCREMENT_PT * multiplier; |
10859 | 0 | } else if (nsGkAtoms::pc == unit) { |
10860 | 0 | f += NS_EDITOR_INDENT_INCREMENT_PC * multiplier; |
10861 | 0 | } else if (nsGkAtoms::em == unit) { |
10862 | 0 | f += NS_EDITOR_INDENT_INCREMENT_EM * multiplier; |
10863 | 0 | } else if (nsGkAtoms::ex == unit) { |
10864 | 0 | f += NS_EDITOR_INDENT_INCREMENT_EX * multiplier; |
10865 | 0 | } else if (nsGkAtoms::px == unit) { |
10866 | 0 | f += NS_EDITOR_INDENT_INCREMENT_PX * multiplier; |
10867 | 0 | } else if (nsGkAtoms::percentage == unit) { |
10868 | 0 | f += NS_EDITOR_INDENT_INCREMENT_PERCENT * multiplier; |
10869 | 0 | } |
10870 | 0 |
|
10871 | 0 | if (0 < f) { |
10872 | 0 | nsAutoString newValue; |
10873 | 0 | newValue.AppendFloat(f); |
10874 | 0 | newValue.Append(nsDependentAtomString(unit)); |
10875 | 0 | HTMLEditorRef().mCSSEditUtils->SetCSSProperty(aElement, marginProperty, |
10876 | 0 | newValue); |
10877 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10878 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10879 | 0 | } |
10880 | 0 | return NS_OK; |
10881 | 0 | } |
10882 | 0 | |
10883 | 0 | HTMLEditorRef().mCSSEditUtils->RemoveCSSProperty(aElement, marginProperty, |
10884 | 0 | value); |
10885 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10886 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10887 | 0 | } |
10888 | 0 | |
10889 | 0 | // Remove unnecessary divs |
10890 | 0 | if (!aElement.IsHTMLElement(nsGkAtoms::div) || |
10891 | 0 | &aElement == HTMLEditorRef().GetActiveEditingHost() || |
10892 | 0 | !HTMLEditorRef().IsDescendantOfEditorRoot(&aElement) || |
10893 | 0 | HTMLEditor::HasAttributes(&aElement)) { |
10894 | 0 | return NS_OK; |
10895 | 0 | } |
10896 | 0 | |
10897 | 0 | nsresult rv = HTMLEditorRef().RemoveContainerWithTransaction(aElement); |
10898 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10899 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10900 | 0 | } |
10901 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10902 | 0 | return rv; |
10903 | 0 | } |
10904 | 0 | return NS_OK; |
10905 | 0 | } |
10906 | | |
10907 | | nsresult |
10908 | | HTMLEditRules::WillAbsolutePosition(bool* aCancel, |
10909 | | bool* aHandled) |
10910 | 0 | { |
10911 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10912 | 0 | MOZ_ASSERT(aCancel && aHandled); |
10913 | 0 |
|
10914 | 0 | // FYI: Ignore cancel result of WillInsert(). |
10915 | 0 | nsresult rv = WillInsert(); |
10916 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
10917 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10918 | 0 | } |
10919 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
10920 | 0 |
|
10921 | 0 | *aCancel = false; |
10922 | 0 | *aHandled = true; |
10923 | 0 |
|
10924 | 0 | RefPtr<Element> focusElement = |
10925 | 0 | HTMLEditorRef().GetSelectionContainerElement(SelectionRef()); |
10926 | 0 | if (focusElement && HTMLEditUtils::IsImage(focusElement)) { |
10927 | 0 | mNewBlock = focusElement; |
10928 | 0 | return NS_OK; |
10929 | 0 | } |
10930 | 0 | |
10931 | 0 | rv = NormalizeSelection(); |
10932 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10933 | 0 | return rv; |
10934 | 0 | } |
10935 | 0 | |
10936 | 0 | rv = PrepareToMakeElementAbsolutePosition(aHandled, address_of(mNewBlock)); |
10937 | 0 | // PrepareToMakeElementAbsolutePosition() may restore selection with |
10938 | 0 | // AutoSelectionRestorer. Therefore, the editor might have already been |
10939 | 0 | // destroyed now. |
10940 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
10941 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
10942 | 0 | } |
10943 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10944 | 0 | return rv; |
10945 | 0 | } |
10946 | 0 | return NS_OK; |
10947 | 0 | } |
10948 | | |
10949 | | nsresult |
10950 | | HTMLEditRules::PrepareToMakeElementAbsolutePosition( |
10951 | | bool* aHandled, |
10952 | | RefPtr<Element>* aTargetElement) |
10953 | 0 | { |
10954 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
10955 | 0 |
|
10956 | 0 | MOZ_ASSERT(aHandled); |
10957 | 0 | MOZ_ASSERT(aTargetElement); |
10958 | 0 |
|
10959 | 0 | AutoSelectionRestorer selectionRestorer(&SelectionRef(), &HTMLEditorRef()); |
10960 | 0 |
|
10961 | 0 | // Convert the selection ranges into "promoted" selection ranges: this |
10962 | 0 | // basically just expands the range to include the immediate block parent, |
10963 | 0 | // and then further expands to include any ancestors whose children are all |
10964 | 0 | // in the range. |
10965 | 0 |
|
10966 | 0 | nsTArray<RefPtr<nsRange>> arrayOfRanges; |
10967 | 0 | GetPromotedRanges(arrayOfRanges, EditSubAction::eSetPositionToAbsolute); |
10968 | 0 |
|
10969 | 0 | // Use these ranges to contruct a list of nodes to act on. |
10970 | 0 | nsTArray<OwningNonNull<nsINode>> arrayOfNodes; |
10971 | 0 | nsresult rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes, |
10972 | 0 | EditSubAction::eSetPositionToAbsolute, |
10973 | 0 | TouchContent::yes); |
10974 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
10975 | 0 | return rv; |
10976 | 0 | } |
10977 | 0 | |
10978 | 0 | // If nothing visible in list, make an empty block |
10979 | 0 | if (ListIsEmptyLine(arrayOfNodes)) { |
10980 | 0 | nsRange* firstRange = SelectionRef().GetRangeAt(0); |
10981 | 0 | if (NS_WARN_IF(!firstRange)) { |
10982 | 0 | return NS_ERROR_FAILURE; |
10983 | 0 | } |
10984 | 0 | |
10985 | 0 | EditorDOMPoint atStartOfSelection(firstRange->StartRef()); |
10986 | 0 | if (NS_WARN_IF(!atStartOfSelection.IsSet())) { |
10987 | 0 | return NS_ERROR_FAILURE; |
10988 | 0 | } |
10989 | 0 | |
10990 | 0 | // Make sure we can put a block here. |
10991 | 0 | SplitNodeResult splitNodeResult = |
10992 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div, |
10993 | 0 | atStartOfSelection); |
10994 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
10995 | 0 | return splitNodeResult.Rv(); |
10996 | 0 | } |
10997 | 0 | RefPtr<Element> positionedDiv = |
10998 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::div, |
10999 | 0 | splitNodeResult.SplitPoint()); |
11000 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11001 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11002 | 0 | } |
11003 | 0 | if (NS_WARN_IF(!positionedDiv)) { |
11004 | 0 | return NS_ERROR_FAILURE; |
11005 | 0 | } |
11006 | 0 | // Remember our new block for postprocessing |
11007 | 0 | *aTargetElement = positionedDiv; |
11008 | 0 | // Delete anything that was in the list of nodes |
11009 | 0 | while (!arrayOfNodes.IsEmpty()) { |
11010 | 0 | OwningNonNull<nsINode> curNode = arrayOfNodes[0]; |
11011 | 0 | rv = HTMLEditorRef().DeleteNodeWithTransaction(*curNode); |
11012 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11013 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11014 | 0 | } |
11015 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
11016 | 0 | return rv; |
11017 | 0 | } |
11018 | 0 | arrayOfNodes.RemoveElementAt(0); |
11019 | 0 | } |
11020 | 0 | // Put selection in new block |
11021 | 0 | *aHandled = true; |
11022 | 0 | // Don't restore the selection |
11023 | 0 | selectionRestorer.Abort(); |
11024 | 0 | ErrorResult error; |
11025 | 0 | SelectionRef().Collapse(RawRangeBoundary(positionedDiv, 0), error); |
11026 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11027 | 0 | error.SuppressException(); |
11028 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11029 | 0 | } |
11030 | 0 | if (NS_WARN_IF(error.Failed())) { |
11031 | 0 | return error.StealNSResult(); |
11032 | 0 | } |
11033 | 0 | return NS_OK; |
11034 | 0 | } |
11035 | 0 | |
11036 | 0 | // Okay, now go through all the nodes and put them in a blockquote, or |
11037 | 0 | // whatever is appropriate. Woohoo! |
11038 | 0 | nsCOMPtr<Element> curList, curPositionedDiv, indentedLI; |
11039 | 0 | for (OwningNonNull<nsINode>& curNode : arrayOfNodes) { |
11040 | 0 | // Here's where we actually figure out what to do. |
11041 | 0 | EditorDOMPoint atCurNode(curNode); |
11042 | 0 | if (NS_WARN_IF(!atCurNode.IsSet())) { |
11043 | 0 | return NS_ERROR_FAILURE; // XXX not continue?? |
11044 | 0 | } |
11045 | 0 | |
11046 | 0 | // Ignore all non-editable nodes. Leave them be. |
11047 | 0 | if (!HTMLEditorRef().IsEditable(curNode)) { |
11048 | 0 | continue; |
11049 | 0 | } |
11050 | 0 | |
11051 | 0 | nsCOMPtr<nsIContent> sibling; |
11052 | 0 |
|
11053 | 0 | // Some logic for putting list items into nested lists... |
11054 | 0 | if (HTMLEditUtils::IsList(atCurNode.GetContainer())) { |
11055 | 0 | // Check to see if curList is still appropriate. Which it is if curNode |
11056 | 0 | // is still right after it in the same list. |
11057 | 0 | if (curList) { |
11058 | 0 | sibling = HTMLEditorRef().GetPriorHTMLSibling(curNode); |
11059 | 0 | } |
11060 | 0 |
|
11061 | 0 | if (!curList || (sibling && sibling != curList)) { |
11062 | 0 | nsAtom* containerName = |
11063 | 0 | atCurNode.GetContainer()->NodeInfo()->NameAtom(); |
11064 | 0 | // Create a new nested list of correct type. |
11065 | 0 | SplitNodeResult splitNodeResult = |
11066 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*containerName, |
11067 | 0 | atCurNode); |
11068 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
11069 | 0 | return splitNodeResult.Rv(); |
11070 | 0 | } |
11071 | 0 | if (!curPositionedDiv) { |
11072 | 0 | curPositionedDiv = |
11073 | 0 | HTMLEditorRef().CreateNodeWithTransaction( |
11074 | 0 | *nsGkAtoms::div, splitNodeResult.SplitPoint()); |
11075 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11076 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11077 | 0 | } |
11078 | 0 | NS_WARNING_ASSERTION(curPositionedDiv, |
11079 | 0 | "Failed to create current positioned div element"); |
11080 | 0 | *aTargetElement = curPositionedDiv; |
11081 | 0 | } |
11082 | 0 | EditorRawDOMPoint atEndOfCurPositionedDiv; |
11083 | 0 | atEndOfCurPositionedDiv.SetToEndOf(curPositionedDiv); |
11084 | 0 | curList = |
11085 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*containerName, |
11086 | 0 | atEndOfCurPositionedDiv); |
11087 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11088 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11089 | 0 | } |
11090 | 0 | if (NS_WARN_IF(!curList)) { |
11091 | 0 | return NS_ERROR_FAILURE; |
11092 | 0 | } |
11093 | 0 | // curList is now the correct thing to put curNode in. Remember our |
11094 | 0 | // new block for postprocessing. |
11095 | 0 | } |
11096 | 0 | // Tuck the node into the end of the active list |
11097 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
11098 | 0 | *curList); |
11099 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11100 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11101 | 0 | } |
11102 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
11103 | 0 | return rv; |
11104 | 0 | } |
11105 | 0 | continue; |
11106 | 0 | } |
11107 | 0 | |
11108 | 0 | // Not a list item, use blockquote? If we are inside a list item, we |
11109 | 0 | // don't want to blockquote, we want to sublist the list item. We may |
11110 | 0 | // have several nodes listed in the array of nodes to act on, that are in |
11111 | 0 | // the same list item. Since we only want to indent that li once, we |
11112 | 0 | // must keep track of the most recent indented list item, and not indent |
11113 | 0 | // it if we find another node to act on that is still inside the same li. |
11114 | 0 | RefPtr<Element> listItem = IsInListItem(curNode); |
11115 | 0 | if (listItem) { |
11116 | 0 | if (indentedLI == listItem) { |
11117 | 0 | // Already indented this list item |
11118 | 0 | continue; |
11119 | 0 | } |
11120 | 0 | // Check to see if curList is still appropriate. Which it is if |
11121 | 0 | // curNode is still right after it in the same list. |
11122 | 0 | if (curList) { |
11123 | 0 | sibling = HTMLEditorRef().GetPriorHTMLSibling(listItem); |
11124 | 0 | } |
11125 | 0 |
|
11126 | 0 | if (!curList || (sibling && sibling != curList)) { |
11127 | 0 | EditorDOMPoint atListItem(listItem); |
11128 | 0 | if (NS_WARN_IF(!atListItem.IsSet())) { |
11129 | 0 | return NS_ERROR_FAILURE; |
11130 | 0 | } |
11131 | 0 | nsAtom* containerName = |
11132 | 0 | atListItem.GetContainer()->NodeInfo()->NameAtom(); |
11133 | 0 | // Create a new nested list of correct type |
11134 | 0 | SplitNodeResult splitNodeResult = |
11135 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*containerName, |
11136 | 0 | atListItem); |
11137 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
11138 | 0 | return splitNodeResult.Rv(); |
11139 | 0 | } |
11140 | 0 | if (!curPositionedDiv) { |
11141 | 0 | EditorRawDOMPoint atListItemParent(atListItem.GetContainer()); |
11142 | 0 | curPositionedDiv = |
11143 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::div, |
11144 | 0 | atListItemParent); |
11145 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11146 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11147 | 0 | } |
11148 | 0 | NS_WARNING_ASSERTION(curPositionedDiv, |
11149 | 0 | "Failed to create current positioned div element"); |
11150 | 0 | *aTargetElement = curPositionedDiv; |
11151 | 0 | } |
11152 | 0 | EditorRawDOMPoint atEndOfCurPositionedDiv; |
11153 | 0 | atEndOfCurPositionedDiv.SetToEndOf(curPositionedDiv); |
11154 | 0 | curList = |
11155 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*containerName, |
11156 | 0 | atEndOfCurPositionedDiv); |
11157 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11158 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11159 | 0 | } |
11160 | 0 | if (NS_WARN_IF(!curList)) { |
11161 | 0 | return NS_ERROR_FAILURE; |
11162 | 0 | } |
11163 | 0 | } |
11164 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*listItem, *curList); |
11165 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11166 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11167 | 0 | } |
11168 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
11169 | 0 | return rv; |
11170 | 0 | } |
11171 | 0 | // Remember we indented this li |
11172 | 0 | indentedLI = listItem; |
11173 | 0 | continue; |
11174 | 0 | } |
11175 | 0 | |
11176 | 0 | // Need to make a div to put things in if we haven't already |
11177 | 0 | if (!curPositionedDiv) { |
11178 | 0 | if (curNode->IsHTMLElement(nsGkAtoms::div)) { |
11179 | 0 | curPositionedDiv = curNode->AsElement(); |
11180 | 0 | *aTargetElement = curPositionedDiv; |
11181 | 0 | curList = nullptr; |
11182 | 0 | continue; |
11183 | 0 | } |
11184 | 0 | SplitNodeResult splitNodeResult = |
11185 | 0 | MaybeSplitAncestorsForInsertWithTransaction(*nsGkAtoms::div, |
11186 | 0 | atCurNode); |
11187 | 0 | if (NS_WARN_IF(splitNodeResult.Failed())) { |
11188 | 0 | return splitNodeResult.Rv(); |
11189 | 0 | } |
11190 | 0 | curPositionedDiv = |
11191 | 0 | HTMLEditorRef().CreateNodeWithTransaction(*nsGkAtoms::div, |
11192 | 0 | splitNodeResult.SplitPoint()); |
11193 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11194 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11195 | 0 | } |
11196 | 0 | if (NS_WARN_IF(!curPositionedDiv)) { |
11197 | 0 | return NS_ERROR_FAILURE; |
11198 | 0 | } |
11199 | 0 | // Remember our new block for postprocessing |
11200 | 0 | *aTargetElement = curPositionedDiv; |
11201 | 0 | // curPositionedDiv is now the correct thing to put curNode in |
11202 | 0 | } |
11203 | 0 |
|
11204 | 0 | // Tuck the node into the end of the active blockquote |
11205 | 0 | rv = HTMLEditorRef().MoveNodeToEndWithTransaction(*curNode->AsContent(), |
11206 | 0 | *curPositionedDiv); |
11207 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11208 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11209 | 0 | } |
11210 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
11211 | 0 | return rv; |
11212 | 0 | } |
11213 | 0 | // Forget curList, if any |
11214 | 0 | curList = nullptr; |
11215 | 0 | } |
11216 | 0 | return NS_OK; |
11217 | 0 | } |
11218 | | |
11219 | | nsresult |
11220 | | HTMLEditRules::DidAbsolutePosition() |
11221 | 0 | { |
11222 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
11223 | 0 |
|
11224 | 0 | if (!mNewBlock) { |
11225 | 0 | return NS_OK; |
11226 | 0 | } |
11227 | 0 | nsresult rv = HTMLEditorRef().SetPositionToAbsoluteOrStatic(*mNewBlock, true); |
11228 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11229 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11230 | 0 | } |
11231 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
11232 | 0 | return rv; |
11233 | 0 | } |
11234 | 0 | return NS_OK; |
11235 | 0 | } |
11236 | | |
11237 | | nsresult |
11238 | | HTMLEditRules::WillRemoveAbsolutePosition(bool* aCancel, |
11239 | | bool* aHandled) |
11240 | 0 | { |
11241 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
11242 | 0 |
|
11243 | 0 | if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) { |
11244 | 0 | return NS_ERROR_INVALID_ARG; |
11245 | 0 | } |
11246 | 0 | |
11247 | 0 | // FYI: Ignore cancel result of WillInsert(). |
11248 | 0 | nsresult rv = WillInsert(); |
11249 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
11250 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11251 | 0 | } |
11252 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
11253 | 0 |
|
11254 | 0 | *aCancel = false; |
11255 | 0 | *aHandled = true; |
11256 | 0 |
|
11257 | 0 | RefPtr<Element> element = |
11258 | 0 | HTMLEditorRef().GetAbsolutelyPositionedSelectionContainer(); |
11259 | 0 | if (NS_WARN_IF(!element)) { |
11260 | 0 | return NS_ERROR_FAILURE; |
11261 | 0 | } |
11262 | 0 | |
11263 | 0 | { |
11264 | 0 | AutoSelectionRestorer selectionRestorer(&SelectionRef(), &HTMLEditorRef()); |
11265 | 0 |
|
11266 | 0 | nsresult rv = HTMLEditorRef().SetPositionToAbsoluteOrStatic(*element, false); |
11267 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11268 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11269 | 0 | } |
11270 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
11271 | 0 | return rv; |
11272 | 0 | } |
11273 | 0 | } |
11274 | 0 | |
11275 | 0 | // Restoring Selection might cause destroying the HTML editor. |
11276 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11277 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11278 | 0 | } |
11279 | 0 | return NS_OK; |
11280 | 0 | } |
11281 | | |
11282 | | nsresult |
11283 | | HTMLEditRules::WillRelativeChangeZIndex(int32_t aChange, |
11284 | | bool* aCancel, |
11285 | | bool* aHandled) |
11286 | 0 | { |
11287 | 0 | MOZ_ASSERT(IsEditorDataAvailable()); |
11288 | 0 |
|
11289 | 0 | if (NS_WARN_IF(!aCancel) || NS_WARN_IF(!aHandled)) { |
11290 | 0 | return NS_ERROR_INVALID_ARG; |
11291 | 0 | } |
11292 | 0 | |
11293 | 0 | // FYI: Ignore cancel result of WillInsert(). |
11294 | 0 | nsresult rv = WillInsert(); |
11295 | 0 | if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { |
11296 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11297 | 0 | } |
11298 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed"); |
11299 | 0 |
|
11300 | 0 | *aCancel = false; |
11301 | 0 | *aHandled = true; |
11302 | 0 |
|
11303 | 0 | RefPtr<Element> element = |
11304 | 0 | HTMLEditorRef().GetAbsolutelyPositionedSelectionContainer(); |
11305 | 0 | if (NS_WARN_IF(!element)) { |
11306 | 0 | return NS_ERROR_FAILURE; |
11307 | 0 | } |
11308 | 0 | |
11309 | 0 | { |
11310 | 0 | AutoSelectionRestorer selectionRestorer(&SelectionRef(), &HTMLEditorRef()); |
11311 | 0 |
|
11312 | 0 | int32_t zIndex; |
11313 | 0 | nsresult rv = HTMLEditorRef().RelativeChangeElementZIndex(*element, aChange, |
11314 | 0 | &zIndex); |
11315 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11316 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11317 | 0 | } |
11318 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
11319 | 0 | return rv; |
11320 | 0 | } |
11321 | 0 | } |
11322 | 0 | |
11323 | 0 | // Restoring Selection might cause destroying the HTML editor. |
11324 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11325 | 0 | return NS_ERROR_EDITOR_DESTROYED; |
11326 | 0 | } |
11327 | 0 | return NS_OK; |
11328 | 0 | } |
11329 | | |
11330 | | nsresult |
11331 | | HTMLEditRules::DocumentModified() |
11332 | 0 | { |
11333 | 0 | nsContentUtils::AddScriptRunner( |
11334 | 0 | NewRunnableMethod("HTMLEditRules::DocumentModifiedWorker", |
11335 | 0 | this, |
11336 | 0 | &HTMLEditRules::DocumentModifiedWorker)); |
11337 | 0 | // Be aware, if DocumentModifiedWorker() is called synchronously, the |
11338 | 0 | // editor might have been destroyed here. |
11339 | 0 | return NS_OK; |
11340 | 0 | } |
11341 | | |
11342 | | void |
11343 | | HTMLEditRules::DocumentModifiedWorker() |
11344 | 0 | { |
11345 | 0 | if (NS_WARN_IF(!CanHandleEditAction())) { |
11346 | 0 | return; |
11347 | 0 | } |
11348 | 0 | |
11349 | 0 | Selection* selection = mHTMLEditor->GetSelection(); |
11350 | 0 | if (NS_WARN_IF(!selection)) { |
11351 | 0 | return; |
11352 | 0 | } |
11353 | 0 | |
11354 | 0 | AutoSafeEditorData setData(*this, *mHTMLEditor, *selection); |
11355 | 0 |
|
11356 | 0 | // DeleteNodeWithTransaction() below may cause a flush, which could destroy |
11357 | 0 | // the editor |
11358 | 0 | nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker; |
11359 | 0 |
|
11360 | 0 | // Delete our bogus node, if we have one, since the document might not be |
11361 | 0 | // empty any more. |
11362 | 0 | if (mBogusNode) { |
11363 | 0 | DebugOnly<nsresult> rv = |
11364 | 0 | HTMLEditorRef().DeleteNodeWithTransaction(*mBogusNode); |
11365 | 0 | NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), |
11366 | 0 | "Failed to remove the bogus node"); |
11367 | 0 | mBogusNode = nullptr; |
11368 | 0 | } |
11369 | 0 |
|
11370 | 0 | // Try to recreate the bogus node if needed. |
11371 | 0 | DebugOnly<nsresult> rv = CreateBogusNodeIfNeeded(); |
11372 | 0 | NS_WARNING_ASSERTION(rv.value != NS_ERROR_EDITOR_DESTROYED, |
11373 | 0 | "The editor has been destroyed during creating a bogus node"); |
11374 | 0 | } |
11375 | | |
11376 | | } // namespace mozilla |