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/OwningNonNull.h"
28 #include "mozilla/mozalloc.h"
29 #include "nsAString.h"
30 #include "nsAlgorithm.h"
31 #include "nsCRT.h"
32 #include "nsCRTGlue.h"
33 #include "nsComponentManagerUtils.h"
34 #include "nsContentUtils.h"
35 #include "nsDebug.h"
36 #include "nsError.h"
37 #include "nsGkAtoms.h"
38 #include "nsAtom.h"
39 #include "nsIContent.h"
40 #include "nsIContentIterator.h"
41 #include "nsID.h"
42 #include "nsIDOMCharacterData.h"
43 #include "nsIDOMDocument.h"
44 #include "nsIDOMElement.h"
45 #include "nsIDOMNode.h"
46 #include "nsIFrame.h"
47 #include "nsIHTMLAbsPosEditor.h"
48 #include "nsIHTMLDocument.h"
49 #include "nsINode.h"
50 #include "nsLiteralString.h"
51 #include "nsRange.h"
52 #include "nsReadableUtils.h"
53 #include "nsString.h"
54 #include "nsStringFwd.h"
55 #include "nsTArray.h"
56 #include "nsTextNode.h"
57 #include "nsThreadUtils.h"
58 #include "nsUnicharUtils.h"
59 #include <algorithm>
60
61 // Workaround for windows headers
62 #ifdef SetProp
63 #undef SetProp
64 #endif
65
66 class nsISupports;
67
68 namespace mozilla {
69
70 class RulesInfo;
71
72 using namespace dom;
73
74 // const static char* kMOZEditorBogusNodeAttr="MOZ_EDITOR_BOGUS_NODE";
75 // const static char* kMOZEditorBogusNodeValue="TRUE";
76
77 enum { kLonely = 0, kPrevSib = 1, kNextSib = 2, kBothSibs = 3 };
78
79 /********************************************************
80 * first some helpful functors we will use
81 ********************************************************/
82
IsBlockNode(const nsINode & node)83 static bool IsBlockNode(const nsINode& node) {
84 return HTMLEditor::NodeIsBlockStatic(&node);
85 }
86
IsInlineNode(const nsINode & node)87 static bool IsInlineNode(const nsINode& node) { return !IsBlockNode(node); }
88
IsStyleCachePreservingAction(EditAction action)89 static bool IsStyleCachePreservingAction(EditAction action) {
90 return action == EditAction::deleteSelection ||
91 action == EditAction::insertBreak || action == EditAction::makeList ||
92 action == EditAction::indent || action == EditAction::outdent ||
93 action == EditAction::align || action == EditAction::makeBasicBlock ||
94 action == EditAction::removeList ||
95 action == EditAction::makeDefListItem ||
96 action == EditAction::insertElement ||
97 action == EditAction::insertQuotation;
98 }
99
ParagraphSeparatorElement(ParagraphSeparator separator)100 static nsAtom& ParagraphSeparatorElement(ParagraphSeparator separator) {
101 switch (separator) {
102 default:
103 MOZ_FALLTHROUGH_ASSERT("Unexpected paragraph separator!");
104
105 case ParagraphSeparator::div:
106 return *nsGkAtoms::div;
107
108 case ParagraphSeparator::p:
109 return *nsGkAtoms::p;
110
111 case ParagraphSeparator::br:
112 return *nsGkAtoms::br;
113 }
114 }
115
116 class TableCellAndListItemFunctor final : public BoolDomIterFunctor {
117 public:
118 // Used to build list of all li's, td's & th's iterator covers
operator ()(nsINode * aNode) const119 virtual bool operator()(nsINode* aNode) const override {
120 return HTMLEditUtils::IsTableCell(aNode) ||
121 HTMLEditUtils::IsListItem(aNode);
122 }
123 };
124
125 class BRNodeFunctor final : public BoolDomIterFunctor {
126 public:
operator ()(nsINode * aNode) const127 virtual bool operator()(nsINode* aNode) const override {
128 return aNode->IsHTMLElement(nsGkAtoms::br);
129 }
130 };
131
132 class EmptyEditableFunctor final : public BoolDomIterFunctor {
133 public:
EmptyEditableFunctor(HTMLEditor * aHTMLEditor)134 explicit EmptyEditableFunctor(HTMLEditor* aHTMLEditor)
135 : mHTMLEditor(aHTMLEditor) {}
136
operator ()(nsINode * aNode) const137 virtual bool operator()(nsINode* aNode) const override {
138 if (mHTMLEditor->IsEditable(aNode) &&
139 (HTMLEditUtils::IsListItem(aNode) ||
140 HTMLEditUtils::IsTableCellOrCaption(*aNode))) {
141 bool bIsEmptyNode;
142 nsresult rv =
143 mHTMLEditor->IsEmptyNode(aNode, &bIsEmptyNode, false, false);
144 NS_ENSURE_SUCCESS(rv, false);
145 if (bIsEmptyNode) {
146 return true;
147 }
148 }
149 return false;
150 }
151
152 protected:
153 HTMLEditor* mHTMLEditor;
154 };
155
156 /********************************************************
157 * mozilla::HTMLEditRules
158 ********************************************************/
159
HTMLEditRules()160 HTMLEditRules::HTMLEditRules()
161 : mHTMLEditor(nullptr),
162 mListenerEnabled(false),
163 mReturnInEmptyLIKillsList(false),
164 mDidDeleteSelection(false),
165 mDidRangedDelete(false),
166 mRestoreContentEditableCount(false),
167 mJoinOffset(0) {
168 mIsHTMLEditRules = true;
169 InitFields();
170 }
171
InitFields()172 void HTMLEditRules::InitFields() {
173 mHTMLEditor = nullptr;
174 mDocChangeRange = nullptr;
175 mReturnInEmptyLIKillsList = true;
176 mDidDeleteSelection = false;
177 mDidRangedDelete = false;
178 mRestoreContentEditableCount = false;
179 mUtilRange = nullptr;
180 mJoinOffset = 0;
181 mNewBlock = nullptr;
182 mRangeItem = new RangeItem();
183
184 InitStyleCacheArray(mCachedStyles);
185 }
186
InitStyleCacheArray(StyleCache aStyleCache[SIZE_STYLE_TABLE])187 void HTMLEditRules::InitStyleCacheArray(
188 StyleCache aStyleCache[SIZE_STYLE_TABLE]) {
189 aStyleCache[0] = StyleCache(nsGkAtoms::b, nullptr);
190 aStyleCache[1] = StyleCache(nsGkAtoms::i, nullptr);
191 aStyleCache[2] = StyleCache(nsGkAtoms::u, nullptr);
192 aStyleCache[3] = StyleCache(nsGkAtoms::font, nsGkAtoms::face);
193 aStyleCache[4] = StyleCache(nsGkAtoms::font, nsGkAtoms::size);
194 aStyleCache[5] = StyleCache(nsGkAtoms::font, nsGkAtoms::color);
195 aStyleCache[6] = StyleCache(nsGkAtoms::tt, nullptr);
196 aStyleCache[7] = StyleCache(nsGkAtoms::em, nullptr);
197 aStyleCache[8] = StyleCache(nsGkAtoms::strong, nullptr);
198 aStyleCache[9] = StyleCache(nsGkAtoms::dfn, nullptr);
199 aStyleCache[10] = StyleCache(nsGkAtoms::code, nullptr);
200 aStyleCache[11] = StyleCache(nsGkAtoms::samp, nullptr);
201 aStyleCache[12] = StyleCache(nsGkAtoms::var, nullptr);
202 aStyleCache[13] = StyleCache(nsGkAtoms::cite, nullptr);
203 aStyleCache[14] = StyleCache(nsGkAtoms::abbr, nullptr);
204 aStyleCache[15] = StyleCache(nsGkAtoms::acronym, nullptr);
205 aStyleCache[16] = StyleCache(nsGkAtoms::backgroundColor, nullptr);
206 aStyleCache[17] = StyleCache(nsGkAtoms::sub, nullptr);
207 aStyleCache[18] = StyleCache(nsGkAtoms::sup, nullptr);
208 }
209
~HTMLEditRules()210 HTMLEditRules::~HTMLEditRules() {}
211
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLEditRules,TextEditRules)212 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLEditRules, TextEditRules)
213
214 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLEditRules, TextEditRules,
215 mDocChangeRange, mUtilRange, mNewBlock,
216 mRangeItem)
217
218 nsresult HTMLEditRules::Init(TextEditor* aTextEditor) {
219 if (NS_WARN_IF(!aTextEditor)) {
220 return NS_ERROR_INVALID_ARG;
221 }
222
223 InitFields();
224
225 mHTMLEditor = aTextEditor->AsHTMLEditor();
226 if (NS_WARN_IF(!mHTMLEditor)) {
227 return NS_ERROR_INVALID_ARG;
228 }
229
230 // call through to base class Init
231 nsresult rv = TextEditRules::Init(aTextEditor);
232 NS_ENSURE_SUCCESS(rv, rv);
233
234 // cache any prefs we care about
235 static const char kPrefName[] =
236 "editor.html.typing.returnInEmptyListItemClosesList";
237 nsAutoCString returnInEmptyLIKillsList;
238 Preferences::GetCString(kPrefName, returnInEmptyLIKillsList);
239
240 // only when "false", becomes FALSE. Otherwise (including empty), TRUE.
241 // XXX Why was this pref designed as a string and not bool?
242 mReturnInEmptyLIKillsList = !returnInEmptyLIKillsList.EqualsLiteral("false");
243
244 // make a utility range for use by the listenter
245 nsCOMPtr<nsINode> node = mHTMLEditor->GetRoot();
246 if (!node) {
247 node = mHTMLEditor->GetDocument();
248 }
249
250 NS_ENSURE_STATE(node);
251
252 mUtilRange = new nsRange(node);
253
254 // set up mDocChangeRange to be whole doc
255 // temporarily turn off rules sniffing
256 AutoLockRulesSniffing lockIt(this);
257 if (!mDocChangeRange) {
258 mDocChangeRange = new nsRange(node);
259 }
260
261 if (node->IsElement()) {
262 ErrorResult rv;
263 mDocChangeRange->SelectNode(*node, rv);
264 NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
265 AdjustSpecialBreaks();
266 }
267
268 StartToListenToEditActions();
269
270 return NS_OK;
271 }
272
DetachEditor()273 nsresult HTMLEditRules::DetachEditor() {
274 EndListeningToEditActions();
275 mHTMLEditor = nullptr;
276 return TextEditRules::DetachEditor();
277 }
278
BeforeEdit(EditAction aAction,nsIEditor::EDirection aDirection)279 nsresult HTMLEditRules::BeforeEdit(EditAction aAction,
280 nsIEditor::EDirection aDirection) {
281 if (mLockRulesSniffing) {
282 return NS_OK;
283 }
284
285 NS_ENSURE_STATE(mHTMLEditor);
286 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
287
288 AutoLockRulesSniffing lockIt(this);
289 mDidExplicitlySetInterline = false;
290
291 if (!mActionNesting) {
292 mActionNesting++;
293
294 // Clear our flag about if just deleted a range
295 mDidRangedDelete = false;
296
297 // Remember where our selection was before edit action took place:
298
299 // Get selection
300 RefPtr<Selection> selection = htmlEditor->GetSelection();
301
302 // Get the selection location
303 if (NS_WARN_IF(!selection) || !selection->RangeCount()) {
304 return NS_ERROR_UNEXPECTED;
305 }
306 mRangeItem->mStartContainer = selection->GetRangeAt(0)->GetStartContainer();
307 mRangeItem->mStartOffset = selection->GetRangeAt(0)->StartOffset();
308 mRangeItem->mEndContainer = selection->GetRangeAt(0)->GetEndContainer();
309 mRangeItem->mEndOffset = selection->GetRangeAt(0)->EndOffset();
310 nsCOMPtr<nsINode> selStartNode = mRangeItem->mStartContainer;
311 nsCOMPtr<nsINode> selEndNode = mRangeItem->mEndContainer;
312
313 // Register with range updater to track this as we perturb the doc
314 htmlEditor->mRangeUpdater.RegisterRangeItem(mRangeItem);
315
316 // Clear deletion state bool
317 mDidDeleteSelection = false;
318
319 // Clear out mDocChangeRange and mUtilRange
320 if (mDocChangeRange) {
321 // Clear out our accounting of what changed
322 mDocChangeRange->Reset();
323 }
324 if (mUtilRange) {
325 // Ditto for mUtilRange.
326 mUtilRange->Reset();
327 }
328
329 // Remember current inline styles for deletion and normal insertion ops
330 if (aAction == EditAction::insertText ||
331 aAction == EditAction::insertIMEText ||
332 aAction == EditAction::deleteSelection ||
333 IsStyleCachePreservingAction(aAction)) {
334 nsCOMPtr<nsINode> selNode =
335 aDirection == nsIEditor::eNext ? selEndNode : selStartNode;
336 nsresult rv = CacheInlineStyles(selNode);
337 NS_ENSURE_SUCCESS(rv, rv);
338 }
339
340 // Stabilize the document against contenteditable count changes
341 nsCOMPtr<nsIDOMDocument> doc = htmlEditor->GetDOMDocument();
342 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
343 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
344 NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE);
345 if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
346 htmlDoc->ChangeContentEditableCount(nullptr, +1);
347 mRestoreContentEditableCount = true;
348 }
349
350 // Check that selection is in subtree defined by body node
351 ConfirmSelectionInBody();
352 // Let rules remember the top level action
353 mTheAction = aAction;
354 }
355 return NS_OK;
356 }
357
AfterEdit(EditAction aAction,nsIEditor::EDirection aDirection)358 nsresult HTMLEditRules::AfterEdit(EditAction aAction,
359 nsIEditor::EDirection aDirection) {
360 if (mLockRulesSniffing) {
361 return NS_OK;
362 }
363
364 NS_ENSURE_STATE(mHTMLEditor);
365 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
366
367 AutoLockRulesSniffing lockIt(this);
368
369 MOZ_ASSERT(mActionNesting > 0);
370 nsresult rv = NS_OK;
371 mActionNesting--;
372 if (!mActionNesting) {
373 // Do all the tricky stuff
374 rv = AfterEditInner(aAction, aDirection);
375
376 // Free up selectionState range item
377 htmlEditor->mRangeUpdater.DropRangeItem(mRangeItem);
378
379 // Reset the contenteditable count to its previous value
380 if (mRestoreContentEditableCount) {
381 nsCOMPtr<nsIDOMDocument> doc = htmlEditor->GetDOMDocument();
382 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
383 nsCOMPtr<nsIHTMLDocument> htmlDoc = do_QueryInterface(doc);
384 NS_ENSURE_TRUE(htmlDoc, NS_ERROR_FAILURE);
385 if (htmlDoc->GetEditingState() == nsIHTMLDocument::eContentEditable) {
386 htmlDoc->ChangeContentEditableCount(nullptr, -1);
387 }
388 mRestoreContentEditableCount = false;
389 }
390 }
391
392 NS_ENSURE_SUCCESS(rv, rv);
393
394 return NS_OK;
395 }
396
AfterEditInner(EditAction aAction,nsIEditor::EDirection aDirection)397 nsresult HTMLEditRules::AfterEditInner(EditAction aAction,
398 nsIEditor::EDirection aDirection) {
399 ConfirmSelectionInBody();
400 if (aAction == EditAction::ignore) {
401 return NS_OK;
402 }
403
404 NS_ENSURE_STATE(mHTMLEditor);
405 RefPtr<Selection> selection = mHTMLEditor->GetSelection();
406 NS_ENSURE_STATE(selection);
407
408 nsCOMPtr<nsINode> rangeStartContainer, rangeEndContainer;
409 uint32_t rangeStartOffset = 0, rangeEndOffset = 0;
410 // do we have a real range to act on?
411 bool bDamagedRange = false;
412 if (mDocChangeRange) {
413 rangeStartContainer = mDocChangeRange->GetStartContainer();
414 rangeEndContainer = mDocChangeRange->GetEndContainer();
415 rangeStartOffset = mDocChangeRange->StartOffset();
416 rangeEndOffset = mDocChangeRange->EndOffset();
417 if (rangeStartContainer && rangeEndContainer) {
418 bDamagedRange = true;
419 }
420 }
421
422 if (bDamagedRange &&
423 !((aAction == EditAction::undo) || (aAction == EditAction::redo))) {
424 // don't let any txns in here move the selection around behind our back.
425 // Note that this won't prevent explicit selection setting from working.
426 NS_ENSURE_STATE(mHTMLEditor);
427 AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
428
429 // expand the "changed doc range" as needed
430 PromoteRange(*mDocChangeRange, aAction);
431
432 // if we did a ranged deletion or handling backspace key, make sure we have
433 // a place to put caret.
434 // Note we only want to do this if the overall operation was deletion,
435 // not if deletion was done along the way for EditAction::loadHTML,
436 // EditAction::insertText, etc. That's why this is here rather than
437 // DidDeleteSelection().
438 if (aAction == EditAction::deleteSelection && mDidRangedDelete) {
439 nsresult rv = InsertBRIfNeeded(selection);
440 NS_ENSURE_SUCCESS(rv, rv);
441 }
442
443 // add in any needed <br>s, and remove any unneeded ones.
444 AdjustSpecialBreaks();
445
446 // merge any adjacent text nodes
447 if (aAction != EditAction::insertText &&
448 aAction != EditAction::insertIMEText) {
449 NS_ENSURE_STATE(mHTMLEditor);
450 nsresult rv = mHTMLEditor->CollapseAdjacentTextNodes(mDocChangeRange);
451 NS_ENSURE_SUCCESS(rv, rv);
452 }
453
454 // clean up any empty nodes in the selection
455 nsresult rv = RemoveEmptyNodes();
456 NS_ENSURE_SUCCESS(rv, rv);
457
458 // attempt to transform any unneeded nbsp's into spaces after doing various
459 // operations
460 if (aAction == EditAction::insertText ||
461 aAction == EditAction::insertIMEText ||
462 aAction == EditAction::deleteSelection ||
463 aAction == EditAction::insertBreak ||
464 aAction == EditAction::htmlPaste || aAction == EditAction::loadHTML) {
465 rv = AdjustWhitespace(selection);
466 NS_ENSURE_SUCCESS(rv, rv);
467
468 // also do this for original selection endpoints.
469 NS_ENSURE_STATE(mHTMLEditor);
470 NS_ENSURE_STATE(mRangeItem->mStartContainer);
471 NS_ENSURE_STATE(mRangeItem->mEndContainer);
472 WSRunObject(mHTMLEditor, mRangeItem->mStartContainer,
473 mRangeItem->mStartOffset)
474 .AdjustWhitespace();
475 // we only need to handle old selection endpoint if it was different from
476 // start
477 if (mRangeItem->mStartContainer != mRangeItem->mEndContainer ||
478 mRangeItem->mStartOffset != mRangeItem->mEndOffset) {
479 NS_ENSURE_STATE(mHTMLEditor);
480 WSRunObject(mHTMLEditor, mRangeItem->mEndContainer,
481 mRangeItem->mEndOffset)
482 .AdjustWhitespace();
483 }
484 }
485
486 // if we created a new block, make sure selection lands in it
487 if (mNewBlock) {
488 rv = PinSelectionToNewBlock(selection);
489 mNewBlock = nullptr;
490 }
491
492 // adjust selection for insert text, html paste, and delete actions
493 if (aAction == EditAction::insertText ||
494 aAction == EditAction::insertIMEText ||
495 aAction == EditAction::deleteSelection ||
496 aAction == EditAction::insertBreak ||
497 aAction == EditAction::htmlPaste || aAction == EditAction::loadHTML) {
498 rv = AdjustSelection(selection, aDirection);
499 NS_ENSURE_SUCCESS(rv, rv);
500 }
501
502 // check for any styles which were removed inappropriately
503 if (aAction == EditAction::insertText ||
504 aAction == EditAction::insertIMEText ||
505 aAction == EditAction::deleteSelection ||
506 IsStyleCachePreservingAction(aAction)) {
507 NS_ENSURE_STATE(mHTMLEditor);
508 mHTMLEditor->mTypeInState->UpdateSelState(selection);
509 rv = ReapplyCachedStyles();
510 NS_ENSURE_SUCCESS(rv, rv);
511 ClearCachedStyles();
512 }
513 }
514
515 NS_ENSURE_STATE(mHTMLEditor);
516
517 nsresult rv = mHTMLEditor->HandleInlineSpellCheck(
518 aAction, selection, mRangeItem->mStartContainer, mRangeItem->mStartOffset,
519 rangeStartContainer, rangeStartOffset, rangeEndContainer, rangeEndOffset);
520 NS_ENSURE_SUCCESS(rv, rv);
521
522 // detect empty doc
523 rv = CreateBogusNodeIfNeeded(selection);
524 NS_ENSURE_SUCCESS(rv, rv);
525
526 // adjust selection HINT if needed
527 if (!mDidExplicitlySetInterline) {
528 CheckInterlinePosition(*selection);
529 }
530
531 return NS_OK;
532 }
533
WillDoAction(Selection * aSelection,RulesInfo * aInfo,bool * aCancel,bool * aHandled)534 nsresult HTMLEditRules::WillDoAction(Selection* aSelection, RulesInfo* aInfo,
535 bool* aCancel, bool* aHandled) {
536 MOZ_ASSERT(aInfo && aCancel && aHandled);
537
538 *aCancel = false;
539 *aHandled = false;
540
541 // Deal with actions for which we don't need to check whether the selection is
542 // editable.
543 if (aInfo->action == EditAction::outputText ||
544 aInfo->action == EditAction::undo || aInfo->action == EditAction::redo) {
545 return TextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
546 }
547
548 // Nothing to do if there's no selection to act on
549 if (!aSelection) {
550 return NS_OK;
551 }
552 NS_ENSURE_TRUE(aSelection->RangeCount(), NS_OK);
553
554 RefPtr<nsRange> range = aSelection->GetRangeAt(0);
555 nsCOMPtr<nsINode> selStartNode = range->GetStartContainer();
556
557 NS_ENSURE_STATE(mHTMLEditor);
558 if (!mHTMLEditor->IsModifiableNode(selStartNode)) {
559 *aCancel = true;
560 return NS_OK;
561 }
562
563 nsCOMPtr<nsINode> selEndNode = range->GetEndContainer();
564
565 if (selStartNode != selEndNode) {
566 NS_ENSURE_STATE(mHTMLEditor);
567 if (!mHTMLEditor->IsModifiableNode(selEndNode)) {
568 *aCancel = true;
569 return NS_OK;
570 }
571
572 NS_ENSURE_STATE(mHTMLEditor);
573 if (!mHTMLEditor->IsModifiableNode(range->GetCommonAncestor())) {
574 *aCancel = true;
575 return NS_OK;
576 }
577 }
578
579 switch (aInfo->action) {
580 case EditAction::insertText:
581 case EditAction::insertIMEText:
582 UndefineCaretBidiLevel(aSelection);
583 return WillInsertText(aInfo->action, aSelection, aCancel, aHandled,
584 aInfo->inString, aInfo->outString,
585 aInfo->maxLength);
586 case EditAction::loadHTML:
587 return WillLoadHTML(aSelection, aCancel);
588 case EditAction::insertBreak:
589 UndefineCaretBidiLevel(aSelection);
590 return WillInsertBreak(*aSelection, aCancel, aHandled);
591 case EditAction::deleteSelection:
592 return WillDeleteSelection(aSelection, aInfo->collapsedAction,
593 aInfo->stripWrappers, aCancel, aHandled);
594 case EditAction::makeList:
595 return WillMakeList(aSelection, aInfo->blockType, aInfo->entireList,
596 aInfo->bulletType, aCancel, aHandled);
597 case EditAction::indent:
598 return WillIndent(aSelection, aCancel, aHandled);
599 case EditAction::outdent:
600 return WillOutdent(*aSelection, aCancel, aHandled);
601 case EditAction::setAbsolutePosition:
602 return WillAbsolutePosition(*aSelection, aCancel, aHandled);
603 case EditAction::removeAbsolutePosition:
604 return WillRemoveAbsolutePosition(aSelection, aCancel, aHandled);
605 case EditAction::align:
606 return WillAlign(*aSelection, *aInfo->alignType, aCancel, aHandled);
607 case EditAction::makeBasicBlock:
608 return WillMakeBasicBlock(*aSelection, *aInfo->blockType, aCancel,
609 aHandled);
610 case EditAction::removeList:
611 return WillRemoveList(aSelection, aInfo->bOrdered, aCancel, aHandled);
612 case EditAction::makeDefListItem:
613 return WillMakeDefListItem(aSelection, aInfo->blockType,
614 aInfo->entireList, aCancel, aHandled);
615 case EditAction::insertElement:
616 WillInsert(*aSelection, aCancel);
617 return NS_OK;
618 case EditAction::decreaseZIndex:
619 return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled);
620 case EditAction::increaseZIndex:
621 return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled);
622 default:
623 return TextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
624 }
625 }
626
DidDoAction(Selection * aSelection,RulesInfo * aInfo,nsresult aResult)627 nsresult HTMLEditRules::DidDoAction(Selection* aSelection, RulesInfo* aInfo,
628 nsresult aResult) {
629 switch (aInfo->action) {
630 case EditAction::insertBreak:
631 return DidInsertBreak(aSelection, aResult);
632 case EditAction::deleteSelection:
633 return DidDeleteSelection(aSelection, aInfo->collapsedAction, aResult);
634 case EditAction::makeBasicBlock:
635 case EditAction::indent:
636 case EditAction::outdent:
637 case EditAction::align:
638 return DidMakeBasicBlock(aSelection, aInfo, aResult);
639 case EditAction::setAbsolutePosition: {
640 nsresult rv = DidMakeBasicBlock(aSelection, aInfo, aResult);
641 NS_ENSURE_SUCCESS(rv, rv);
642 return DidAbsolutePosition();
643 }
644 default:
645 // pass through to TextEditRules
646 return TextEditRules::DidDoAction(aSelection, aInfo, aResult);
647 }
648 }
649
DocumentIsEmpty()650 bool HTMLEditRules::DocumentIsEmpty() { return !!mBogusNode; }
651
GetListState(bool * aMixed,bool * aOL,bool * aUL,bool * aDL)652 nsresult HTMLEditRules::GetListState(bool* aMixed, bool* aOL, bool* aUL,
653 bool* aDL) {
654 NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER);
655 *aMixed = false;
656 *aOL = false;
657 *aUL = false;
658 *aDL = false;
659 bool bNonList = false;
660
661 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
662 nsresult rv =
663 GetListActionNodes(arrayOfNodes, EntireList::no, TouchContent::no);
664 NS_ENSURE_SUCCESS(rv, rv);
665
666 // Examine list type for nodes in selection.
667 for (const auto& curNode : arrayOfNodes) {
668 if (!curNode->IsElement()) {
669 bNonList = true;
670 } else if (curNode->IsHTMLElement(nsGkAtoms::ul)) {
671 *aUL = true;
672 } else if (curNode->IsHTMLElement(nsGkAtoms::ol)) {
673 *aOL = true;
674 } else if (curNode->IsHTMLElement(nsGkAtoms::li)) {
675 if (dom::Element* parent = curNode->GetParentElement()) {
676 if (parent->IsHTMLElement(nsGkAtoms::ul)) {
677 *aUL = true;
678 } else if (parent->IsHTMLElement(nsGkAtoms::ol)) {
679 *aOL = true;
680 }
681 }
682 } else if (curNode->IsAnyOfHTMLElements(nsGkAtoms::dl, nsGkAtoms::dt,
683 nsGkAtoms::dd)) {
684 *aDL = true;
685 } else {
686 bNonList = true;
687 }
688 }
689
690 // hokey arithmetic with booleans
691 if ((*aUL + *aOL + *aDL + bNonList) > 1) {
692 *aMixed = true;
693 }
694
695 return NS_OK;
696 }
697
GetListItemState(bool * aMixed,bool * aLI,bool * aDT,bool * aDD)698 nsresult HTMLEditRules::GetListItemState(bool* aMixed, bool* aLI, bool* aDT,
699 bool* aDD) {
700 NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER);
701 *aMixed = false;
702 *aLI = false;
703 *aDT = false;
704 *aDD = false;
705 bool bNonList = false;
706
707 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
708 nsresult rv =
709 GetListActionNodes(arrayOfNodes, EntireList::no, TouchContent::no);
710 NS_ENSURE_SUCCESS(rv, rv);
711
712 // examine list type for nodes in selection
713 for (const auto& node : arrayOfNodes) {
714 if (!node->IsElement()) {
715 bNonList = true;
716 } else if (node->IsAnyOfHTMLElements(nsGkAtoms::ul, nsGkAtoms::ol,
717 nsGkAtoms::li)) {
718 *aLI = true;
719 } else if (node->IsHTMLElement(nsGkAtoms::dt)) {
720 *aDT = true;
721 } else if (node->IsHTMLElement(nsGkAtoms::dd)) {
722 *aDD = true;
723 } else if (node->IsHTMLElement(nsGkAtoms::dl)) {
724 // need to look inside dl and see which types of items it has
725 bool bDT, bDD;
726 GetDefinitionListItemTypes(node->AsElement(), &bDT, &bDD);
727 *aDT |= bDT;
728 *aDD |= bDD;
729 } else {
730 bNonList = true;
731 }
732 }
733
734 // hokey arithmetic with booleans
735 if (*aDT + *aDD + bNonList > 1) {
736 *aMixed = true;
737 }
738
739 return NS_OK;
740 }
741
GetAlignment(bool * aMixed,nsIHTMLEditor::EAlignment * aAlign)742 nsresult HTMLEditRules::GetAlignment(bool* aMixed,
743 nsIHTMLEditor::EAlignment* aAlign) {
744 MOZ_ASSERT(aMixed && aAlign);
745
746 NS_ENSURE_STATE(mHTMLEditor);
747 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
748
749 // For now, just return first alignment. We'll lie about if it's mixed.
750 // This is for efficiency given that our current ui doesn't care if it's
751 // mixed.
752 // cmanske: NOT TRUE! We would like to pay attention to mixed state in Format
753 // | Align submenu!
754
755 // This routine assumes that alignment is done ONLY via divs
756
757 // Default alignment is left
758 *aMixed = false;
759 *aAlign = nsIHTMLEditor::eLeft;
760
761 // Get selection
762 NS_ENSURE_STATE(htmlEditor->GetSelection());
763 OwningNonNull<Selection> selection = *htmlEditor->GetSelection();
764
765 // Get selection location
766 NS_ENSURE_TRUE(htmlEditor->GetRoot(), NS_ERROR_FAILURE);
767 OwningNonNull<Element> root = *htmlEditor->GetRoot();
768
769 int32_t rootOffset =
770 root->GetParentNode() ? root->GetParentNode()->ComputeIndexOf(root) : -1;
771
772 nsRange* firstRange = selection->GetRangeAt(0);
773 if (NS_WARN_IF(!firstRange)) {
774 return NS_ERROR_FAILURE;
775 }
776 EditorRawDOMPoint atStartOfSelection(firstRange->StartRef());
777 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
778 return NS_ERROR_FAILURE;
779 }
780 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
781
782 // Is the selection collapsed?
783 nsCOMPtr<nsINode> nodeToExamine;
784 if (selection->Collapsed() || atStartOfSelection.GetContainerAsText()) {
785 // If selection is collapsed, we want to look at the container of selection
786 // start and its ancestors for divs with alignment on them. If we are in a
787 // text node, then that is the node of interest.
788 nodeToExamine = atStartOfSelection.GetContainer();
789 } else if (atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::html) &&
790 atStartOfSelection.Offset() == static_cast<uint32_t>(rootOffset)) {
791 // If we have selected the body, let's look at the first editable node
792 nodeToExamine = htmlEditor->GetNextEditableNode(atStartOfSelection);
793 } else {
794 nsTArray<RefPtr<nsRange>> arrayOfRanges;
795 GetPromotedRanges(selection, arrayOfRanges, EditAction::align);
796
797 // Use these ranges to construct a list of nodes to act on.
798 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
799 nsresult rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
800 EditAction::align, TouchContent::no);
801 NS_ENSURE_SUCCESS(rv, rv);
802 nodeToExamine = arrayOfNodes.SafeElementAt(0);
803 }
804
805 NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER);
806
807 nsCOMPtr<Element> blockParent = htmlEditor->GetBlock(*nodeToExamine);
808
809 NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
810
811 if (htmlEditor->IsCSSEnabled() &&
812 CSSEditUtils::IsCSSEditableProperty(blockParent, nullptr,
813 nsGkAtoms::align)) {
814 // We are in CSS mode and we know how to align this element with CSS
815 nsAutoString value;
816 // Let's get the value(s) of text-align or margin-left/margin-right
817 CSSEditUtils::GetCSSEquivalentToHTMLInlineStyleSet(
818 blockParent, nullptr, nsGkAtoms::align, value, CSSEditUtils::eComputed);
819 if (value.EqualsLiteral("center") || value.EqualsLiteral("-moz-center") ||
820 value.EqualsLiteral("auto auto")) {
821 *aAlign = nsIHTMLEditor::eCenter;
822 return NS_OK;
823 }
824 if (value.EqualsLiteral("right") || value.EqualsLiteral("-moz-right") ||
825 value.EqualsLiteral("auto 0px")) {
826 *aAlign = nsIHTMLEditor::eRight;
827 return NS_OK;
828 }
829 if (value.EqualsLiteral("justify")) {
830 *aAlign = nsIHTMLEditor::eJustify;
831 return NS_OK;
832 }
833 *aAlign = nsIHTMLEditor::eLeft;
834 return NS_OK;
835 }
836
837 // Check up the ladder for divs with alignment
838 bool isFirstNodeToExamine = true;
839 for (; nodeToExamine; nodeToExamine = nodeToExamine->GetParentNode()) {
840 if (!isFirstNodeToExamine &&
841 nodeToExamine->IsHTMLElement(nsGkAtoms::table)) {
842 // The node to examine is a table and this is not the first node we
843 // examine; let's break here to materialize the 'inline-block' behaviour
844 // of html tables regarding to text alignment
845 return NS_OK;
846 }
847
848 if (CSSEditUtils::IsCSSEditableProperty(nodeToExamine, nullptr,
849 nsGkAtoms::align)) {
850 nsAutoString value;
851 CSSEditUtils::GetSpecifiedProperty(*nodeToExamine, *nsGkAtoms::textAlign,
852 value);
853 if (!value.IsEmpty()) {
854 if (value.EqualsLiteral("center")) {
855 *aAlign = nsIHTMLEditor::eCenter;
856 return NS_OK;
857 }
858 if (value.EqualsLiteral("right")) {
859 *aAlign = nsIHTMLEditor::eRight;
860 return NS_OK;
861 }
862 if (value.EqualsLiteral("justify")) {
863 *aAlign = nsIHTMLEditor::eJustify;
864 return NS_OK;
865 }
866 if (value.EqualsLiteral("left")) {
867 *aAlign = nsIHTMLEditor::eLeft;
868 return NS_OK;
869 }
870 // XXX
871 // text-align: start and end aren't supported yet
872 }
873 }
874
875 if (HTMLEditUtils::SupportsAlignAttr(*nodeToExamine)) {
876 // Check for alignment
877 nsAutoString typeAttrVal;
878 nodeToExamine->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::align,
879 typeAttrVal);
880 ToLowerCase(typeAttrVal);
881 if (!typeAttrVal.IsEmpty()) {
882 if (typeAttrVal.EqualsLiteral("center")) {
883 *aAlign = nsIHTMLEditor::eCenter;
884 } else if (typeAttrVal.EqualsLiteral("right")) {
885 *aAlign = nsIHTMLEditor::eRight;
886 } else if (typeAttrVal.EqualsLiteral("justify")) {
887 *aAlign = nsIHTMLEditor::eJustify;
888 } else {
889 *aAlign = nsIHTMLEditor::eLeft;
890 }
891 return NS_OK;
892 }
893 }
894 isFirstNodeToExamine = false;
895 }
896 return NS_OK;
897 }
898
MarginPropertyAtomForIndent(nsINode & aNode)899 static nsAtom& MarginPropertyAtomForIndent(nsINode& aNode) {
900 nsAutoString direction;
901 CSSEditUtils::GetComputedProperty(aNode, *nsGkAtoms::direction, direction);
902 return direction.EqualsLiteral("rtl") ? *nsGkAtoms::marginRight
903 : *nsGkAtoms::marginLeft;
904 }
905
GetIndentState(bool * aCanIndent,bool * aCanOutdent)906 nsresult HTMLEditRules::GetIndentState(bool* aCanIndent, bool* aCanOutdent) {
907 // XXX Looks like that this is implementation of
908 // nsIHTMLEditor::getIndentState() however nobody calls this method
909 // even with the interface method.
910 NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_FAILURE);
911 *aCanIndent = true;
912 *aCanOutdent = false;
913
914 // get selection
915 NS_ENSURE_STATE(mHTMLEditor && mHTMLEditor->GetSelection());
916 OwningNonNull<Selection> selection = *mHTMLEditor->GetSelection();
917
918 // contruct a list of nodes to act on.
919 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
920 nsresult rv = GetNodesFromSelection(*selection, EditAction::indent,
921 arrayOfNodes, TouchContent::no);
922 NS_ENSURE_SUCCESS(rv, rv);
923
924 // examine nodes in selection for blockquotes or list elements;
925 // these we can outdent. Note that we return true for canOutdent
926 // if *any* of the selection is outdentable, rather than all of it.
927 NS_ENSURE_STATE(mHTMLEditor);
928 bool useCSS = mHTMLEditor->IsCSSEnabled();
929 for (auto& curNode : Reversed(arrayOfNodes)) {
930 if (HTMLEditUtils::IsNodeThatCanOutdent(curNode)) {
931 *aCanOutdent = true;
932 break;
933 } else if (useCSS) {
934 // we are in CSS mode, indentation is done using the margin-left (or
935 // margin-right) property
936 nsAtom& marginProperty = MarginPropertyAtomForIndent(curNode);
937 nsAutoString value;
938 // retrieve its specified value
939 CSSEditUtils::GetSpecifiedProperty(*curNode, marginProperty, value);
940 float f;
941 RefPtr<nsAtom> unit;
942 // get its number part and its unit
943 CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
944 // if the number part is strictly positive, outdent is possible
945 if (0 < f) {
946 *aCanOutdent = true;
947 break;
948 }
949 }
950 }
951
952 if (!*aCanOutdent) {
953 // if we haven't found something to outdent yet, also check the parents
954 // of selection endpoints. We might have a blockquote or list item
955 // in the parent hierarchy.
956
957 // gather up info we need for test
958 NS_ENSURE_STATE(mHTMLEditor);
959 nsCOMPtr<nsINode> parent, root = mHTMLEditor->GetRoot();
960 NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER);
961 int32_t selOffset;
962 NS_ENSURE_STATE(mHTMLEditor);
963 RefPtr<Selection> selection = mHTMLEditor->GetSelection();
964 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
965
966 // test start parent hierarchy
967 rv = EditorBase::GetStartNodeAndOffset(selection, getter_AddRefs(parent),
968 &selOffset);
969 NS_ENSURE_SUCCESS(rv, rv);
970 while (parent && parent != root) {
971 if (HTMLEditUtils::IsNodeThatCanOutdent(parent)) {
972 *aCanOutdent = true;
973 break;
974 }
975 parent = parent->GetParentNode();
976 }
977
978 // test end parent hierarchy
979 rv = EditorBase::GetEndNodeAndOffset(selection, getter_AddRefs(parent),
980 &selOffset);
981 NS_ENSURE_SUCCESS(rv, rv);
982 while (parent && parent != root) {
983 if (HTMLEditUtils::IsNodeThatCanOutdent(parent)) {
984 *aCanOutdent = true;
985 break;
986 }
987 parent = parent->GetParentNode();
988 }
989 }
990 return NS_OK;
991 }
992
GetParagraphState(bool * aMixed,nsAString & outFormat)993 nsresult HTMLEditRules::GetParagraphState(bool* aMixed, nsAString& outFormat) {
994 // This routine is *heavily* tied to our ui choices in the paragraph
995 // style popup. I can't see a way around that.
996 NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
997 *aMixed = true;
998 outFormat.Truncate(0);
999
1000 bool bMixed = false;
1001 // using "x" as an uninitialized value, since "" is meaningful
1002 nsAutoString formatStr(NS_LITERAL_STRING("x"));
1003
1004 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
1005 nsresult rv = GetParagraphFormatNodes(arrayOfNodes, TouchContent::no);
1006 NS_ENSURE_SUCCESS(rv, rv);
1007
1008 // post process list. We need to replace any block nodes that are not format
1009 // nodes with their content. This is so we only have to look "up" the
1010 // hierarchy to find format nodes, instead of both up and down.
1011 for (int32_t i = arrayOfNodes.Length() - 1; i >= 0; i--) {
1012 auto& curNode = arrayOfNodes[i];
1013 nsAutoString format;
1014 // if it is a known format node we have it easy
1015 if (IsBlockNode(curNode) && !HTMLEditUtils::IsFormatNode(curNode)) {
1016 // arrayOfNodes.RemoveObject(curNode);
1017 rv = AppendInnerFormatNodes(arrayOfNodes, curNode);
1018 NS_ENSURE_SUCCESS(rv, rv);
1019 }
1020 }
1021
1022 // we might have an empty node list. if so, find selection parent
1023 // and put that on the list
1024 if (arrayOfNodes.IsEmpty()) {
1025 nsCOMPtr<nsINode> selNode;
1026 int32_t selOffset;
1027 NS_ENSURE_STATE(mHTMLEditor);
1028 RefPtr<Selection> selection = mHTMLEditor->GetSelection();
1029 NS_ENSURE_STATE(selection);
1030 rv = EditorBase::GetStartNodeAndOffset(selection, getter_AddRefs(selNode),
1031 &selOffset);
1032 NS_ENSURE_SUCCESS(rv, rv);
1033 NS_ENSURE_TRUE(selNode, NS_ERROR_NULL_POINTER);
1034 arrayOfNodes.AppendElement(*selNode);
1035 }
1036
1037 // remember root node
1038 NS_ENSURE_STATE(mHTMLEditor);
1039 nsCOMPtr<Element> rootElem = mHTMLEditor->GetRoot();
1040 NS_ENSURE_TRUE(rootElem, NS_ERROR_NULL_POINTER);
1041
1042 // loop through the nodes in selection and examine their paragraph format
1043 for (auto& curNode : Reversed(arrayOfNodes)) {
1044 nsAutoString format;
1045 // if it is a known format node we have it easy
1046 if (HTMLEditUtils::IsFormatNode(curNode)) {
1047 GetFormatString(curNode, format);
1048 } else if (IsBlockNode(curNode)) {
1049 // this is a div or some other non-format block.
1050 // we should ignore it. Its children were appended to this list
1051 // by AppendInnerFormatNodes() call above. We will get needed
1052 // info when we examine them instead.
1053 continue;
1054 } else {
1055 nsINode* node = curNode->GetParentNode();
1056 while (node) {
1057 if (node == rootElem) {
1058 format.Truncate(0);
1059 break;
1060 } else if (HTMLEditUtils::IsFormatNode(node)) {
1061 GetFormatString(node, format);
1062 break;
1063 }
1064 // else keep looking up
1065 node = node->GetParentNode();
1066 }
1067 }
1068
1069 // if this is the first node, we've found, remember it as the format
1070 if (formatStr.EqualsLiteral("x")) {
1071 formatStr = format;
1072 }
1073 // else make sure it matches previously found format
1074 else if (format != formatStr) {
1075 bMixed = true;
1076 break;
1077 }
1078 }
1079
1080 *aMixed = bMixed;
1081 outFormat = formatStr;
1082 return NS_OK;
1083 }
1084
AppendInnerFormatNodes(nsTArray<OwningNonNull<nsINode>> & aArray,nsINode * aNode)1085 nsresult HTMLEditRules::AppendInnerFormatNodes(
1086 nsTArray<OwningNonNull<nsINode>>& aArray, nsINode* aNode) {
1087 MOZ_ASSERT(aNode);
1088
1089 // we only need to place any one inline inside this node onto
1090 // the list. They are all the same for purposes of determining
1091 // paragraph style. We use foundInline to track this as we are
1092 // going through the children in the loop below.
1093 bool foundInline = false;
1094 for (nsIContent* child = aNode->GetFirstChild(); child;
1095 child = child->GetNextSibling()) {
1096 bool isBlock = IsBlockNode(*child);
1097 bool isFormat = HTMLEditUtils::IsFormatNode(child);
1098 if (isBlock && !isFormat) {
1099 // if it's a div, etc., recurse
1100 AppendInnerFormatNodes(aArray, child);
1101 } else if (isFormat) {
1102 aArray.AppendElement(*child);
1103 } else if (!foundInline) {
1104 // if this is the first inline we've found, use it
1105 foundInline = true;
1106 aArray.AppendElement(*child);
1107 }
1108 }
1109 return NS_OK;
1110 }
1111
GetFormatString(nsINode * aNode,nsAString & outFormat)1112 nsresult HTMLEditRules::GetFormatString(nsINode* aNode, nsAString& outFormat) {
1113 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
1114
1115 if (HTMLEditUtils::IsFormatNode(aNode)) {
1116 aNode->NodeInfo()->NameAtom()->ToString(outFormat);
1117 } else {
1118 outFormat.Truncate();
1119 }
1120 return NS_OK;
1121 }
1122
WillInsert(Selection & aSelection,bool * aCancel)1123 void HTMLEditRules::WillInsert(Selection& aSelection, bool* aCancel) {
1124 MOZ_ASSERT(aCancel);
1125
1126 TextEditRules::WillInsert(aSelection, aCancel);
1127
1128 NS_ENSURE_TRUE_VOID(mHTMLEditor);
1129 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
1130
1131 // Adjust selection to prevent insertion after a moz-BR. This next only
1132 // works for collapsed selections right now, because selection is a pain to
1133 // work with when not collapsed. (no good way to extend start or end of
1134 // selection), so we ignore those types of selections.
1135 if (!aSelection.Collapsed()) {
1136 return;
1137 }
1138
1139 // If we are after a mozBR in the same block, then move selection to be
1140 // before it
1141 nsRange* firstRange = aSelection.GetRangeAt(0);
1142 if (NS_WARN_IF(!firstRange)) {
1143 return;
1144 }
1145
1146 EditorRawDOMPoint atStartOfSelection(firstRange->StartRef());
1147 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
1148 return;
1149 }
1150 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
1151
1152 // Get prior node
1153 nsCOMPtr<nsIContent> priorNode =
1154 htmlEditor->GetPreviousEditableHTMLNode(atStartOfSelection);
1155 if (priorNode && TextEditUtils::IsMozBR(priorNode)) {
1156 RefPtr<Element> block1 =
1157 htmlEditor->GetBlock(*atStartOfSelection.GetContainer());
1158 RefPtr<Element> block2 = htmlEditor->GetBlockNodeParent(priorNode);
1159
1160 if (block1 && block1 == block2) {
1161 // If we are here then the selection is right after a mozBR that is in
1162 // the same block as the selection. We need to move the selection start
1163 // to be before the mozBR.
1164 EditorRawDOMPoint point(priorNode);
1165 nsresult rv = aSelection.Collapse(point.AsRaw());
1166 NS_ENSURE_SUCCESS_VOID(rv);
1167 }
1168 }
1169
1170 if (mDidDeleteSelection && (mTheAction == EditAction::insertText ||
1171 mTheAction == EditAction::insertIMEText ||
1172 mTheAction == EditAction::deleteSelection)) {
1173 nsresult rv = ReapplyCachedStyles();
1174 NS_ENSURE_SUCCESS_VOID(rv);
1175 }
1176 // For most actions we want to clear the cached styles, but there are
1177 // exceptions
1178 if (!IsStyleCachePreservingAction(mTheAction)) {
1179 ClearCachedStyles();
1180 }
1181 }
1182
WillInsertText(EditAction aAction,Selection * aSelection,bool * aCancel,bool * aHandled,const nsAString * inString,nsAString * outString,int32_t aMaxLength)1183 nsresult HTMLEditRules::WillInsertText(
1184 EditAction aAction, Selection* aSelection, bool* aCancel, bool* aHandled,
1185 const nsAString* inString, nsAString* outString, int32_t aMaxLength) {
1186 if (!aSelection || !aCancel || !aHandled) {
1187 return NS_ERROR_NULL_POINTER;
1188 }
1189
1190 // initialize out param
1191 *aCancel = false;
1192 *aHandled = true;
1193 // If the selection isn't collapsed, delete it. Don't delete existing inline
1194 // tags, because we're hopefully going to insert text (bug 787432).
1195 if (!aSelection->Collapsed()) {
1196 NS_ENSURE_STATE(mHTMLEditor);
1197 nsresult rv =
1198 mHTMLEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eNoStrip);
1199 NS_ENSURE_SUCCESS(rv, rv);
1200 }
1201
1202 WillInsert(*aSelection, aCancel);
1203 // initialize out param
1204 // we want to ignore result of WillInsert()
1205 *aCancel = false;
1206
1207 // we need to get the doc
1208 NS_ENSURE_STATE(mHTMLEditor);
1209 nsCOMPtr<nsIDocument> doc = mHTMLEditor->GetDocument();
1210 NS_ENSURE_STATE(doc);
1211
1212 // for every property that is set, insert a new inline style node
1213 nsresult rv = CreateStyleForInsertText(*aSelection, *doc);
1214 NS_ENSURE_SUCCESS(rv, rv);
1215
1216 // get the (collapsed) selection location
1217 NS_ENSURE_STATE(mHTMLEditor);
1218 nsRange* firstRange = aSelection->GetRangeAt(0);
1219 if (NS_WARN_IF(!firstRange)) {
1220 return NS_ERROR_FAILURE;
1221 }
1222 EditorDOMPoint pointToInsert(firstRange->StartRef());
1223 if (NS_WARN_IF(!pointToInsert.IsSet())) {
1224 return NS_ERROR_FAILURE;
1225 }
1226 MOZ_ASSERT(pointToInsert.IsSetAndValid());
1227
1228 // dont put text in places that can't have it
1229 if (NS_WARN_IF(!mHTMLEditor) ||
1230 (!EditorBase::IsTextNode(pointToInsert.GetContainer()) &&
1231 !mHTMLEditor->CanContainTag(*pointToInsert.GetContainer(),
1232 *nsGkAtoms::textTagName))) {
1233 return NS_ERROR_FAILURE;
1234 }
1235
1236 if (aAction == EditAction::insertIMEText) {
1237 // Right now the WSRunObject code bails on empty strings, but IME needs
1238 // the InsertTextImpl() call to still happen since empty strings are
1239 // meaningful there.
1240 NS_ENSURE_STATE(mHTMLEditor);
1241 // If there is one or more IME selections, its minimum offset should be
1242 // the insertion point.
1243 int32_t IMESelectionOffset =
1244 mHTMLEditor->GetIMESelectionStartOffsetIn(pointToInsert.GetContainer());
1245 if (IMESelectionOffset >= 0) {
1246 pointToInsert.Set(pointToInsert.GetContainer(), IMESelectionOffset);
1247 }
1248
1249 if (inString->IsEmpty()) {
1250 rv = mHTMLEditor->InsertTextImpl(*doc, *inString, pointToInsert.AsRaw());
1251 if (NS_WARN_IF(NS_FAILED(rv))) {
1252 return rv;
1253 }
1254 return NS_OK;
1255 }
1256
1257 WSRunObject wsObj(mHTMLEditor, pointToInsert.GetContainer(),
1258 pointToInsert.Offset());
1259 rv = wsObj.InsertText(*doc, *inString, pointToInsert.AsRaw());
1260 if (NS_WARN_IF(NS_FAILED(rv))) {
1261 return rv;
1262 }
1263 return NS_OK;
1264 }
1265
1266 // aAction == kInsertText
1267
1268 // find where we are
1269 EditorDOMPoint currentPoint(pointToInsert);
1270
1271 // is our text going to be PREformatted?
1272 // We remember this so that we know how to handle tabs.
1273 bool isPRE;
1274 NS_ENSURE_STATE(mHTMLEditor);
1275 rv = mHTMLEditor->IsPreformatted(GetAsDOMNode(pointToInsert.GetContainer()),
1276 &isPRE);
1277 NS_ENSURE_SUCCESS(rv, rv);
1278
1279 // turn off the edit listener: we know how to
1280 // build the "doc changed range" ourselves, and it's
1281 // must faster to do it once here than to track all
1282 // the changes one at a time.
1283 AutoLockListener lockit(&mListenerEnabled);
1284
1285 // don't change my selection in subtransactions
1286 NS_ENSURE_STATE(mHTMLEditor);
1287 AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
1288 nsAutoString tString(*inString);
1289 const char16_t* unicodeBuf = tString.get();
1290 int32_t pos = 0;
1291 NS_NAMED_LITERAL_STRING(newlineStr, LFSTR);
1292
1293 {
1294 NS_ENSURE_STATE(mHTMLEditor);
1295 AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater, &pointToInsert);
1296
1297 // for efficiency, break out the pre case separately. This is because
1298 // its a lot cheaper to search the input string for only newlines than
1299 // it is to search for both tabs and newlines.
1300 if (isPRE || IsPlaintextEditor()) {
1301 while (unicodeBuf && pos != -1 &&
1302 pos < static_cast<int32_t>(inString->Length())) {
1303 int32_t oldPos = pos;
1304 int32_t subStrLen;
1305 pos = tString.FindChar(nsCRT::LF, oldPos);
1306
1307 if (pos != -1) {
1308 subStrLen = pos - oldPos;
1309 // if first char is newline, then use just it
1310 if (!subStrLen) {
1311 subStrLen = 1;
1312 }
1313 } else {
1314 subStrLen = tString.Length() - oldPos;
1315 pos = tString.Length();
1316 }
1317
1318 nsDependentSubstring subStr(tString, oldPos, subStrLen);
1319
1320 // is it a return?
1321 if (subStr.Equals(newlineStr)) {
1322 NS_ENSURE_STATE(mHTMLEditor);
1323 nsCOMPtr<Element> br = mHTMLEditor->CreateBRImpl(
1324 *aSelection, currentPoint.AsRaw(), nsIEditor::eNone);
1325 NS_ENSURE_STATE(br);
1326 pos++;
1327 if (br->GetNextSibling()) {
1328 pointToInsert.Set(br->GetNextSibling());
1329 } else {
1330 pointToInsert.SetToEndOf(currentPoint.GetContainer());
1331 }
1332 // XXX In most cases, pointToInsert and currentPoint are same here.
1333 // But if the <br> element has been moved to different point by
1334 // mutation observer, those points become different.
1335 currentPoint.Set(br);
1336 DebugOnly<bool> advanced = currentPoint.AdvanceOffset();
1337 NS_WARNING_ASSERTION(
1338 advanced, "Failed to advance offset after the new <br> element");
1339 NS_WARNING_ASSERTION(currentPoint == pointToInsert,
1340 "Perhaps, <br> element position has been moved "
1341 "to different point "
1342 "by mutation observer");
1343 } else {
1344 NS_ENSURE_STATE(mHTMLEditor);
1345 EditorRawDOMPoint pointAfterInsertedString;
1346 rv = mHTMLEditor->InsertTextImpl(*doc, subStr, currentPoint.AsRaw(),
1347 &pointAfterInsertedString);
1348 NS_ENSURE_SUCCESS(rv, rv);
1349 currentPoint = pointAfterInsertedString;
1350 pointToInsert = pointAfterInsertedString;
1351 }
1352 }
1353 } else {
1354 NS_NAMED_LITERAL_STRING(tabStr, "\t");
1355 NS_NAMED_LITERAL_STRING(spacesStr, " ");
1356 char specialChars[] = {TAB, nsCRT::LF, 0};
1357 while (unicodeBuf && pos != -1 &&
1358 pos < static_cast<int32_t>(inString->Length())) {
1359 int32_t oldPos = pos;
1360 int32_t subStrLen;
1361 pos = tString.FindCharInSet(specialChars, oldPos);
1362
1363 if (pos != -1) {
1364 subStrLen = pos - oldPos;
1365 // if first char is newline, then use just it
1366 if (!subStrLen) {
1367 subStrLen = 1;
1368 }
1369 } else {
1370 subStrLen = tString.Length() - oldPos;
1371 pos = tString.Length();
1372 }
1373
1374 nsDependentSubstring subStr(tString, oldPos, subStrLen);
1375 NS_ENSURE_STATE(mHTMLEditor);
1376 WSRunObject wsObj(mHTMLEditor, currentPoint.GetContainer(),
1377 currentPoint.Offset());
1378
1379 // is it a tab?
1380 if (subStr.Equals(tabStr)) {
1381 EditorRawDOMPoint pointAfterInsertedSpaces;
1382 rv = wsObj.InsertText(*doc, spacesStr, currentPoint.AsRaw(),
1383 &pointAfterInsertedSpaces);
1384 if (NS_WARN_IF(NS_FAILED(rv))) {
1385 return rv;
1386 }
1387 pos++;
1388 currentPoint = pointAfterInsertedSpaces;
1389 pointToInsert = pointAfterInsertedSpaces;
1390 }
1391 // is it a return?
1392 else if (subStr.Equals(newlineStr)) {
1393 RefPtr<Element> newBRElement = wsObj.InsertBreak(
1394 *aSelection, currentPoint.AsRaw(), nsIEditor::eNone);
1395 if (NS_WARN_IF(!newBRElement)) {
1396 return NS_ERROR_FAILURE;
1397 }
1398 pos++;
1399 if (newBRElement->GetNextSibling()) {
1400 pointToInsert.Set(newBRElement->GetNextSibling());
1401 } else {
1402 pointToInsert.SetToEndOf(currentPoint.GetContainer());
1403 }
1404 currentPoint.Set(newBRElement);
1405 DebugOnly<bool> advanced = currentPoint.AdvanceOffset();
1406 NS_WARNING_ASSERTION(
1407 advanced, "Failed to advance offset to after the new <br> node");
1408 // XXX If the newBRElement has been moved or removed by mutation
1409 // observer, we hit this assert. We need to check if
1410 // newBRElement is in expected point, though, we must have
1411 // a lot of same bugs...
1412 NS_WARNING_ASSERTION(
1413 currentPoint == pointToInsert,
1414 "Perhaps, newBRElement has been moved or removed unexpectedly");
1415 } else {
1416 EditorRawDOMPoint pointAfterInsertedString;
1417 rv = wsObj.InsertText(*doc, subStr, currentPoint.AsRaw(),
1418 &pointAfterInsertedString);
1419 if (NS_WARN_IF(NS_FAILED(rv))) {
1420 return rv;
1421 }
1422 currentPoint = pointAfterInsertedString;
1423 pointToInsert = pointAfterInsertedString;
1424 }
1425 }
1426 }
1427
1428 // After this block, pointToInsert is updated by AutoTrackDOMPoint.
1429 }
1430
1431 aSelection->SetInterlinePosition(false);
1432
1433 if (currentPoint.IsSet()) {
1434 ErrorResult error;
1435 aSelection->Collapse(currentPoint.AsRaw(), error);
1436 if (error.Failed()) {
1437 NS_WARNING("Failed to collapse at current point");
1438 error.SuppressException();
1439 }
1440 }
1441
1442 // manually update the doc changed range so that AfterEdit will clean up
1443 // the correct portion of the document.
1444 if (!mDocChangeRange) {
1445 mDocChangeRange = new nsRange(pointToInsert.GetContainer());
1446 }
1447
1448 if (currentPoint.IsSet()) {
1449 rv = mDocChangeRange->SetStartAndEnd(pointToInsert.AsRaw(),
1450 currentPoint.AsRaw());
1451 if (NS_WARN_IF(NS_FAILED(rv))) {
1452 return rv;
1453 }
1454 return NS_OK;
1455 }
1456
1457 rv = mDocChangeRange->CollapseTo(pointToInsert.AsRaw());
1458 if (NS_WARN_IF(NS_FAILED(rv))) {
1459 return rv;
1460 }
1461 return NS_OK;
1462 }
1463
WillLoadHTML(Selection * aSelection,bool * aCancel)1464 nsresult HTMLEditRules::WillLoadHTML(Selection* aSelection, bool* aCancel) {
1465 NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER);
1466
1467 *aCancel = false;
1468
1469 // Delete mBogusNode if it exists. If we really need one,
1470 // it will be added during post-processing in AfterEditInner().
1471
1472 if (mBogusNode) {
1473 if (NS_WARN_IF(!mHTMLEditor)) {
1474 return NS_ERROR_UNEXPECTED;
1475 }
1476 mHTMLEditor->DeleteNode(mBogusNode);
1477 mBogusNode = nullptr;
1478 }
1479
1480 return NS_OK;
1481 }
1482
CanContainParagraph(Element & aElement) const1483 bool HTMLEditRules::CanContainParagraph(Element& aElement) const {
1484 if (NS_WARN_IF(!mHTMLEditor)) {
1485 return false;
1486 }
1487
1488 if (mHTMLEditor->CanContainTag(aElement, *nsGkAtoms::p)) {
1489 return true;
1490 }
1491
1492 // Even if the element cannot have a <p> element as a child, it can contain
1493 // <p> element as a descendant if it's one of the following elements.
1494 if (aElement.IsAnyOfHTMLElements(nsGkAtoms::ol, nsGkAtoms::ul, nsGkAtoms::dl,
1495 nsGkAtoms::table, nsGkAtoms::thead,
1496 nsGkAtoms::tbody, nsGkAtoms::tfoot,
1497 nsGkAtoms::tr)) {
1498 return true;
1499 }
1500
1501 // XXX Otherwise, Chromium checks the CSS box is a block, but we don't do it
1502 // for now.
1503 return false;
1504 }
1505
WillInsertBreak(Selection & aSelection,bool * aCancel,bool * aHandled)1506 nsresult HTMLEditRules::WillInsertBreak(Selection& aSelection, bool* aCancel,
1507 bool* aHandled) {
1508 MOZ_ASSERT(aCancel && aHandled);
1509 *aCancel = false;
1510 *aHandled = false;
1511
1512 NS_ENSURE_STATE(mHTMLEditor);
1513 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
1514
1515 // If the selection isn't collapsed, delete it.
1516 if (!aSelection.Collapsed()) {
1517 nsresult rv =
1518 htmlEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
1519 NS_ENSURE_SUCCESS(rv, rv);
1520 }
1521
1522 WillInsert(aSelection, aCancel);
1523
1524 // Initialize out param. We want to ignore result of WillInsert().
1525 *aCancel = false;
1526
1527 // Split any mailcites in the way. Should we abort this if we encounter
1528 // table cell boundaries?
1529 if (IsMailEditor()) {
1530 nsresult rv = SplitMailCites(&aSelection, aHandled);
1531 NS_ENSURE_SUCCESS(rv, rv);
1532 if (*aHandled) {
1533 return NS_OK;
1534 }
1535 }
1536
1537 // Smart splitting rules
1538 nsRange* firstRange = aSelection.GetRangeAt(0);
1539 if (NS_WARN_IF(!firstRange)) {
1540 return NS_ERROR_FAILURE;
1541 }
1542
1543 EditorDOMPoint atStartOfSelection(firstRange->StartRef());
1544 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
1545 return NS_ERROR_FAILURE;
1546 }
1547 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
1548
1549 // Do nothing if the node is read-only
1550 if (!htmlEditor->IsModifiableNode(atStartOfSelection.GetContainer())) {
1551 *aCancel = true;
1552 return NS_OK;
1553 }
1554
1555 // If the active editing host is an inline element, or if the active editing
1556 // host is the block parent itself and we're configured to use <br> as a
1557 // paragraph separator, just append a <br>.
1558 nsCOMPtr<Element> host = htmlEditor->GetActiveEditingHost();
1559 if (NS_WARN_IF(!host)) {
1560 return NS_ERROR_FAILURE;
1561 }
1562
1563 // Look for the nearest parent block. However, don't return error even if
1564 // there is no block parent here because in such case, i.e., editing host
1565 // is an inline element, we should insert <br> simply.
1566 RefPtr<Element> blockParent =
1567 HTMLEditor::GetBlock(*atStartOfSelection.GetContainer(), host);
1568
1569 ParagraphSeparator separator = htmlEditor->GetDefaultParagraphSeparator();
1570 bool insertBRElement;
1571 // If there is no block parent in the editing host, i.e., the editing host
1572 // itself is also a non-block element, we should insert a <br> element.
1573 if (!blockParent) {
1574 // XXX Chromium checks if the CSS box of the editing host is block.
1575 insertBRElement = true;
1576 }
1577 // If only the editing host is block, and the default paragraph separator
1578 // is <br> or the editing host cannot contain a <p> element, we should
1579 // insert a <br> element.
1580 else if (host == blockParent) {
1581 insertBRElement =
1582 separator == ParagraphSeparator::br || !CanContainParagraph(*host);
1583 }
1584 // If the nearest block parent is a single-line container declared in
1585 // the execCommand spec and not the editing host, we should separate the
1586 // block even if the default paragraph separator is <br> element.
1587 else if (HTMLEditUtils::IsSingleLineContainer(*blockParent)) {
1588 insertBRElement = false;
1589 }
1590 // Otherwise, unless there is no block ancestor which can contain <p>
1591 // element, we shouldn't insert a <br> element here.
1592 else {
1593 insertBRElement = true;
1594 for (Element* blockAncestor = blockParent; blockAncestor && insertBRElement;
1595 blockAncestor = HTMLEditor::GetBlockNodeParent(blockAncestor, host)) {
1596 insertBRElement = !CanContainParagraph(*blockAncestor);
1597 }
1598 }
1599
1600 // If we cannot insert a <p>/<div> element at the selection, we should insert
1601 // a <br> element instead.
1602 if (insertBRElement) {
1603 nsresult rv = InsertBRElement(aSelection, atStartOfSelection);
1604 if (NS_WARN_IF(NS_FAILED(rv))) {
1605 return rv;
1606 }
1607 *aHandled = true;
1608 return NS_OK;
1609 }
1610
1611 if (host == blockParent && separator != ParagraphSeparator::br) {
1612 // Insert a new block first
1613 MOZ_ASSERT(separator == ParagraphSeparator::div ||
1614 separator == ParagraphSeparator::p);
1615 nsresult rv =
1616 MakeBasicBlock(aSelection, ParagraphSeparatorElement(separator));
1617 // We warn on failure, but don't handle it, because it might be harmless.
1618 // Instead we just check that a new block was actually created.
1619 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1620 "HTMLEditRules::MakeBasicBlock() failed");
1621
1622 firstRange = aSelection.GetRangeAt(0);
1623 if (NS_WARN_IF(!firstRange)) {
1624 return NS_ERROR_FAILURE;
1625 }
1626
1627 atStartOfSelection = firstRange->StartRef();
1628 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
1629 return NS_ERROR_FAILURE;
1630 }
1631 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
1632
1633 blockParent =
1634 mHTMLEditor->GetBlock(*atStartOfSelection.GetContainer(), host);
1635 if (NS_WARN_IF(!blockParent)) {
1636 return NS_ERROR_UNEXPECTED;
1637 }
1638 if (NS_WARN_IF(blockParent == host)) {
1639 // Didn't create a new block for some reason, fall back to <br>
1640 rv = InsertBRElement(aSelection, atStartOfSelection);
1641 if (NS_WARN_IF(NS_FAILED(rv))) {
1642 return rv;
1643 }
1644 *aHandled = true;
1645 return NS_OK;
1646 }
1647 // Now, mNewBlock is last created block element for wrapping inline
1648 // elements around the caret position and AfterEditInner() will move
1649 // caret into it. However, it may be different from block parent of
1650 // the caret position. E.g., MakeBasicBlock() may wrap following
1651 // inline elements of a <br> element which is next sibling of container
1652 // of the caret. So, we need to adjust mNewBlock here for avoiding
1653 // jumping caret to odd position.
1654 mNewBlock = blockParent;
1655 }
1656
1657 // If block is empty, populate with br. (For example, imagine a div that
1658 // contains the word "text". The user selects "text" and types return.
1659 // "Text" is deleted leaving an empty block. We want to put in one br to
1660 // make block have a line. Then code further below will put in a second br.)
1661 if (IsEmptyBlockElement(*blockParent, IgnoreSingleBR::eNo)) {
1662 AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection);
1663 EditorRawDOMPoint endOfBlockParent;
1664 endOfBlockParent.SetToEndOf(blockParent);
1665 RefPtr<Element> br = htmlEditor->CreateBR(endOfBlockParent);
1666 NS_ENSURE_STATE(br);
1667 }
1668
1669 nsCOMPtr<Element> listItem = IsInListItem(blockParent);
1670 if (listItem && listItem != host) {
1671 ReturnInListItem(aSelection, *listItem, *atStartOfSelection.GetContainer(),
1672 atStartOfSelection.Offset());
1673 *aHandled = true;
1674 return NS_OK;
1675 }
1676
1677 if (HTMLEditUtils::IsHeader(*blockParent)) {
1678 // Headers: close (or split) header
1679 ReturnInHeader(aSelection, *blockParent, *atStartOfSelection.GetContainer(),
1680 atStartOfSelection.Offset());
1681 *aHandled = true;
1682 return NS_OK;
1683 }
1684
1685 // XXX Ideally, we should take same behavior with both <p> container and
1686 // <div> container. However, we are still using <br> as default
1687 // paragraph separator (non-standard) and we've split only <p> container
1688 // long time. Therefore, some web apps may depend on this behavior like
1689 // Gmail. So, let's use traditional odd behavior only when the default
1690 // paragraph separator is <br>. Otherwise, take consistent behavior
1691 // between <p> container and <div> container.
1692 if ((separator == ParagraphSeparator::br &&
1693 blockParent->IsHTMLElement(nsGkAtoms::p)) ||
1694 (separator != ParagraphSeparator::br &&
1695 blockParent->IsAnyOfHTMLElements(nsGkAtoms::p, nsGkAtoms::div))) {
1696 AutoEditorDOMPointChildInvalidator lockOffset(atStartOfSelection);
1697 // Paragraphs: special rules to look for <br>s
1698 EditActionResult result = ReturnInParagraph(aSelection, *blockParent);
1699 if (NS_WARN_IF(result.Failed())) {
1700 return result.Rv();
1701 }
1702 *aHandled = result.Handled();
1703 *aCancel = result.Canceled();
1704 if (result.Handled()) {
1705 // Now, atStartOfSelection may be invalid because the left paragraph
1706 // may have less children than its offset. For avoiding warnings of
1707 // validation of EditorDOMPoint, we should not touch it anymore.
1708 lockOffset.Cancel();
1709 return NS_OK;
1710 }
1711 // Fall through, if ReturnInParagraph() didn't handle it.
1712 MOZ_ASSERT(!*aCancel,
1713 "ReturnInParagraph canceled this edit action, "
1714 "WillInsertBreak() needs to handle such case");
1715 }
1716
1717 // If nobody handles this edit action, let's insert new <br> at the selection.
1718 MOZ_ASSERT(!*aHandled,
1719 "Reached last resort of WillInsertBreak() "
1720 "after the edit action is handled");
1721 nsresult rv = InsertBRElement(aSelection, atStartOfSelection);
1722 *aHandled = true;
1723 if (NS_WARN_IF(NS_FAILED(rv))) {
1724 return rv;
1725 }
1726 return NS_OK;
1727 }
1728
InsertBRElement(Selection & aSelection,const EditorDOMPoint & aPointToBreak)1729 nsresult HTMLEditRules::InsertBRElement(Selection& aSelection,
1730 const EditorDOMPoint& aPointToBreak) {
1731 if (NS_WARN_IF(!aPointToBreak.IsSet())) {
1732 return NS_ERROR_INVALID_ARG;
1733 }
1734
1735 if (NS_WARN_IF(!mHTMLEditor)) {
1736 return NS_ERROR_NOT_AVAILABLE;
1737 }
1738 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
1739
1740 bool brElementIsAfterBlock = false;
1741 bool brElementIsBeforeBlock = false;
1742
1743 // First, insert a <br> element.
1744 RefPtr<Element> brElement;
1745 if (IsPlaintextEditor()) {
1746 brElement = htmlEditor->CreateBR(aPointToBreak.AsRaw());
1747 if (NS_WARN_IF(!brElement)) {
1748 return NS_ERROR_FAILURE;
1749 }
1750 } else {
1751 EditorDOMPoint pointToBreak(aPointToBreak);
1752 WSRunObject wsObj(htmlEditor, pointToBreak.GetContainer(),
1753 pointToBreak.Offset());
1754 int32_t visOffset = 0;
1755 WSType wsType;
1756 nsCOMPtr<nsINode> visNode;
1757 wsObj.PriorVisibleNode(pointToBreak.GetContainer(), pointToBreak.Offset(),
1758 address_of(visNode), &visOffset, &wsType);
1759 if (wsType & WSType::block) {
1760 brElementIsAfterBlock = true;
1761 }
1762 wsObj.NextVisibleNode(pointToBreak.GetContainer(), pointToBreak.Offset(),
1763 address_of(visNode), &visOffset, &wsType);
1764 if (wsType & WSType::block) {
1765 brElementIsBeforeBlock = true;
1766 }
1767 // If the container of the break is a link, we need to split it and
1768 // insert new <br> between the split links.
1769 nsCOMPtr<nsINode> linkDOMNode;
1770 if (htmlEditor->IsInLink(pointToBreak.GetContainer(),
1771 address_of(linkDOMNode))) {
1772 nsCOMPtr<Element> linkNode = do_QueryInterface(linkDOMNode);
1773 if (NS_WARN_IF(!linkNode)) {
1774 return NS_ERROR_FAILURE;
1775 }
1776 SplitNodeResult splitLinkNodeResult =
1777 htmlEditor->SplitNodeDeep(*linkNode, pointToBreak.AsRaw(),
1778 SplitAtEdges::eDoNotCreateEmptyContainer);
1779 if (NS_WARN_IF(splitLinkNodeResult.Failed())) {
1780 return splitLinkNodeResult.Rv();
1781 }
1782 pointToBreak = splitLinkNodeResult.SplitPoint();
1783 }
1784 brElement =
1785 wsObj.InsertBreak(aSelection, pointToBreak.AsRaw(), nsIEditor::eNone);
1786 if (NS_WARN_IF(!brElement)) {
1787 return NS_ERROR_FAILURE;
1788 }
1789 }
1790
1791 // If the <br> element has already been removed from the DOM tree by a
1792 // mutation observer, don't continue handling this.
1793 if (NS_WARN_IF(!brElement->GetParentNode())) {
1794 return NS_ERROR_FAILURE;
1795 }
1796
1797 if (brElementIsAfterBlock && brElementIsBeforeBlock) {
1798 // We just placed a <br> between block boundaries. This is the one case
1799 // where we want the selection to be before the br we just placed, as the
1800 // br will be on a new line, rather than at end of prior line.
1801 // XXX brElementIsAfterBlock and brElementIsBeforeBlock were set before
1802 // modifying the DOM tree. So, now, the <br> element may not be
1803 // between blocks.
1804 aSelection.SetInterlinePosition(true);
1805 EditorRawDOMPoint point(brElement);
1806 ErrorResult error;
1807 aSelection.Collapse(point, error);
1808 if (NS_WARN_IF(error.Failed())) {
1809 return error.StealNSResult();
1810 }
1811 return NS_OK;
1812 }
1813
1814 EditorDOMPoint afterBRElement(brElement);
1815 DebugOnly<bool> advanced = afterBRElement.AdvanceOffset();
1816 NS_WARNING_ASSERTION(advanced,
1817 "Failed to advance offset after the new <br> element");
1818 WSRunObject wsObj(htmlEditor, afterBRElement.GetContainer(),
1819 afterBRElement.Offset());
1820 nsCOMPtr<nsINode> maybeSecondBRNode;
1821 int32_t visOffset = 0;
1822 WSType wsType;
1823 wsObj.NextVisibleNode(afterBRElement.GetContainer(), afterBRElement.Offset(),
1824 address_of(maybeSecondBRNode), &visOffset, &wsType);
1825 if (wsType == WSType::br) {
1826 // The next thing after the break we inserted is another break. Move the
1827 // second break to be the first break's sibling. This will prevent them
1828 // from being in different inline nodes, which would break
1829 // SetInterlinePosition(). It will also assure that if the user clicks
1830 // away and then clicks back on their new blank line, they will still get
1831 // the style from the line above.
1832 EditorDOMPoint atSecondBRElement(maybeSecondBRNode);
1833 if (brElement->GetNextSibling() != maybeSecondBRNode) {
1834 nsresult rv = htmlEditor->MoveNode(maybeSecondBRNode->AsContent(),
1835 afterBRElement.GetContainer(),
1836 afterBRElement.Offset());
1837 if (NS_WARN_IF(NS_FAILED(rv))) {
1838 return rv;
1839 }
1840 }
1841 }
1842
1843 // SetInterlinePosition(true) means we want the caret to stick to the
1844 // content on the "right". We want the caret to stick to whatever is past
1845 // the break. This is because the break is on the same line we were on,
1846 // but the next content will be on the following line.
1847
1848 // An exception to this is if the break has a next sibling that is a block
1849 // node. Then we stick to the left to avoid an uber caret.
1850 nsIContent* nextSiblingOfBRElement = brElement->GetNextSibling();
1851 aSelection.SetInterlinePosition(
1852 !(nextSiblingOfBRElement && IsBlockNode(*nextSiblingOfBRElement)));
1853 ErrorResult error;
1854 aSelection.Collapse(afterBRElement.AsRaw(), error);
1855 if (NS_WARN_IF(error.Failed())) {
1856 return error.StealNSResult();
1857 }
1858 return NS_OK;
1859 }
1860
DidInsertBreak(Selection * aSelection,nsresult aResult)1861 nsresult HTMLEditRules::DidInsertBreak(Selection* aSelection,
1862 nsresult aResult) {
1863 return NS_OK;
1864 }
1865
SplitMailCites(Selection * aSelection,bool * aHandled)1866 nsresult HTMLEditRules::SplitMailCites(Selection* aSelection, bool* aHandled) {
1867 if (NS_WARN_IF(!aSelection) || NS_WARN_IF(!aHandled)) {
1868 return NS_ERROR_INVALID_ARG;
1869 }
1870
1871 if (NS_WARN_IF(!mHTMLEditor)) {
1872 return NS_ERROR_NOT_AVAILABLE;
1873 }
1874 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
1875
1876 nsCOMPtr<nsINode> selNode;
1877 nsCOMPtr<Element> citeNode;
1878 int32_t selOffset;
1879 nsresult rv = EditorBase::GetStartNodeAndOffset(
1880 aSelection, getter_AddRefs(selNode), &selOffset);
1881 NS_ENSURE_SUCCESS(rv, rv);
1882 citeNode = GetTopEnclosingMailCite(*selNode);
1883 if (citeNode) {
1884 // If our selection is just before a break, nudge it to be
1885 // just after it. This does two things for us. It saves us the trouble of
1886 // having to add a break here ourselves to preserve the "blockness" of the
1887 // inline span mailquote (in the inline case), and : it means the break
1888 // won't end up making an empty line that happens to be inside a mailquote
1889 // (in either inline or block case). The latter can confuse a user if they
1890 // click there and start typing, because being in the mailquote may affect
1891 // wrapping behavior, or font color, etc.
1892 WSRunObject wsObj(htmlEditor, selNode, selOffset);
1893 nsCOMPtr<nsINode> visNode;
1894 int32_t visOffset = 0;
1895 WSType wsType;
1896 wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode), &visOffset,
1897 &wsType);
1898 if (wsType == WSType::br) {
1899 // ok, we are just before a break. is it inside the mailquote?
1900 if (visNode != citeNode && citeNode->Contains(visNode)) {
1901 // it is. so lets reset our selection to be just after it.
1902 selNode = EditorBase::GetNodeLocation(visNode, &selOffset);
1903 ++selOffset;
1904 }
1905 }
1906
1907 NS_ENSURE_STATE(selNode->IsContent());
1908 SplitNodeResult splitCiteNodeResult = htmlEditor->SplitNodeDeep(
1909 *citeNode, EditorRawDOMPoint(selNode, selOffset),
1910 SplitAtEdges::eDoNotCreateEmptyContainer);
1911 if (NS_WARN_IF(splitCiteNodeResult.Failed())) {
1912 return splitCiteNodeResult.Rv();
1913 }
1914
1915 // Add an invisible <br> to the end of current cite node (If new left cite
1916 // has not been created, we're at the end of it. Otherwise, we're still at
1917 // the right node) if it was a <span> of style="display: block". This is
1918 // important, since when serializing the cite to plain text, the span which
1919 // caused the visual break is discarded. So the added <br> will guarantee
1920 // that the serializer will insert a break where the user saw one.
1921 // FYI: splitCiteNodeResult grabs the previous node with nsCOMPtr.
1922 // So, it's safe to access previousNodeOfSplitPoint even after
1923 // changing the DOM tree and/or selection even though it's raw pointer.
1924 nsIContent* previousNodeOfSplitPoint =
1925 splitCiteNodeResult.GetPreviousNode();
1926 if (previousNodeOfSplitPoint &&
1927 previousNodeOfSplitPoint->IsHTMLElement(nsGkAtoms::span) &&
1928 previousNodeOfSplitPoint->GetPrimaryFrame()->IsFrameOfType(
1929 nsIFrame::eBlockFrame)) {
1930 nsCOMPtr<nsINode> lastChild = previousNodeOfSplitPoint->GetLastChild();
1931 if (lastChild && !lastChild->IsHTMLElement(nsGkAtoms::br)) {
1932 // We ignore the result here.
1933 EditorRawDOMPoint endOfPreviousNodeOfSplitPoint;
1934 endOfPreviousNodeOfSplitPoint.SetToEndOf(previousNodeOfSplitPoint);
1935 RefPtr<Element> invisBR =
1936 htmlEditor->CreateBR(endOfPreviousNodeOfSplitPoint);
1937 }
1938 }
1939
1940 // In most cases, <br> should be inserted after current cite. However, if
1941 // left cite hasn't been created because the split point was start of the
1942 // cite node, <br> should be inserted before the current cite.
1943 EditorRawDOMPoint pointToInsertBrNode(splitCiteNodeResult.SplitPoint());
1944 RefPtr<Element> brNode = htmlEditor->CreateBR(pointToInsertBrNode);
1945 if (NS_WARN_IF(!brNode)) {
1946 return NS_ERROR_FAILURE;
1947 }
1948 // Now, offset of pointToInsertBrNode is invalid. Let's clear it.
1949 pointToInsertBrNode.Clear();
1950
1951 // Want selection before the break, and on same line.
1952 EditorRawDOMPoint atBrNode(brNode);
1953 aSelection->SetInterlinePosition(true);
1954 ErrorResult error;
1955 aSelection->Collapse(atBrNode, error);
1956 if (NS_WARN_IF(error.Failed())) {
1957 return error.StealNSResult();
1958 }
1959
1960 selNode = atBrNode.GetContainer();
1961 selOffset = atBrNode.Offset();
1962
1963 // if citeNode wasn't a block, we might also want another break before it.
1964 // We need to examine the content both before the br we just added and also
1965 // just after it. If we don't have another br or block boundary adjacent,
1966 // then we will need a 2nd br added to achieve blank line that user expects.
1967 if (IsInlineNode(*citeNode)) {
1968 WSRunObject wsObj(htmlEditor, selNode, selOffset);
1969 nsCOMPtr<nsINode> visNode;
1970 int32_t visOffset = 0;
1971 WSType wsType;
1972 wsObj.PriorVisibleNode(selNode, selOffset, address_of(visNode),
1973 &visOffset, &wsType);
1974 if (wsType == WSType::normalWS || wsType == WSType::text ||
1975 wsType == WSType::special) {
1976 WSRunObject wsObjAfterBR(htmlEditor, selNode, selOffset + 1);
1977 wsObjAfterBR.NextVisibleNode(selNode, selOffset + 1,
1978 address_of(visNode), &visOffset, &wsType);
1979 if (wsType == WSType::normalWS || wsType == WSType::text ||
1980 wsType == WSType::special ||
1981 // In case we're at the very end.
1982 wsType == WSType::thisBlock) {
1983 brNode = htmlEditor->CreateBR(EditorRawDOMPoint(selNode, selOffset));
1984 NS_ENSURE_STATE(brNode);
1985 }
1986 }
1987 }
1988
1989 // delete any empty cites
1990 bool bEmptyCite = false;
1991 if (previousNodeOfSplitPoint) {
1992 rv = htmlEditor->IsEmptyNode(previousNodeOfSplitPoint, &bEmptyCite, true,
1993 false);
1994 if (NS_WARN_IF(NS_FAILED(rv))) {
1995 return rv;
1996 }
1997 if (bEmptyCite) {
1998 rv = htmlEditor->DeleteNode(previousNodeOfSplitPoint);
1999 if (NS_WARN_IF(NS_FAILED(rv))) {
2000 return rv;
2001 }
2002 }
2003 }
2004
2005 if (citeNode) {
2006 rv = htmlEditor->IsEmptyNode(citeNode, &bEmptyCite, true, false);
2007 if (NS_WARN_IF(NS_FAILED(rv))) {
2008 return rv;
2009 }
2010 if (bEmptyCite) {
2011 rv = htmlEditor->DeleteNode(citeNode);
2012 if (NS_WARN_IF(NS_FAILED(rv))) {
2013 return rv;
2014 }
2015 }
2016 }
2017 *aHandled = true;
2018 }
2019 return NS_OK;
2020 }
2021
WillDeleteSelection(Selection * aSelection,nsIEditor::EDirection aAction,nsIEditor::EStripWrappers aStripWrappers,bool * aCancel,bool * aHandled)2022 nsresult HTMLEditRules::WillDeleteSelection(
2023 Selection* aSelection, nsIEditor::EDirection aAction,
2024 nsIEditor::EStripWrappers aStripWrappers, bool* aCancel, bool* aHandled) {
2025 MOZ_ASSERT(aStripWrappers == nsIEditor::eStrip ||
2026 aStripWrappers == nsIEditor::eNoStrip);
2027
2028 if (!aSelection || !aCancel || !aHandled) {
2029 return NS_ERROR_NULL_POINTER;
2030 }
2031 // Initialize out params
2032 *aCancel = false;
2033 *aHandled = false;
2034
2035 // Remember that we did a selection deletion. Used by
2036 // CreateStyleForInsertText()
2037 mDidDeleteSelection = true;
2038
2039 // If there is only bogus content, cancel the operation
2040 if (mBogusNode) {
2041 *aCancel = true;
2042 return NS_OK;
2043 }
2044
2045 // First check for table selection mode. If so, hand off to table editor.
2046 nsCOMPtr<nsIDOMElement> cell;
2047 NS_ENSURE_STATE(mHTMLEditor);
2048 nsresult rv =
2049 mHTMLEditor->GetFirstSelectedCell(nullptr, getter_AddRefs(cell));
2050 if (NS_SUCCEEDED(rv) && cell) {
2051 NS_ENSURE_STATE(mHTMLEditor);
2052 rv = mHTMLEditor->DeleteTableCellContents();
2053 *aHandled = true;
2054 return rv;
2055 }
2056 cell = nullptr;
2057
2058 // origCollapsed is used later to determine whether we should join blocks. We
2059 // don't really care about bCollapsed because it will be modified by
2060 // ExtendSelectionForDelete later. TryToJoinBlocks() should happen if the
2061 // original selection is collapsed and the cursor is at the end of a block
2062 // element, in which case ExtendSelectionForDelete would always make the
2063 // selection not collapsed.
2064 bool bCollapsed = aSelection->Collapsed();
2065 bool join = false;
2066 bool origCollapsed = bCollapsed;
2067
2068 nsCOMPtr<nsINode> selNode;
2069 int32_t selOffset;
2070
2071 NS_ENSURE_STATE(aSelection->GetRangeAt(0));
2072 nsCOMPtr<nsINode> startNode = aSelection->GetRangeAt(0)->GetStartContainer();
2073 int32_t startOffset = aSelection->GetRangeAt(0)->StartOffset();
2074 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
2075
2076 if (bCollapsed) {
2077 // If we are inside an empty block, delete it.
2078 NS_ENSURE_STATE(mHTMLEditor);
2079 nsCOMPtr<Element> host = mHTMLEditor->GetActiveEditingHost();
2080 NS_ENSURE_TRUE(host, NS_ERROR_FAILURE);
2081 rv = CheckForEmptyBlock(startNode, host, aSelection, aAction, aHandled);
2082 NS_ENSURE_SUCCESS(rv, rv);
2083 if (*aHandled) {
2084 return NS_OK;
2085 }
2086
2087 // Test for distance between caret and text that will be deleted
2088 rv = CheckBidiLevelForDeletion(aSelection, startNode, startOffset, aAction,
2089 aCancel);
2090 NS_ENSURE_SUCCESS(rv, rv);
2091 if (*aCancel) {
2092 return NS_OK;
2093 }
2094
2095 NS_ENSURE_STATE(mHTMLEditor);
2096 rv = mHTMLEditor->ExtendSelectionForDelete(aSelection, &aAction);
2097 NS_ENSURE_SUCCESS(rv, rv);
2098
2099 // We should delete nothing.
2100 if (aAction == nsIEditor::eNone) {
2101 return NS_OK;
2102 }
2103
2104 // ExtendSelectionForDelete() may have changed the selection, update it
2105 NS_ENSURE_STATE(aSelection->GetRangeAt(0));
2106 startNode = aSelection->GetRangeAt(0)->GetStartContainer();
2107 startOffset = aSelection->GetRangeAt(0)->StartOffset();
2108 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
2109
2110 bCollapsed = aSelection->Collapsed();
2111 }
2112
2113 if (bCollapsed) {
2114 // What's in the direction we are deleting?
2115 NS_ENSURE_STATE(mHTMLEditor);
2116 WSRunObject wsObj(mHTMLEditor, startNode, startOffset);
2117 nsCOMPtr<nsINode> visNode;
2118 int32_t visOffset;
2119 WSType wsType;
2120
2121 // Find next visible node
2122 if (aAction == nsIEditor::eNext) {
2123 wsObj.NextVisibleNode(startNode, startOffset, address_of(visNode),
2124 &visOffset, &wsType);
2125 } else {
2126 wsObj.PriorVisibleNode(startNode, startOffset, address_of(visNode),
2127 &visOffset, &wsType);
2128 }
2129
2130 if (!visNode) {
2131 // Can't find anything to delete!
2132 *aCancel = true;
2133 // XXX This is the result of mHTMLEditor->GetFirstSelectedCell().
2134 // The value could be both an error and NS_OK.
2135 return rv;
2136 }
2137
2138 if (wsType == WSType::normalWS) {
2139 // We found some visible ws to delete. Let ws code handle it.
2140 *aHandled = true;
2141 if (aAction == nsIEditor::eNext) {
2142 rv = wsObj.DeleteWSForward();
2143 if (NS_WARN_IF(NS_FAILED(rv))) {
2144 return rv;
2145 }
2146 } else {
2147 rv = wsObj.DeleteWSBackward();
2148 if (NS_WARN_IF(NS_FAILED(rv))) {
2149 return rv;
2150 }
2151 }
2152 return InsertBRIfNeeded(aSelection);
2153 }
2154
2155 if (wsType == WSType::text) {
2156 // Found normal text to delete.
2157 OwningNonNull<Text> nodeAsText = *visNode->GetAsText();
2158 int32_t so = visOffset;
2159 int32_t eo = visOffset + 1;
2160 if (aAction == nsIEditor::ePrevious) {
2161 if (!so) {
2162 return NS_ERROR_UNEXPECTED;
2163 }
2164 so--;
2165 eo--;
2166 // Bug 1068979: delete both codepoints if surrogate pair
2167 if (so > 0) {
2168 const nsTextFragment* text = nodeAsText->GetText();
2169 if (NS_IS_LOW_SURROGATE(text->CharAt(so)) &&
2170 NS_IS_HIGH_SURROGATE(text->CharAt(so - 1))) {
2171 so--;
2172 }
2173 }
2174 } else {
2175 RefPtr<nsRange> range = aSelection->GetRangeAt(0);
2176 NS_ENSURE_STATE(range);
2177
2178 NS_ASSERTION(range->GetStartContainer() == visNode,
2179 "selection start not in visNode");
2180 NS_ASSERTION(range->GetEndContainer() == visNode,
2181 "selection end not in visNode");
2182
2183 so = range->StartOffset();
2184 eo = range->EndOffset();
2185 }
2186 NS_ENSURE_STATE(mHTMLEditor);
2187 rv = WSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(visNode),
2188 &so, address_of(visNode), &eo);
2189 NS_ENSURE_SUCCESS(rv, rv);
2190 NS_ENSURE_STATE(mHTMLEditor);
2191 *aHandled = true;
2192 rv = mHTMLEditor->DeleteText(nodeAsText, std::min(so, eo),
2193 DeprecatedAbs(eo - so));
2194 NS_ENSURE_SUCCESS(rv, rv);
2195
2196 // XXX When Backspace key is pressed, Chromium removes following empty
2197 // text nodes when removing the last character of the non-empty text
2198 // node. However, Edge never removes empty text nodes even if
2199 // selection is in the following empty text node(s). For now, we
2200 // should keep our traditional behavior same as Edge for backward
2201 // compatibility.
2202 // XXX When Delete key is pressed, Edge removes all preceding empty
2203 // text nodes when removing the first character of the non-empty
2204 // text node. Chromium removes only selected empty text node and
2205 // following empty text nodes and the first character of the
2206 // non-empty text node. For now, we should keep our traditional
2207 // behavior same as Chromium for backward compatibility.
2208
2209 DeleteNodeIfCollapsedText(nodeAsText);
2210
2211 rv = InsertBRIfNeeded(aSelection);
2212 NS_ENSURE_SUCCESS(rv, rv);
2213
2214 // Remember that we did a ranged delete for the benefit of
2215 // AfterEditInner().
2216 mDidRangedDelete = true;
2217
2218 return NS_OK;
2219 }
2220
2221 if (wsType == WSType::special || wsType == WSType::br ||
2222 visNode->IsHTMLElement(nsGkAtoms::hr)) {
2223 // Short circuit for invisible breaks. delete them and recurse.
2224 if (visNode->IsHTMLElement(nsGkAtoms::br) &&
2225 (!mHTMLEditor || !mHTMLEditor->IsVisibleBRElement(visNode))) {
2226 NS_ENSURE_STATE(mHTMLEditor);
2227 rv = mHTMLEditor->DeleteNode(visNode);
2228 NS_ENSURE_SUCCESS(rv, rv);
2229 return WillDeleteSelection(aSelection, aAction, aStripWrappers, aCancel,
2230 aHandled);
2231 }
2232
2233 // Special handling for backspace when positioned after <hr>
2234 if (aAction == nsIEditor::ePrevious &&
2235 visNode->IsHTMLElement(nsGkAtoms::hr)) {
2236 // Only if the caret is positioned at the end-of-hr-line position, we
2237 // want to delete the <hr>.
2238 //
2239 // In other words, we only want to delete, if our selection position
2240 // (indicated by startNode and startOffset) is the position directly
2241 // after the <hr>, on the same line as the <hr>.
2242 //
2243 // To detect this case we check:
2244 // startNode == parentOfVisNode
2245 // and
2246 // startOffset -1 == visNodeOffsetToVisNodeParent
2247 // and
2248 // interline position is false (left)
2249 //
2250 // In any other case we set the position to startnode -1 and
2251 // interlineposition to false, only moving the caret to the
2252 // end-of-hr-line position.
2253 bool moveOnly = true;
2254
2255 selNode = visNode->GetParentNode();
2256 selOffset = selNode ? selNode->ComputeIndexOf(visNode) : -1;
2257
2258 bool interLineIsRight;
2259 rv = aSelection->GetInterlinePosition(&interLineIsRight);
2260 NS_ENSURE_SUCCESS(rv, rv);
2261
2262 if (startNode == selNode && startOffset - 1 == selOffset &&
2263 !interLineIsRight) {
2264 moveOnly = false;
2265 }
2266
2267 if (moveOnly) {
2268 // Go to the position after the <hr>, but to the end of the <hr> line
2269 // by setting the interline position to left.
2270 ++selOffset;
2271 aSelection->Collapse(selNode, selOffset);
2272 aSelection->SetInterlinePosition(false);
2273 mDidExplicitlySetInterline = true;
2274 *aHandled = true;
2275
2276 // There is one exception to the move only case. If the <hr> is
2277 // followed by a <br> we want to delete the <br>.
2278
2279 WSType otherWSType;
2280 nsCOMPtr<nsINode> otherNode;
2281 int32_t otherOffset;
2282
2283 wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode),
2284 &otherOffset, &otherWSType);
2285
2286 if (otherWSType == WSType::br) {
2287 // Delete the <br>
2288
2289 NS_ENSURE_STATE(mHTMLEditor);
2290 nsCOMPtr<nsIContent> otherContent(do_QueryInterface(otherNode));
2291 rv = WSRunObject::PrepareToDeleteNode(mHTMLEditor, otherContent);
2292 NS_ENSURE_SUCCESS(rv, rv);
2293 NS_ENSURE_STATE(mHTMLEditor);
2294 rv = mHTMLEditor->DeleteNode(otherNode);
2295 NS_ENSURE_SUCCESS(rv, rv);
2296 }
2297
2298 return NS_OK;
2299 }
2300 // Else continue with normal delete code
2301 }
2302
2303 // Found break or image, or hr.
2304 NS_ENSURE_STATE(mHTMLEditor);
2305 NS_ENSURE_STATE(visNode->IsContent());
2306 rv = WSRunObject::PrepareToDeleteNode(mHTMLEditor, visNode->AsContent());
2307 NS_ENSURE_SUCCESS(rv, rv);
2308 // Remember sibling to visnode, if any
2309 NS_ENSURE_STATE(mHTMLEditor);
2310 nsCOMPtr<nsIContent> sibling = mHTMLEditor->GetPriorHTMLSibling(visNode);
2311 // Delete the node, and join like nodes if appropriate
2312 NS_ENSURE_STATE(mHTMLEditor);
2313 rv = mHTMLEditor->DeleteNode(visNode);
2314 NS_ENSURE_SUCCESS(rv, rv);
2315 // We did something, so let's say so.
2316 *aHandled = true;
2317 // Is there a prior node and are they siblings?
2318 nsCOMPtr<nsINode> stepbrother;
2319 if (sibling) {
2320 NS_ENSURE_STATE(mHTMLEditor);
2321 stepbrother = mHTMLEditor->GetNextHTMLSibling(sibling);
2322 }
2323 // Are they both text nodes? If so, join them!
2324 if (startNode == stepbrother && startNode->GetAsText() &&
2325 sibling->GetAsText()) {
2326 EditorDOMPoint pt = JoinNodesSmart(*sibling, *startNode->AsContent());
2327 if (NS_WARN_IF(!pt.IsSet())) {
2328 return NS_ERROR_FAILURE;
2329 }
2330 // Fix up selection
2331 rv = aSelection->Collapse(pt.AsRaw());
2332 NS_ENSURE_SUCCESS(rv, rv);
2333 }
2334 rv = InsertBRIfNeeded(aSelection);
2335 NS_ENSURE_SUCCESS(rv, rv);
2336 return NS_OK;
2337 }
2338
2339 if (wsType == WSType::otherBlock) {
2340 // Make sure it's not a table element. If so, cancel the operation
2341 // (translation: users cannot backspace or delete across table cells)
2342 if (HTMLEditUtils::IsTableElement(visNode)) {
2343 *aCancel = true;
2344 return NS_OK;
2345 }
2346
2347 // Next to a block. See if we are between a block and a br. If so, we
2348 // really want to delete the br. Else join content at selection to the
2349 // block.
2350 bool bDeletedBR = false;
2351 WSType otherWSType;
2352 nsCOMPtr<nsINode> otherNode;
2353 int32_t otherOffset;
2354
2355 // Find node in other direction
2356 if (aAction == nsIEditor::eNext) {
2357 wsObj.PriorVisibleNode(startNode, startOffset, address_of(otherNode),
2358 &otherOffset, &otherWSType);
2359 } else {
2360 wsObj.NextVisibleNode(startNode, startOffset, address_of(otherNode),
2361 &otherOffset, &otherWSType);
2362 }
2363
2364 // First find the adjacent node in the block
2365 nsCOMPtr<nsIContent> leafNode;
2366 nsCOMPtr<nsINode> leftNode, rightNode;
2367 if (aAction == nsIEditor::ePrevious) {
2368 NS_ENSURE_STATE(mHTMLEditor);
2369 leafNode = mHTMLEditor->GetLastEditableLeaf(*visNode);
2370 leftNode = leafNode;
2371 rightNode = startNode;
2372 } else {
2373 NS_ENSURE_STATE(mHTMLEditor);
2374 leafNode = mHTMLEditor->GetFirstEditableLeaf(*visNode);
2375 leftNode = startNode;
2376 rightNode = leafNode;
2377 }
2378
2379 if (otherNode->IsHTMLElement(nsGkAtoms::br)) {
2380 NS_ENSURE_STATE(mHTMLEditor);
2381 rv = mHTMLEditor->DeleteNode(otherNode);
2382 NS_ENSURE_SUCCESS(rv, rv);
2383 // XXX Only in this case, setting "handled" to true only when it
2384 // succeeds?
2385 *aHandled = true;
2386 bDeletedBR = true;
2387 }
2388
2389 // Don't cross table boundaries
2390 if (leftNode && rightNode &&
2391 InDifferentTableElements(leftNode, rightNode)) {
2392 return NS_OK;
2393 }
2394
2395 if (bDeletedBR) {
2396 // Put selection at edge of block and we are done.
2397 NS_ENSURE_STATE(leafNode);
2398 EditorDOMPoint newSel = GetGoodSelPointForNode(*leafNode, aAction);
2399 if (NS_WARN_IF(!newSel.IsSet())) {
2400 return NS_ERROR_FAILURE;
2401 }
2402 aSelection->Collapse(newSel.AsRaw());
2403 return NS_OK;
2404 }
2405
2406 // Else we are joining content to block
2407
2408 nsCOMPtr<nsINode> selPointNode = startNode;
2409 int32_t selPointOffset = startOffset;
2410 {
2411 NS_ENSURE_STATE(mHTMLEditor);
2412 AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
2413 address_of(selPointNode), &selPointOffset);
2414 NS_ENSURE_STATE(leftNode && leftNode->IsContent() && rightNode &&
2415 rightNode->IsContent());
2416 EditActionResult ret =
2417 TryToJoinBlocks(*leftNode->AsContent(), *rightNode->AsContent());
2418 *aHandled |= ret.Handled();
2419 *aCancel |= ret.Canceled();
2420 if (NS_WARN_IF(ret.Failed())) {
2421 return ret.Rv();
2422 }
2423 }
2424
2425 // If TryToJoinBlocks() didn't handle it and it's not canceled,
2426 // user may want to modify the start leaf node or the last leaf node
2427 // of the block.
2428 if (!*aHandled && !*aCancel && leafNode != startNode) {
2429 int32_t offset = aAction == nsIEditor::ePrevious
2430 ? static_cast<int32_t>(leafNode->Length())
2431 : 0;
2432 aSelection->Collapse(leafNode, offset);
2433 return WillDeleteSelection(aSelection, aAction, aStripWrappers, aCancel,
2434 aHandled);
2435 }
2436
2437 // Otherwise, we must have deleted the selection as user expected.
2438 aSelection->Collapse(selPointNode, selPointOffset);
2439 return NS_OK;
2440 }
2441
2442 if (wsType == WSType::thisBlock) {
2443 // At edge of our block. Look beside it and see if we can join to an
2444 // adjacent block
2445
2446 // Make sure it's not a table element. If so, cancel the operation
2447 // (translation: users cannot backspace or delete across table cells)
2448 if (HTMLEditUtils::IsTableElement(visNode)) {
2449 *aCancel = true;
2450 return NS_OK;
2451 }
2452
2453 // First find the relevant nodes
2454 nsCOMPtr<nsINode> leftNode, rightNode;
2455 if (aAction == nsIEditor::ePrevious) {
2456 NS_ENSURE_STATE(mHTMLEditor);
2457 leftNode = mHTMLEditor->GetPreviousEditableHTMLNode(*visNode);
2458 rightNode = startNode;
2459 } else {
2460 NS_ENSURE_STATE(mHTMLEditor);
2461 rightNode = mHTMLEditor->GetNextEditableHTMLNode(*visNode);
2462 leftNode = startNode;
2463 }
2464
2465 // Nothing to join
2466 if (!leftNode || !rightNode) {
2467 *aCancel = true;
2468 return NS_OK;
2469 }
2470
2471 // Don't cross table boundaries -- cancel it
2472 if (InDifferentTableElements(leftNode, rightNode)) {
2473 *aCancel = true;
2474 return NS_OK;
2475 }
2476
2477 nsCOMPtr<nsINode> selPointNode = startNode;
2478 int32_t selPointOffset = startOffset;
2479 {
2480 NS_ENSURE_STATE(mHTMLEditor);
2481 AutoTrackDOMPoint tracker(mHTMLEditor->mRangeUpdater,
2482 address_of(selPointNode), &selPointOffset);
2483 NS_ENSURE_STATE(leftNode->IsContent() && rightNode->IsContent());
2484 EditActionResult ret =
2485 TryToJoinBlocks(*leftNode->AsContent(), *rightNode->AsContent());
2486 // This should claim that trying to join the block means that
2487 // this handles the action because the caller shouldn't do anything
2488 // anymore in this case.
2489 *aHandled = true;
2490 *aCancel |= ret.Canceled();
2491 if (NS_WARN_IF(ret.Failed())) {
2492 return ret.Rv();
2493 }
2494 }
2495 aSelection->Collapse(selPointNode, selPointOffset);
2496 return NS_OK;
2497 }
2498 }
2499
2500 // Else we have a non-collapsed selection. First adjust the selection.
2501 rv = ExpandSelectionForDeletion(*aSelection);
2502 NS_ENSURE_SUCCESS(rv, rv);
2503
2504 // Remember that we did a ranged delete for the benefit of AfterEditInner().
2505 mDidRangedDelete = true;
2506
2507 // Refresh start and end points
2508 NS_ENSURE_STATE(aSelection->GetRangeAt(0));
2509 startNode = aSelection->GetRangeAt(0)->GetStartContainer();
2510 startOffset = aSelection->GetRangeAt(0)->StartOffset();
2511 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
2512 nsCOMPtr<nsINode> endNode = aSelection->GetRangeAt(0)->GetEndContainer();
2513 int32_t endOffset = aSelection->GetRangeAt(0)->EndOffset();
2514 NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE);
2515
2516 // Figure out if the endpoints are in nodes that can be merged. Adjust
2517 // surrounding whitespace in preparation to delete selection.
2518 if (!IsPlaintextEditor()) {
2519 NS_ENSURE_STATE(mHTMLEditor);
2520 AutoTransactionsConserveSelection dontChangeMySelection(mHTMLEditor);
2521 rv = WSRunObject::PrepareToDeleteRange(mHTMLEditor, address_of(startNode),
2522 &startOffset, address_of(endNode),
2523 &endOffset);
2524 NS_ENSURE_SUCCESS(rv, rv);
2525 }
2526
2527 {
2528 // Track location of where we are deleting
2529 NS_ENSURE_STATE(mHTMLEditor);
2530 AutoTrackDOMPoint startTracker(mHTMLEditor->mRangeUpdater,
2531 address_of(startNode), &startOffset);
2532 AutoTrackDOMPoint endTracker(mHTMLEditor->mRangeUpdater,
2533 address_of(endNode), &endOffset);
2534 // We are handling all ranged deletions directly now.
2535 *aHandled = true;
2536
2537 if (endNode == startNode) {
2538 NS_ENSURE_STATE(mHTMLEditor);
2539 rv = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
2540 NS_ENSURE_SUCCESS(rv, rv);
2541 } else {
2542 // Figure out mailcite ancestors
2543 nsCOMPtr<Element> startCiteNode = GetTopEnclosingMailCite(*startNode);
2544 nsCOMPtr<Element> endCiteNode = GetTopEnclosingMailCite(*endNode);
2545
2546 // If we only have a mailcite at one of the two endpoints, set the
2547 // directionality of the deletion so that the selection will end up
2548 // outside the mailcite.
2549 if (startCiteNode && !endCiteNode) {
2550 aAction = nsIEditor::eNext;
2551 } else if (!startCiteNode && endCiteNode) {
2552 aAction = nsIEditor::ePrevious;
2553 }
2554
2555 // Figure out block parents
2556 NS_ENSURE_STATE(mHTMLEditor);
2557 nsCOMPtr<Element> leftParent = mHTMLEditor->GetBlock(*startNode);
2558 nsCOMPtr<Element> rightParent = mHTMLEditor->GetBlock(*endNode);
2559
2560 // Are endpoint block parents the same? Use default deletion
2561 if (leftParent && leftParent == rightParent) {
2562 NS_ENSURE_STATE(mHTMLEditor);
2563 mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
2564 } else {
2565 // Deleting across blocks. Are the blocks of same type?
2566 NS_ENSURE_STATE(leftParent && rightParent);
2567
2568 // Are the blocks siblings?
2569 nsCOMPtr<nsINode> leftBlockParent = leftParent->GetParentNode();
2570 nsCOMPtr<nsINode> rightBlockParent = rightParent->GetParentNode();
2571
2572 // MOOSE: this could conceivably screw up a table.. fix me.
2573 NS_ENSURE_STATE(mHTMLEditor);
2574 if (leftBlockParent == rightBlockParent &&
2575 mHTMLEditor->NodesSameType(GetAsDOMNode(leftParent),
2576 GetAsDOMNode(rightParent)) &&
2577 // XXX What's special about these three types of block?
2578 (leftParent->IsHTMLElement(nsGkAtoms::p) ||
2579 HTMLEditUtils::IsListItem(leftParent) ||
2580 HTMLEditUtils::IsHeader(*leftParent))) {
2581 // First delete the selection
2582 NS_ENSURE_STATE(mHTMLEditor);
2583 rv = mHTMLEditor->DeleteSelectionImpl(aAction, aStripWrappers);
2584 NS_ENSURE_SUCCESS(rv, rv);
2585 // Join blocks
2586 NS_ENSURE_STATE(mHTMLEditor);
2587 EditorDOMPoint pt =
2588 mHTMLEditor->JoinNodeDeep(*leftParent, *rightParent);
2589 if (NS_WARN_IF(!pt.IsSet())) {
2590 return NS_ERROR_FAILURE;
2591 }
2592 // Fix up selection
2593 rv = aSelection->Collapse(pt.AsRaw());
2594 NS_ENSURE_SUCCESS(rv, rv);
2595 return NS_OK;
2596 }
2597
2598 // Else blocks not same type, or not siblings. Delete everything
2599 // except table elements.
2600 join = true;
2601
2602 AutoRangeArray arrayOfRanges(aSelection);
2603 for (auto& range : arrayOfRanges.mRanges) {
2604 // Build a list of nodes in the range
2605 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
2606 TrivialFunctor functor;
2607 DOMSubtreeIterator iter;
2608 nsresult rv = iter.Init(*range);
2609 NS_ENSURE_SUCCESS(rv, rv);
2610 iter.AppendList(functor, arrayOfNodes);
2611
2612 // Now that we have the list, delete non-table elements
2613 int32_t listCount = arrayOfNodes.Length();
2614 for (int32_t j = 0; j < listCount; j++) {
2615 nsCOMPtr<nsINode> somenode = do_QueryInterface(arrayOfNodes[0]);
2616 NS_ENSURE_STATE(somenode);
2617 DeleteNonTableElements(somenode);
2618 arrayOfNodes.RemoveElementAt(0);
2619 // If something visible is deleted, no need to join. Visible means
2620 // all nodes except non-visible textnodes and breaks.
2621 if (join && origCollapsed) {
2622 if (!somenode->IsContent()) {
2623 join = false;
2624 continue;
2625 }
2626 nsCOMPtr<nsIContent> content = somenode->AsContent();
2627 if (Text* text = content->GetAsText()) {
2628 NS_ENSURE_STATE(mHTMLEditor);
2629 join = !mHTMLEditor->IsInVisibleTextFrames(*text);
2630 } else {
2631 NS_ENSURE_STATE(mHTMLEditor);
2632 join = content->IsHTMLElement(nsGkAtoms::br) &&
2633 !mHTMLEditor->IsVisibleBRElement(somenode);
2634 }
2635 }
2636 }
2637 }
2638
2639 // Check endpoints for possible text deletion. We can assume that if
2640 // text node is found, we can delete to end or to begining as
2641 // appropriate, since the case where both sel endpoints in same text
2642 // node was already handled (we wouldn't be here)
2643 if (startNode->GetAsText() &&
2644 startNode->Length() > static_cast<uint32_t>(startOffset)) {
2645 // Delete to last character
2646 OwningNonNull<nsGenericDOMDataNode> dataNode =
2647 *static_cast<nsGenericDOMDataNode*>(startNode.get());
2648 NS_ENSURE_STATE(mHTMLEditor);
2649 rv = mHTMLEditor->DeleteText(dataNode, startOffset,
2650 startNode->Length() - startOffset);
2651 NS_ENSURE_SUCCESS(rv, rv);
2652 }
2653 if (endNode->GetAsText() && endOffset) {
2654 // Delete to first character
2655 NS_ENSURE_STATE(mHTMLEditor);
2656 OwningNonNull<nsGenericDOMDataNode> dataNode =
2657 *static_cast<nsGenericDOMDataNode*>(endNode.get());
2658 rv = mHTMLEditor->DeleteText(dataNode, 0, endOffset);
2659 NS_ENSURE_SUCCESS(rv, rv);
2660 }
2661
2662 if (join) {
2663 EditActionResult ret = TryToJoinBlocks(*leftParent, *rightParent);
2664 MOZ_ASSERT(*aHandled);
2665 *aCancel |= ret.Canceled();
2666 if (NS_WARN_IF(ret.Failed())) {
2667 return ret.Rv();
2668 }
2669 }
2670 }
2671 }
2672 }
2673
2674 // We might have left only collapsed whitespace in the start/end nodes
2675 {
2676 AutoTrackDOMPoint startTracker(mHTMLEditor->mRangeUpdater,
2677 address_of(startNode), &startOffset);
2678 AutoTrackDOMPoint endTracker(mHTMLEditor->mRangeUpdater,
2679 address_of(endNode), &endOffset);
2680
2681 DeleteNodeIfCollapsedText(*startNode);
2682 DeleteNodeIfCollapsedText(*endNode);
2683 }
2684
2685 // If we're joining blocks: if deleting forward the selection should be
2686 // collapsed to the end of the selection, if deleting backward the selection
2687 // should be collapsed to the beginning of the selection. But if we're not
2688 // joining then the selection should collapse to the beginning of the
2689 // selection if we'redeleting forward, because the end of the selection will
2690 // still be in the next block. And same thing for deleting backwards
2691 // (selection should collapse to the end, because the beginning will still be
2692 // in the first block). See Bug 507936
2693 if (aAction == (join ? nsIEditor::eNext : nsIEditor::ePrevious)) {
2694 rv = aSelection->Collapse(endNode, endOffset);
2695 if (NS_WARN_IF(NS_FAILED(rv))) {
2696 return rv;
2697 }
2698 } else {
2699 rv = aSelection->Collapse(startNode, startOffset);
2700 if (NS_WARN_IF(NS_FAILED(rv))) {
2701 return rv;
2702 }
2703 }
2704 return NS_OK;
2705 }
2706
2707 /**
2708 * If aNode is a text node that contains only collapsed whitespace, delete it.
2709 * It doesn't serve any useful purpose, and we don't want it to confuse code
2710 * that doesn't correctly skip over it.
2711 *
2712 * If deleting the node fails (like if it's not editable), the caller should
2713 * proceed as usual, so don't return any errors.
2714 */
DeleteNodeIfCollapsedText(nsINode & aNode)2715 void HTMLEditRules::DeleteNodeIfCollapsedText(nsINode& aNode) {
2716 Text* text = aNode.GetAsText();
2717 if (!text) {
2718 return;
2719 }
2720
2721 if (NS_WARN_IF(!mHTMLEditor)) {
2722 return;
2723 }
2724
2725 if (!mHTMLEditor->IsVisibleTextNode(*text)) {
2726 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
2727 htmlEditor->DeleteNode(&aNode);
2728 }
2729 }
2730
2731 /**
2732 * InsertBRIfNeeded() determines if a br is needed for current selection to not
2733 * be spastic. If so, it inserts one. Callers responsibility to only call
2734 * with collapsed selection.
2735 *
2736 * @param aSelection The collapsed selection.
2737 */
InsertBRIfNeeded(Selection * aSelection)2738 nsresult HTMLEditRules::InsertBRIfNeeded(Selection* aSelection) {
2739 if (NS_WARN_IF(!aSelection)) {
2740 return NS_ERROR_INVALID_ARG;
2741 }
2742
2743 if (NS_WARN_IF(!mHTMLEditor)) {
2744 return NS_ERROR_NOT_AVAILABLE;
2745 }
2746 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
2747
2748 EditorRawDOMPoint atStartOfSelection(EditorBase::GetStartPoint(aSelection));
2749 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
2750 return NS_ERROR_FAILURE;
2751 }
2752
2753 // inline elements don't need any br
2754 if (!IsBlockNode(*atStartOfSelection.GetContainer())) {
2755 return NS_OK;
2756 }
2757
2758 // examine selection
2759 WSRunObject wsObj(htmlEditor, atStartOfSelection.GetContainer(),
2760 atStartOfSelection.Offset());
2761 if (((wsObj.mStartReason & WSType::block) ||
2762 (wsObj.mStartReason & WSType::br)) &&
2763 (wsObj.mEndReason & WSType::block)) {
2764 // if we are tucked between block boundaries then insert a br
2765 // first check that we are allowed to
2766 if (htmlEditor->CanContainTag(*atStartOfSelection.GetContainer(),
2767 *nsGkAtoms::br)) {
2768 RefPtr<Element> br =
2769 htmlEditor->CreateBR(atStartOfSelection, nsIEditor::ePrevious);
2770 if (NS_WARN_IF(!br)) {
2771 return NS_ERROR_FAILURE;
2772 }
2773 return NS_OK;
2774 }
2775 }
2776 return NS_OK;
2777 }
2778
2779 /**
2780 * GetGoodSelPointForNode() finds where at a node you would want to set the
2781 * selection if you were trying to have a caret next to it. Always returns a
2782 * valid value (unless mHTMLEditor has gone away).
2783 *
2784 * @param aNode The node
2785 * @param aAction Which edge to find:
2786 * eNext/eNextWord/eToEndOfLine indicates beginning,
2787 * ePrevious/PreviousWord/eToBeginningOfLine ending.
2788 */
GetGoodSelPointForNode(nsINode & aNode,nsIEditor::EDirection aAction)2789 EditorDOMPoint HTMLEditRules::GetGoodSelPointForNode(
2790 nsINode& aNode, nsIEditor::EDirection aAction) {
2791 MOZ_ASSERT(aAction == nsIEditor::eNext || aAction == nsIEditor::eNextWord ||
2792 aAction == nsIEditor::ePrevious ||
2793 aAction == nsIEditor::ePreviousWord ||
2794 aAction == nsIEditor::eToBeginningOfLine ||
2795 aAction == nsIEditor::eToEndOfLine);
2796
2797 bool isPreviousAction =
2798 (aAction == nsIEditor::ePrevious || aAction == nsIEditor::ePreviousWord ||
2799 aAction == nsIEditor::eToBeginningOfLine);
2800
2801 if (NS_WARN_IF(!mHTMLEditor)) {
2802 return EditorDOMPoint();
2803 }
2804 if (aNode.GetAsText() || mHTMLEditor->IsContainer(&aNode) ||
2805 NS_WARN_IF(!aNode.GetParentNode())) {
2806 return EditorDOMPoint(&aNode, isPreviousAction ? aNode.Length() : 0);
2807 }
2808
2809 if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!aNode.IsContent())) {
2810 return EditorDOMPoint();
2811 }
2812
2813 EditorDOMPoint ret(&aNode);
2814 if ((!aNode.IsHTMLElement(nsGkAtoms::br) ||
2815 mHTMLEditor->IsVisibleBRElement(&aNode)) &&
2816 isPreviousAction) {
2817 ret.AdvanceOffset();
2818 }
2819 return ret;
2820 }
2821
TryToJoinBlocks(nsIContent & aLeftNode,nsIContent & aRightNode)2822 EditActionResult HTMLEditRules::TryToJoinBlocks(nsIContent& aLeftNode,
2823 nsIContent& aRightNode) {
2824 if (NS_WARN_IF(!mHTMLEditor)) {
2825 return EditActionIgnored(NS_ERROR_UNEXPECTED);
2826 }
2827
2828 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
2829
2830 nsCOMPtr<Element> leftBlock = htmlEditor->GetBlock(aLeftNode);
2831 nsCOMPtr<Element> rightBlock = htmlEditor->GetBlock(aRightNode);
2832
2833 // Sanity checks
2834 if (NS_WARN_IF(!leftBlock) || NS_WARN_IF(!rightBlock)) {
2835 return EditActionIgnored(NS_ERROR_NULL_POINTER);
2836 }
2837 if (NS_WARN_IF(leftBlock == rightBlock)) {
2838 return EditActionIgnored(NS_ERROR_UNEXPECTED);
2839 }
2840
2841 if (HTMLEditUtils::IsTableElement(leftBlock) ||
2842 HTMLEditUtils::IsTableElement(rightBlock)) {
2843 // Do not try to merge table elements
2844 return EditActionCanceled();
2845 }
2846
2847 // Make sure we don't try to move things into HR's, which look like blocks
2848 // but aren't containers
2849 if (leftBlock->IsHTMLElement(nsGkAtoms::hr)) {
2850 leftBlock = htmlEditor->GetBlockNodeParent(leftBlock);
2851 if (NS_WARN_IF(!leftBlock)) {
2852 return EditActionIgnored(NS_ERROR_UNEXPECTED);
2853 }
2854 }
2855 if (rightBlock->IsHTMLElement(nsGkAtoms::hr)) {
2856 rightBlock = htmlEditor->GetBlockNodeParent(rightBlock);
2857 if (NS_WARN_IF(!rightBlock)) {
2858 return EditActionIgnored(NS_ERROR_UNEXPECTED);
2859 }
2860 }
2861
2862 // Bail if both blocks the same
2863 if (leftBlock == rightBlock) {
2864 return EditActionIgnored();
2865 }
2866
2867 // Joining a list item to its parent is a NOP.
2868 if (HTMLEditUtils::IsList(leftBlock) &&
2869 HTMLEditUtils::IsListItem(rightBlock) &&
2870 rightBlock->GetParentNode() == leftBlock) {
2871 return EditActionHandled();
2872 }
2873
2874 // Special rule here: if we are trying to join list items, and they are in
2875 // different lists, join the lists instead.
2876 bool mergeLists = false;
2877 nsAtom* existingList = nsGkAtoms::_empty;
2878 EditorDOMPoint atChildInBlock;
2879 nsCOMPtr<Element> leftList, rightList;
2880 if (HTMLEditUtils::IsListItem(leftBlock) &&
2881 HTMLEditUtils::IsListItem(rightBlock)) {
2882 leftList = leftBlock->GetParentElement();
2883 rightList = rightBlock->GetParentElement();
2884 if (leftList && rightList && leftList != rightList &&
2885 !EditorUtils::IsDescendantOf(*leftList, *rightBlock, &atChildInBlock) &&
2886 !EditorUtils::IsDescendantOf(*rightList, *leftBlock, &atChildInBlock)) {
2887 // There are some special complications if the lists are descendants of
2888 // the other lists' items. Note that it is okay for them to be
2889 // descendants of the other lists themselves, which is the usual case for
2890 // sublists in our implementation.
2891 MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock.IsSet());
2892 leftBlock = leftList;
2893 rightBlock = rightList;
2894 mergeLists = true;
2895 existingList = leftList->NodeInfo()->NameAtom();
2896 }
2897 }
2898
2899 AutoTransactionsConserveSelection dontChangeMySelection(htmlEditor);
2900
2901 // offset below is where you find yourself in rightBlock when you traverse
2902 // upwards from leftBlock
2903 EditorDOMPoint atRightBlockChild;
2904 if (EditorUtils::IsDescendantOf(*leftBlock, *rightBlock,
2905 &atRightBlockChild)) {
2906 // Tricky case. Left block is inside right block. Do ws adjustment. This
2907 // just destroys non-visible ws at boundaries we will be joining.
2908 DebugOnly<bool> advanced = atRightBlockChild.AdvanceOffset();
2909 NS_WARNING_ASSERTION(
2910 advanced,
2911 "Failed to advance offset to after child of rightBlock, "
2912 "leftBlock is a descendant of the child");
2913 nsresult rv = WSRunObject::ScrubBlockBoundary(
2914 htmlEditor, WSRunObject::kBlockEnd, leftBlock);
2915 if (NS_WARN_IF(NS_FAILED(rv))) {
2916 return EditActionIgnored(rv);
2917 }
2918
2919 {
2920 // We can't just track rightBlock because it's an Element.
2921 AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater, &atRightBlockChild);
2922 rv = WSRunObject::ScrubBlockBoundary(htmlEditor, WSRunObject::kAfterBlock,
2923 atRightBlockChild.GetContainer(),
2924 atRightBlockChild.Offset());
2925 if (NS_WARN_IF(NS_FAILED(rv))) {
2926 return EditActionIgnored(rv);
2927 }
2928
2929 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
2930 // Do we really need to do update rightBlock here??
2931 MOZ_ASSERT(rightBlock == atRightBlockChild.GetContainer());
2932 if (atRightBlockChild.GetContainerAsElement()) {
2933 rightBlock = atRightBlockChild.GetContainerAsElement();
2934 } else {
2935 if (NS_WARN_IF(!atRightBlockChild.GetContainer()->GetParentElement())) {
2936 return EditActionIgnored(NS_ERROR_UNEXPECTED);
2937 }
2938 rightBlock = atRightBlockChild.GetContainer()->GetParentElement();
2939 }
2940 }
2941
2942 // Do br adjustment.
2943 nsCOMPtr<Element> brNode =
2944 CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd);
2945 EditActionResult ret(NS_OK);
2946 if (NS_WARN_IF(mergeLists)) {
2947 // Since 2002, here was the following comment:
2948 // > The idea here is to take all children in rightList that are past
2949 // > offset, and pull them into leftlist.
2950 // However, this has never been performed because we are here only when
2951 // neither left list nor right list is a descendant of the other but
2952 // in such case, getting a list item in the right list node almost
2953 // always failed since a variable for offset of rightList->GetChildAt()
2954 // was not initialized. So, it might be a bug, but we should keep this
2955 // traditional behavior for now. If you find when we get here, please
2956 // remove this comment if we don't need to do it. Otherwise, please
2957 // move children of the right list node to the end of the left list node.
2958 MOZ_DIAGNOSTIC_ASSERT(!atChildInBlock.IsSet());
2959
2960 // XXX Although, we don't do nothing here, but for keeping traditional
2961 // behavior, we should mark as handled.
2962 ret.MarkAsHandled();
2963 } else {
2964 // XXX Why do we ignore the result of MoveBlock()?
2965 EditActionResult retMoveBlock =
2966 MoveBlock(*leftBlock, *rightBlock, -1, atRightBlockChild.Offset());
2967 if (retMoveBlock.Handled()) {
2968 ret.MarkAsHandled();
2969 }
2970 // Now, all children of rightBlock were moved to leftBlock. So,
2971 // atRightBlockChild is now invalid.
2972 atRightBlockChild.Clear();
2973 }
2974 if (brNode && NS_SUCCEEDED(htmlEditor->DeleteNode(brNode))) {
2975 ret.MarkAsHandled();
2976 }
2977 return ret;
2978 }
2979
2980 MOZ_DIAGNOSTIC_ASSERT(!atRightBlockChild.IsSet());
2981
2982 // Offset below is where you find yourself in leftBlock when you traverse
2983 // upwards from rightBlock
2984 EditorDOMPoint leftBlockChild;
2985 if (EditorUtils::IsDescendantOf(*rightBlock, *leftBlock, &leftBlockChild)) {
2986 // Tricky case. Right block is inside left block. Do ws adjustment. This
2987 // just destroys non-visible ws at boundaries we will be joining.
2988 nsresult rv = WSRunObject::ScrubBlockBoundary(
2989 htmlEditor, WSRunObject::kBlockStart, rightBlock);
2990 if (NS_WARN_IF(NS_FAILED(rv))) {
2991 return EditActionIgnored(rv);
2992 }
2993
2994 {
2995 // We can't just track leftBlock because it's an Element, so track
2996 // something else.
2997 AutoTrackDOMPoint tracker(htmlEditor->mRangeUpdater, &leftBlockChild);
2998 rv =
2999 WSRunObject::ScrubBlockBoundary(htmlEditor, WSRunObject::kBeforeBlock,
3000 leftBlock, leftBlockChild.Offset());
3001 if (NS_WARN_IF(NS_FAILED(rv))) {
3002 return EditActionIgnored(rv);
3003 }
3004 // XXX AutoTrackDOMPoint instance, tracker, hasn't been destroyed here.
3005 // Do we really need to do update rightBlock here??
3006 MOZ_DIAGNOSTIC_ASSERT(leftBlock == leftBlockChild.GetContainer());
3007 if (leftBlockChild.GetContainerAsElement()) {
3008 leftBlock = leftBlockChild.GetContainerAsElement();
3009 } else {
3010 if (NS_WARN_IF(!leftBlockChild.GetContainer()->GetParentElement())) {
3011 return EditActionIgnored(NS_ERROR_UNEXPECTED);
3012 }
3013 leftBlock = leftBlockChild.GetContainer()->GetParentElement();
3014 }
3015 }
3016 // Do br adjustment.
3017 nsCOMPtr<Element> brNode = CheckForInvisibleBR(
3018 *leftBlock, BRLocation::beforeBlock, leftBlockChild.Offset());
3019 EditActionResult ret(NS_OK);
3020 if (mergeLists) {
3021 // XXX Why do we ignore the result of MoveContents()?
3022 int32_t offset = leftBlockChild.Offset();
3023 EditActionResult retMoveContents =
3024 MoveContents(*rightList, *leftList, &offset);
3025 if (retMoveContents.Handled()) {
3026 ret.MarkAsHandled();
3027 }
3028 // leftBlockChild was moved to rightList. So, it's invalid now.
3029 leftBlockChild.Clear();
3030 } else {
3031 // Left block is a parent of right block, and the parent of the previous
3032 // visible content. Right block is a child and contains the contents we
3033 // want to move.
3034
3035 EditorDOMPoint previousContent;
3036 if (&aLeftNode == leftBlock) {
3037 // We are working with valid HTML, aLeftNode is a block node, and is
3038 // therefore allowed to contain rightBlock. This is the simple case,
3039 // we will simply move the content in rightBlock out of its block.
3040 previousContent = leftBlockChild;
3041 } else {
3042 // We try to work as well as possible with HTML that's already invalid.
3043 // Although "right block" is a block, and a block must not be contained
3044 // in inline elements, reality is that broken documents do exist. The
3045 // DIRECT parent of "left NODE" might be an inline element. Previous
3046 // versions of this code skipped inline parents until the first block
3047 // parent was found (and used "left block" as the destination).
3048 // However, in some situations this strategy moves the content to an
3049 // unexpected position. (see bug 200416) The new idea is to make the
3050 // moving content a sibling, next to the previous visible content.
3051 previousContent.Set(&aLeftNode);
3052
3053 // We want to move our content just after the previous visible node.
3054 previousContent.AdvanceOffset();
3055 }
3056
3057 // Because we don't want the moving content to receive the style of the
3058 // previous content, we split the previous content's style.
3059
3060 nsCOMPtr<Element> editorRoot = htmlEditor->GetEditorRoot();
3061 if (!editorRoot || &aLeftNode != editorRoot) {
3062 nsCOMPtr<nsIContent> splittedPreviousContent;
3063 nsCOMPtr<nsINode> previousContentParent =
3064 previousContent.GetContainer();
3065 int32_t previousContentOffset = previousContent.Offset();
3066 rv = htmlEditor->SplitStyleAbovePoint(
3067 address_of(previousContentParent), &previousContentOffset, nullptr,
3068 nullptr, nullptr, getter_AddRefs(splittedPreviousContent));
3069 if (NS_WARN_IF(NS_FAILED(rv))) {
3070 return EditActionIgnored(rv);
3071 }
3072
3073 if (splittedPreviousContent) {
3074 previousContent.Set(splittedPreviousContent);
3075 } else {
3076 previousContent.Set(previousContentParent, previousContentOffset);
3077 }
3078 }
3079
3080 if (NS_WARN_IF(!previousContent.IsSet())) {
3081 return EditActionIgnored(NS_ERROR_NULL_POINTER);
3082 }
3083
3084 ret |= MoveBlock(*previousContent.GetContainerAsElement(), *rightBlock,
3085 previousContent.Offset(), 0);
3086 if (NS_WARN_IF(ret.Failed())) {
3087 return ret;
3088 }
3089 }
3090 if (brNode && NS_SUCCEEDED(htmlEditor->DeleteNode(brNode))) {
3091 ret.MarkAsHandled();
3092 }
3093 return ret;
3094 }
3095
3096 MOZ_DIAGNOSTIC_ASSERT(!atRightBlockChild.IsSet());
3097 MOZ_DIAGNOSTIC_ASSERT(!leftBlockChild.IsSet());
3098
3099 // Normal case. Blocks are siblings, or at least close enough. An example
3100 // of the latter is <p>paragraph</p><ul><li>one<li>two<li>three</ul>. The
3101 // first li and the p are not true siblings, but we still want to join them
3102 // if you backspace from li into p.
3103
3104 // Adjust whitespace at block boundaries
3105 nsresult rv =
3106 WSRunObject::PrepareToJoinBlocks(htmlEditor, leftBlock, rightBlock);
3107 if (NS_WARN_IF(NS_FAILED(rv))) {
3108 return EditActionIgnored(rv);
3109 }
3110 // Do br adjustment.
3111 nsCOMPtr<Element> brNode =
3112 CheckForInvisibleBR(*leftBlock, BRLocation::blockEnd);
3113 EditActionResult ret(NS_OK);
3114 if (mergeLists ||
3115 leftBlock->NodeInfo()->NameAtom() == rightBlock->NodeInfo()->NameAtom()) {
3116 // Nodes are same type. merge them.
3117 EditorDOMPoint pt = JoinNodesSmart(*leftBlock, *rightBlock);
3118 if (pt.IsSet() && mergeLists) {
3119 RefPtr<Element> newBlock =
3120 ConvertListType(rightBlock, existingList, nsGkAtoms::li);
3121 }
3122 ret.MarkAsHandled();
3123 } else {
3124 // Nodes are dissimilar types.
3125 ret |= MoveBlock(*leftBlock, *rightBlock, -1, 0);
3126 if (NS_WARN_IF(ret.Failed())) {
3127 return ret;
3128 }
3129 }
3130 if (brNode) {
3131 rv = htmlEditor->DeleteNode(brNode);
3132 // XXX In other top level if blocks, the result of DeleteNode()
3133 // is ignored. Why does only this result is respected?
3134 if (NS_WARN_IF(NS_FAILED(rv))) {
3135 return ret.SetResult(rv);
3136 }
3137 ret.MarkAsHandled();
3138 }
3139 return ret;
3140 }
3141
MoveBlock(Element & aLeftBlock,Element & aRightBlock,int32_t aLeftOffset,int32_t aRightOffset)3142 EditActionResult HTMLEditRules::MoveBlock(Element& aLeftBlock,
3143 Element& aRightBlock,
3144 int32_t aLeftOffset,
3145 int32_t aRightOffset) {
3146 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
3147 // GetNodesFromPoint is the workhorse that figures out what we wnat to move.
3148 nsresult rv =
3149 GetNodesFromPoint(EditorDOMPoint(&aRightBlock, aRightOffset),
3150 EditAction::makeList, arrayOfNodes, TouchContent::yes);
3151 if (NS_WARN_IF(NS_FAILED(rv))) {
3152 return EditActionIgnored(rv);
3153 }
3154
3155 EditActionResult ret(NS_OK);
3156 for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) {
3157 // get the node to act on
3158 if (IsBlockNode(arrayOfNodes[i])) {
3159 // For block nodes, move their contents only, then delete block.
3160 ret |=
3161 MoveContents(*arrayOfNodes[i]->AsElement(), aLeftBlock, &aLeftOffset);
3162 if (NS_WARN_IF(ret.Failed())) {
3163 return ret;
3164 }
3165 if (NS_WARN_IF(!mHTMLEditor)) {
3166 return ret.SetResult(NS_ERROR_UNEXPECTED);
3167 }
3168 rv = mHTMLEditor->DeleteNode(arrayOfNodes[i]);
3169 ret.MarkAsHandled();
3170 } else {
3171 // Otherwise move the content as is, checking against the DTD.
3172 ret |= MoveNodeSmart(*arrayOfNodes[i]->AsContent(), aLeftBlock,
3173 &aLeftOffset);
3174 }
3175 }
3176
3177 // XXX We're only checking return value of the last iteration
3178 if (NS_WARN_IF(ret.Failed())) {
3179 return ret;
3180 }
3181
3182 return ret;
3183 }
3184
MoveNodeSmart(nsIContent & aNode,Element & aDestElement,int32_t * aInOutDestOffset)3185 EditActionResult HTMLEditRules::MoveNodeSmart(nsIContent& aNode,
3186 Element& aDestElement,
3187 int32_t* aInOutDestOffset) {
3188 MOZ_ASSERT(aInOutDestOffset);
3189
3190 if (NS_WARN_IF(!mHTMLEditor)) {
3191 return EditActionIgnored(NS_ERROR_UNEXPECTED);
3192 }
3193
3194 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
3195
3196 // Check if this node can go into the destination node
3197 if (htmlEditor->CanContain(aDestElement, aNode)) {
3198 // If it can, move it there
3199 nsresult rv =
3200 htmlEditor->MoveNode(&aNode, &aDestElement, *aInOutDestOffset);
3201 if (NS_WARN_IF(NS_FAILED(rv))) {
3202 return EditActionIgnored(rv);
3203 }
3204 if (*aInOutDestOffset != -1) {
3205 (*aInOutDestOffset)++;
3206 }
3207 // XXX Should we check if the node is actually moved in this case?
3208 return EditActionHandled();
3209 }
3210
3211 // If it can't, move its children (if any), and then delete it.
3212 EditActionResult ret(NS_OK);
3213 if (aNode.IsElement()) {
3214 ret = MoveContents(*aNode.AsElement(), aDestElement, aInOutDestOffset);
3215 if (NS_WARN_IF(ret.Failed())) {
3216 return ret;
3217 }
3218 }
3219
3220 nsresult rv = htmlEditor->DeleteNode(&aNode);
3221 if (NS_WARN_IF(NS_FAILED(rv))) {
3222 return ret.SetResult(rv);
3223 }
3224 return ret.MarkAsHandled();
3225 }
3226
MoveContents(Element & aElement,Element & aDestElement,int32_t * aInOutDestOffset)3227 EditActionResult HTMLEditRules::MoveContents(Element& aElement,
3228 Element& aDestElement,
3229 int32_t* aInOutDestOffset) {
3230 MOZ_ASSERT(aInOutDestOffset);
3231
3232 if (NS_WARN_IF(&aElement == &aDestElement)) {
3233 return EditActionIgnored(NS_ERROR_ILLEGAL_VALUE);
3234 }
3235
3236 EditActionResult ret(NS_OK);
3237 while (aElement.GetFirstChild()) {
3238 ret |= MoveNodeSmart(*aElement.GetFirstChild(), aDestElement,
3239 aInOutDestOffset);
3240 if (NS_WARN_IF(ret.Failed())) {
3241 return ret;
3242 }
3243 }
3244 return ret;
3245 }
3246
DeleteNonTableElements(nsINode * aNode)3247 nsresult HTMLEditRules::DeleteNonTableElements(nsINode* aNode) {
3248 MOZ_ASSERT(aNode);
3249 if (!HTMLEditUtils::IsTableElementButNotTable(aNode)) {
3250 NS_ENSURE_STATE(mHTMLEditor);
3251 return mHTMLEditor->DeleteNode(aNode->AsDOMNode());
3252 }
3253
3254 AutoTArray<nsCOMPtr<nsIContent>, 10> childList;
3255 for (nsIContent* child = aNode->GetFirstChild(); child;
3256 child = child->GetNextSibling()) {
3257 childList.AppendElement(child);
3258 }
3259
3260 for (const auto& child : childList) {
3261 nsresult rv = DeleteNonTableElements(child);
3262 NS_ENSURE_SUCCESS(rv, rv);
3263 }
3264 return NS_OK;
3265 }
3266
DidDeleteSelection(Selection * aSelection,nsIEditor::EDirection aDir,nsresult aResult)3267 nsresult HTMLEditRules::DidDeleteSelection(Selection* aSelection,
3268 nsIEditor::EDirection aDir,
3269 nsresult aResult) {
3270 if (!aSelection) {
3271 return NS_ERROR_NULL_POINTER;
3272 }
3273
3274 if (NS_WARN_IF(!mHTMLEditor)) {
3275 return NS_ERROR_NOT_AVAILABLE;
3276 }
3277 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
3278
3279 // find where we are
3280 EditorDOMPoint atStartOfSelection(EditorBase::GetStartPoint(aSelection));
3281 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
3282 return NS_ERROR_FAILURE;
3283 }
3284
3285 // find any enclosing mailcite
3286 nsCOMPtr<Element> citeNode =
3287 GetTopEnclosingMailCite(*atStartOfSelection.GetContainer());
3288 if (citeNode) {
3289 bool isEmpty = true, seenBR = false;
3290 htmlEditor->IsEmptyNodeImpl(citeNode, &isEmpty, true, true, false, &seenBR);
3291 if (isEmpty) {
3292 EditorDOMPoint atCiteNode(citeNode);
3293 {
3294 AutoEditorDOMPointChildInvalidator lockOffset(atCiteNode);
3295 nsresult rv = htmlEditor->DeleteNode(citeNode);
3296 if (NS_WARN_IF(NS_FAILED(rv))) {
3297 return rv;
3298 }
3299 }
3300 if (atCiteNode.IsSet() && seenBR) {
3301 RefPtr<Element> brNode = htmlEditor->CreateBR(atCiteNode.AsRaw());
3302 if (NS_WARN_IF(!brNode)) {
3303 return NS_ERROR_FAILURE;
3304 }
3305 IgnoredErrorResult error;
3306 aSelection->Collapse(EditorRawDOMPoint(brNode), error);
3307 NS_WARNING_ASSERTION(
3308 !error.Failed(),
3309 "Failed to collapse selection at the new <br> element");
3310 }
3311 }
3312 }
3313
3314 // call through to base class
3315 return TextEditRules::DidDeleteSelection(aSelection, aDir, aResult);
3316 }
3317
WillMakeList(Selection * aSelection,const nsAString * aListType,bool aEntireList,const nsAString * aBulletType,bool * aCancel,bool * aHandled,const nsAString * aItemType)3318 nsresult HTMLEditRules::WillMakeList(Selection* aSelection,
3319 const nsAString* aListType,
3320 bool aEntireList,
3321 const nsAString* aBulletType,
3322 bool* aCancel, bool* aHandled,
3323 const nsAString* aItemType) {
3324 if (!aSelection || !aListType || !aCancel || !aHandled) {
3325 return NS_ERROR_NULL_POINTER;
3326 }
3327 if (NS_WARN_IF(!mHTMLEditor)) {
3328 return NS_ERROR_NOT_AVAILABLE;
3329 }
3330 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
3331
3332 OwningNonNull<nsAtom> listType = NS_Atomize(*aListType);
3333
3334 WillInsert(*aSelection, aCancel);
3335
3336 // initialize out param
3337 // we want to ignore result of WillInsert()
3338 *aCancel = false;
3339 *aHandled = false;
3340
3341 // deduce what tag to use for list items
3342 RefPtr<nsAtom> itemType;
3343 if (aItemType) {
3344 itemType = NS_Atomize(*aItemType);
3345 NS_ENSURE_TRUE(itemType, NS_ERROR_OUT_OF_MEMORY);
3346 } else if (listType == nsGkAtoms::dl) {
3347 itemType = nsGkAtoms::dd;
3348 } else {
3349 itemType = nsGkAtoms::li;
3350 }
3351
3352 // convert the selection ranges into "promoted" selection ranges:
3353 // this basically just expands the range to include the immediate
3354 // block parent, and then further expands to include any ancestors
3355 // whose children are all in the range
3356
3357 *aHandled = true;
3358
3359 nsresult rv = NormalizeSelection(aSelection);
3360 NS_ENSURE_SUCCESS(rv, rv);
3361
3362 AutoSelectionRestorer selectionRestorer(aSelection, htmlEditor);
3363
3364 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
3365 rv = GetListActionNodes(arrayOfNodes,
3366 aEntireList ? EntireList::yes : EntireList::no);
3367 NS_ENSURE_SUCCESS(rv, rv);
3368
3369 // check if all our nodes are <br>s, or empty inlines
3370 bool bOnlyBreaks = true;
3371 for (auto& curNode : arrayOfNodes) {
3372 // if curNode is not a Break or empty inline, we're done
3373 if (!TextEditUtils::IsBreak(curNode) && !IsEmptyInline(curNode)) {
3374 bOnlyBreaks = false;
3375 break;
3376 }
3377 }
3378
3379 // if no nodes, we make empty list. Ditto if the user tried to make a list
3380 // of some # of breaks.
3381 if (arrayOfNodes.IsEmpty() || bOnlyBreaks) {
3382 // if only breaks, delete them
3383 if (bOnlyBreaks) {
3384 for (auto& node : arrayOfNodes) {
3385 rv = htmlEditor->DeleteNode(node);
3386 NS_ENSURE_SUCCESS(rv, rv);
3387 }
3388 }
3389
3390 nsRange* firstRange = aSelection->GetRangeAt(0);
3391 if (NS_WARN_IF(!firstRange)) {
3392 return NS_ERROR_FAILURE;
3393 }
3394
3395 EditorDOMPoint atStartOfSelection(firstRange->StartRef());
3396 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
3397 return NS_ERROR_FAILURE;
3398 }
3399
3400 // Make sure we can put a list here.
3401 if (!htmlEditor->CanContainTag(*atStartOfSelection.GetContainer(),
3402 listType)) {
3403 *aCancel = true;
3404 return NS_OK;
3405 }
3406
3407 SplitNodeResult splitAtSelectionStartResult =
3408 MaybeSplitAncestorsForInsert(listType, atStartOfSelection.AsRaw());
3409 if (NS_WARN_IF(splitAtSelectionStartResult.Failed())) {
3410 return splitAtSelectionStartResult.Rv();
3411 }
3412 RefPtr<Element> theList = htmlEditor->CreateNode(
3413 listType, splitAtSelectionStartResult.SplitPoint());
3414 NS_ENSURE_STATE(theList);
3415
3416 EditorRawDOMPoint atFirstListItemToInsertBefore(theList, 0);
3417 RefPtr<Element> theListItem =
3418 htmlEditor->CreateNode(itemType, atFirstListItemToInsertBefore);
3419 NS_ENSURE_STATE(theListItem);
3420
3421 // remember our new block for postprocessing
3422 mNewBlock = theListItem;
3423 // put selection in new list item
3424 *aHandled = true;
3425 ErrorResult error;
3426 aSelection->Collapse(EditorRawDOMPoint(theListItem, 0), error);
3427 // Don't restore the selection
3428 selectionRestorer.Abort();
3429 if (NS_WARN_IF(!error.Failed())) {
3430 return error.StealNSResult();
3431 }
3432 return NS_OK;
3433 }
3434
3435 // if there is only one node in the array, and it is a list, div, or
3436 // blockquote, then look inside of it until we find inner list or content.
3437
3438 LookInsideDivBQandList(arrayOfNodes);
3439
3440 // Ok, now go through all the nodes and put then in the list,
3441 // or whatever is approriate. Wohoo!
3442
3443 uint32_t listCount = arrayOfNodes.Length();
3444 nsCOMPtr<Element> curList, prevListItem;
3445
3446 for (uint32_t i = 0; i < listCount; i++) {
3447 // here's where we actually figure out what to do
3448 nsCOMPtr<Element> newBlock;
3449 NS_ENSURE_STATE(arrayOfNodes[i]->IsContent());
3450 OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent();
3451
3452 // make sure we don't assemble content that is in different table cells
3453 // into the same list. respect table cell boundaries when listifying.
3454 if (curList && InDifferentTableElements(curList, curNode)) {
3455 curList = nullptr;
3456 }
3457
3458 // If curNode is a break, delete it, and quit remembering prev list item.
3459 // If an empty inline container, delete it, but still remember the previous
3460 // item.
3461 if (htmlEditor->IsEditable(curNode) &&
3462 (TextEditUtils::IsBreak(curNode) || IsEmptyInline(curNode))) {
3463 rv = htmlEditor->DeleteNode(curNode);
3464 NS_ENSURE_SUCCESS(rv, rv);
3465 if (TextEditUtils::IsBreak(curNode)) {
3466 prevListItem = nullptr;
3467 }
3468 continue;
3469 }
3470
3471 if (HTMLEditUtils::IsList(curNode)) {
3472 // do we have a curList already?
3473 if (curList && !EditorUtils::IsDescendantOf(*curNode, *curList)) {
3474 // move all of our children into curList. cheezy way to do it: move
3475 // whole list and then RemoveContainer() on the list. ConvertListType
3476 // first: that routine handles converting the list item types, if
3477 // needed
3478 rv = htmlEditor->MoveNode(curNode, curList, -1);
3479 NS_ENSURE_SUCCESS(rv, rv);
3480 newBlock = ConvertListType(curNode->AsElement(), listType, itemType);
3481 if (NS_WARN_IF(!newBlock)) {
3482 return NS_ERROR_FAILURE;
3483 }
3484 rv = htmlEditor->RemoveBlockContainer(*newBlock);
3485 NS_ENSURE_SUCCESS(rv, rv);
3486 } else {
3487 // replace list with new list type
3488 curList = ConvertListType(curNode->AsElement(), listType, itemType);
3489 if (NS_WARN_IF(!curList)) {
3490 return NS_ERROR_FAILURE;
3491 }
3492 }
3493 prevListItem = nullptr;
3494 continue;
3495 }
3496
3497 EditorRawDOMPoint atCurNode(curNode);
3498 if (NS_WARN_IF(!atCurNode.IsSet())) {
3499 return NS_ERROR_FAILURE;
3500 }
3501 MOZ_ASSERT(atCurNode.IsSetAndValid());
3502 if (HTMLEditUtils::IsListItem(curNode)) {
3503 if (!atCurNode.IsContainerHTMLElement(listType)) {
3504 // list item is in wrong type of list. if we don't have a curList,
3505 // split the old list and make a new list of correct type.
3506 if (!curList || EditorUtils::IsDescendantOf(*curNode, *curList)) {
3507 if (NS_WARN_IF(!atCurNode.GetContainerAsContent())) {
3508 return NS_ERROR_FAILURE;
3509 }
3510 ErrorResult error;
3511 nsCOMPtr<nsIContent> newLeftNode =
3512 htmlEditor->SplitNode(atCurNode, error);
3513 if (NS_WARN_IF(error.Failed())) {
3514 return error.StealNSResult();
3515 }
3516 newBlock = newLeftNode ? newLeftNode->AsElement() : nullptr;
3517 EditorRawDOMPoint atParentOfCurNode(atCurNode.GetContainer());
3518 curList = htmlEditor->CreateNode(listType, atParentOfCurNode);
3519 NS_ENSURE_STATE(curList);
3520 }
3521 // move list item to new list
3522 rv = htmlEditor->MoveNode(curNode, curList, -1);
3523 NS_ENSURE_SUCCESS(rv, rv);
3524 // convert list item type if needed
3525 if (!curNode->IsHTMLElement(itemType)) {
3526 newBlock =
3527 htmlEditor->ReplaceContainer(curNode->AsElement(), itemType);
3528 NS_ENSURE_STATE(newBlock);
3529 }
3530 } else {
3531 // item is in right type of list. But we might still have to move it.
3532 // and we might need to convert list item types.
3533 if (!curList) {
3534 curList = atCurNode.GetContainerAsElement();
3535 } else if (atCurNode.GetContainer() != curList) {
3536 // move list item to new list
3537 rv = htmlEditor->MoveNode(curNode, curList, -1);
3538 NS_ENSURE_SUCCESS(rv, rv);
3539 }
3540 if (!curNode->IsHTMLElement(itemType)) {
3541 newBlock =
3542 htmlEditor->ReplaceContainer(curNode->AsElement(), itemType);
3543 NS_ENSURE_STATE(newBlock);
3544 }
3545 }
3546 nsCOMPtr<Element> curElement = do_QueryInterface(curNode);
3547 if (aBulletType && !aBulletType->IsEmpty()) {
3548 rv =
3549 htmlEditor->SetAttribute(curElement, nsGkAtoms::type, *aBulletType);
3550 if (NS_WARN_IF(NS_FAILED(rv))) {
3551 return rv;
3552 }
3553 } else {
3554 rv = htmlEditor->RemoveAttribute(curElement, nsGkAtoms::type);
3555 if (NS_WARN_IF(NS_FAILED(rv))) {
3556 return rv;
3557 }
3558 }
3559 continue;
3560 }
3561
3562 // if we hit a div clear our prevListItem, insert divs contents
3563 // into our node array, and remove the div
3564 if (curNode->IsHTMLElement(nsGkAtoms::div)) {
3565 prevListItem = nullptr;
3566 int32_t j = i + 1;
3567 GetInnerContent(*curNode, arrayOfNodes, &j);
3568 rv = htmlEditor->RemoveContainer(curNode);
3569 NS_ENSURE_SUCCESS(rv, rv);
3570 listCount = arrayOfNodes.Length();
3571 continue;
3572 }
3573
3574 // need to make a list to put things in if we haven't already,
3575 if (!curList) {
3576 SplitNodeResult splitCurNodeResult =
3577 MaybeSplitAncestorsForInsert(listType, atCurNode);
3578 if (NS_WARN_IF(splitCurNodeResult.Failed())) {
3579 return splitCurNodeResult.Rv();
3580 }
3581 curList =
3582 htmlEditor->CreateNode(listType, splitCurNodeResult.SplitPoint());
3583 if (NS_WARN_IF(!curList)) {
3584 return NS_ERROR_FAILURE;
3585 }
3586 // remember our new block for postprocessing
3587 mNewBlock = curList;
3588 // curList is now the correct thing to put curNode in
3589 prevListItem = nullptr;
3590
3591 // atCurNode is now referring the right node with mOffset but
3592 // referring the left node with mRef. So, invalidate it now.
3593 atCurNode.Clear();
3594 }
3595
3596 // if curNode isn't a list item, we must wrap it in one
3597 nsCOMPtr<Element> listItem;
3598 if (!HTMLEditUtils::IsListItem(curNode)) {
3599 if (IsInlineNode(curNode) && prevListItem) {
3600 // this is a continuation of some inline nodes that belong together in
3601 // the same list item. use prevListItem
3602 rv = htmlEditor->MoveNode(curNode, prevListItem, -1);
3603 NS_ENSURE_SUCCESS(rv, rv);
3604 } else {
3605 // don't wrap li around a paragraph. instead replace paragraph with li
3606 if (curNode->IsHTMLElement(nsGkAtoms::p)) {
3607 listItem =
3608 htmlEditor->ReplaceContainer(curNode->AsElement(), itemType);
3609 NS_ENSURE_STATE(listItem);
3610 } else {
3611 listItem = htmlEditor->InsertContainerAbove(curNode, itemType);
3612 NS_ENSURE_STATE(listItem);
3613 }
3614 if (IsInlineNode(curNode)) {
3615 prevListItem = listItem;
3616 } else {
3617 prevListItem = nullptr;
3618 }
3619 }
3620 } else {
3621 listItem = curNode->AsElement();
3622 }
3623
3624 if (listItem) {
3625 // if we made a new list item, deal with it: tuck the listItem into the
3626 // end of the active list
3627 rv = htmlEditor->MoveNode(listItem, curList, -1);
3628 NS_ENSURE_SUCCESS(rv, rv);
3629 }
3630 }
3631
3632 return NS_OK;
3633 }
3634
WillRemoveList(Selection * aSelection,bool aOrdered,bool * aCancel,bool * aHandled)3635 nsresult HTMLEditRules::WillRemoveList(Selection* aSelection, bool aOrdered,
3636 bool* aCancel, bool* aHandled) {
3637 if (!aSelection || !aCancel || !aHandled) {
3638 return NS_ERROR_NULL_POINTER;
3639 }
3640 // initialize out param
3641 *aCancel = false;
3642 *aHandled = true;
3643
3644 nsresult rv = NormalizeSelection(aSelection);
3645 NS_ENSURE_SUCCESS(rv, rv);
3646 NS_ENSURE_STATE(mHTMLEditor);
3647 AutoSelectionRestorer selectionRestorer(aSelection, mHTMLEditor);
3648
3649 nsTArray<RefPtr<nsRange>> arrayOfRanges;
3650 GetPromotedRanges(*aSelection, arrayOfRanges, EditAction::makeList);
3651
3652 // use these ranges to contruct a list of nodes to act on.
3653 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
3654 rv = GetListActionNodes(arrayOfNodes, EntireList::no);
3655 NS_ENSURE_SUCCESS(rv, rv);
3656
3657 // Remove all non-editable nodes. Leave them be.
3658 for (int32_t i = arrayOfNodes.Length() - 1; i >= 0; i--) {
3659 OwningNonNull<nsINode> testNode = arrayOfNodes[i];
3660 NS_ENSURE_STATE(mHTMLEditor);
3661 if (!mHTMLEditor->IsEditable(testNode)) {
3662 arrayOfNodes.RemoveElementAt(i);
3663 }
3664 }
3665
3666 // Only act on lists or list items in the array
3667 for (auto& curNode : arrayOfNodes) {
3668 // here's where we actually figure out what to do
3669 if (HTMLEditUtils::IsListItem(curNode)) {
3670 // unlist this listitem
3671 bool bOutOfList;
3672 do {
3673 rv = PopListItem(*curNode->AsContent(), &bOutOfList);
3674 NS_ENSURE_SUCCESS(rv, rv);
3675 } while (
3676 !bOutOfList); // keep popping it out until it's not in a list anymore
3677 } else if (HTMLEditUtils::IsList(curNode)) {
3678 // node is a list, move list items out
3679 rv = RemoveListStructure(*curNode->AsElement());
3680 NS_ENSURE_SUCCESS(rv, rv);
3681 }
3682 }
3683 return NS_OK;
3684 }
3685
WillMakeDefListItem(Selection * aSelection,const nsAString * aItemType,bool aEntireList,bool * aCancel,bool * aHandled)3686 nsresult HTMLEditRules::WillMakeDefListItem(Selection* aSelection,
3687 const nsAString* aItemType,
3688 bool aEntireList, bool* aCancel,
3689 bool* aHandled) {
3690 // for now we let WillMakeList handle this
3691 NS_NAMED_LITERAL_STRING(listType, "dl");
3692 return WillMakeList(aSelection, &listType.AsString(), aEntireList, nullptr,
3693 aCancel, aHandled, aItemType);
3694 }
3695
WillMakeBasicBlock(Selection & aSelection,const nsAString & aBlockType,bool * aCancel,bool * aHandled)3696 nsresult HTMLEditRules::WillMakeBasicBlock(Selection& aSelection,
3697 const nsAString& aBlockType,
3698 bool* aCancel, bool* aHandled) {
3699 MOZ_ASSERT(aCancel && aHandled);
3700
3701 OwningNonNull<nsAtom> blockType = NS_Atomize(aBlockType);
3702
3703 WillInsert(aSelection, aCancel);
3704 // We want to ignore result of WillInsert()
3705 *aCancel = false;
3706 *aHandled = true;
3707
3708 nsresult rv = MakeBasicBlock(aSelection, blockType);
3709 Unused << NS_WARN_IF(NS_FAILED(rv));
3710 return rv;
3711 }
3712
MakeBasicBlock(Selection & aSelection,nsAtom & blockType)3713 nsresult HTMLEditRules::MakeBasicBlock(Selection& aSelection,
3714 nsAtom& blockType) {
3715 NS_ENSURE_STATE(mHTMLEditor);
3716 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
3717
3718 nsresult rv = NormalizeSelection(&aSelection);
3719 NS_ENSURE_SUCCESS(rv, rv);
3720 AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);
3721 AutoTransactionsConserveSelection dontChangeMySelection(htmlEditor);
3722
3723 // Contruct a list of nodes to act on.
3724 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
3725 rv = GetNodesFromSelection(aSelection, EditAction::makeBasicBlock,
3726 arrayOfNodes);
3727 NS_ENSURE_SUCCESS(rv, rv);
3728
3729 // If nothing visible in list, make an empty block
3730 if (ListIsEmptyLine(arrayOfNodes)) {
3731 nsRange* firstRange = aSelection.GetRangeAt(0);
3732 if (NS_WARN_IF(!firstRange)) {
3733 return NS_ERROR_FAILURE;
3734 }
3735
3736 EditorDOMPoint pointToInsertBlock(firstRange->StartRef());
3737 if (&blockType == nsGkAtoms::normal || &blockType == nsGkAtoms::_empty) {
3738 // We are removing blocks (going to "body text")
3739 RefPtr<Element> curBlock =
3740 htmlEditor->GetBlock(*pointToInsertBlock.GetContainer());
3741 if (NS_WARN_IF(!curBlock)) {
3742 return NS_ERROR_FAILURE;
3743 }
3744 if (!HTMLEditUtils::IsFormatNode(curBlock)) {
3745 return NS_OK;
3746 }
3747
3748 // If the first editable node after selection is a br, consume it.
3749 // Otherwise it gets pushed into a following block after the split,
3750 // which is visually bad.
3751 nsCOMPtr<nsIContent> brNode =
3752 htmlEditor->GetNextEditableHTMLNode(pointToInsertBlock.AsRaw());
3753 if (brNode && brNode->IsHTMLElement(nsGkAtoms::br)) {
3754 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
3755 rv = htmlEditor->DeleteNode(brNode);
3756 NS_ENSURE_SUCCESS(rv, rv);
3757 }
3758 // Do the splits!
3759 SplitNodeResult splitNodeResult =
3760 htmlEditor->SplitNodeDeep(*curBlock, pointToInsertBlock.AsRaw(),
3761 SplitAtEdges::eDoNotCreateEmptyContainer);
3762 if (NS_WARN_IF(splitNodeResult.Failed())) {
3763 return splitNodeResult.Rv();
3764 }
3765 EditorRawDOMPoint pointToInsertBrNode(splitNodeResult.SplitPoint());
3766 // Put a br at the split point
3767 brNode = htmlEditor->CreateBR(pointToInsertBrNode);
3768 NS_ENSURE_STATE(brNode);
3769 // Put selection at the split point
3770 EditorRawDOMPoint atBrNode(brNode);
3771 ErrorResult error;
3772 aSelection.Collapse(atBrNode, error);
3773 // Don't restore the selection
3774 selectionRestorer.Abort();
3775 if (NS_WARN_IF(error.Failed())) {
3776 return error.StealNSResult();
3777 }
3778 return NS_OK;
3779 }
3780
3781 // We are making a block. Consume a br, if needed.
3782 nsCOMPtr<nsIContent> brNode =
3783 htmlEditor->GetNextEditableHTMLNodeInBlock(pointToInsertBlock.AsRaw());
3784 if (brNode && brNode->IsHTMLElement(nsGkAtoms::br)) {
3785 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertBlock);
3786 rv = htmlEditor->DeleteNode(brNode);
3787 NS_ENSURE_SUCCESS(rv, rv);
3788 // We don't need to act on this node any more
3789 arrayOfNodes.RemoveElement(brNode);
3790 }
3791 // Make sure we can put a block here.
3792 SplitNodeResult splitNodeResult =
3793 MaybeSplitAncestorsForInsert(blockType, pointToInsertBlock.AsRaw());
3794 if (NS_WARN_IF(splitNodeResult.Failed())) {
3795 return splitNodeResult.Rv();
3796 }
3797 RefPtr<Element> block =
3798 htmlEditor->CreateNode(&blockType, splitNodeResult.SplitPoint());
3799 NS_ENSURE_STATE(block);
3800 // Remember our new block for postprocessing
3801 mNewBlock = block;
3802 // Delete anything that was in the list of nodes
3803 while (!arrayOfNodes.IsEmpty()) {
3804 OwningNonNull<nsINode> curNode = arrayOfNodes[0];
3805 rv = htmlEditor->DeleteNode(curNode);
3806 NS_ENSURE_SUCCESS(rv, rv);
3807 arrayOfNodes.RemoveElementAt(0);
3808 }
3809 // Put selection in new block
3810 rv = aSelection.Collapse(block, 0);
3811 // Don't restore the selection
3812 selectionRestorer.Abort();
3813 NS_ENSURE_SUCCESS(rv, rv);
3814 return NS_OK;
3815 }
3816 // Okay, now go through all the nodes and make the right kind of blocks, or
3817 // whatever is approriate. Woohoo! Note: blockquote is handled a little
3818 // differently.
3819 if (&blockType == nsGkAtoms::blockquote) {
3820 rv = MakeBlockquote(arrayOfNodes);
3821 NS_ENSURE_SUCCESS(rv, rv);
3822 } else if (&blockType == nsGkAtoms::normal ||
3823 &blockType == nsGkAtoms::_empty) {
3824 rv = RemoveBlockStyle(arrayOfNodes);
3825 NS_ENSURE_SUCCESS(rv, rv);
3826 } else {
3827 rv = ApplyBlockStyle(arrayOfNodes, blockType);
3828 NS_ENSURE_SUCCESS(rv, rv);
3829 }
3830 return NS_OK;
3831 }
3832
DidMakeBasicBlock(Selection * aSelection,RulesInfo * aInfo,nsresult aResult)3833 nsresult HTMLEditRules::DidMakeBasicBlock(Selection* aSelection,
3834 RulesInfo* aInfo, nsresult aResult) {
3835 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
3836 // check for empty block. if so, put a moz br in it.
3837 if (!aSelection->Collapsed()) {
3838 return NS_OK;
3839 }
3840
3841 NS_ENSURE_STATE(aSelection->GetRangeAt(0) &&
3842 aSelection->GetRangeAt(0)->GetStartContainer());
3843 nsresult rv =
3844 InsertMozBRIfNeeded(*aSelection->GetRangeAt(0)->GetStartContainer());
3845 NS_ENSURE_SUCCESS(rv, rv);
3846 return NS_OK;
3847 }
3848
WillIndent(Selection * aSelection,bool * aCancel,bool * aHandled)3849 nsresult HTMLEditRules::WillIndent(Selection* aSelection, bool* aCancel,
3850 bool* aHandled) {
3851 NS_ENSURE_STATE(mHTMLEditor);
3852 if (mHTMLEditor->IsCSSEnabled()) {
3853 nsresult rv = WillCSSIndent(aSelection, aCancel, aHandled);
3854 if (NS_WARN_IF(NS_FAILED(rv))) {
3855 return rv;
3856 }
3857 } else {
3858 nsresult rv = WillHTMLIndent(aSelection, aCancel, aHandled);
3859 if (NS_WARN_IF(NS_FAILED(rv))) {
3860 return rv;
3861 }
3862 }
3863 return NS_OK;
3864 }
3865
WillCSSIndent(Selection * aSelection,bool * aCancel,bool * aHandled)3866 nsresult HTMLEditRules::WillCSSIndent(Selection* aSelection, bool* aCancel,
3867 bool* aHandled) {
3868 if (!aSelection || !aCancel || !aHandled) {
3869 return NS_ERROR_NULL_POINTER;
3870 }
3871
3872 if (NS_WARN_IF(!mHTMLEditor)) {
3873 return NS_ERROR_NOT_AVAILABLE;
3874 }
3875 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
3876
3877 WillInsert(*aSelection, aCancel);
3878
3879 // initialize out param
3880 // we want to ignore result of WillInsert()
3881 *aCancel = false;
3882 *aHandled = true;
3883
3884 nsresult rv = NormalizeSelection(aSelection);
3885 NS_ENSURE_SUCCESS(rv, rv);
3886 AutoSelectionRestorer selectionRestorer(aSelection, htmlEditor);
3887 nsTArray<OwningNonNull<nsRange>> arrayOfRanges;
3888 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
3889
3890 // short circuit: detect case of collapsed selection inside an <li>.
3891 // just sublist that <li>. This prevents bug 97797.
3892
3893 nsCOMPtr<Element> liNode;
3894 if (aSelection->Collapsed()) {
3895 nsCOMPtr<nsINode> node;
3896 int32_t offset;
3897 nsresult rv = EditorBase::GetStartNodeAndOffset(
3898 aSelection, getter_AddRefs(node), &offset);
3899 NS_ENSURE_SUCCESS(rv, rv);
3900 RefPtr<Element> block = htmlEditor->GetBlock(*node);
3901 if (block && HTMLEditUtils::IsListItem(block)) {
3902 liNode = block;
3903 }
3904 }
3905
3906 if (liNode) {
3907 arrayOfNodes.AppendElement(*liNode);
3908 } else {
3909 // convert the selection ranges into "promoted" selection ranges:
3910 // this basically just expands the range to include the immediate
3911 // block parent, and then further expands to include any ancestors
3912 // whose children are all in the range
3913 rv = GetNodesFromSelection(*aSelection, EditAction::indent, arrayOfNodes);
3914 NS_ENSURE_SUCCESS(rv, rv);
3915 }
3916
3917 // if nothing visible in list, make an empty block
3918 if (ListIsEmptyLine(arrayOfNodes)) {
3919 // get selection location
3920 nsRange* firstRange = aSelection->GetRangeAt(0);
3921 if (NS_WARN_IF(!firstRange)) {
3922 return NS_ERROR_FAILURE;
3923 }
3924
3925 EditorDOMPoint atStartOfSelection(firstRange->StartRef());
3926 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
3927 return NS_ERROR_FAILURE;
3928 }
3929
3930 // make sure we can put a block here
3931 SplitNodeResult splitNodeResult = MaybeSplitAncestorsForInsert(
3932 *nsGkAtoms::div, atStartOfSelection.AsRaw());
3933 if (NS_WARN_IF(splitNodeResult.Failed())) {
3934 return splitNodeResult.Rv();
3935 }
3936 RefPtr<Element> theBlock =
3937 htmlEditor->CreateNode(nsGkAtoms::div, splitNodeResult.SplitPoint());
3938 NS_ENSURE_STATE(theBlock);
3939 // remember our new block for postprocessing
3940 mNewBlock = theBlock;
3941 ChangeIndentation(*theBlock, Change::plus);
3942 // delete anything that was in the list of nodes
3943 while (!arrayOfNodes.IsEmpty()) {
3944 OwningNonNull<nsINode> curNode = arrayOfNodes[0];
3945 rv = htmlEditor->DeleteNode(curNode);
3946 NS_ENSURE_SUCCESS(rv, rv);
3947 arrayOfNodes.RemoveElementAt(0);
3948 }
3949 // put selection in new block
3950 *aHandled = true;
3951 EditorRawDOMPoint atStartOfTheBlock(theBlock, 0);
3952 ErrorResult error;
3953 aSelection->Collapse(atStartOfTheBlock, error);
3954 // Don't restore the selection
3955 selectionRestorer.Abort();
3956 if (NS_WARN_IF(!error.Failed())) {
3957 return error.StealNSResult();
3958 }
3959 return NS_OK;
3960 }
3961
3962 // Ok, now go through all the nodes and put them in a blockquote,
3963 // or whatever is appropriate. Wohoo!
3964 nsCOMPtr<Element> curList, curQuote;
3965 nsCOMPtr<nsIContent> sibling;
3966 for (OwningNonNull<nsINode>& curNode : arrayOfNodes) {
3967 // Here's where we actually figure out what to do.
3968 EditorDOMPoint atCurNode(curNode);
3969 if (NS_WARN_IF(!atCurNode.IsSet())) {
3970 continue;
3971 }
3972
3973 // Ignore all non-editable nodes. Leave them be.
3974 if (!htmlEditor->IsEditable(curNode)) {
3975 continue;
3976 }
3977
3978 // some logic for putting list items into nested lists...
3979 if (HTMLEditUtils::IsList(atCurNode.GetContainer())) {
3980 // Check for whether we should join a list that follows curNode.
3981 // We do this if the next element is a list, and the list is of the
3982 // same type (li/ol) as curNode was a part it.
3983 sibling = htmlEditor->GetNextHTMLSibling(curNode);
3984 if (sibling && HTMLEditUtils::IsList(sibling) &&
3985 atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
3986 sibling->NodeInfo()->NameAtom() &&
3987 atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
3988 sibling->NodeInfo()->NamespaceID()) {
3989 rv = htmlEditor->MoveNode(curNode->AsContent(), sibling, 0);
3990 NS_ENSURE_SUCCESS(rv, rv);
3991 continue;
3992 }
3993
3994 // Check for whether we should join a list that preceeds curNode.
3995 // We do this if the previous element is a list, and the list is of
3996 // the same type (li/ol) as curNode was a part of.
3997 sibling = htmlEditor->GetPriorHTMLSibling(curNode);
3998 if (sibling && HTMLEditUtils::IsList(sibling) &&
3999 atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
4000 sibling->NodeInfo()->NameAtom() &&
4001 atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
4002 sibling->NodeInfo()->NamespaceID()) {
4003 rv = htmlEditor->MoveNode(curNode->AsContent(), sibling, -1);
4004 NS_ENSURE_SUCCESS(rv, rv);
4005 continue;
4006 }
4007
4008 // check to see if curList is still appropriate. Which it is if
4009 // curNode is still right after it in the same list.
4010 sibling = nullptr;
4011 if (curList) {
4012 sibling = htmlEditor->GetPriorHTMLSibling(curNode);
4013 }
4014
4015 if (!curList || (sibling && sibling != curList)) {
4016 nsAtom* containerName =
4017 atCurNode.GetContainer()->NodeInfo()->NameAtom();
4018 // Create a new nested list of correct type.
4019 SplitNodeResult splitNodeResult =
4020 MaybeSplitAncestorsForInsert(*containerName, atCurNode.AsRaw());
4021 if (NS_WARN_IF(splitNodeResult.Failed())) {
4022 return splitNodeResult.Rv();
4023 }
4024 curList =
4025 htmlEditor->CreateNode(containerName, splitNodeResult.SplitPoint());
4026 NS_ENSURE_STATE(curList);
4027 // curList is now the correct thing to put curNode in
4028 // remember our new block for postprocessing
4029 mNewBlock = curList;
4030 }
4031 // tuck the node into the end of the active list
4032 uint32_t listLen = curList->Length();
4033 rv = htmlEditor->MoveNode(curNode->AsContent(), curList, listLen);
4034 NS_ENSURE_SUCCESS(rv, rv);
4035
4036 continue;
4037 }
4038
4039 // Not a list item.
4040
4041 if (IsBlockNode(*curNode)) {
4042 ChangeIndentation(*curNode->AsElement(), Change::plus);
4043 curQuote = nullptr;
4044 continue;
4045 }
4046
4047 if (!curQuote) {
4048 // First, check that our element can contain a div.
4049 if (!htmlEditor->CanContainTag(*atCurNode.GetContainer(),
4050 *nsGkAtoms::div)) {
4051 return NS_OK; // cancelled
4052 }
4053
4054 SplitNodeResult splitNodeResult =
4055 MaybeSplitAncestorsForInsert(*nsGkAtoms::div, atCurNode.AsRaw());
4056 if (NS_WARN_IF(splitNodeResult.Failed())) {
4057 return splitNodeResult.Rv();
4058 }
4059 curQuote =
4060 htmlEditor->CreateNode(nsGkAtoms::div, splitNodeResult.SplitPoint());
4061 NS_ENSURE_STATE(curQuote);
4062 ChangeIndentation(*curQuote, Change::plus);
4063 // remember our new block for postprocessing
4064 mNewBlock = curQuote;
4065 // curQuote is now the correct thing to put curNode in
4066 }
4067
4068 // tuck the node into the end of the active blockquote
4069 uint32_t quoteLen = curQuote->Length();
4070 rv = htmlEditor->MoveNode(curNode->AsContent(), curQuote, quoteLen);
4071 NS_ENSURE_SUCCESS(rv, rv);
4072 }
4073 return NS_OK;
4074 }
4075
WillHTMLIndent(Selection * aSelection,bool * aCancel,bool * aHandled)4076 nsresult HTMLEditRules::WillHTMLIndent(Selection* aSelection, bool* aCancel,
4077 bool* aHandled) {
4078 if (!aSelection || !aCancel || !aHandled) {
4079 return NS_ERROR_NULL_POINTER;
4080 }
4081
4082 if (NS_WARN_IF(!mHTMLEditor)) {
4083 return NS_ERROR_NOT_AVAILABLE;
4084 }
4085 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
4086
4087 WillInsert(*aSelection, aCancel);
4088
4089 // initialize out param
4090 // we want to ignore result of WillInsert()
4091 *aCancel = false;
4092 *aHandled = true;
4093
4094 nsresult rv = NormalizeSelection(aSelection);
4095 NS_ENSURE_SUCCESS(rv, rv);
4096
4097 AutoSelectionRestorer selectionRestorer(aSelection, htmlEditor);
4098
4099 // convert the selection ranges into "promoted" selection ranges:
4100 // this basically just expands the range to include the immediate
4101 // block parent, and then further expands to include any ancestors
4102 // whose children are all in the range
4103
4104 nsTArray<RefPtr<nsRange>> arrayOfRanges;
4105 GetPromotedRanges(*aSelection, arrayOfRanges, EditAction::indent);
4106
4107 // use these ranges to contruct a list of nodes to act on.
4108 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
4109 rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes, EditAction::indent);
4110 NS_ENSURE_SUCCESS(rv, rv);
4111
4112 // if nothing visible in list, make an empty block
4113 if (ListIsEmptyLine(arrayOfNodes)) {
4114 nsRange* firstRange = aSelection->GetRangeAt(0);
4115 if (NS_WARN_IF(!firstRange)) {
4116 return NS_ERROR_FAILURE;
4117 }
4118
4119 EditorDOMPoint atStartOfSelection(firstRange->StartRef());
4120 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
4121 return NS_ERROR_FAILURE;
4122 }
4123
4124 // Make sure we can put a block here.
4125 SplitNodeResult splitNodeResult = MaybeSplitAncestorsForInsert(
4126 *nsGkAtoms::blockquote, atStartOfSelection.AsRaw());
4127 if (NS_WARN_IF(splitNodeResult.Failed())) {
4128 return splitNodeResult.Rv();
4129 }
4130 RefPtr<Element> theBlock = htmlEditor->CreateNode(
4131 nsGkAtoms::blockquote, splitNodeResult.SplitPoint());
4132 NS_ENSURE_STATE(theBlock);
4133 // remember our new block for postprocessing
4134 mNewBlock = theBlock;
4135 // delete anything that was in the list of nodes
4136 while (!arrayOfNodes.IsEmpty()) {
4137 OwningNonNull<nsINode> curNode = arrayOfNodes[0];
4138 rv = htmlEditor->DeleteNode(curNode);
4139 NS_ENSURE_SUCCESS(rv, rv);
4140 arrayOfNodes.RemoveElementAt(0);
4141 }
4142 // put selection in new block
4143 *aHandled = true;
4144 EditorRawDOMPoint atStartOfTheBlock(theBlock, 0);
4145 ErrorResult error;
4146 aSelection->Collapse(atStartOfTheBlock, error);
4147 // Don't restore the selection
4148 selectionRestorer.Abort();
4149 if (NS_WARN_IF(!error.Failed())) {
4150 return error.StealNSResult();
4151 }
4152 return NS_OK;
4153 }
4154
4155 // Ok, now go through all the nodes and put them in a blockquote,
4156 // or whatever is appropriate. Wohoo!
4157 nsCOMPtr<nsIContent> sibling;
4158 nsCOMPtr<Element> curList, curQuote, indentedLI;
4159 for (OwningNonNull<nsINode>& curNode : arrayOfNodes) {
4160 // Here's where we actually figure out what to do.
4161 EditorDOMPoint atCurNode(curNode);
4162 if (NS_WARN_IF(!atCurNode.IsSet())) {
4163 continue;
4164 }
4165
4166 // Ignore all non-editable nodes. Leave them be.
4167 if (!htmlEditor->IsEditable(curNode)) {
4168 continue;
4169 }
4170
4171 // some logic for putting list items into nested lists...
4172 if (HTMLEditUtils::IsList(atCurNode.GetContainer())) {
4173 // Check for whether we should join a list that follows curNode.
4174 // We do this if the next element is a list, and the list is of the
4175 // same type (li/ol) as curNode was a part it.
4176 sibling = htmlEditor->GetNextHTMLSibling(curNode);
4177 if (sibling && HTMLEditUtils::IsList(sibling) &&
4178 atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
4179 sibling->NodeInfo()->NameAtom() &&
4180 atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
4181 sibling->NodeInfo()->NamespaceID()) {
4182 rv = htmlEditor->MoveNode(curNode->AsContent(), sibling, 0);
4183 NS_ENSURE_SUCCESS(rv, rv);
4184 continue;
4185 }
4186
4187 // Check for whether we should join a list that preceeds curNode.
4188 // We do this if the previous element is a list, and the list is of
4189 // the same type (li/ol) as curNode was a part of.
4190 sibling = htmlEditor->GetPriorHTMLSibling(curNode);
4191 if (sibling && HTMLEditUtils::IsList(sibling) &&
4192 atCurNode.GetContainer()->NodeInfo()->NameAtom() ==
4193 sibling->NodeInfo()->NameAtom() &&
4194 atCurNode.GetContainer()->NodeInfo()->NamespaceID() ==
4195 sibling->NodeInfo()->NamespaceID()) {
4196 rv = htmlEditor->MoveNode(curNode->AsContent(), sibling, -1);
4197 NS_ENSURE_SUCCESS(rv, rv);
4198 continue;
4199 }
4200
4201 // check to see if curList is still appropriate. Which it is if
4202 // curNode is still right after it in the same list.
4203 sibling = nullptr;
4204 if (curList) {
4205 sibling = htmlEditor->GetPriorHTMLSibling(curNode);
4206 }
4207
4208 if (!curList || (sibling && sibling != curList)) {
4209 nsAtom* containerName =
4210 atCurNode.GetContainer()->NodeInfo()->NameAtom();
4211 // Create a new nested list of correct type.
4212 SplitNodeResult splitNodeResult =
4213 MaybeSplitAncestorsForInsert(*containerName, atCurNode.AsRaw());
4214 if (NS_WARN_IF(splitNodeResult.Failed())) {
4215 return splitNodeResult.Rv();
4216 }
4217 curList =
4218 htmlEditor->CreateNode(containerName, splitNodeResult.SplitPoint());
4219 NS_ENSURE_STATE(curList);
4220 // curList is now the correct thing to put curNode in
4221 // remember our new block for postprocessing
4222 mNewBlock = curList;
4223 }
4224 // tuck the node into the end of the active list
4225 rv = htmlEditor->MoveNode(curNode->AsContent(), curList, -1);
4226 NS_ENSURE_SUCCESS(rv, rv);
4227 // forget curQuote, if any
4228 curQuote = nullptr;
4229
4230 continue;
4231 }
4232
4233 // Not a list item, use blockquote?
4234
4235 // if we are inside a list item, we don't want to blockquote, we want
4236 // to sublist the list item. We may have several nodes listed in the
4237 // array of nodes to act on, that are in the same list item. Since
4238 // we only want to indent that li once, we must keep track of the most
4239 // recent indented list item, and not indent it if we find another node
4240 // to act on that is still inside the same li.
4241 RefPtr<Element> listItem = IsInListItem(curNode);
4242 if (listItem) {
4243 if (indentedLI == listItem) {
4244 // already indented this list item
4245 continue;
4246 }
4247 // check to see if curList is still appropriate. Which it is if
4248 // curNode is still right after it in the same list.
4249 if (curList) {
4250 sibling = htmlEditor->GetPriorHTMLSibling(listItem);
4251 }
4252
4253 if (!curList || (sibling && sibling != curList)) {
4254 EditorDOMPoint atListItem(listItem);
4255 if (NS_WARN_IF(!listItem)) {
4256 return NS_ERROR_FAILURE;
4257 }
4258 nsAtom* containerName =
4259 atListItem.GetContainer()->NodeInfo()->NameAtom();
4260 // Create a new nested list of correct type.
4261 SplitNodeResult splitNodeResult =
4262 MaybeSplitAncestorsForInsert(*containerName, atListItem.AsRaw());
4263 if (NS_WARN_IF(splitNodeResult.Failed())) {
4264 return splitNodeResult.Rv();
4265 }
4266 curList =
4267 htmlEditor->CreateNode(containerName, splitNodeResult.SplitPoint());
4268 NS_ENSURE_STATE(curList);
4269 }
4270
4271 rv = htmlEditor->MoveNode(listItem, curList, -1);
4272 NS_ENSURE_SUCCESS(rv, rv);
4273
4274 // remember we indented this li
4275 indentedLI = listItem;
4276
4277 continue;
4278 }
4279
4280 // need to make a blockquote to put things in if we haven't already,
4281 // or if this node doesn't go in blockquote we used earlier.
4282 // One reason it might not go in prio blockquote is if we are now
4283 // in a different table cell.
4284 if (curQuote && InDifferentTableElements(curQuote, curNode)) {
4285 curQuote = nullptr;
4286 }
4287
4288 if (!curQuote) {
4289 // First, check that our element can contain a blockquote.
4290 if (!htmlEditor->CanContainTag(*atCurNode.GetContainer(),
4291 *nsGkAtoms::blockquote)) {
4292 return NS_OK; // cancelled
4293 }
4294
4295 SplitNodeResult splitNodeResult = MaybeSplitAncestorsForInsert(
4296 *nsGkAtoms::blockquote, atCurNode.AsRaw());
4297 if (NS_WARN_IF(splitNodeResult.Failed())) {
4298 return splitNodeResult.Rv();
4299 }
4300 curQuote = htmlEditor->CreateNode(nsGkAtoms::blockquote,
4301 splitNodeResult.SplitPoint());
4302 NS_ENSURE_STATE(curQuote);
4303 // remember our new block for postprocessing
4304 mNewBlock = curQuote;
4305 // curQuote is now the correct thing to put curNode in
4306 }
4307
4308 // tuck the node into the end of the active blockquote
4309 rv = htmlEditor->MoveNode(curNode->AsContent(), curQuote, -1);
4310 NS_ENSURE_SUCCESS(rv, rv);
4311 // forget curList, if any
4312 curList = nullptr;
4313 }
4314 return NS_OK;
4315 }
4316
WillOutdent(Selection & aSelection,bool * aCancel,bool * aHandled)4317 nsresult HTMLEditRules::WillOutdent(Selection& aSelection, bool* aCancel,
4318 bool* aHandled) {
4319 MOZ_ASSERT(aCancel && aHandled);
4320 *aCancel = false;
4321 *aHandled = true;
4322 nsCOMPtr<nsIContent> rememberedLeftBQ, rememberedRightBQ;
4323 NS_ENSURE_STATE(mHTMLEditor);
4324 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
4325 bool useCSS = htmlEditor->IsCSSEnabled();
4326
4327 nsresult rv = NormalizeSelection(&aSelection);
4328 NS_ENSURE_SUCCESS(rv, rv);
4329
4330 // Some scoping for selection resetting - we may need to tweak it
4331 {
4332 AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);
4333
4334 // Convert the selection ranges into "promoted" selection ranges: this
4335 // basically just expands the range to include the immediate block parent,
4336 // and then further expands to include any ancestors whose children are all
4337 // in the range
4338 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
4339 rv = GetNodesFromSelection(aSelection, EditAction::outdent, arrayOfNodes);
4340 NS_ENSURE_SUCCESS(rv, rv);
4341
4342 // Okay, now go through all the nodes and remove a level of blockquoting,
4343 // or whatever is appropriate. Wohoo!
4344
4345 nsCOMPtr<Element> curBlockQuote;
4346 nsCOMPtr<nsIContent> firstBQChild, lastBQChild;
4347 bool curBlockQuoteIsIndentedWithCSS = false;
4348 for (uint32_t i = 0; i < arrayOfNodes.Length(); i++) {
4349 if (!arrayOfNodes[i]->IsContent()) {
4350 continue;
4351 }
4352 OwningNonNull<nsIContent> curNode = *arrayOfNodes[i]->AsContent();
4353
4354 // Here's where we actually figure out what to do
4355 int32_t offset;
4356 nsCOMPtr<nsINode> curParent =
4357 EditorBase::GetNodeLocation(curNode, &offset);
4358 if (!curParent) {
4359 continue;
4360 }
4361
4362 // Is it a blockquote?
4363 if (curNode->IsHTMLElement(nsGkAtoms::blockquote)) {
4364 // If it is a blockquote, remove it. So we need to finish up dealng
4365 // with any curBlockQuote first.
4366 if (curBlockQuote) {
4367 rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
4368 curBlockQuoteIsIndentedWithCSS,
4369 getter_AddRefs(rememberedLeftBQ),
4370 getter_AddRefs(rememberedRightBQ));
4371 NS_ENSURE_SUCCESS(rv, rv);
4372 curBlockQuote = nullptr;
4373 firstBQChild = nullptr;
4374 lastBQChild = nullptr;
4375 curBlockQuoteIsIndentedWithCSS = false;
4376 }
4377 rv = htmlEditor->RemoveBlockContainer(curNode);
4378 NS_ENSURE_SUCCESS(rv, rv);
4379 continue;
4380 }
4381 // Is it a block with a 'margin' property?
4382 if (useCSS && IsBlockNode(curNode)) {
4383 nsAtom& marginProperty = MarginPropertyAtomForIndent(curNode);
4384 nsAutoString value;
4385 CSSEditUtils::GetSpecifiedProperty(curNode, marginProperty, value);
4386 float f;
4387 RefPtr<nsAtom> unit;
4388 CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
4389 if (f > 0) {
4390 ChangeIndentation(*curNode->AsElement(), Change::minus);
4391 continue;
4392 }
4393 }
4394 // Is it a list item?
4395 if (HTMLEditUtils::IsListItem(curNode)) {
4396 // If it is a list item, that means we are not outdenting whole list.
4397 // So we need to finish up dealing with any curBlockQuote, and then pop
4398 // this list item.
4399 if (curBlockQuote) {
4400 rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
4401 curBlockQuoteIsIndentedWithCSS,
4402 getter_AddRefs(rememberedLeftBQ),
4403 getter_AddRefs(rememberedRightBQ));
4404 NS_ENSURE_SUCCESS(rv, rv);
4405 curBlockQuote = nullptr;
4406 firstBQChild = nullptr;
4407 lastBQChild = nullptr;
4408 curBlockQuoteIsIndentedWithCSS = false;
4409 }
4410 rv = PopListItem(*curNode->AsContent());
4411 NS_ENSURE_SUCCESS(rv, rv);
4412 continue;
4413 }
4414 // Do we have a blockquote that we are already committed to removing?
4415 if (curBlockQuote) {
4416 // If so, is this node a descendant?
4417 if (EditorUtils::IsDescendantOf(*curNode, *curBlockQuote)) {
4418 lastBQChild = curNode;
4419 // Then we don't need to do anything different for this node
4420 continue;
4421 }
4422 // Otherwise, we have progressed beyond end of curBlockQuote, so
4423 // let's handle it now. We need to remove the portion of
4424 // curBlockQuote that contains [firstBQChild - lastBQChild].
4425 rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
4426 curBlockQuoteIsIndentedWithCSS,
4427 getter_AddRefs(rememberedLeftBQ),
4428 getter_AddRefs(rememberedRightBQ));
4429 NS_ENSURE_SUCCESS(rv, rv);
4430 curBlockQuote = nullptr;
4431 firstBQChild = nullptr;
4432 lastBQChild = nullptr;
4433 curBlockQuoteIsIndentedWithCSS = false;
4434 // Fall out and handle curNode
4435 }
4436
4437 // Are we inside a blockquote?
4438 OwningNonNull<nsINode> n = curNode;
4439 curBlockQuoteIsIndentedWithCSS = false;
4440 // Keep looking up the hierarchy as long as we don't hit the body or the
4441 // active editing host or a table element (other than an entire table)
4442 while (!n->IsHTMLElement(nsGkAtoms::body) &&
4443 htmlEditor->IsDescendantOfEditorRoot(n) &&
4444 (n->IsHTMLElement(nsGkAtoms::table) ||
4445 !HTMLEditUtils::IsTableElement(n))) {
4446 if (!n->GetParentNode()) {
4447 break;
4448 }
4449 n = *n->GetParentNode();
4450 if (n->IsHTMLElement(nsGkAtoms::blockquote)) {
4451 // If so, remember it and the first node we are taking out of it.
4452 curBlockQuote = n->AsElement();
4453 firstBQChild = curNode;
4454 lastBQChild = curNode;
4455 break;
4456 } else if (useCSS) {
4457 nsAtom& marginProperty = MarginPropertyAtomForIndent(curNode);
4458 nsAutoString value;
4459 CSSEditUtils::GetSpecifiedProperty(*n, marginProperty, value);
4460 float f;
4461 RefPtr<nsAtom> unit;
4462 CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
4463 if (f > 0 && !(HTMLEditUtils::IsList(curParent) &&
4464 HTMLEditUtils::IsList(curNode))) {
4465 curBlockQuote = n->AsElement();
4466 firstBQChild = curNode;
4467 lastBQChild = curNode;
4468 curBlockQuoteIsIndentedWithCSS = true;
4469 break;
4470 }
4471 }
4472 }
4473
4474 if (!curBlockQuote) {
4475 // Couldn't find enclosing blockquote. Handle list cases.
4476 if (HTMLEditUtils::IsList(curParent)) {
4477 // Move node out of list
4478 if (HTMLEditUtils::IsList(curNode)) {
4479 // Just unwrap this sublist
4480 rv = htmlEditor->RemoveBlockContainer(curNode);
4481 NS_ENSURE_SUCCESS(rv, rv);
4482 }
4483 // handled list item case above
4484 } else if (HTMLEditUtils::IsList(curNode)) {
4485 // node is a list, but parent is non-list: move list items out
4486 nsCOMPtr<nsIContent> child = curNode->GetLastChild();
4487 while (child) {
4488 if (HTMLEditUtils::IsListItem(child)) {
4489 rv = PopListItem(*child);
4490 NS_ENSURE_SUCCESS(rv, rv);
4491 } else if (HTMLEditUtils::IsList(child)) {
4492 // We have an embedded list, so move it out from under the parent
4493 // list. Be sure to put it after the parent list because this
4494 // loop iterates backwards through the parent's list of children.
4495
4496 rv = htmlEditor->MoveNode(child, curParent, offset + 1);
4497 NS_ENSURE_SUCCESS(rv, rv);
4498 } else {
4499 // Delete any non-list items for now
4500 rv = htmlEditor->DeleteNode(child);
4501 NS_ENSURE_SUCCESS(rv, rv);
4502 }
4503 child = curNode->GetLastChild();
4504 }
4505 // Delete the now-empty list
4506 rv = htmlEditor->RemoveBlockContainer(curNode);
4507 NS_ENSURE_SUCCESS(rv, rv);
4508 } else if (useCSS) {
4509 nsCOMPtr<Element> element;
4510 if (curNode->GetAsText()) {
4511 // We want to outdent the parent of text nodes
4512 element = curNode->GetParentElement();
4513 } else if (curNode->IsElement()) {
4514 element = curNode->AsElement();
4515 }
4516 if (element) {
4517 ChangeIndentation(*element, Change::minus);
4518 }
4519 }
4520 }
4521 }
4522 if (curBlockQuote) {
4523 // We have a blockquote we haven't finished handling
4524 rv = OutdentPartOfBlock(*curBlockQuote, *firstBQChild, *lastBQChild,
4525 curBlockQuoteIsIndentedWithCSS,
4526 getter_AddRefs(rememberedLeftBQ),
4527 getter_AddRefs(rememberedRightBQ));
4528 NS_ENSURE_SUCCESS(rv, rv);
4529 }
4530 }
4531 // Make sure selection didn't stick to last piece of content in old bq (only
4532 // a problem for collapsed selections)
4533 if (rememberedLeftBQ || rememberedRightBQ) {
4534 if (aSelection.Collapsed()) {
4535 // Push selection past end of rememberedLeftBQ
4536 NS_ENSURE_TRUE(aSelection.GetRangeAt(0), NS_OK);
4537 nsCOMPtr<nsINode> startNode =
4538 aSelection.GetRangeAt(0)->GetStartContainer();
4539 if (rememberedLeftBQ &&
4540 (startNode == rememberedLeftBQ ||
4541 EditorUtils::IsDescendantOf(*startNode, *rememberedLeftBQ))) {
4542 // Selection is inside rememberedLeftBQ - push it past it.
4543 EditorRawDOMPoint afterRememberedLeftBQ(rememberedLeftBQ);
4544 afterRememberedLeftBQ.AdvanceOffset();
4545 aSelection.Collapse(afterRememberedLeftBQ);
4546 }
4547 // And pull selection before beginning of rememberedRightBQ
4548 startNode = aSelection.GetRangeAt(0)->GetStartContainer();
4549 if (rememberedRightBQ &&
4550 (startNode == rememberedRightBQ ||
4551 EditorUtils::IsDescendantOf(*startNode, *rememberedRightBQ))) {
4552 // Selection is inside rememberedRightBQ - push it before it.
4553 EditorRawDOMPoint atRememberedRightBQ(rememberedRightBQ);
4554 aSelection.Collapse(atRememberedRightBQ);
4555 }
4556 }
4557 return NS_OK;
4558 }
4559 return NS_OK;
4560 }
4561
4562 /**
4563 * RemovePartOfBlock() splits aBlock and move aStartChild to aEndChild out of
4564 * aBlock.
4565 */
RemovePartOfBlock(Element & aBlock,nsIContent & aStartChild,nsIContent & aEndChild)4566 nsresult HTMLEditRules::RemovePartOfBlock(Element& aBlock,
4567 nsIContent& aStartChild,
4568 nsIContent& aEndChild) {
4569 SplitBlock(aBlock, aStartChild, aEndChild);
4570 // Get rid of part of blockquote we are outdenting
4571
4572 NS_ENSURE_STATE(mHTMLEditor);
4573 nsresult rv = mHTMLEditor->RemoveBlockContainer(aBlock);
4574 NS_ENSURE_SUCCESS(rv, rv);
4575
4576 return NS_OK;
4577 }
4578
SplitBlock(Element & aBlock,nsIContent & aStartChild,nsIContent & aEndChild,nsIContent ** aOutLeftNode,nsIContent ** aOutRightNode,nsIContent ** aOutMiddleNode)4579 void HTMLEditRules::SplitBlock(Element& aBlock, nsIContent& aStartChild,
4580 nsIContent& aEndChild, nsIContent** aOutLeftNode,
4581 nsIContent** aOutRightNode,
4582 nsIContent** aOutMiddleNode) {
4583 // aStartChild and aEndChild must be exclusive descendants of aBlock
4584 MOZ_ASSERT(EditorUtils::IsDescendantOf(aStartChild, aBlock) &&
4585 EditorUtils::IsDescendantOf(aEndChild, aBlock));
4586 NS_ENSURE_TRUE_VOID(mHTMLEditor);
4587 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
4588
4589 // Split at the start.
4590 SplitNodeResult splitAtStartResult =
4591 htmlEditor->SplitNodeDeep(aBlock, EditorRawDOMPoint(&aStartChild),
4592 SplitAtEdges::eDoNotCreateEmptyContainer);
4593 NS_WARNING_ASSERTION(splitAtStartResult.Succeeded(),
4594 "Failed to split aBlock at start");
4595
4596 // Split at after the end
4597 EditorRawDOMPoint atAfterEnd(&aEndChild);
4598 DebugOnly<bool> advanced = atAfterEnd.AdvanceOffset();
4599 NS_WARNING_ASSERTION(advanced, "Failed to advance offset after the end node");
4600 SplitNodeResult splitAtEndResult = htmlEditor->SplitNodeDeep(
4601 aBlock, atAfterEnd, SplitAtEdges::eDoNotCreateEmptyContainer);
4602 NS_WARNING_ASSERTION(splitAtEndResult.Succeeded(),
4603 "Failed to split aBlock at after end");
4604
4605 if (aOutLeftNode) {
4606 NS_IF_ADDREF(*aOutLeftNode = splitAtStartResult.GetPreviousNode());
4607 }
4608
4609 if (aOutRightNode) {
4610 NS_IF_ADDREF(*aOutRightNode = splitAtEndResult.GetNextNode());
4611 }
4612
4613 if (aOutMiddleNode) {
4614 if (splitAtEndResult.GetPreviousNode()) {
4615 NS_IF_ADDREF(*aOutMiddleNode = splitAtEndResult.GetPreviousNode());
4616 } else {
4617 NS_IF_ADDREF(*aOutMiddleNode = splitAtStartResult.GetNextNode());
4618 }
4619 }
4620 }
4621
OutdentPartOfBlock(Element & aBlock,nsIContent & aStartChild,nsIContent & aEndChild,bool aIsBlockIndentedWithCSS,nsIContent ** aOutLeftNode,nsIContent ** aOutRightNode)4622 nsresult HTMLEditRules::OutdentPartOfBlock(Element& aBlock,
4623 nsIContent& aStartChild,
4624 nsIContent& aEndChild,
4625 bool aIsBlockIndentedWithCSS,
4626 nsIContent** aOutLeftNode,
4627 nsIContent** aOutRightNode) {
4628 MOZ_ASSERT(aOutLeftNode && aOutRightNode);
4629
4630 nsCOMPtr<nsIContent> middleNode;
4631 SplitBlock(aBlock, aStartChild, aEndChild, aOutLeftNode, aOutRightNode,
4632 getter_AddRefs(middleNode));
4633
4634 NS_ENSURE_STATE(middleNode);
4635
4636 if (!aIsBlockIndentedWithCSS) {
4637 NS_ENSURE_STATE(mHTMLEditor);
4638 nsresult rv = mHTMLEditor->RemoveBlockContainer(*middleNode);
4639 NS_ENSURE_SUCCESS(rv, rv);
4640 } else if (middleNode->IsElement()) {
4641 // We do nothing if middleNode isn't an element
4642 nsresult rv = ChangeIndentation(*middleNode->AsElement(), Change::minus);
4643 NS_ENSURE_SUCCESS(rv, rv);
4644 }
4645
4646 return NS_OK;
4647 }
4648
4649 /**
4650 * ConvertListType() converts list type and list item type.
4651 */
ConvertListType(Element * aList,nsAtom * aListType,nsAtom * aItemType)4652 already_AddRefed<Element> HTMLEditRules::ConvertListType(Element* aList,
4653 nsAtom* aListType,
4654 nsAtom* aItemType) {
4655 MOZ_ASSERT(aList);
4656 MOZ_ASSERT(aListType);
4657 MOZ_ASSERT(aItemType);
4658
4659 nsCOMPtr<nsINode> child = aList->GetFirstChild();
4660 while (child) {
4661 if (child->IsElement()) {
4662 dom::Element* element = child->AsElement();
4663 if (HTMLEditUtils::IsListItem(element) &&
4664 !element->IsHTMLElement(aItemType)) {
4665 child = mHTMLEditor->ReplaceContainer(element, aItemType);
4666 if (NS_WARN_IF(!child)) {
4667 return nullptr;
4668 }
4669 } else if (HTMLEditUtils::IsList(element) &&
4670 !element->IsHTMLElement(aListType)) {
4671 child = ConvertListType(child->AsElement(), aListType, aItemType);
4672 if (NS_WARN_IF(!child)) {
4673 return nullptr;
4674 }
4675 }
4676 }
4677 child = child->GetNextSibling();
4678 }
4679
4680 if (aList->IsHTMLElement(aListType)) {
4681 RefPtr<dom::Element> list = aList->AsElement();
4682 return list.forget();
4683 }
4684
4685 return mHTMLEditor->ReplaceContainer(aList, aListType);
4686 }
4687
4688 /**
4689 * CreateStyleForInsertText() takes care of clearing and setting appropriate
4690 * style nodes for text insertion.
4691 */
CreateStyleForInsertText(Selection & aSelection,nsIDocument & aDoc)4692 nsresult HTMLEditRules::CreateStyleForInsertText(Selection& aSelection,
4693 nsIDocument& aDoc) {
4694 MOZ_ASSERT(mHTMLEditor->mTypeInState);
4695
4696 bool weDidSomething = false;
4697 NS_ENSURE_STATE(aSelection.GetRangeAt(0));
4698 nsCOMPtr<nsINode> node = aSelection.GetRangeAt(0)->GetStartContainer();
4699 int32_t offset = aSelection.GetRangeAt(0)->StartOffset();
4700
4701 nsCOMPtr<Element> rootElement = aDoc.GetRootElement();
4702 NS_ENSURE_STATE(rootElement);
4703
4704 // process clearing any styles first
4705 UniquePtr<PropItem> item =
4706 Move(mHTMLEditor->mTypeInState->TakeClearProperty());
4707 while (item && node != rootElement) {
4708 NS_ENSURE_STATE(mHTMLEditor);
4709 // XXX If we redesign ClearStyle(), we can use EditorDOMPoint in this
4710 // method.
4711 nsresult rv = mHTMLEditor->ClearStyle(address_of(node), &offset, item->tag,
4712 item->attr);
4713 NS_ENSURE_SUCCESS(rv, rv);
4714 item = Move(mHTMLEditor->mTypeInState->TakeClearProperty());
4715 weDidSomething = true;
4716 }
4717
4718 // then process setting any styles
4719 int32_t relFontSize = mHTMLEditor->mTypeInState->TakeRelativeFontSize();
4720 item = Move(mHTMLEditor->mTypeInState->TakeSetProperty());
4721
4722 if (item || relFontSize) {
4723 // we have at least one style to add; make a new text node to insert style
4724 // nodes above.
4725 if (RefPtr<Text> text = node->GetAsText()) {
4726 if (NS_WARN_IF(!mHTMLEditor)) {
4727 return NS_ERROR_FAILURE;
4728 }
4729 // if we are in a text node, split it
4730 SplitNodeResult splitTextNodeResult = mHTMLEditor->SplitNodeDeep(
4731 *text, EditorRawDOMPoint(text, offset),
4732 SplitAtEdges::eAllowToCreateEmptyContainer);
4733 if (NS_WARN_IF(splitTextNodeResult.Failed())) {
4734 return splitTextNodeResult.Rv();
4735 }
4736 EditorRawDOMPoint splitPoint(splitTextNodeResult.SplitPoint());
4737 node = splitPoint.GetContainer();
4738 offset = splitPoint.Offset();
4739 }
4740 if (!mHTMLEditor->IsContainer(node)) {
4741 return NS_OK;
4742 }
4743 OwningNonNull<Text> newNode =
4744 EditorBase::CreateTextNode(aDoc, EmptyString());
4745 NS_ENSURE_STATE(mHTMLEditor);
4746 nsresult rv =
4747 mHTMLEditor->InsertNode(*newNode, EditorRawDOMPoint(node, offset));
4748 NS_ENSURE_SUCCESS(rv, rv);
4749 node = newNode;
4750 offset = 0;
4751 weDidSomething = true;
4752
4753 if (relFontSize) {
4754 // dir indicated bigger versus smaller. 1 = bigger, -1 = smaller
4755 HTMLEditor::FontSize dir = relFontSize > 0 ? HTMLEditor::FontSize::incr
4756 : HTMLEditor::FontSize::decr;
4757 for (int32_t j = 0; j < DeprecatedAbs(relFontSize); j++) {
4758 NS_ENSURE_STATE(mHTMLEditor);
4759 rv = mHTMLEditor->RelativeFontChangeOnTextNode(dir, newNode, 0, -1);
4760 NS_ENSURE_SUCCESS(rv, rv);
4761 }
4762 }
4763
4764 while (item) {
4765 NS_ENSURE_STATE(mHTMLEditor);
4766 rv = mHTMLEditor->SetInlinePropertyOnNode(*node->AsContent(), *item->tag,
4767 item->attr, item->value);
4768 NS_ENSURE_SUCCESS(rv, rv);
4769 item = mHTMLEditor->mTypeInState->TakeSetProperty();
4770 }
4771 }
4772 if (weDidSomething) {
4773 return aSelection.Collapse(node, offset);
4774 }
4775
4776 return NS_OK;
4777 }
4778
IsEmptyBlockElement(Element & aElement,IgnoreSingleBR aIgnoreSingleBR)4779 bool HTMLEditRules::IsEmptyBlockElement(Element& aElement,
4780 IgnoreSingleBR aIgnoreSingleBR) {
4781 if (NS_WARN_IF(!IsBlockNode(aElement))) {
4782 return false;
4783 }
4784 bool isEmpty = true;
4785 nsresult rv = mHTMLEditor->IsEmptyNode(
4786 &aElement, &isEmpty, aIgnoreSingleBR == IgnoreSingleBR::eYes);
4787 if (NS_WARN_IF(NS_FAILED(rv))) {
4788 return false;
4789 }
4790 return isEmpty;
4791 }
4792
WillAlign(Selection & aSelection,const nsAString & aAlignType,bool * aCancel,bool * aHandled)4793 nsresult HTMLEditRules::WillAlign(Selection& aSelection,
4794 const nsAString& aAlignType, bool* aCancel,
4795 bool* aHandled) {
4796 MOZ_ASSERT(aCancel && aHandled);
4797
4798 NS_ENSURE_STATE(mHTMLEditor);
4799 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
4800
4801 WillInsert(aSelection, aCancel);
4802
4803 // Initialize out param. We want to ignore result of WillInsert().
4804 *aCancel = false;
4805 *aHandled = false;
4806
4807 nsresult rv = NormalizeSelection(&aSelection);
4808 NS_ENSURE_SUCCESS(rv, rv);
4809 AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);
4810
4811 // Convert the selection ranges into "promoted" selection ranges: This
4812 // basically just expands the range to include the immediate block parent,
4813 // and then further expands to include any ancestors whose children are all
4814 // in the range
4815 *aHandled = true;
4816 nsTArray<OwningNonNull<nsINode>> nodeArray;
4817 rv = GetNodesFromSelection(aSelection, EditAction::align, nodeArray);
4818 NS_ENSURE_SUCCESS(rv, rv);
4819
4820 // If we don't have any nodes, or we have only a single br, then we are
4821 // creating an empty alignment div. We have to do some different things for
4822 // these.
4823 bool emptyDiv = nodeArray.IsEmpty();
4824 if (nodeArray.Length() == 1) {
4825 OwningNonNull<nsINode> node = nodeArray[0];
4826
4827 if (HTMLEditUtils::SupportsAlignAttr(*node)) {
4828 // The node is a table element, an hr, a paragraph, a div or a section
4829 // header; in HTML 4, it can directly carry the ALIGN attribute and we
4830 // don't need to make a div! If we are in CSS mode, all the work is done
4831 // in AlignBlock
4832 rv = AlignBlock(*node->AsElement(), aAlignType, ContentsOnly::yes);
4833 NS_ENSURE_SUCCESS(rv, rv);
4834 return NS_OK;
4835 }
4836
4837 if (TextEditUtils::IsBreak(node)) {
4838 // The special case emptyDiv code (below) that consumes BRs can cause
4839 // tables to split if the start node of the selection is not in a table
4840 // cell or caption, for example parent is a <tr>. Avoid this unnecessary
4841 // splitting if possible by leaving emptyDiv FALSE so that we fall
4842 // through to the normal case alignment code.
4843 //
4844 // XXX: It seems a little error prone for the emptyDiv special case code
4845 // to assume that the start node of the selection is the parent of the
4846 // single node in the nodeArray, as the paragraph above points out. Do we
4847 // rely on the selection start node because of the fact that nodeArray
4848 // can be empty? We should probably revisit this issue. - kin
4849
4850 NS_ENSURE_STATE(aSelection.GetRangeAt(0) &&
4851 aSelection.GetRangeAt(0)->GetStartContainer());
4852 OwningNonNull<nsINode> parent =
4853 *aSelection.GetRangeAt(0)->GetStartContainer();
4854
4855 emptyDiv = !HTMLEditUtils::IsTableElement(parent) ||
4856 HTMLEditUtils::IsTableCellOrCaption(parent);
4857 }
4858 }
4859 if (emptyDiv) {
4860 nsRange* firstRange = aSelection.GetRangeAt(0);
4861 if (NS_WARN_IF(!firstRange)) {
4862 return NS_ERROR_FAILURE;
4863 }
4864
4865 EditorDOMPoint atStartOfSelection(firstRange->StartRef());
4866 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
4867 return NS_ERROR_FAILURE;
4868 }
4869
4870 SplitNodeResult splitNodeResult = MaybeSplitAncestorsForInsert(
4871 *nsGkAtoms::div, atStartOfSelection.AsRaw());
4872 if (NS_WARN_IF(splitNodeResult.Failed())) {
4873 return splitNodeResult.Rv();
4874 }
4875
4876 // Consume a trailing br, if any. This is to keep an alignment from
4877 // creating extra lines, if possible.
4878 nsCOMPtr<nsIContent> brContent = htmlEditor->GetNextEditableHTMLNodeInBlock(
4879 splitNodeResult.SplitPoint());
4880 EditorDOMPoint pointToInsertDiv(splitNodeResult.SplitPoint());
4881 if (brContent && TextEditUtils::IsBreak(brContent)) {
4882 // Making use of html structure... if next node after where we are
4883 // putting our div is not a block, then the br we found is in same block
4884 // we are, so it's safe to consume it.
4885 nsCOMPtr<nsIContent> sibling;
4886 if (pointToInsertDiv.GetChild()) {
4887 sibling = htmlEditor->GetNextHTMLSibling(pointToInsertDiv.GetChild());
4888 }
4889 if (sibling && !IsBlockNode(*sibling)) {
4890 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsertDiv);
4891 rv = htmlEditor->DeleteNode(brContent);
4892 NS_ENSURE_SUCCESS(rv, rv);
4893 }
4894 }
4895 RefPtr<Element> div =
4896 htmlEditor->CreateNode(nsGkAtoms::div, pointToInsertDiv.AsRaw());
4897 NS_ENSURE_STATE(div);
4898 // Remember our new block for postprocessing
4899 mNewBlock = div;
4900 // Set up the alignment on the div, using HTML or CSS
4901 rv = AlignBlock(*div, aAlignType, ContentsOnly::yes);
4902 NS_ENSURE_SUCCESS(rv, rv);
4903 *aHandled = true;
4904 // Put in a moz-br so that it won't get deleted
4905 RefPtr<Element> brElement = CreateMozBR(EditorRawDOMPoint(div, 0));
4906 if (NS_WARN_IF(!brElement)) {
4907 return NS_ERROR_FAILURE;
4908 }
4909 EditorRawDOMPoint atStartOfDiv(div, 0);
4910 ErrorResult error;
4911 aSelection.Collapse(atStartOfDiv, error);
4912 // Don't restore the selection
4913 selectionRestorer.Abort();
4914 if (NS_WARN_IF(error.Failed())) {
4915 return error.StealNSResult();
4916 }
4917 return NS_OK;
4918 }
4919
4920 // Next we detect all the transitions in the array, where a transition
4921 // means that adjacent nodes in the array don't have the same parent.
4922
4923 nsTArray<bool> transitionList;
4924 MakeTransitionList(nodeArray, transitionList);
4925
4926 // Okay, now go through all the nodes and give them an align attrib or put
4927 // them in a div, or whatever is appropriate. Woohoo!
4928
4929 nsCOMPtr<Element> curDiv;
4930 bool useCSS = htmlEditor->IsCSSEnabled();
4931 int32_t indexOfTransitionList = -1;
4932 for (OwningNonNull<nsINode>& curNode : nodeArray) {
4933 ++indexOfTransitionList;
4934
4935 // Ignore all non-editable nodes. Leave them be.
4936 if (!htmlEditor->IsEditable(curNode)) {
4937 continue;
4938 }
4939
4940 // The node is a table element, an hr, a paragraph, a div or a section
4941 // header; in HTML 4, it can directly carry the ALIGN attribute and we
4942 // don't need to nest it, just set the alignment. In CSS, assign the
4943 // corresponding CSS styles in AlignBlock
4944 if (HTMLEditUtils::SupportsAlignAttr(*curNode)) {
4945 rv = AlignBlock(*curNode->AsElement(), aAlignType, ContentsOnly::no);
4946 NS_ENSURE_SUCCESS(rv, rv);
4947 // Clear out curDiv so that we don't put nodes after this one into it
4948 curDiv = nullptr;
4949 continue;
4950 }
4951
4952 EditorDOMPoint atCurNode(curNode);
4953 if (NS_WARN_IF(!atCurNode.IsSet())) {
4954 continue;
4955 }
4956
4957 // Skip insignificant formatting text nodes to prevent unnecessary
4958 // structure splitting!
4959 bool isEmptyTextNode = false;
4960 if (curNode->GetAsText() &&
4961 ((HTMLEditUtils::IsTableElement(atCurNode.GetContainer()) &&
4962 !HTMLEditUtils::IsTableCellOrCaption(*atCurNode.GetContainer())) ||
4963 HTMLEditUtils::IsList(atCurNode.GetContainer()) ||
4964 (NS_SUCCEEDED(htmlEditor->IsEmptyNode(curNode, &isEmptyTextNode)) &&
4965 isEmptyTextNode))) {
4966 continue;
4967 }
4968
4969 // If it's a list item, or a list inside a list, forget any "current" div,
4970 // and instead put divs inside the appropriate block (td, li, etc.)
4971 if (HTMLEditUtils::IsListItem(curNode) || HTMLEditUtils::IsList(curNode)) {
4972 AutoEditorDOMPointOffsetInvalidator lockChild(atCurNode);
4973 rv = RemoveAlignment(*curNode, aAlignType, true);
4974 NS_ENSURE_SUCCESS(rv, rv);
4975 if (useCSS) {
4976 htmlEditor->mCSSEditUtils->SetCSSEquivalentToHTMLStyle(
4977 curNode->AsElement(), nullptr, nsGkAtoms::align, &aAlignType,
4978 false);
4979 curDiv = nullptr;
4980 continue;
4981 }
4982 if (HTMLEditUtils::IsList(atCurNode.GetContainer())) {
4983 // If we don't use CSS, add a contraint to list element: they have to
4984 // be inside another list, i.e., >= second level of nesting
4985 rv = AlignInnerBlocks(*curNode, &aAlignType);
4986 NS_ENSURE_SUCCESS(rv, rv);
4987 curDiv = nullptr;
4988 continue;
4989 }
4990 // Clear out curDiv so that we don't put nodes after this one into it
4991 }
4992
4993 // Need to make a div to put things in if we haven't already, or if this
4994 // node doesn't go in div we used earlier.
4995 if (!curDiv || transitionList[indexOfTransitionList]) {
4996 // First, check that our element can contain a div.
4997 if (!htmlEditor->CanContainTag(*atCurNode.GetContainer(),
4998 *nsGkAtoms::div)) {
4999 // Cancelled
5000 return NS_OK;
5001 }
5002
5003 SplitNodeResult splitNodeResult =
5004 MaybeSplitAncestorsForInsert(*nsGkAtoms::div, atCurNode.AsRaw());
5005 if (NS_WARN_IF(splitNodeResult.Failed())) {
5006 return splitNodeResult.Rv();
5007 }
5008 curDiv =
5009 htmlEditor->CreateNode(nsGkAtoms::div, splitNodeResult.SplitPoint());
5010 NS_ENSURE_STATE(curDiv);
5011 // Remember our new block for postprocessing
5012 mNewBlock = curDiv;
5013 // Set up the alignment on the div
5014 rv = AlignBlock(*curDiv, aAlignType, ContentsOnly::yes);
5015 }
5016
5017 // Tuck the node into the end of the active div
5018 rv = htmlEditor->MoveNode(curNode->AsContent(), curDiv, -1);
5019 NS_ENSURE_SUCCESS(rv, rv);
5020 }
5021
5022 return NS_OK;
5023 }
5024
5025 /**
5026 * AlignInnerBlocks() aligns inside table cells or list items.
5027 */
AlignInnerBlocks(nsINode & aNode,const nsAString * alignType)5028 nsresult HTMLEditRules::AlignInnerBlocks(nsINode& aNode,
5029 const nsAString* alignType) {
5030 NS_ENSURE_TRUE(alignType, NS_ERROR_NULL_POINTER);
5031
5032 // Gather list of table cells or list items
5033 nsTArray<OwningNonNull<nsINode>> nodeArray;
5034 TableCellAndListItemFunctor functor;
5035 DOMIterator iter(aNode);
5036 iter.AppendList(functor, nodeArray);
5037
5038 // Now that we have the list, align their contents as requested
5039 for (auto& node : nodeArray) {
5040 nsresult rv = AlignBlockContents(GetAsDOMNode(node), alignType);
5041 NS_ENSURE_SUCCESS(rv, rv);
5042 }
5043
5044 return NS_OK;
5045 }
5046
5047 /**
5048 * AlignBlockContents() aligns contents of a block element.
5049 */
AlignBlockContents(nsIDOMNode * aNode,const nsAString * alignType)5050 nsresult HTMLEditRules::AlignBlockContents(nsIDOMNode* aNode,
5051 const nsAString* alignType) {
5052 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
5053 NS_ENSURE_TRUE(node && alignType, NS_ERROR_NULL_POINTER);
5054 nsCOMPtr<nsIContent> firstChild, lastChild;
5055
5056 NS_ENSURE_STATE(mHTMLEditor);
5057 firstChild = mHTMLEditor->GetFirstEditableChild(*node);
5058 NS_ENSURE_STATE(mHTMLEditor);
5059 lastChild = mHTMLEditor->GetLastEditableChild(*node);
5060 if (!firstChild) {
5061 // this cell has no content, nothing to align
5062 } else if (firstChild == lastChild &&
5063 firstChild->IsHTMLElement(nsGkAtoms::div)) {
5064 // the cell already has a div containing all of its content: just
5065 // act on this div.
5066 RefPtr<Element> divElem = firstChild->AsElement();
5067 NS_ENSURE_STATE(mHTMLEditor);
5068 nsresult rv = mHTMLEditor->SetAttributeOrEquivalent(
5069 divElem, nsGkAtoms::align, *alignType, false);
5070 if (NS_WARN_IF(NS_FAILED(rv))) {
5071 return rv;
5072 }
5073 } else {
5074 // else we need to put in a div, set the alignment, and toss in all the
5075 // children
5076 NS_ENSURE_STATE(mHTMLEditor);
5077 EditorRawDOMPoint atStartOfNode(node, 0);
5078 RefPtr<Element> divElem =
5079 mHTMLEditor->CreateNode(nsGkAtoms::div, atStartOfNode);
5080 NS_ENSURE_STATE(divElem);
5081 // set up the alignment on the div
5082 NS_ENSURE_STATE(mHTMLEditor);
5083 nsresult rv = mHTMLEditor->SetAttributeOrEquivalent(
5084 divElem, nsGkAtoms::align, *alignType, false);
5085 if (NS_WARN_IF(NS_FAILED(rv))) {
5086 return rv;
5087 }
5088 // tuck the children into the end of the active div
5089 while (lastChild && (lastChild != divElem)) {
5090 NS_ENSURE_STATE(mHTMLEditor);
5091 nsresult rv = mHTMLEditor->MoveNode(lastChild, divElem, 0);
5092 NS_ENSURE_SUCCESS(rv, rv);
5093 NS_ENSURE_STATE(mHTMLEditor);
5094 lastChild = mHTMLEditor->GetLastEditableChild(*node);
5095 }
5096 }
5097 return NS_OK;
5098 }
5099
5100 /**
5101 * CheckForEmptyBlock() is called by WillDeleteSelection() to detect and handle
5102 * case of deleting from inside an empty block.
5103 */
CheckForEmptyBlock(nsINode * aStartNode,Element * aBodyNode,Selection * aSelection,nsIEditor::EDirection aAction,bool * aHandled)5104 nsresult HTMLEditRules::CheckForEmptyBlock(nsINode* aStartNode,
5105 Element* aBodyNode,
5106 Selection* aSelection,
5107 nsIEditor::EDirection aAction,
5108 bool* aHandled) {
5109 // If the editing host is an inline element, bail out early.
5110 if (aBodyNode && IsInlineNode(*aBodyNode)) {
5111 return NS_OK;
5112 }
5113
5114 if (NS_WARN_IF(!mHTMLEditor)) {
5115 return NS_ERROR_NOT_AVAILABLE;
5116 }
5117 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
5118
5119 // If we are inside an empty block, delete it. Note: do NOT delete table
5120 // elements this way.
5121 nsCOMPtr<Element> block = htmlEditor->GetBlock(*aStartNode);
5122 bool bIsEmptyNode;
5123 nsCOMPtr<Element> emptyBlock;
5124 if (block && block != aBodyNode) {
5125 // Efficiency hack, avoiding IsEmptyNode() call when in body
5126 nsresult rv = htmlEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
5127 NS_ENSURE_SUCCESS(rv, rv);
5128 while (block && bIsEmptyNode && !HTMLEditUtils::IsTableElement(block) &&
5129 block != aBodyNode) {
5130 emptyBlock = block;
5131 block = htmlEditor->GetBlockNodeParent(emptyBlock);
5132 if (block) {
5133 rv = htmlEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
5134 NS_ENSURE_SUCCESS(rv, rv);
5135 }
5136 }
5137 }
5138
5139 if (emptyBlock && emptyBlock->IsEditable()) {
5140 nsCOMPtr<nsINode> blockParent = emptyBlock->GetParentNode();
5141 NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
5142
5143 if (HTMLEditUtils::IsListItem(emptyBlock)) {
5144 // Are we the first list item in the list?
5145 if (htmlEditor->IsFirstEditableChild(emptyBlock)) {
5146 EditorDOMPoint atBlockParent(blockParent);
5147 if (NS_WARN_IF(!atBlockParent.IsSet())) {
5148 return NS_ERROR_FAILURE;
5149 }
5150 // If we are a sublist, skip the br creation
5151 if (!HTMLEditUtils::IsList(atBlockParent.GetContainer())) {
5152 // Create a br before list
5153 RefPtr<Element> br = htmlEditor->CreateBR(atBlockParent.AsRaw());
5154 if (NS_WARN_IF(!br)) {
5155 return NS_ERROR_FAILURE;
5156 }
5157 // Adjust selection to be right before it
5158 ErrorResult error;
5159 aSelection->Collapse(EditorRawDOMPoint(br), error);
5160 if (NS_WARN_IF(error.Failed())) {
5161 return error.StealNSResult();
5162 }
5163 }
5164 // Else just let selection percolate up. We'll adjust it in
5165 // AfterEdit()
5166 }
5167 } else {
5168 if (aAction == nsIEditor::eNext || aAction == nsIEditor::eNextWord ||
5169 aAction == nsIEditor::eToEndOfLine) {
5170 // Move to the start of the next node, if any
5171 EditorRawDOMPoint afterEmptyBlock(emptyBlock);
5172 DebugOnly<bool> advanced = afterEmptyBlock.AdvanceOffset();
5173 NS_WARNING_ASSERTION(
5174 advanced, "Failed to set selection to the after the empty block");
5175 nsCOMPtr<nsIContent> nextNode =
5176 htmlEditor->GetNextNode(afterEmptyBlock);
5177 if (nextNode) {
5178 EditorDOMPoint pt = GetGoodSelPointForNode(*nextNode, aAction);
5179 nsresult rv = aSelection->Collapse(pt.AsRaw());
5180 NS_ENSURE_SUCCESS(rv, rv);
5181 } else {
5182 // Adjust selection to be right after it.
5183 EditorRawDOMPoint afterEmptyBlock(emptyBlock);
5184 if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
5185 return NS_ERROR_FAILURE;
5186 }
5187 nsresult rv = aSelection->Collapse(afterEmptyBlock);
5188 NS_ENSURE_SUCCESS(rv, rv);
5189 }
5190 } else if (aAction == nsIEditor::ePrevious ||
5191 aAction == nsIEditor::ePreviousWord ||
5192 aAction == nsIEditor::eToBeginningOfLine) {
5193 // Move to the end of the previous node
5194 EditorRawDOMPoint atEmptyBlock(emptyBlock);
5195 nsCOMPtr<nsIContent> priorNode =
5196 htmlEditor->GetPreviousEditableNode(atEmptyBlock);
5197 if (priorNode) {
5198 EditorDOMPoint pt = GetGoodSelPointForNode(*priorNode, aAction);
5199 nsresult rv = aSelection->Collapse(pt.AsRaw());
5200 NS_ENSURE_SUCCESS(rv, rv);
5201 } else {
5202 EditorRawDOMPoint afterEmptyBlock(emptyBlock);
5203 if (NS_WARN_IF(!afterEmptyBlock.AdvanceOffset())) {
5204 return NS_ERROR_FAILURE;
5205 }
5206 nsresult rv = aSelection->Collapse(afterEmptyBlock);
5207 NS_ENSURE_SUCCESS(rv, rv);
5208 }
5209 } else if (aAction != nsIEditor::eNone) {
5210 MOZ_CRASH("CheckForEmptyBlock doesn't support this action yet");
5211 }
5212 }
5213 *aHandled = true;
5214 nsresult rv = htmlEditor->DeleteNode(emptyBlock);
5215 NS_ENSURE_SUCCESS(rv, rv);
5216 }
5217 return NS_OK;
5218 }
5219
CheckForInvisibleBR(Element & aBlock,BRLocation aWhere,int32_t aOffset)5220 Element* HTMLEditRules::CheckForInvisibleBR(Element& aBlock, BRLocation aWhere,
5221 int32_t aOffset) {
5222 nsCOMPtr<nsINode> testNode;
5223 int32_t testOffset = 0;
5224
5225 if (aWhere == BRLocation::blockEnd) {
5226 // No block crossing
5227 nsCOMPtr<nsIContent> rightmostNode =
5228 mHTMLEditor->GetRightmostChild(&aBlock, true);
5229
5230 if (!rightmostNode) {
5231 return nullptr;
5232 }
5233
5234 testNode = rightmostNode->GetParentNode();
5235 // Since rightmostNode is always the last child, its index is equal to the
5236 // child count, so instead of ComputeIndexOf() we use the faster
5237 // GetChildCount(), and assert the equivalence below.
5238 testOffset = testNode->GetChildCount();
5239
5240 // Use offset + 1, so last node is included in our evaluation
5241 MOZ_ASSERT(testNode->ComputeIndexOf(rightmostNode) + 1 == testOffset);
5242 } else if (aOffset) {
5243 testNode = &aBlock;
5244 // We'll check everything to the left of the input position
5245 testOffset = aOffset;
5246 } else {
5247 return nullptr;
5248 }
5249
5250 WSRunObject wsTester(mHTMLEditor, testNode, testOffset);
5251 if (WSType::br == wsTester.mStartReason) {
5252 return wsTester.mStartReasonNode->AsElement();
5253 }
5254
5255 return nullptr;
5256 }
5257
5258 /**
5259 * aLists and aTables allow the caller to specify what kind of content to
5260 * "look inside". If aTables is Tables::yes, look inside any table content,
5261 * and insert the inner content into the supplied nsTArray at offset
5262 * aIndex. Similarly with aLists and list content. aIndex is updated to
5263 * point past inserted elements.
5264 */
GetInnerContent(nsINode & aNode,nsTArray<OwningNonNull<nsINode>> & aOutArrayOfNodes,int32_t * aIndex,Lists aLists,Tables aTables)5265 void HTMLEditRules::GetInnerContent(
5266 nsINode& aNode, nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
5267 int32_t* aIndex, Lists aLists, Tables aTables) {
5268 MOZ_ASSERT(aIndex);
5269
5270 for (nsCOMPtr<nsIContent> node = mHTMLEditor->GetFirstEditableChild(aNode);
5271 node; node = node->GetNextSibling()) {
5272 if ((aLists == Lists::yes &&
5273 (HTMLEditUtils::IsList(node) || HTMLEditUtils::IsListItem(node))) ||
5274 (aTables == Tables::yes && HTMLEditUtils::IsTableElement(node))) {
5275 GetInnerContent(*node, aOutArrayOfNodes, aIndex, aLists, aTables);
5276 } else {
5277 aOutArrayOfNodes.InsertElementAt(*aIndex, *node);
5278 (*aIndex)++;
5279 }
5280 }
5281 }
5282
5283 /**
5284 * Promotes selection to include blocks that have all their children selected.
5285 */
ExpandSelectionForDeletion(Selection & aSelection)5286 nsresult HTMLEditRules::ExpandSelectionForDeletion(Selection& aSelection) {
5287 // Don't need to touch collapsed selections
5288 if (aSelection.Collapsed()) {
5289 return NS_OK;
5290 }
5291
5292 // We don't need to mess with cell selections, and we assume multirange
5293 // selections are those.
5294 if (aSelection.RangeCount() != 1) {
5295 return NS_OK;
5296 }
5297
5298 // Find current sel start and end
5299 NS_ENSURE_TRUE(aSelection.GetRangeAt(0), NS_ERROR_NULL_POINTER);
5300 OwningNonNull<nsRange> range = *aSelection.GetRangeAt(0);
5301
5302 nsCOMPtr<nsINode> selStartNode = range->GetStartContainer();
5303 int32_t selStartOffset = range->StartOffset();
5304 nsCOMPtr<nsINode> selEndNode = range->GetEndContainer();
5305 int32_t selEndOffset = range->EndOffset();
5306
5307 // Find current selection common block parent
5308 nsCOMPtr<Element> selCommon =
5309 HTMLEditor::GetBlock(*range->GetCommonAncestor());
5310 NS_ENSURE_STATE(selCommon);
5311
5312 // Set up for loops and cache our root element
5313 nsCOMPtr<nsINode> firstBRParent;
5314 nsCOMPtr<nsINode> unused;
5315 int32_t visOffset = 0, firstBROffset = 0;
5316 WSType wsType;
5317 nsCOMPtr<Element> root = mHTMLEditor->GetActiveEditingHost();
5318 NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
5319
5320 // Find previous visible thingy before start of selection
5321 if (selStartNode != selCommon && selStartNode != root) {
5322 while (true) {
5323 WSRunObject wsObj(mHTMLEditor, selStartNode, selStartOffset);
5324 wsObj.PriorVisibleNode(selStartNode, selStartOffset, address_of(unused),
5325 &visOffset, &wsType);
5326 if (wsType != WSType::thisBlock) {
5327 break;
5328 }
5329 // We want to keep looking up. But stop if we are crossing table
5330 // element boundaries, or if we hit the root.
5331 if (HTMLEditUtils::IsTableElement(wsObj.mStartReasonNode) ||
5332 selCommon == wsObj.mStartReasonNode ||
5333 root == wsObj.mStartReasonNode) {
5334 break;
5335 }
5336 selStartNode = wsObj.mStartReasonNode->GetParentNode();
5337 selStartOffset =
5338 selStartNode ? selStartNode->ComputeIndexOf(wsObj.mStartReasonNode)
5339 : -1;
5340 }
5341 }
5342
5343 // Find next visible thingy after end of selection
5344 if (selEndNode != selCommon && selEndNode != root) {
5345 for (;;) {
5346 WSRunObject wsObj(mHTMLEditor, selEndNode, selEndOffset);
5347 wsObj.NextVisibleNode(selEndNode, selEndOffset, address_of(unused),
5348 &visOffset, &wsType);
5349 if (wsType == WSType::br) {
5350 if (mHTMLEditor->IsVisibleBRElement(wsObj.mEndReasonNode)) {
5351 break;
5352 }
5353 if (!firstBRParent) {
5354 firstBRParent = selEndNode;
5355 firstBROffset = selEndOffset;
5356 }
5357 selEndNode = wsObj.mEndReasonNode->GetParentNode();
5358 selEndOffset =
5359 selEndNode ? selEndNode->ComputeIndexOf(wsObj.mEndReasonNode) + 1
5360 : 0;
5361 } else if (wsType == WSType::thisBlock) {
5362 // We want to keep looking up. But stop if we are crossing table
5363 // element boundaries, or if we hit the root.
5364 if (HTMLEditUtils::IsTableElement(wsObj.mEndReasonNode) ||
5365 selCommon == wsObj.mEndReasonNode || root == wsObj.mEndReasonNode) {
5366 break;
5367 }
5368 selEndNode = wsObj.mEndReasonNode->GetParentNode();
5369 selEndOffset = 1 + selEndNode->ComputeIndexOf(wsObj.mEndReasonNode);
5370 } else {
5371 break;
5372 }
5373 }
5374 }
5375 // Now set the selection to the new range
5376 aSelection.Collapse(selStartNode, selStartOffset);
5377
5378 // Expand selection endpoint only if we didn't pass a br, or if we really
5379 // needed to pass that br (i.e., its block is now totally selected)
5380 bool doEndExpansion = true;
5381 if (firstBRParent) {
5382 // Find block node containing br
5383 nsCOMPtr<Element> brBlock = HTMLEditor::GetBlock(*firstBRParent);
5384 bool nodeBefore = false, nodeAfter = false;
5385
5386 // Create a range that represents expanded selection
5387 RefPtr<nsRange> range = new nsRange(selStartNode);
5388 nsresult rv = range->SetStartAndEnd(selStartNode, selStartOffset,
5389 selEndNode, selEndOffset);
5390 if (NS_WARN_IF(NS_FAILED(rv))) {
5391 return rv;
5392 }
5393
5394 // Check if block is entirely inside range
5395 if (brBlock) {
5396 nsRange::CompareNodeToRange(brBlock, range, &nodeBefore, &nodeAfter);
5397 }
5398
5399 // If block isn't contained, forgo grabbing the br in expanded selection
5400 if (nodeBefore || nodeAfter) {
5401 doEndExpansion = false;
5402 }
5403 }
5404 if (doEndExpansion) {
5405 nsresult rv = aSelection.Extend(selEndNode, selEndOffset);
5406 NS_ENSURE_SUCCESS(rv, rv);
5407 } else {
5408 // Only expand to just before br
5409 nsresult rv = aSelection.Extend(firstBRParent, firstBROffset);
5410 NS_ENSURE_SUCCESS(rv, rv);
5411 }
5412
5413 return NS_OK;
5414 }
5415
5416 /**
5417 * NormalizeSelection() tweaks non-collapsed selections to be more "natural".
5418 * Idea here is to adjust selection endpoint so that they do not cross breaks
5419 * or block boundaries unless something editable beyond that boundary is also
5420 * selected. This adjustment makes it much easier for the various block
5421 * operations to determine what nodes to act on.
5422 */
NormalizeSelection(Selection * inSelection)5423 nsresult HTMLEditRules::NormalizeSelection(Selection* inSelection) {
5424 NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER);
5425
5426 // don't need to touch collapsed selections
5427 if (inSelection->Collapsed()) {
5428 return NS_OK;
5429 }
5430
5431 // we don't need to mess with cell selections, and we assume multirange
5432 // selections are those.
5433 if (inSelection->RangeCount() != 1) {
5434 return NS_OK;
5435 }
5436
5437 if (NS_WARN_IF(!mHTMLEditor)) {
5438 return NS_ERROR_UNEXPECTED;
5439 }
5440 RefPtr<HTMLEditor> htmlEditor = mHTMLEditor;
5441
5442 RefPtr<nsRange> range = inSelection->GetRangeAt(0);
5443 NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
5444
5445 nsCOMPtr<nsINode> startNode = range->GetStartContainer();
5446 if (NS_WARN_IF(!startNode)) {
5447 return NS_ERROR_FAILURE;
5448 }
5449 nsCOMPtr<nsINode> endNode = range->GetEndContainer();
5450 if (NS_WARN_IF(!endNode)) {
5451 return NS_ERROR_FAILURE;
5452 }
5453 nsIContent* startChild = range->GetChildAtStartOffset();
5454 nsIContent* endChild = range->GetChildAtEndOffset();
5455 uint32_t startOffset = range->StartOffset();
5456 uint32_t endOffset = range->EndOffset();
5457
5458 // adjusted values default to original values
5459 nsCOMPtr<nsINode> newStartNode = startNode;
5460 uint32_t newStartOffset = startOffset;
5461 nsCOMPtr<nsINode> newEndNode = endNode;
5462 uint32_t newEndOffset = endOffset;
5463
5464 // some locals we need for whitespace code
5465 nsCOMPtr<nsINode> unused;
5466 int32_t offset = -1;
5467 WSType wsType;
5468
5469 // let the whitespace code do the heavy lifting
5470 WSRunObject wsEndObj(htmlEditor, endNode, static_cast<int32_t>(endOffset));
5471 // is there any intervening visible whitespace? if so we can't push selection
5472 // past that, it would visibly change maening of users selection
5473 wsEndObj.PriorVisibleNode(endNode, static_cast<int32_t>(endOffset),
5474 address_of(unused), &offset, &wsType);
5475 if (wsType != WSType::text && wsType != WSType::normalWS) {
5476 // eThisBlock and eOtherBlock conveniently distinquish cases
5477 // of going "down" into a block and "up" out of a block.
5478 if (wsEndObj.mStartReason == WSType::otherBlock) {
5479 // endpoint is just after the close of a block.
5480 nsINode* child =
5481 htmlEditor->GetRightmostChild(wsEndObj.mStartReasonNode, true);
5482 if (child) {
5483 int32_t offset = -1;
5484 newEndNode = EditorBase::GetNodeLocation(child, &offset);
5485 // offset *after* child
5486 newEndOffset = static_cast<uint32_t>(offset + 1);
5487 }
5488 // else block is empty - we can leave selection alone here, i think.
5489 } else if (wsEndObj.mStartReason == WSType::thisBlock) {
5490 // endpoint is just after start of this block
5491 EditorRawDOMPoint atEnd(endNode, endChild, endOffset);
5492 nsINode* child = htmlEditor->GetPreviousEditableHTMLNode(atEnd);
5493 if (child) {
5494 int32_t offset = -1;
5495 newEndNode = EditorBase::GetNodeLocation(child, &offset);
5496 // offset *after* child
5497 newEndOffset = static_cast<uint32_t>(offset + 1);
5498 }
5499 // else block is empty - we can leave selection alone here, i think.
5500 } else if (wsEndObj.mStartReason == WSType::br) {
5501 // endpoint is just after break. lets adjust it to before it.
5502 int32_t offset = -1;
5503 newEndNode =
5504 EditorBase::GetNodeLocation(wsEndObj.mStartReasonNode, &offset);
5505 newEndOffset = static_cast<uint32_t>(offset);
5506 ;
5507 }
5508 }
5509
5510 // similar dealio for start of range
5511 WSRunObject wsStartObj(htmlEditor, startNode,
5512 static_cast<int32_t>(startOffset));
5513 // is there any intervening visible whitespace? if so we can't push selection
5514 // past that, it would visibly change maening of users selection
5515 wsStartObj.NextVisibleNode(startNode, static_cast<int32_t>(startOffset),
5516 address_of(unused), &offset, &wsType);
5517 if (wsType != WSType::text && wsType != WSType::normalWS) {
5518 // eThisBlock and eOtherBlock conveniently distinquish cases
5519 // of going "down" into a block and "up" out of a block.
5520 if (wsStartObj.mEndReason == WSType::otherBlock) {
5521 // startpoint is just before the start of a block.
5522 nsINode* child =
5523 htmlEditor->GetLeftmostChild(wsStartObj.mEndReasonNode, true);
5524 if (child) {
5525 int32_t offset = -1;
5526 newStartNode = EditorBase::GetNodeLocation(child, &offset);
5527 newStartOffset = static_cast<uint32_t>(offset);
5528 }
5529 // else block is empty - we can leave selection alone here, i think.
5530 } else if (wsStartObj.mEndReason == WSType::thisBlock) {
5531 // startpoint is just before end of this block
5532 nsINode* child = htmlEditor->GetNextEditableHTMLNode(
5533 EditorRawDOMPoint(startNode, startChild, startOffset));
5534 if (child) {
5535 int32_t offset = -1;
5536 newStartNode = EditorBase::GetNodeLocation(child, &offset);
5537 newStartOffset = static_cast<uint32_t>(offset);
5538 }
5539 // else block is empty - we can leave selection alone here, i think.
5540 } else if (wsStartObj.mEndReason == WSType::br) {
5541 // startpoint is just before a break. lets adjust it to after it.
5542 int32_t offset = -1;
5543 newStartNode =
5544 EditorBase::GetNodeLocation(wsStartObj.mEndReasonNode, &offset);
5545 // offset *after* break
5546 newStartOffset = static_cast<uint32_t>(offset + 1);
5547 }
5548 }
5549
5550 // there is a demented possiblity we have to check for. We might have a very
5551 // strange selection that is not collapsed and yet does not contain any
5552 // editable content, and satisfies some of the above conditions that cause
5553 // tweaking. In this case we don't want to tweak the selection into a block
5554 // it was never in, etc. There are a variety of strategies one might use to
5555 // try to detect these cases, but I think the most straightforward is to see
5556 // if the adjusted locations "cross" the old values: ie, new end before old
5557 // start, or new start after old end. If so then just leave things alone.
5558
5559 int16_t comp;
5560 comp = nsContentUtils::ComparePoints(startNode, startOffset, newEndNode,
5561 newEndOffset);
5562 if (comp == 1) {
5563 return NS_OK; // New end before old start.
5564 }
5565 comp = nsContentUtils::ComparePoints(newStartNode, newStartOffset, endNode,
5566 endOffset);
5567 if (comp == 1) {
5568 return NS_OK; // New start after old end.
5569 }
5570
5571 // otherwise set selection to new values.
5572 inSelection->Collapse(newStartNode, newStartOffset);
5573 inSelection->Extend(newEndNode, newEndOffset);
5574 return NS_OK;
5575 }
5576
5577 /**
5578 * GetPromotedPoint() figures out where a start or end point for a block
5579 * operation really is.
5580 */
GetPromotedPoint(RulesEndpoint aWhere,nsINode & aNode,int32_t aOffset,EditAction actionID)5581 EditorDOMPoint HTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere,
5582 nsINode& aNode, int32_t aOffset,
5583 EditAction actionID) {
5584 if (NS_WARN_IF(!mHTMLEditor)) {
5585 return EditorDOMPoint(&aNode, aOffset);
5586 }
5587 RefPtr<HTMLEditor> htmlEditor = mHTMLEditor;
5588
5589 // we do one thing for text actions, something else entirely for other
5590 // actions
5591 if (actionID == EditAction::insertText ||
5592 actionID == EditAction::insertIMEText ||
5593 actionID == EditAction::insertBreak ||
5594 actionID == EditAction::deleteText) {
5595 bool isSpace, isNBSP;
5596 nsCOMPtr<nsIContent> content =
5597 aNode.IsContent() ? aNode.AsContent() : nullptr;
5598 nsCOMPtr<nsIContent> temp;
5599 int32_t newOffset = aOffset;
5600 // for text actions, we want to look backwards (or forwards, as
5601 // appropriate) for additional whitespace or nbsp's. We may have to act on
5602 // these later even though they are outside of the initial selection. Even
5603 // if they are in another node!
5604 while (content) {
5605 int32_t offset;
5606 if (aWhere == kStart) {
5607 htmlEditor->IsPrevCharInNodeWhitespace(content, newOffset, &isSpace,
5608 &isNBSP, getter_AddRefs(temp),
5609 &offset);
5610 } else {
5611 htmlEditor->IsNextCharInNodeWhitespace(content, newOffset, &isSpace,
5612 &isNBSP, getter_AddRefs(temp),
5613 &offset);
5614 }
5615 if (isSpace || isNBSP) {
5616 content = temp;
5617 newOffset = offset;
5618 } else {
5619 break;
5620 }
5621 }
5622
5623 return EditorDOMPoint(content, newOffset);
5624 }
5625
5626 EditorDOMPoint point(&aNode, aOffset);
5627
5628 // else not a text section. In this case we want to see if we should grab
5629 // any adjacent inline nodes and/or parents and other ancestors
5630 if (aWhere == kStart) {
5631 // some special casing for text nodes
5632 if (point.IsInTextNode()) {
5633 if (!point.GetContainer()->GetParentNode()) {
5634 // Okay, can't promote any further
5635 return point;
5636 }
5637 point.Set(point.GetContainer());
5638 }
5639
5640 // look back through any further inline nodes that aren't across a <br>
5641 // from us, and that are enclosed in the same block.
5642 nsCOMPtr<nsINode> priorNode =
5643 htmlEditor->GetPreviousEditableHTMLNodeInBlock(point.AsRaw());
5644
5645 while (priorNode && priorNode->GetParentNode() &&
5646 !htmlEditor->IsVisibleBRElement(priorNode) &&
5647 !IsBlockNode(*priorNode)) {
5648 point.Set(priorNode);
5649 priorNode = htmlEditor->GetPreviousEditableHTMLNodeInBlock(point.AsRaw());
5650 }
5651
5652 // finding the real start for this point. look up the tree for as long as
5653 // we are the first node in the container, and as long as we haven't hit
5654 // the body node.
5655 nsCOMPtr<nsIContent> nearNode =
5656 htmlEditor->GetPreviousEditableHTMLNodeInBlock(point.AsRaw());
5657 while (!nearNode && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
5658 point.GetContainer()->GetParentNode()) {
5659 // some cutoffs are here: we don't need to also include them in the
5660 // aWhere == kEnd case. as long as they are in one or the other it will
5661 // work. special case for outdent: don't keep looking up if we have
5662 // found a blockquote element to act on
5663 if (actionID == EditAction::outdent &&
5664 point.IsContainerHTMLElement(nsGkAtoms::blockquote)) {
5665 break;
5666 }
5667
5668 // Don't walk past the editable section. Note that we need to check
5669 // before walking up to a parent because we need to return the parent
5670 // object, so the parent itself might not be in the editable area, but
5671 // it's OK if we're not performing a block-level action.
5672 bool blockLevelAction = actionID == EditAction::indent ||
5673 actionID == EditAction::outdent ||
5674 actionID == EditAction::align ||
5675 actionID == EditAction::makeBasicBlock;
5676 if (!htmlEditor->IsDescendantOfEditorRoot(
5677 point.GetContainer()->GetParentNode()) &&
5678 (blockLevelAction ||
5679 !htmlEditor->IsDescendantOfEditorRoot(point.GetContainer()))) {
5680 break;
5681 }
5682
5683 point.Set(point.GetContainer());
5684 nearNode = htmlEditor->GetPreviousEditableHTMLNodeInBlock(point.AsRaw());
5685 }
5686 return point;
5687 }
5688
5689 // aWhere == kEnd
5690 // some special casing for text nodes
5691 if (point.IsInTextNode()) {
5692 if (!point.GetContainer()->GetParentNode()) {
5693 // Okay, can't promote any further
5694 return point;
5695 }
5696 // want to be after the text node
5697 point.Set(point.GetContainer());
5698 DebugOnly<bool> advanced = point.AdvanceOffset();
5699 NS_WARNING_ASSERTION(advanced,
5700 "Failed to advance offset to after the text node");
5701 }
5702
5703 // look ahead through any further inline nodes that aren't across a <br> from
5704 // us, and that are enclosed in the same block.
5705 // XXX Currently, we stop block-extending when finding visible <br> element.
5706 // This might be different from "block-extend" of execCommand spec.
5707 // However, the spec is really unclear.
5708 // XXX Probably, scanning only editable nodes is wrong for
5709 // EditAction::makeBasicBlock because it might be better to wrap existing
5710 // inline elements even if it's non-editable. For example, following
5711 // examples with insertParagraph causes different result:
5712 // * <div contenteditable>foo[]<b contenteditable="false">bar</b></div>
5713 // * <div contenteditable>foo[]<b>bar</b></div>
5714 // * <div contenteditable>foo[]<b contenteditable="false">bar</b>baz</div>
5715 // Only in the first case, after the caret position isn't wrapped with
5716 // new <div> element.
5717 nsCOMPtr<nsIContent> nextNode =
5718 htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
5719
5720 while (nextNode && !IsBlockNode(*nextNode) && nextNode->GetParentNode()) {
5721 point.Set(nextNode);
5722 if (NS_WARN_IF(!point.AdvanceOffset())) {
5723 break;
5724 }
5725 if (htmlEditor->IsVisibleBRElement(nextNode)) {
5726 break;
5727 }
5728
5729 // Check for newlines in pre-formatted text nodes.
5730 bool isPRE;
5731 htmlEditor->IsPreformatted(nextNode->AsDOMNode(), &isPRE);
5732 if (isPRE) {
5733 if (EditorBase::IsTextNode(nextNode)) {
5734 nsAutoString tempString;
5735 nextNode->GetAsText()->GetData(tempString);
5736 int32_t newlinePos = tempString.FindChar(nsCRT::LF);
5737 if (newlinePos >= 0) {
5738 if (static_cast<uint32_t>(newlinePos) + 1 == tempString.Length()) {
5739 // No need for special processing if the newline is at the end.
5740 break;
5741 }
5742 return EditorDOMPoint(nextNode, newlinePos + 1);
5743 }
5744 }
5745 }
5746 nextNode = htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
5747 }
5748
5749 // finding the real end for this point. look up the tree for as long as we
5750 // are the last node in the container, and as long as we haven't hit the body
5751 // node.
5752 nsCOMPtr<nsIContent> nearNode =
5753 htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
5754 while (!nearNode && !point.IsContainerHTMLElement(nsGkAtoms::body) &&
5755 point.GetContainer()->GetParentNode()) {
5756 // Don't walk past the editable section. Note that we need to check before
5757 // walking up to a parent because we need to return the parent object, so
5758 // the parent itself might not be in the editable area, but it's OK.
5759 if (!htmlEditor->IsDescendantOfEditorRoot(point.GetContainer()) &&
5760 !htmlEditor->IsDescendantOfEditorRoot(
5761 point.GetContainer()->GetParentNode())) {
5762 break;
5763 }
5764
5765 point.Set(point.GetContainer());
5766 if (NS_WARN_IF(!point.AdvanceOffset())) {
5767 break;
5768 }
5769 nearNode = htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
5770 }
5771 return point;
5772 }
5773
5774 /**
5775 * GetPromotedRanges() runs all the selection range endpoint through
5776 * GetPromotedPoint().
5777 */
GetPromotedRanges(Selection & aSelection,nsTArray<RefPtr<nsRange>> & outArrayOfRanges,EditAction inOperationType)5778 void HTMLEditRules::GetPromotedRanges(
5779 Selection& aSelection, nsTArray<RefPtr<nsRange>>& outArrayOfRanges,
5780 EditAction inOperationType) {
5781 uint32_t rangeCount = aSelection.RangeCount();
5782
5783 for (uint32_t i = 0; i < rangeCount; i++) {
5784 RefPtr<nsRange> selectionRange = aSelection.GetRangeAt(i);
5785 MOZ_ASSERT(selectionRange);
5786
5787 // Clone range so we don't muck with actual selection ranges
5788 RefPtr<nsRange> opRange = selectionRange->CloneRange();
5789
5790 // Make a new adjusted range to represent the appropriate block content.
5791 // The basic idea is to push out the range endpoints to truly enclose the
5792 // blocks that we will affect. This call alters opRange.
5793 PromoteRange(*opRange, inOperationType);
5794
5795 // Stuff new opRange into array
5796 outArrayOfRanges.AppendElement(opRange);
5797 }
5798 }
5799
5800 /**
5801 * PromoteRange() expands a range to include any parents for which all editable
5802 * children are already in range.
5803 */
PromoteRange(nsRange & aRange,EditAction aOperationType)5804 void HTMLEditRules::PromoteRange(nsRange& aRange, EditAction aOperationType) {
5805 NS_ENSURE_TRUE(mHTMLEditor, );
5806 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
5807
5808 if (!aRange.IsPositioned()) {
5809 return;
5810 }
5811
5812 nsCOMPtr<nsINode> startNode = aRange.GetStartContainer();
5813 nsCOMPtr<nsINode> endNode = aRange.GetEndContainer();
5814 int32_t startOffset = aRange.StartOffset();
5815 int32_t endOffset = aRange.EndOffset();
5816
5817 // MOOSE major hack:
5818 // GetPromotedPoint doesn't really do the right thing for collapsed ranges
5819 // inside block elements that contain nothing but a solo <br>. It's easier
5820 // to put a workaround here than to revamp GetPromotedPoint. :-(
5821 if (startNode == endNode && startOffset == endOffset) {
5822 nsCOMPtr<Element> block = htmlEditor->GetBlock(*startNode);
5823 if (block) {
5824 bool bIsEmptyNode = false;
5825 nsCOMPtr<nsIContent> root = htmlEditor->GetActiveEditingHost();
5826 // Make sure we don't go higher than our root element in the content tree
5827 NS_ENSURE_TRUE(root, );
5828 if (!nsContentUtils::ContentIsDescendantOf(root, block)) {
5829 htmlEditor->IsEmptyNode(block, &bIsEmptyNode, true, false);
5830 }
5831 if (bIsEmptyNode) {
5832 startNode = block;
5833 endNode = block;
5834 startOffset = 0;
5835 endOffset = block->Length();
5836 }
5837 }
5838 }
5839
5840 if (aOperationType == EditAction::insertText ||
5841 aOperationType == EditAction::insertIMEText ||
5842 aOperationType == EditAction::insertBreak ||
5843 aOperationType == EditAction::deleteText) {
5844 if (!startNode->IsContent() || !endNode->IsContent()) {
5845 // GetPromotedPoint cannot promote node when action type is text
5846 // operation and selected node isn't content node.
5847 return;
5848 }
5849 }
5850
5851 // Make a new adjusted range to represent the appropriate block content.
5852 // This is tricky. The basic idea is to push out the range endpoints to
5853 // truly enclose the blocks that we will affect.
5854
5855 // Make sure that the new range ends up to be in the editable section.
5856 // XXX Looks like that this check wastes the time. Perhaps, we should
5857 // implement a method which checks both two DOM points in the editor
5858 // root.
5859 EditorDOMPoint startPoint =
5860 GetPromotedPoint(kStart, *startNode, startOffset, aOperationType);
5861 if (!htmlEditor->IsDescendantOfEditorRoot(
5862 EditorBase::GetNodeAtRangeOffsetPoint(startPoint.AsRaw()))) {
5863 return;
5864 }
5865 EditorDOMPoint endPoint =
5866 GetPromotedPoint(kEnd, *endNode, endOffset, aOperationType);
5867 EditorRawDOMPoint lastRawPoint(endPoint.AsRaw());
5868 lastRawPoint.RewindOffset();
5869 if (!htmlEditor->IsDescendantOfEditorRoot(
5870 EditorBase::GetNodeAtRangeOffsetPoint(lastRawPoint))) {
5871 return;
5872 }
5873
5874 DebugOnly<nsresult> rv =
5875 aRange.SetStartAndEnd(startPoint.AsRaw(), endPoint.AsRaw());
5876 MOZ_ASSERT(NS_SUCCEEDED(rv));
5877 }
5878
5879 class UniqueFunctor final : public BoolDomIterFunctor {
5880 public:
UniqueFunctor(nsTArray<OwningNonNull<nsINode>> & aArray)5881 explicit UniqueFunctor(nsTArray<OwningNonNull<nsINode>>& aArray)
5882 : mArray(aArray) {}
5883
5884 // Used to build list of all nodes iterator covers.
operator ()(nsINode * aNode) const5885 virtual bool operator()(nsINode* aNode) const override {
5886 return !mArray.Contains(aNode);
5887 }
5888
5889 private:
5890 nsTArray<OwningNonNull<nsINode>>& mArray;
5891 };
5892
5893 /**
5894 * GetNodesForOperation() runs through the ranges in the array and construct a
5895 * new array of nodes to be acted on.
5896 */
GetNodesForOperation(nsTArray<RefPtr<nsRange>> & aArrayOfRanges,nsTArray<OwningNonNull<nsINode>> & aOutArrayOfNodes,EditAction aOperationType,TouchContent aTouchContent)5897 nsresult HTMLEditRules::GetNodesForOperation(
5898 nsTArray<RefPtr<nsRange>>& aArrayOfRanges,
5899 nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes,
5900 EditAction aOperationType, TouchContent aTouchContent) {
5901 NS_ENSURE_STATE(mHTMLEditor);
5902 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
5903
5904 if (aTouchContent == TouchContent::yes) {
5905 // Split text nodes. This is necessary, since GetPromotedPoint() may return
5906 // a range ending in a text node in case where part of a pre-formatted
5907 // elements needs to be moved.
5908 for (RefPtr<nsRange>& range : aArrayOfRanges) {
5909 EditorDOMPoint atEnd(range->EndRef());
5910 if (NS_WARN_IF(!atEnd.IsSet()) || !atEnd.IsInTextNode()) {
5911 continue;
5912 }
5913
5914 if (!atEnd.IsStartOfContainer() && !atEnd.IsEndOfContainer()) {
5915 // Split the text node.
5916 ErrorResult error;
5917 nsCOMPtr<nsIContent> newLeftNode =
5918 htmlEditor->SplitNode(atEnd.AsRaw(), error);
5919 if (NS_WARN_IF(error.Failed())) {
5920 return error.StealNSResult();
5921 }
5922
5923 // Correct the range.
5924 // The new end parent becomes the parent node of the text.
5925 // XXX We want nsRange::SetEnd(const RawRangeBoundary&)
5926 EditorRawDOMPoint atContainerOfSplitNode(atEnd.GetContainer());
5927 range->SetEnd(atContainerOfSplitNode, error);
5928 if (NS_WARN_IF(error.Failed())) {
5929 error.SuppressException();
5930 }
5931 }
5932 }
5933 }
5934
5935 // Bust up any inlines that cross our range endpoints, but only if we are
5936 // allowed to touch content.
5937
5938 if (aTouchContent == TouchContent::yes) {
5939 nsTArray<OwningNonNull<RangeItem>> rangeItemArray;
5940 rangeItemArray.AppendElements(aArrayOfRanges.Length());
5941
5942 // First register ranges for special editor gravity
5943 for (auto& rangeItem : rangeItemArray) {
5944 rangeItem = new RangeItem();
5945 rangeItem->StoreRange(aArrayOfRanges[0]);
5946 htmlEditor->mRangeUpdater.RegisterRangeItem(rangeItem);
5947 aArrayOfRanges.RemoveElementAt(0);
5948 }
5949 // Now bust up inlines.
5950 nsresult rv = NS_OK;
5951 for (auto& item : Reversed(rangeItemArray)) {
5952 rv = BustUpInlinesAtRangeEndpoints(*item);
5953 if (NS_FAILED(rv)) {
5954 break;
5955 }
5956 }
5957 // Then unregister the ranges
5958 for (auto& item : rangeItemArray) {
5959 htmlEditor->mRangeUpdater.DropRangeItem(item);
5960 RefPtr<nsRange> range = item->GetRange();
5961 if (range) {
5962 aArrayOfRanges.AppendElement(range);
5963 }
5964 }
5965 NS_ENSURE_SUCCESS(rv, rv);
5966 }
5967 // Gather up a list of all the nodes
5968 for (auto& range : aArrayOfRanges) {
5969 DOMSubtreeIterator iter;
5970 nsresult rv = iter.Init(*range);
5971 NS_ENSURE_SUCCESS(rv, rv);
5972 if (aOutArrayOfNodes.IsEmpty()) {
5973 iter.AppendList(TrivialFunctor(), aOutArrayOfNodes);
5974 } else {
5975 // We don't want duplicates in aOutArrayOfNodes, so we use an
5976 // iterator/functor that only return nodes that are not already in
5977 // aOutArrayOfNodes.
5978 nsTArray<OwningNonNull<nsINode>> nodes;
5979 iter.AppendList(UniqueFunctor(aOutArrayOfNodes), nodes);
5980 aOutArrayOfNodes.AppendElements(nodes);
5981 }
5982 }
5983
5984 // Certain operations should not act on li's and td's, but rather inside
5985 // them. Alter the list as needed.
5986 if (aOperationType == EditAction::makeBasicBlock) {
5987 for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
5988 OwningNonNull<nsINode> node = aOutArrayOfNodes[i];
5989 if (HTMLEditUtils::IsListItem(node)) {
5990 int32_t j = i;
5991 aOutArrayOfNodes.RemoveElementAt(i);
5992 GetInnerContent(*node, aOutArrayOfNodes, &j);
5993 }
5994 }
5995 // Empty text node shouldn't be selected if unnecessary
5996 for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
5997 if (Text* text = aOutArrayOfNodes[i]->GetAsText()) {
5998 // Don't select empty text except to empty block
5999 if (!htmlEditor->IsVisibleTextNode(*text)) {
6000 aOutArrayOfNodes.RemoveElementAt(i);
6001 }
6002 }
6003 }
6004 }
6005 // Indent/outdent already do something special for list items, but we still
6006 // need to make sure we don't act on table elements
6007 else if (aOperationType == EditAction::outdent ||
6008 aOperationType == EditAction::indent ||
6009 aOperationType == EditAction::setAbsolutePosition) {
6010 for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
6011 OwningNonNull<nsINode> node = aOutArrayOfNodes[i];
6012 if (HTMLEditUtils::IsTableElementButNotTable(node)) {
6013 int32_t j = i;
6014 aOutArrayOfNodes.RemoveElementAt(i);
6015 GetInnerContent(*node, aOutArrayOfNodes, &j);
6016 }
6017 }
6018 }
6019 // Outdent should look inside of divs.
6020 if (aOperationType == EditAction::outdent && !htmlEditor->IsCSSEnabled()) {
6021 for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
6022 OwningNonNull<nsINode> node = aOutArrayOfNodes[i];
6023 if (node->IsHTMLElement(nsGkAtoms::div)) {
6024 int32_t j = i;
6025 aOutArrayOfNodes.RemoveElementAt(i);
6026 GetInnerContent(*node, aOutArrayOfNodes, &j, Lists::no, Tables::no);
6027 }
6028 }
6029 }
6030
6031 // Post-process the list to break up inline containers that contain br's, but
6032 // only for operations that might care, like making lists or paragraphs
6033 if (aOperationType == EditAction::makeBasicBlock ||
6034 aOperationType == EditAction::makeList ||
6035 aOperationType == EditAction::align ||
6036 aOperationType == EditAction::setAbsolutePosition ||
6037 aOperationType == EditAction::indent ||
6038 aOperationType == EditAction::outdent) {
6039 for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
6040 OwningNonNull<nsINode> node = aOutArrayOfNodes[i];
6041 if (aTouchContent == TouchContent::yes && IsInlineNode(node) &&
6042 htmlEditor->IsContainer(node) && !EditorBase::IsTextNode(node)) {
6043 nsTArray<OwningNonNull<nsINode>> arrayOfInlines;
6044 nsresult rv = BustUpInlinesAtBRs(*node->AsContent(), arrayOfInlines);
6045 NS_ENSURE_SUCCESS(rv, rv);
6046
6047 // Put these nodes in aOutArrayOfNodes, replacing the current node
6048 aOutArrayOfNodes.RemoveElementAt(i);
6049 aOutArrayOfNodes.InsertElementsAt(i, arrayOfInlines);
6050 }
6051 }
6052 }
6053 return NS_OK;
6054 }
6055
GetChildNodesForOperation(nsINode & aNode,nsTArray<OwningNonNull<nsINode>> & outArrayOfNodes)6056 void HTMLEditRules::GetChildNodesForOperation(
6057 nsINode& aNode, nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes) {
6058 for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild(); child;
6059 child = child->GetNextSibling()) {
6060 outArrayOfNodes.AppendElement(*child);
6061 }
6062 }
6063
GetListActionNodes(nsTArray<OwningNonNull<nsINode>> & aOutArrayOfNodes,EntireList aEntireList,TouchContent aTouchContent)6064 nsresult HTMLEditRules::GetListActionNodes(
6065 nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes, EntireList aEntireList,
6066 TouchContent aTouchContent) {
6067 NS_ENSURE_STATE(mHTMLEditor);
6068 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
6069
6070 RefPtr<Selection> selection = htmlEditor->GetSelection();
6071 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
6072
6073 // Added this in so that ui code can ask to change an entire list, even if
6074 // selection is only in part of it. used by list item dialog.
6075 if (aEntireList == EntireList::yes) {
6076 uint32_t rangeCount = selection->RangeCount();
6077 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
6078 RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
6079 for (nsCOMPtr<nsINode> parent = range->GetCommonAncestor(); parent;
6080 parent = parent->GetParentNode()) {
6081 if (HTMLEditUtils::IsList(parent)) {
6082 aOutArrayOfNodes.AppendElement(*parent);
6083 break;
6084 }
6085 }
6086 }
6087 // If we didn't find any nodes this way, then try the normal way. Perhaps
6088 // the selection spans multiple lists but with no common list parent.
6089 if (!aOutArrayOfNodes.IsEmpty()) {
6090 return NS_OK;
6091 }
6092 }
6093
6094 {
6095 // We don't like other people messing with our selection!
6096 AutoTransactionsConserveSelection dontChangeMySelection(htmlEditor);
6097
6098 // contruct a list of nodes to act on.
6099 nsresult rv = GetNodesFromSelection(*selection, EditAction::makeList,
6100 aOutArrayOfNodes, aTouchContent);
6101 NS_ENSURE_SUCCESS(rv, rv);
6102 }
6103
6104 // Pre-process our list of nodes
6105 for (int32_t i = aOutArrayOfNodes.Length() - 1; i >= 0; i--) {
6106 OwningNonNull<nsINode> testNode = aOutArrayOfNodes[i];
6107
6108 // Remove all non-editable nodes. Leave them be.
6109 if (!htmlEditor->IsEditable(testNode)) {
6110 aOutArrayOfNodes.RemoveElementAt(i);
6111 continue;
6112 }
6113
6114 // Scan for table elements and divs. If we find table elements other than
6115 // table, replace it with a list of any editable non-table content.
6116 if (HTMLEditUtils::IsTableElementButNotTable(testNode)) {
6117 int32_t j = i;
6118 aOutArrayOfNodes.RemoveElementAt(i);
6119 GetInnerContent(*testNode, aOutArrayOfNodes, &j, Lists::no);
6120 }
6121 }
6122
6123 // If there is only one node in the array, and it is a list, div, or
6124 // blockquote, then look inside of it until we find inner list or content.
6125 LookInsideDivBQandList(aOutArrayOfNodes);
6126
6127 return NS_OK;
6128 }
6129
LookInsideDivBQandList(nsTArray<OwningNonNull<nsINode>> & aNodeArray)6130 void HTMLEditRules::LookInsideDivBQandList(
6131 nsTArray<OwningNonNull<nsINode>>& aNodeArray) {
6132 NS_ENSURE_TRUE(mHTMLEditor, );
6133 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
6134
6135 // If there is only one node in the array, and it is a list, div, or
6136 // blockquote, then look inside of it until we find inner list or content.
6137 if (aNodeArray.Length() != 1) {
6138 return;
6139 }
6140
6141 OwningNonNull<nsINode> curNode = aNodeArray[0];
6142
6143 while (curNode->IsHTMLElement(nsGkAtoms::div) ||
6144 HTMLEditUtils::IsList(curNode) ||
6145 curNode->IsHTMLElement(nsGkAtoms::blockquote)) {
6146 // Dive as long as there's only one child, and it's a list, div, blockquote
6147 uint32_t numChildren = htmlEditor->CountEditableChildren(curNode);
6148 if (numChildren != 1) {
6149 break;
6150 }
6151
6152 // Keep diving! XXX One would expect to dive into the one editable node.
6153 nsCOMPtr<nsIContent> child = curNode->GetFirstChild();
6154 if (!child->IsHTMLElement(nsGkAtoms::div) &&
6155 !HTMLEditUtils::IsList(child) &&
6156 !child->IsHTMLElement(nsGkAtoms::blockquote)) {
6157 break;
6158 }
6159
6160 // check editability XXX floppy moose
6161 curNode = child;
6162 }
6163
6164 // We've found innermost list/blockquote/div: replace the one node in the
6165 // array with these nodes
6166 aNodeArray.RemoveElementAt(0);
6167 if (curNode->IsAnyOfHTMLElements(nsGkAtoms::div, nsGkAtoms::blockquote)) {
6168 int32_t j = 0;
6169 GetInnerContent(*curNode, aNodeArray, &j, Lists::no, Tables::no);
6170 return;
6171 }
6172
6173 aNodeArray.AppendElement(*curNode);
6174 }
6175
GetDefinitionListItemTypes(dom::Element * aElement,bool * aDT,bool * aDD)6176 void HTMLEditRules::GetDefinitionListItemTypes(dom::Element* aElement,
6177 bool* aDT, bool* aDD) {
6178 MOZ_ASSERT(aElement);
6179 MOZ_ASSERT(aElement->IsHTMLElement(nsGkAtoms::dl));
6180 MOZ_ASSERT(aDT);
6181 MOZ_ASSERT(aDD);
6182
6183 *aDT = *aDD = false;
6184 for (nsIContent* child = aElement->GetFirstChild(); child;
6185 child = child->GetNextSibling()) {
6186 if (child->IsHTMLElement(nsGkAtoms::dt)) {
6187 *aDT = true;
6188 } else if (child->IsHTMLElement(nsGkAtoms::dd)) {
6189 *aDD = true;
6190 }
6191 }
6192 }
6193
GetParagraphFormatNodes(nsTArray<OwningNonNull<nsINode>> & outArrayOfNodes,TouchContent aTouchContent)6194 nsresult HTMLEditRules::GetParagraphFormatNodes(
6195 nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
6196 TouchContent aTouchContent) {
6197 NS_ENSURE_STATE(mHTMLEditor);
6198 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
6199
6200 RefPtr<Selection> selection = htmlEditor->GetSelection();
6201 NS_ENSURE_STATE(selection);
6202
6203 // Contruct a list of nodes to act on.
6204 nsresult rv = GetNodesFromSelection(*selection, EditAction::makeBasicBlock,
6205 outArrayOfNodes, aTouchContent);
6206 NS_ENSURE_SUCCESS(rv, rv);
6207
6208 // Pre-process our list of nodes
6209 for (int32_t i = outArrayOfNodes.Length() - 1; i >= 0; i--) {
6210 OwningNonNull<nsINode> testNode = outArrayOfNodes[i];
6211
6212 // Remove all non-editable nodes. Leave them be.
6213 if (!htmlEditor->IsEditable(testNode)) {
6214 outArrayOfNodes.RemoveElementAt(i);
6215 continue;
6216 }
6217
6218 // Scan for table elements. If we find table elements other than table,
6219 // replace it with a list of any editable non-table content. Ditto for
6220 // list elements.
6221 if (HTMLEditUtils::IsTableElement(testNode) ||
6222 HTMLEditUtils::IsList(testNode) ||
6223 HTMLEditUtils::IsListItem(testNode)) {
6224 int32_t j = i;
6225 outArrayOfNodes.RemoveElementAt(i);
6226 GetInnerContent(testNode, outArrayOfNodes, &j);
6227 }
6228 }
6229 return NS_OK;
6230 }
6231
BustUpInlinesAtRangeEndpoints(RangeItem & item)6232 nsresult HTMLEditRules::BustUpInlinesAtRangeEndpoints(RangeItem& item) {
6233 if (NS_WARN_IF(!mHTMLEditor)) {
6234 return NS_ERROR_NOT_AVAILABLE;
6235 }
6236
6237 bool isCollapsed = item.mStartContainer == item.mEndContainer &&
6238 item.mStartOffset == item.mEndOffset;
6239
6240 nsCOMPtr<nsIContent> endInline = GetHighestInlineParent(*item.mEndContainer);
6241 if (NS_WARN_IF(!mHTMLEditor)) {
6242 return NS_ERROR_FAILURE;
6243 }
6244
6245 // XXX Oh, then, if the range is collapsed, we don't need to call
6246 // GetHighestInlineParent(), isn't it?
6247 if (endInline && !isCollapsed) {
6248 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
6249 SplitNodeResult splitEndInlineResult = htmlEditor->SplitNodeDeep(
6250 *endInline, EditorRawDOMPoint(item.mEndContainer, item.mEndOffset),
6251 SplitAtEdges::eDoNotCreateEmptyContainer);
6252 if (NS_WARN_IF(splitEndInlineResult.Failed())) {
6253 return splitEndInlineResult.Rv();
6254 }
6255 if (NS_WARN_IF(!mHTMLEditor)) {
6256 return NS_ERROR_FAILURE;
6257 }
6258 EditorRawDOMPoint splitPointAtEnd(splitEndInlineResult.SplitPoint());
6259 item.mEndContainer = splitPointAtEnd.GetContainer();
6260 item.mEndOffset = splitPointAtEnd.Offset();
6261 }
6262
6263 nsCOMPtr<nsIContent> startInline =
6264 GetHighestInlineParent(*item.mStartContainer);
6265 if (NS_WARN_IF(!mHTMLEditor)) {
6266 return NS_ERROR_FAILURE;
6267 }
6268
6269 if (startInline) {
6270 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
6271 SplitNodeResult splitStartInlineResult = htmlEditor->SplitNodeDeep(
6272 *startInline,
6273 EditorRawDOMPoint(item.mStartContainer, item.mStartOffset),
6274 SplitAtEdges::eDoNotCreateEmptyContainer);
6275 if (NS_WARN_IF(splitStartInlineResult.Failed())) {
6276 return splitStartInlineResult.Rv();
6277 }
6278 if (NS_WARN_IF(!mHTMLEditor)) {
6279 return NS_ERROR_FAILURE;
6280 }
6281 EditorRawDOMPoint splitPointAtStart(splitStartInlineResult.SplitPoint());
6282 item.mStartContainer = splitPointAtStart.GetContainer();
6283 item.mStartOffset = splitPointAtStart.Offset();
6284 }
6285
6286 return NS_OK;
6287 }
6288
BustUpInlinesAtBRs(nsIContent & aNode,nsTArray<OwningNonNull<nsINode>> & aOutArrayOfNodes)6289 nsresult HTMLEditRules::BustUpInlinesAtBRs(
6290 nsIContent& aNode, nsTArray<OwningNonNull<nsINode>>& aOutArrayOfNodes) {
6291 NS_ENSURE_STATE(mHTMLEditor);
6292 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
6293
6294 // First build up a list of all the break nodes inside the inline container.
6295 nsTArray<OwningNonNull<nsINode>> arrayOfBreaks;
6296 BRNodeFunctor functor;
6297 DOMIterator iter(aNode);
6298 iter.AppendList(functor, arrayOfBreaks);
6299
6300 // If there aren't any breaks, just put inNode itself in the array
6301 if (arrayOfBreaks.IsEmpty()) {
6302 aOutArrayOfNodes.AppendElement(aNode);
6303 return NS_OK;
6304 }
6305
6306 // Else we need to bust up aNode along all the breaks
6307 nsCOMPtr<nsIContent> nextNode = &aNode;
6308 for (OwningNonNull<nsINode>& brNode : arrayOfBreaks) {
6309 EditorRawDOMPoint atBrNode(brNode);
6310 if (NS_WARN_IF(!atBrNode.IsSet())) {
6311 return NS_ERROR_FAILURE;
6312 }
6313 SplitNodeResult splitNodeResult = htmlEditor->SplitNodeDeep(
6314 *nextNode, atBrNode, SplitAtEdges::eAllowToCreateEmptyContainer);
6315 if (NS_WARN_IF(splitNodeResult.Failed())) {
6316 return splitNodeResult.Rv();
6317 }
6318
6319 // Put previous node at the split point.
6320 if (splitNodeResult.GetPreviousNode()) {
6321 // Might not be a left node. A break might have been at the very
6322 // beginning of inline container, in which case SplitNodeDeep would not
6323 // actually split anything
6324 aOutArrayOfNodes.AppendElement(*splitNodeResult.GetPreviousNode());
6325 }
6326
6327 // Move break outside of container and also put in node list
6328 EditorRawDOMPoint atNextNode(splitNodeResult.GetNextNode());
6329 nsresult rv = htmlEditor->MoveNode(
6330 brNode->AsContent(), atNextNode.GetContainer(), atNextNode.Offset());
6331 NS_ENSURE_SUCCESS(rv, rv);
6332 aOutArrayOfNodes.AppendElement(*brNode);
6333
6334 nextNode = splitNodeResult.GetNextNode();
6335 }
6336
6337 // Now tack on remaining next node.
6338 aOutArrayOfNodes.AppendElement(*nextNode);
6339
6340 return NS_OK;
6341 }
6342
GetHighestInlineParent(nsINode & aNode)6343 nsIContent* HTMLEditRules::GetHighestInlineParent(nsINode& aNode) {
6344 if (!aNode.IsContent() || IsBlockNode(aNode)) {
6345 return nullptr;
6346 }
6347
6348 if (NS_WARN_IF(!mHTMLEditor)) {
6349 return nullptr;
6350 }
6351
6352 Element* host = mHTMLEditor->GetActiveEditingHost();
6353 if (NS_WARN_IF(!host)) {
6354 return nullptr;
6355 }
6356
6357 // If aNode is the editing host itself, there is no modifiable inline parent.
6358 if (&aNode == host) {
6359 return nullptr;
6360 }
6361
6362 // If aNode is outside of the <body> element, we don't support to edit
6363 // such elements for now.
6364 // XXX This should be MOZ_ASSERT after fixing bug 1413131 for avoiding
6365 // calling this expensive method.
6366 if (NS_WARN_IF(!EditorUtils::IsDescendantOf(aNode, *host))) {
6367 return nullptr;
6368 }
6369
6370 // Looks for the highest inline parent in the editing host.
6371 nsIContent* content = aNode.AsContent();
6372 for (nsIContent* parent = content->GetParent();
6373 parent && parent != host && IsInlineNode(*parent);
6374 parent = parent->GetParent()) {
6375 content = parent;
6376 }
6377 return content;
6378 }
6379
6380 /**
6381 * GetNodesFromPoint() constructs a list of nodes from a point that will be
6382 * operated on.
6383 */
GetNodesFromPoint(const EditorDOMPoint & aPoint,EditAction aOperation,nsTArray<OwningNonNull<nsINode>> & outArrayOfNodes,TouchContent aTouchContent)6384 nsresult HTMLEditRules::GetNodesFromPoint(
6385 const EditorDOMPoint& aPoint, EditAction aOperation,
6386 nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
6387 TouchContent aTouchContent) {
6388 if (NS_WARN_IF(!aPoint.IsSet())) {
6389 return NS_ERROR_INVALID_ARG;
6390 }
6391 RefPtr<nsRange> range = new nsRange(aPoint.GetContainer());
6392 ErrorResult error;
6393 range->SetStart(aPoint, error);
6394 // error will assert on failure, because we are not cleaning it up,
6395 // but we're asserting in that case anyway.
6396 MOZ_ASSERT(!error.Failed());
6397
6398 // Expand the range to include adjacent inlines
6399 PromoteRange(*range, aOperation);
6400
6401 // Make array of ranges
6402 nsTArray<RefPtr<nsRange>> arrayOfRanges;
6403
6404 // Stuff new opRange into array
6405 arrayOfRanges.AppendElement(range);
6406
6407 // Use these ranges to contruct a list of nodes to act on
6408 nsresult rv2 = GetNodesForOperation(arrayOfRanges, outArrayOfNodes,
6409 aOperation, aTouchContent);
6410 if (NS_WARN_IF(NS_FAILED(rv2))) {
6411 return rv2;
6412 }
6413
6414 return NS_OK;
6415 }
6416
6417 /**
6418 * GetNodesFromSelection() constructs a list of nodes from the selection that
6419 * will be operated on.
6420 */
GetNodesFromSelection(Selection & aSelection,EditAction aOperation,nsTArray<OwningNonNull<nsINode>> & outArrayOfNodes,TouchContent aTouchContent)6421 nsresult HTMLEditRules::GetNodesFromSelection(
6422 Selection& aSelection, EditAction aOperation,
6423 nsTArray<OwningNonNull<nsINode>>& outArrayOfNodes,
6424 TouchContent aTouchContent) {
6425 // Promote selection ranges
6426 nsTArray<RefPtr<nsRange>> arrayOfRanges;
6427 GetPromotedRanges(aSelection, arrayOfRanges, aOperation);
6428
6429 // Use these ranges to contruct a list of nodes to act on.
6430 nsresult rv = GetNodesForOperation(arrayOfRanges, outArrayOfNodes, aOperation,
6431 aTouchContent);
6432 NS_ENSURE_SUCCESS(rv, rv);
6433
6434 return NS_OK;
6435 }
6436
6437 /**
6438 * MakeTransitionList() detects all the transitions in the array, where a
6439 * transition means that adjacent nodes in the array don't have the same parent.
6440 */
MakeTransitionList(nsTArray<OwningNonNull<nsINode>> & aNodeArray,nsTArray<bool> & aTransitionArray)6441 void HTMLEditRules::MakeTransitionList(
6442 nsTArray<OwningNonNull<nsINode>>& aNodeArray,
6443 nsTArray<bool>& aTransitionArray) {
6444 nsCOMPtr<nsINode> prevParent;
6445
6446 aTransitionArray.EnsureLengthAtLeast(aNodeArray.Length());
6447 for (uint32_t i = 0; i < aNodeArray.Length(); i++) {
6448 if (aNodeArray[i]->GetParentNode() != prevParent) {
6449 // Different parents: transition point
6450 aTransitionArray[i] = true;
6451 } else {
6452 // Same parents: these nodes grew up together
6453 aTransitionArray[i] = false;
6454 }
6455 prevParent = aNodeArray[i]->GetParentNode();
6456 }
6457 }
6458
6459 /**
6460 * If aNode is the descendant of a listitem, return that li. But table element
6461 * boundaries are stoppers on the search. Also stops on the active editor host
6462 * (contenteditable). Also test if aNode is an li itself.
6463 */
IsInListItem(nsINode * aNode)6464 Element* HTMLEditRules::IsInListItem(nsINode* aNode) {
6465 NS_ENSURE_TRUE(aNode, nullptr);
6466 if (HTMLEditUtils::IsListItem(aNode)) {
6467 return aNode->AsElement();
6468 }
6469
6470 Element* parent = aNode->GetParentElement();
6471 while (parent && mHTMLEditor &&
6472 mHTMLEditor->IsDescendantOfEditorRoot(parent) &&
6473 !HTMLEditUtils::IsTableElement(parent)) {
6474 if (HTMLEditUtils::IsListItem(parent)) {
6475 return parent;
6476 }
6477 parent = parent->GetParentElement();
6478 }
6479 return nullptr;
6480 }
6481
DefaultParagraphSeparator()6482 nsAtom& HTMLEditRules::DefaultParagraphSeparator() {
6483 MOZ_ASSERT(mHTMLEditor);
6484 if (!mHTMLEditor) {
6485 return *nsGkAtoms::div;
6486 }
6487 return ParagraphSeparatorElement(mHTMLEditor->GetDefaultParagraphSeparator());
6488 }
6489
6490 /**
6491 * ReturnInHeader: do the right thing for returns pressed in headers
6492 */
ReturnInHeader(Selection & aSelection,Element & aHeader,nsINode & aNode,int32_t aOffset)6493 nsresult HTMLEditRules::ReturnInHeader(Selection& aSelection, Element& aHeader,
6494 nsINode& aNode, int32_t aOffset) {
6495 NS_ENSURE_STATE(mHTMLEditor);
6496 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
6497
6498 // Remember where the header is
6499 nsCOMPtr<nsINode> headerParent = aHeader.GetParentNode();
6500 int32_t offset = headerParent ? headerParent->ComputeIndexOf(&aHeader) : -1;
6501
6502 // Get ws code to adjust any ws
6503 nsCOMPtr<nsINode> node = &aNode;
6504 nsresult rv = WSRunObject::PrepareToSplitAcrossBlocks(
6505 htmlEditor, address_of(node), &aOffset);
6506 NS_ENSURE_SUCCESS(rv, rv);
6507 if (NS_WARN_IF(!node->IsContent())) {
6508 return NS_ERROR_FAILURE;
6509 }
6510
6511 // Split the header
6512 ErrorResult error;
6513 SplitNodeResult splitHeaderResult =
6514 htmlEditor->SplitNodeDeep(aHeader, EditorRawDOMPoint(node, aOffset),
6515 SplitAtEdges::eAllowToCreateEmptyContainer);
6516 NS_WARNING_ASSERTION(splitHeaderResult.Succeeded(),
6517 "Failed to split aHeader");
6518
6519 // If the previous heading of split point is empty, put a mozbr into it.
6520 nsCOMPtr<nsIContent> prevItem = htmlEditor->GetPriorHTMLSibling(&aHeader);
6521 if (prevItem) {
6522 MOZ_DIAGNOSTIC_ASSERT(HTMLEditUtils::IsHeader(*prevItem));
6523 bool isEmptyNode;
6524 rv = htmlEditor->IsEmptyNode(prevItem, &isEmptyNode);
6525 NS_ENSURE_SUCCESS(rv, rv);
6526 if (isEmptyNode) {
6527 RefPtr<Element> brElement = CreateMozBR(EditorRawDOMPoint(prevItem, 0));
6528 if (NS_WARN_IF(!brElement)) {
6529 return NS_ERROR_FAILURE;
6530 }
6531 }
6532 }
6533
6534 // If the new (righthand) header node is empty, delete it
6535 if (IsEmptyBlockElement(aHeader, IgnoreSingleBR::eYes)) {
6536 rv = htmlEditor->DeleteNode(&aHeader);
6537 NS_ENSURE_SUCCESS(rv, rv);
6538 // Layout tells the caret to blink in a weird place if we don't place a
6539 // break after the header.
6540 nsCOMPtr<nsIContent> sibling;
6541 if (aHeader.GetNextSibling()) {
6542 sibling = htmlEditor->GetNextHTMLSibling(aHeader.GetNextSibling());
6543 }
6544 if (!sibling || !sibling->IsHTMLElement(nsGkAtoms::br)) {
6545 ClearCachedStyles();
6546 htmlEditor->mTypeInState->ClearAllProps();
6547
6548 // Create a paragraph
6549 nsAtom& paraAtom = DefaultParagraphSeparator();
6550 // We want a wrapper element even if we separate with <br>
6551 EditorRawDOMPoint nextToHeader(headerParent, offset + 1);
6552 RefPtr<Element> pNode = htmlEditor->CreateNode(
6553 ¶Atom == nsGkAtoms::br ? nsGkAtoms::p : ¶Atom, nextToHeader);
6554 NS_ENSURE_STATE(pNode);
6555
6556 // Append a <br> to it
6557 RefPtr<Element> brNode =
6558 htmlEditor->CreateBR(EditorRawDOMPoint(pNode, 0));
6559 if (NS_WARN_IF(!brNode)) {
6560 return NS_ERROR_FAILURE;
6561 }
6562
6563 // Set selection to before the break
6564 ErrorResult error;
6565 aSelection.Collapse(EditorRawDOMPoint(pNode, 0), error);
6566 if (NS_WARN_IF(error.Failed())) {
6567 return error.StealNSResult();
6568 }
6569 } else {
6570 EditorRawDOMPoint afterSibling(sibling);
6571 if (NS_WARN_IF(!afterSibling.AdvanceOffset())) {
6572 return NS_ERROR_FAILURE;
6573 }
6574 // Put selection after break
6575 rv = aSelection.Collapse(afterSibling);
6576 NS_ENSURE_SUCCESS(rv, rv);
6577 }
6578 } else {
6579 // Put selection at front of righthand heading
6580 rv = aSelection.Collapse(&aHeader, 0);
6581 NS_ENSURE_SUCCESS(rv, rv);
6582 }
6583 return NS_OK;
6584 }
6585
ReturnInParagraph(Selection & aSelection,Element & aParentDivOrP)6586 EditActionResult HTMLEditRules::ReturnInParagraph(Selection& aSelection,
6587 Element& aParentDivOrP) {
6588 if (NS_WARN_IF(!mHTMLEditor)) {
6589 return EditActionResult(NS_ERROR_NOT_AVAILABLE);
6590 }
6591
6592 RefPtr<HTMLEditor> htmlEditor = mHTMLEditor;
6593
6594 nsRange* firstRange = aSelection.GetRangeAt(0);
6595 if (NS_WARN_IF(!firstRange)) {
6596 return EditActionResult(NS_ERROR_FAILURE);
6597 }
6598
6599 EditorDOMPoint atStartOfSelection(firstRange->StartRef());
6600 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
6601 return EditActionResult(NS_ERROR_FAILURE);
6602 }
6603 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
6604
6605 // We shouldn't create new anchor element which has non-empty href unless
6606 // splitting middle of it because we assume that users don't want to create
6607 // *same* anchor element across two or more paragraphs in most cases.
6608 // So, adjust selection start if it's edge of anchor element(s).
6609 // XXX We don't support whitespace collapsing in these cases since it needs
6610 // some additional work with WSRunObject but it's not usual case.
6611 // E.g., |<a href="foo"><b>foo []</b> </a>|
6612 if (atStartOfSelection.IsStartOfContainer()) {
6613 for (nsIContent* container = atStartOfSelection.GetContainerAsContent();
6614 container && container != &aParentDivOrP;
6615 container = container->GetParent()) {
6616 if (HTMLEditUtils::IsLink(container)) {
6617 // Found link should be only in right node. So, we shouldn't split it.
6618 atStartOfSelection.Set(container);
6619 // Even if we found an anchor element, don't break because DOM API
6620 // allows to nest anchor elements.
6621 }
6622 // If the container is middle of its parent, stop adjusting split point.
6623 if (container->GetPreviousSibling()) {
6624 // XXX Should we check if previous sibling is visible content?
6625 // E.g., should we ignore comment node, invisible <br> element?
6626 break;
6627 }
6628 }
6629 }
6630 // We also need to check if selection is at invisible <br> element at end
6631 // of an <a href="foo"> element because editor inserts a <br> element when
6632 // user types Enter key after a whitespace which is at middle of
6633 // <a href="foo"> element and when setting selection at end of the element,
6634 // selection becomes referring the <br> element. We may need to change this
6635 // behavior later if it'd be standardized.
6636 else if (atStartOfSelection.IsEndOfContainer() ||
6637 atStartOfSelection.IsBRElementAtEndOfContainer()) {
6638 // If there are 2 <br> elements, the first <br> element is visible. E.g.,
6639 // |<a href="foo"><b>boo[]<br></b><br></a>|, we should split the <a>
6640 // element. Otherwise, E.g., |<a href="foo"><b>boo[]<br></b></a>|,
6641 // we should not split the <a> element and ignore inline elements in it.
6642 bool foundBRElement = atStartOfSelection.IsBRElementAtEndOfContainer();
6643 for (nsIContent* container = atStartOfSelection.GetContainerAsContent();
6644 container && container != &aParentDivOrP;
6645 container = container->GetParent()) {
6646 if (HTMLEditUtils::IsLink(container)) {
6647 // Found link should be only in left node. So, we shouldn't split it.
6648 atStartOfSelection.SetAfter(container);
6649 // Even if we found an anchor element, don't break because DOM API
6650 // allows to nest anchor elements.
6651 }
6652 // If the container is middle of its parent, stop adjusting split point.
6653 if (nsIContent* nextSibling = container->GetNextSibling()) {
6654 if (foundBRElement) {
6655 // If we've already found a <br> element, we assume found node is
6656 // visible <br> or something other node.
6657 // XXX Should we check if non-text data node like comment?
6658 break;
6659 }
6660
6661 // XXX Should we check if non-text data node like comment?
6662 if (!nextSibling->IsHTMLElement(nsGkAtoms::br)) {
6663 break;
6664 }
6665 foundBRElement = true;
6666 }
6667 }
6668 }
6669
6670 bool doesCRCreateNewP = htmlEditor->GetReturnInParagraphCreatesNewParagraph();
6671
6672 bool splitAfterNewBR = false;
6673 nsCOMPtr<nsIContent> brNode;
6674
6675 EditorDOMPoint pointToSplitParentDivOrP(atStartOfSelection);
6676
6677 EditorRawDOMPoint pointToInsertBR;
6678 if (doesCRCreateNewP && atStartOfSelection.GetContainer() == &aParentDivOrP) {
6679 // We are at the edges of the block, so, we don't need to create new <br>.
6680 brNode = nullptr;
6681 } else if (atStartOfSelection.IsInTextNode()) {
6682 // at beginning of text node?
6683 if (atStartOfSelection.IsStartOfContainer()) {
6684 // is there a BR prior to it?
6685 brNode =
6686 htmlEditor->GetPriorHTMLSibling(atStartOfSelection.GetContainer());
6687 if (!brNode || !htmlEditor->IsVisibleBRElement(brNode) ||
6688 TextEditUtils::HasMozAttr(GetAsDOMNode(brNode))) {
6689 pointToInsertBR.Set(atStartOfSelection.GetContainer());
6690 brNode = nullptr;
6691 }
6692 } else if (atStartOfSelection.IsEndOfContainer()) {
6693 // we're at the end of text node...
6694 // is there a BR after to it?
6695 brNode =
6696 htmlEditor->GetNextHTMLSibling(atStartOfSelection.GetContainer());
6697 if (!brNode || !htmlEditor->IsVisibleBRElement(brNode) ||
6698 TextEditUtils::HasMozAttr(GetAsDOMNode(brNode))) {
6699 pointToInsertBR.Set(atStartOfSelection.GetContainer());
6700 DebugOnly<bool> advanced = pointToInsertBR.AdvanceOffset();
6701 NS_WARNING_ASSERTION(advanced,
6702 "Failed to advance offset to after the container "
6703 "of selection start");
6704 brNode = nullptr;
6705 }
6706 } else {
6707 if (doesCRCreateNewP) {
6708 ErrorResult error;
6709 nsCOMPtr<nsIContent> newLeftDivOrP =
6710 htmlEditor->SplitNode(pointToSplitParentDivOrP.AsRaw(), error);
6711 if (NS_WARN_IF(error.Failed())) {
6712 return EditActionResult(error.StealNSResult());
6713 }
6714 pointToSplitParentDivOrP.SetToEndOf(newLeftDivOrP);
6715 }
6716
6717 // We need to put new <br> after the left node if given node was split
6718 // above.
6719 pointToInsertBR.Set(pointToSplitParentDivOrP.GetContainer());
6720 DebugOnly<bool> advanced = pointToInsertBR.AdvanceOffset();
6721 NS_WARNING_ASSERTION(
6722 advanced,
6723 "Failed to advance offset to after the container of selection start");
6724 }
6725 } else {
6726 // not in a text node.
6727 // is there a BR prior to it?
6728 nsCOMPtr<nsIContent> nearNode;
6729 nearNode =
6730 htmlEditor->GetPreviousEditableHTMLNode(atStartOfSelection.AsRaw());
6731 if (!nearNode || !htmlEditor->IsVisibleBRElement(nearNode) ||
6732 TextEditUtils::HasMozAttr(GetAsDOMNode(nearNode))) {
6733 // is there a BR after it?
6734 nearNode =
6735 htmlEditor->GetNextEditableHTMLNode(atStartOfSelection.AsRaw());
6736 if (!nearNode || !htmlEditor->IsVisibleBRElement(nearNode) ||
6737 TextEditUtils::HasMozAttr(GetAsDOMNode(nearNode))) {
6738 pointToInsertBR = atStartOfSelection;
6739 splitAfterNewBR = true;
6740 }
6741 }
6742 if (!pointToInsertBR.IsSet() && TextEditUtils::IsBreak(nearNode)) {
6743 brNode = nearNode;
6744 }
6745 }
6746 if (pointToInsertBR.IsSet()) {
6747 // Don't modify the DOM tree if mHTMLEditor disappeared.
6748 if (NS_WARN_IF(!mHTMLEditor)) {
6749 return EditActionResult(NS_ERROR_NOT_AVAILABLE);
6750 }
6751
6752 // if CR does not create a new P, default to BR creation
6753 if (NS_WARN_IF(!doesCRCreateNewP)) {
6754 return EditActionResult(NS_OK);
6755 }
6756
6757 brNode = htmlEditor->CreateBR(pointToInsertBR);
6758 if (splitAfterNewBR) {
6759 // We split the parent after the br we've just inserted.
6760 pointToSplitParentDivOrP.Set(brNode);
6761 DebugOnly<bool> advanced = pointToSplitParentDivOrP.AdvanceOffset();
6762 NS_WARNING_ASSERTION(advanced,
6763 "Failed to advance offset after the new <br>");
6764 }
6765 }
6766 EditActionResult result(SplitParagraph(
6767 aSelection, aParentDivOrP, pointToSplitParentDivOrP.AsRaw(), brNode));
6768 result.MarkAsHandled();
6769 if (NS_WARN_IF(result.Failed())) {
6770 return result;
6771 }
6772 return result;
6773 }
6774
SplitParagraph(Selection & aSelection,Element & aParentDivOrP,const EditorRawDOMPoint & aStartOfRightNode,nsIContent * aNextBRNode)6775 nsresult HTMLEditRules::SplitParagraph(
6776 Selection& aSelection, Element& aParentDivOrP,
6777 const EditorRawDOMPoint& aStartOfRightNode, nsIContent* aNextBRNode) {
6778 if (NS_WARN_IF(!mHTMLEditor)) {
6779 return NS_ERROR_NOT_AVAILABLE;
6780 }
6781
6782 RefPtr<HTMLEditor> htmlEditor = mHTMLEditor;
6783
6784 // split para
6785 // get ws code to adjust any ws
6786 nsCOMPtr<nsINode> selNode = aStartOfRightNode.GetContainer();
6787 int32_t selOffset = aStartOfRightNode.Offset();
6788 nsresult rv = WSRunObject::PrepareToSplitAcrossBlocks(
6789 htmlEditor, address_of(selNode), &selOffset);
6790 NS_ENSURE_SUCCESS(rv, rv);
6791 if (NS_WARN_IF(!selNode->IsContent())) {
6792 return NS_ERROR_FAILURE;
6793 }
6794
6795 // Split the paragraph.
6796 SplitNodeResult splitDivOrPResult = htmlEditor->SplitNodeDeep(
6797 aParentDivOrP, EditorRawDOMPoint(selNode, selOffset),
6798 SplitAtEdges::eAllowToCreateEmptyContainer);
6799 if (NS_WARN_IF(splitDivOrPResult.Failed())) {
6800 return splitDivOrPResult.Rv();
6801 }
6802 if (NS_WARN_IF(!splitDivOrPResult.DidSplit())) {
6803 return NS_ERROR_FAILURE;
6804 }
6805
6806 // Get rid of the break, if it is visible (otherwise it may be needed to
6807 // prevent an empty p).
6808 if (aNextBRNode && htmlEditor->IsVisibleBRElement(aNextBRNode)) {
6809 rv = htmlEditor->DeleteNode(aNextBRNode);
6810 NS_ENSURE_SUCCESS(rv, rv);
6811 }
6812
6813 // Remove ID attribute on the paragraph from the existing right node.
6814 rv = htmlEditor->RemoveAttribute(aParentDivOrP.AsElement(), nsGkAtoms::id);
6815 NS_ENSURE_SUCCESS(rv, rv);
6816
6817 // We need to ensure to both paragraphs visible even if they are empty.
6818 // However, moz-<br> element isn't useful in this case because moz-<br>
6819 // elements will be ignored by PlaintextSerializer. Additionally,
6820 // moz-<br> will be exposed as <br> with Element.innerHTML. Therefore,
6821 // we can use normal <br> elements for placeholder in this case.
6822 // Note that Chromium also behaves so.
6823 rv = InsertBRIfNeeded(*splitDivOrPResult.GetPreviousNode());
6824 NS_ENSURE_SUCCESS(rv, rv);
6825 rv = InsertBRIfNeeded(*splitDivOrPResult.GetNextNode());
6826 NS_ENSURE_SUCCESS(rv, rv);
6827
6828 // selection to beginning of right hand para;
6829 // look inside any containers that are up front.
6830 nsIContent* child = htmlEditor->GetLeftmostChild(&aParentDivOrP, true);
6831 if (EditorBase::IsTextNode(child) || htmlEditor->IsContainer(child)) {
6832 EditorRawDOMPoint atStartOfChild(child, 0);
6833 ErrorResult error;
6834 aSelection.Collapse(atStartOfChild, error);
6835 if (NS_WARN_IF(error.Failed())) {
6836 error.SuppressException();
6837 }
6838 } else {
6839 EditorRawDOMPoint atChild(child);
6840 ErrorResult error;
6841 aSelection.Collapse(atChild, error);
6842 if (NS_WARN_IF(error.Failed())) {
6843 error.SuppressException();
6844 }
6845 }
6846 return NS_OK;
6847 }
6848
6849 /**
6850 * ReturnInListItem: do the right thing for returns pressed in list items
6851 */
ReturnInListItem(Selection & aSelection,Element & aListItem,nsINode & aNode,int32_t aOffset)6852 nsresult HTMLEditRules::ReturnInListItem(Selection& aSelection,
6853 Element& aListItem, nsINode& aNode,
6854 int32_t aOffset) {
6855 MOZ_ASSERT(HTMLEditUtils::IsListItem(&aListItem));
6856
6857 NS_ENSURE_STATE(mHTMLEditor);
6858 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
6859
6860 // Get the item parent and the active editing host.
6861 nsCOMPtr<Element> root = htmlEditor->GetActiveEditingHost();
6862
6863 // If we are in an empty item, then we want to pop up out of the list, but
6864 // only if prefs say it's okay and if the parent isn't the active editing
6865 // host.
6866 if (mReturnInEmptyLIKillsList && root != aListItem.GetParentElement() &&
6867 IsEmptyBlockElement(aListItem, IgnoreSingleBR::eYes)) {
6868 nsCOMPtr<nsIContent> leftListNode = aListItem.GetParent();
6869 // Are we the last list item in the list?
6870 if (!htmlEditor->IsLastEditableChild(&aListItem)) {
6871 // We need to split the list!
6872 EditorRawDOMPoint atListItem(&aListItem);
6873 ErrorResult error;
6874 leftListNode = htmlEditor->SplitNode(atListItem, error);
6875 if (NS_WARN_IF(error.Failed())) {
6876 return error.StealNSResult();
6877 }
6878 }
6879
6880 // Are we in a sublist?
6881 EditorRawDOMPoint atNextSiblingOfLeftList(leftListNode);
6882 DebugOnly<bool> advanced = atNextSiblingOfLeftList.AdvanceOffset();
6883 NS_WARNING_ASSERTION(advanced,
6884 "Failed to advance offset after the right list node");
6885 if (HTMLEditUtils::IsList(atNextSiblingOfLeftList.GetContainer())) {
6886 // If so, move item out of this list and into the grandparent list
6887 nsresult rv = htmlEditor->MoveNode(&aListItem,
6888 atNextSiblingOfLeftList.GetContainer(),
6889 atNextSiblingOfLeftList.Offset());
6890 NS_ENSURE_SUCCESS(rv, rv);
6891 rv = aSelection.Collapse(&aListItem, 0);
6892 NS_ENSURE_SUCCESS(rv, rv);
6893 } else {
6894 // Otherwise kill this item
6895 nsresult rv = htmlEditor->DeleteNode(&aListItem);
6896 NS_ENSURE_SUCCESS(rv, rv);
6897
6898 // Time to insert a paragraph
6899 nsAtom& paraAtom = DefaultParagraphSeparator();
6900 // We want a wrapper even if we separate with <br>
6901 RefPtr<Element> pNode = htmlEditor->CreateNode(
6902 ¶Atom == nsGkAtoms::br ? nsGkAtoms::p : ¶Atom,
6903 atNextSiblingOfLeftList);
6904 NS_ENSURE_STATE(pNode);
6905
6906 // Append a <br> to it
6907 RefPtr<Element> brNode =
6908 htmlEditor->CreateBR(EditorRawDOMPoint(pNode, 0));
6909 if (NS_WARN_IF(!brNode)) {
6910 return NS_ERROR_FAILURE;
6911 }
6912
6913 // Set selection to before the break
6914 ErrorResult error;
6915 aSelection.Collapse(EditorRawDOMPoint(pNode, 0), error);
6916 if (NS_WARN_IF(error.Failed())) {
6917 return error.StealNSResult();
6918 }
6919 }
6920 return NS_OK;
6921 }
6922
6923 // Else we want a new list item at the same list level. Get ws code to
6924 // adjust any ws.
6925 nsCOMPtr<nsINode> selNode = &aNode;
6926 nsresult rv = WSRunObject::PrepareToSplitAcrossBlocks(
6927 htmlEditor, address_of(selNode), &aOffset);
6928 NS_ENSURE_SUCCESS(rv, rv);
6929 if (NS_WARN_IF(!selNode->IsContent())) {
6930 return NS_ERROR_FAILURE;
6931 }
6932
6933 // Now split the list item.
6934 SplitNodeResult splitListItemResult =
6935 htmlEditor->SplitNodeDeep(aListItem, EditorRawDOMPoint(selNode, aOffset),
6936 SplitAtEdges::eAllowToCreateEmptyContainer);
6937 NS_WARNING_ASSERTION(splitListItemResult.Succeeded(),
6938 "Failed to split the list item");
6939
6940 // Hack: until I can change the damaged doc range code back to being
6941 // extra-inclusive, I have to manually detect certain list items that may be
6942 // left empty.
6943 nsCOMPtr<nsIContent> prevItem = htmlEditor->GetPriorHTMLSibling(&aListItem);
6944 if (prevItem && HTMLEditUtils::IsListItem(prevItem)) {
6945 bool isEmptyNode;
6946 rv = htmlEditor->IsEmptyNode(prevItem, &isEmptyNode);
6947 NS_ENSURE_SUCCESS(rv, rv);
6948 if (isEmptyNode) {
6949 RefPtr<Element> brElement = CreateMozBR(EditorRawDOMPoint(prevItem, 0));
6950 if (NS_WARN_IF(!brElement)) {
6951 return NS_ERROR_FAILURE;
6952 }
6953 } else {
6954 rv = htmlEditor->IsEmptyNode(&aListItem, &isEmptyNode, true);
6955 NS_ENSURE_SUCCESS(rv, rv);
6956 if (isEmptyNode) {
6957 RefPtr<nsAtom> nodeAtom = aListItem.NodeInfo()->NameAtom();
6958 if (nodeAtom == nsGkAtoms::dd || nodeAtom == nsGkAtoms::dt) {
6959 nsCOMPtr<nsINode> list = aListItem.GetParentNode();
6960 int32_t itemOffset = list ? list->ComputeIndexOf(&aListItem) : -1;
6961
6962 nsAtom* listAtom =
6963 nodeAtom == nsGkAtoms::dt ? nsGkAtoms::dd : nsGkAtoms::dt;
6964 MOZ_DIAGNOSTIC_ASSERT(itemOffset != -1);
6965 EditorRawDOMPoint atNextListItem(list, aListItem.GetNextSibling(),
6966 itemOffset + 1);
6967 RefPtr<Element> newListItem =
6968 htmlEditor->CreateNode(listAtom, atNextListItem);
6969 NS_ENSURE_STATE(newListItem);
6970 rv = htmlEditor->DeleteNode(&aListItem);
6971 NS_ENSURE_SUCCESS(rv, rv);
6972 rv = aSelection.Collapse(newListItem, 0);
6973 NS_ENSURE_SUCCESS(rv, rv);
6974
6975 return NS_OK;
6976 }
6977
6978 nsCOMPtr<Element> brNode;
6979 rv = htmlEditor->CopyLastEditableChildStyles(prevItem, &aListItem,
6980 getter_AddRefs(brNode));
6981 NS_ENSURE_SUCCESS(rv, rv);
6982 if (brNode) {
6983 EditorRawDOMPoint atBrNode(brNode);
6984 if (NS_WARN_IF(!atBrNode.IsSetAndValid())) {
6985 return NS_ERROR_FAILURE;
6986 }
6987 ErrorResult error;
6988 aSelection.Collapse(atBrNode, error);
6989 if (NS_WARN_IF(error.Failed())) {
6990 return error.StealNSResult();
6991 }
6992 return NS_OK;
6993 }
6994 } else {
6995 WSRunObject wsObj(htmlEditor, &aListItem, 0);
6996 nsCOMPtr<nsINode> visNode;
6997 int32_t visOffset = 0;
6998 WSType wsType;
6999 wsObj.NextVisibleNode(&aListItem, 0, address_of(visNode), &visOffset,
7000 &wsType);
7001 if (wsType == WSType::special || wsType == WSType::br ||
7002 visNode->IsHTMLElement(nsGkAtoms::hr)) {
7003 EditorRawDOMPoint atVisNode(visNode);
7004 if (NS_WARN_IF(!atVisNode.IsSetAndValid())) {
7005 return NS_ERROR_FAILURE;
7006 }
7007 ErrorResult error;
7008 aSelection.Collapse(atVisNode, error);
7009 if (NS_WARN_IF(error.Failed())) {
7010 return error.StealNSResult();
7011 }
7012 return NS_OK;
7013 }
7014
7015 rv = aSelection.Collapse(visNode, visOffset);
7016 NS_ENSURE_SUCCESS(rv, rv);
7017 return NS_OK;
7018 }
7019 }
7020 }
7021
7022 ErrorResult error;
7023 aSelection.Collapse(EditorRawDOMPoint(&aListItem, 0), error);
7024 if (NS_WARN_IF(error.Failed())) {
7025 return error.StealNSResult();
7026 }
7027 return NS_OK;
7028 }
7029
7030 /**
7031 * MakeBlockquote() puts the list of nodes into one or more blockquotes.
7032 */
MakeBlockquote(nsTArray<OwningNonNull<nsINode>> & aNodeArray)7033 nsresult HTMLEditRules::MakeBlockquote(
7034 nsTArray<OwningNonNull<nsINode>>& aNodeArray) {
7035 if (NS_WARN_IF(!mHTMLEditor)) {
7036 return NS_ERROR_NOT_AVAILABLE;
7037 }
7038 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
7039
7040 // The idea here is to put the nodes into a minimal number of blockquotes.
7041 // When the user blockquotes something, they expect one blockquote. That may
7042 // not be possible (for instance, if they have two table cells selected, you
7043 // need two blockquotes inside the cells).
7044 nsCOMPtr<Element> curBlock;
7045 nsCOMPtr<nsINode> prevParent;
7046
7047 for (auto& curNode : aNodeArray) {
7048 // Get the node to act on, and its location
7049 NS_ENSURE_STATE(curNode->IsContent());
7050
7051 // If the node is a table element or list item, dive inside
7052 if (HTMLEditUtils::IsTableElementButNotTable(curNode) ||
7053 HTMLEditUtils::IsListItem(curNode)) {
7054 // Forget any previous block
7055 curBlock = nullptr;
7056 // Recursion time
7057 nsTArray<OwningNonNull<nsINode>> childArray;
7058 GetChildNodesForOperation(*curNode, childArray);
7059 nsresult rv = MakeBlockquote(childArray);
7060 NS_ENSURE_SUCCESS(rv, rv);
7061 }
7062
7063 // If the node has different parent than previous node, further nodes in a
7064 // new parent
7065 if (prevParent) {
7066 if (prevParent != curNode->GetParentNode()) {
7067 // Forget any previous blockquote node we were using
7068 curBlock = nullptr;
7069 prevParent = curNode->GetParentNode();
7070 }
7071 } else {
7072 prevParent = curNode->GetParentNode();
7073 }
7074
7075 // If no curBlock, make one
7076 if (!curBlock) {
7077 EditorDOMPoint atCurNode(curNode);
7078 SplitNodeResult splitNodeResult = MaybeSplitAncestorsForInsert(
7079 *nsGkAtoms::blockquote, atCurNode.AsRaw());
7080 if (NS_WARN_IF(splitNodeResult.Failed())) {
7081 return splitNodeResult.Rv();
7082 }
7083 curBlock = htmlEditor->CreateNode(nsGkAtoms::blockquote,
7084 splitNodeResult.SplitPoint());
7085 NS_ENSURE_STATE(curBlock);
7086 // remember our new block for postprocessing
7087 mNewBlock = curBlock;
7088 // note: doesn't matter if we set mNewBlock multiple times.
7089 }
7090
7091 nsresult rv = htmlEditor->MoveNode(curNode->AsContent(), curBlock, -1);
7092 NS_ENSURE_SUCCESS(rv, rv);
7093 }
7094 return NS_OK;
7095 }
7096
7097 /**
7098 * RemoveBlockStyle() makes the nodes have no special block type.
7099 */
RemoveBlockStyle(nsTArray<OwningNonNull<nsINode>> & aNodeArray)7100 nsresult HTMLEditRules::RemoveBlockStyle(
7101 nsTArray<OwningNonNull<nsINode>>& aNodeArray) {
7102 NS_ENSURE_STATE(mHTMLEditor);
7103 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
7104
7105 // Intent of this routine is to be used for converting to/from headers,
7106 // paragraphs, pre, and address. Those blocks that pretty much just contain
7107 // inline things...
7108 nsCOMPtr<Element> curBlock;
7109 nsCOMPtr<nsIContent> firstNode, lastNode;
7110 for (auto& curNode : aNodeArray) {
7111 // If curNode is a address, p, header, address, or pre, remove it
7112 if (HTMLEditUtils::IsFormatNode(curNode)) {
7113 // Process any partial progress saved
7114 if (curBlock) {
7115 nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
7116 NS_ENSURE_SUCCESS(rv, rv);
7117 firstNode = lastNode = curBlock = nullptr;
7118 }
7119 if (!mHTMLEditor->IsEditable(curNode)) {
7120 continue;
7121 }
7122 // Remove current block
7123 nsresult rv = htmlEditor->RemoveBlockContainer(*curNode->AsContent());
7124 NS_ENSURE_SUCCESS(rv, rv);
7125 } else if (curNode->IsAnyOfHTMLElements(
7126 nsGkAtoms::table, nsGkAtoms::tr, nsGkAtoms::tbody,
7127 nsGkAtoms::td, nsGkAtoms::li, nsGkAtoms::blockquote,
7128 nsGkAtoms::div) ||
7129 HTMLEditUtils::IsList(curNode)) {
7130 // Process any partial progress saved
7131 if (curBlock) {
7132 nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
7133 NS_ENSURE_SUCCESS(rv, rv);
7134 firstNode = lastNode = curBlock = nullptr;
7135 }
7136 if (!mHTMLEditor->IsEditable(curNode)) {
7137 continue;
7138 }
7139 // Recursion time
7140 nsTArray<OwningNonNull<nsINode>> childArray;
7141 GetChildNodesForOperation(*curNode, childArray);
7142 nsresult rv = RemoveBlockStyle(childArray);
7143 NS_ENSURE_SUCCESS(rv, rv);
7144 } else if (IsInlineNode(curNode)) {
7145 if (curBlock) {
7146 // If so, is this node a descendant?
7147 if (EditorUtils::IsDescendantOf(*curNode, *curBlock)) {
7148 // Then we don't need to do anything different for this node
7149 lastNode = curNode->AsContent();
7150 continue;
7151 }
7152 // Otherwise, we have progressed beyond end of curBlock, so let's
7153 // handle it now. We need to remove the portion of curBlock that
7154 // contains [firstNode - lastNode].
7155 nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
7156 NS_ENSURE_SUCCESS(rv, rv);
7157 firstNode = lastNode = curBlock = nullptr;
7158 // Fall out and handle curNode
7159 }
7160 curBlock = htmlEditor->GetBlockNodeParent(curNode);
7161 if (!curBlock || !HTMLEditUtils::IsFormatNode(curBlock) ||
7162 !mHTMLEditor->IsEditable(curBlock)) {
7163 // Not a block kind that we care about.
7164 curBlock = nullptr;
7165 } else {
7166 firstNode = lastNode = curNode->AsContent();
7167 }
7168 } else if (curBlock) {
7169 // Some node that is already sans block style. Skip over it and process
7170 // any partial progress saved.
7171 nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
7172 NS_ENSURE_SUCCESS(rv, rv);
7173 firstNode = lastNode = curBlock = nullptr;
7174 }
7175 }
7176 // Process any partial progress saved
7177 if (curBlock) {
7178 nsresult rv = RemovePartOfBlock(*curBlock, *firstNode, *lastNode);
7179 NS_ENSURE_SUCCESS(rv, rv);
7180 firstNode = lastNode = curBlock = nullptr;
7181 }
7182 return NS_OK;
7183 }
7184
7185 /**
7186 * ApplyBlockStyle() does whatever it takes to make the list of nodes into one
7187 * or more blocks of type aBlockTag.
7188 */
ApplyBlockStyle(nsTArray<OwningNonNull<nsINode>> & aNodeArray,nsAtom & aBlockTag)7189 nsresult HTMLEditRules::ApplyBlockStyle(
7190 nsTArray<OwningNonNull<nsINode>>& aNodeArray, nsAtom& aBlockTag) {
7191 // Intent of this routine is to be used for converting to/from headers,
7192 // paragraphs, pre, and address. Those blocks that pretty much just contain
7193 // inline things...
7194 if (NS_WARN_IF(!mHTMLEditor)) {
7195 return NS_ERROR_NOT_AVAILABLE;
7196 }
7197 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
7198
7199 nsCOMPtr<Element> newBlock;
7200
7201 nsCOMPtr<Element> curBlock;
7202 for (auto& curNode : aNodeArray) {
7203 EditorDOMPoint atCurNode(curNode);
7204
7205 // Is it already the right kind of block, or an uneditable block?
7206 if (curNode->IsHTMLElement(&aBlockTag) ||
7207 (!mHTMLEditor->IsEditable(curNode) && IsBlockNode(curNode))) {
7208 // Forget any previous block used for previous inline nodes
7209 curBlock = nullptr;
7210 // Do nothing to this block
7211 continue;
7212 }
7213
7214 // If curNode is a address, p, header, address, or pre, replace it with a
7215 // new block of correct type.
7216 // XXX: pre can't hold everything the others can
7217 if (HTMLEditUtils::IsMozDiv(curNode) ||
7218 HTMLEditUtils::IsFormatNode(curNode)) {
7219 // Forget any previous block used for previous inline nodes
7220 curBlock = nullptr;
7221 newBlock = htmlEditor->ReplaceContainer(curNode->AsElement(), &aBlockTag,
7222 nullptr, nullptr,
7223 EditorBase::eCloneAttributes);
7224 NS_ENSURE_STATE(newBlock);
7225 continue;
7226 }
7227
7228 if (HTMLEditUtils::IsTable(curNode) || HTMLEditUtils::IsList(curNode) ||
7229 curNode->IsAnyOfHTMLElements(nsGkAtoms::tbody, nsGkAtoms::tr,
7230 nsGkAtoms::td, nsGkAtoms::li,
7231 nsGkAtoms::blockquote, nsGkAtoms::div)) {
7232 // Forget any previous block used for previous inline nodes
7233 curBlock = nullptr;
7234 // Recursion time
7235 nsTArray<OwningNonNull<nsINode>> childArray;
7236 GetChildNodesForOperation(*curNode, childArray);
7237 if (!childArray.IsEmpty()) {
7238 nsresult rv = ApplyBlockStyle(childArray, aBlockTag);
7239 NS_ENSURE_SUCCESS(rv, rv);
7240 continue;
7241 }
7242
7243 // Make sure we can put a block here
7244 SplitNodeResult splitNodeResult =
7245 MaybeSplitAncestorsForInsert(aBlockTag, atCurNode.AsRaw());
7246 if (NS_WARN_IF(splitNodeResult.Failed())) {
7247 return splitNodeResult.Rv();
7248 }
7249 RefPtr<Element> theBlock =
7250 htmlEditor->CreateNode(&aBlockTag, splitNodeResult.SplitPoint());
7251 NS_ENSURE_STATE(theBlock);
7252 // Remember our new block for postprocessing
7253 mNewBlock = theBlock;
7254 continue;
7255 }
7256
7257 if (curNode->IsHTMLElement(nsGkAtoms::br)) {
7258 // If the node is a break, we honor it by putting further nodes in a new
7259 // parent
7260 if (curBlock) {
7261 // Forget any previous block used for previous inline nodes
7262 curBlock = nullptr;
7263 nsresult rv = htmlEditor->DeleteNode(curNode);
7264 NS_ENSURE_SUCCESS(rv, rv);
7265 continue;
7266 }
7267
7268 // The break is the first (or even only) node we encountered. Create a
7269 // block for it.
7270 SplitNodeResult splitNodeResult =
7271 MaybeSplitAncestorsForInsert(aBlockTag, atCurNode.AsRaw());
7272 if (NS_WARN_IF(splitNodeResult.Failed())) {
7273 return splitNodeResult.Rv();
7274 }
7275 curBlock =
7276 htmlEditor->CreateNode(&aBlockTag, splitNodeResult.SplitPoint());
7277 NS_ENSURE_STATE(curBlock);
7278 // Remember our new block for postprocessing
7279 mNewBlock = curBlock;
7280 // Note: doesn't matter if we set mNewBlock multiple times.
7281 nsresult rv = htmlEditor->MoveNode(curNode->AsContent(), curBlock, -1);
7282 NS_ENSURE_SUCCESS(rv, rv);
7283 continue;
7284 }
7285
7286 if (IsInlineNode(curNode)) {
7287 // If curNode is inline, pull it into curBlock. Note: it's assumed that
7288 // consecutive inline nodes in aNodeArray are actually members of the
7289 // same block parent. This happens to be true now as a side effect of
7290 // how aNodeArray is contructed, but some additional logic should be
7291 // added here if that should change
7292 //
7293 // If curNode is a non editable, drop it if we are going to <pre>.
7294 if (&aBlockTag == nsGkAtoms::pre && !htmlEditor->IsEditable(curNode)) {
7295 // Do nothing to this block
7296 continue;
7297 }
7298
7299 // If no curBlock, make one
7300 if (!curBlock) {
7301 AutoEditorDOMPointOffsetInvalidator lockChild(atCurNode);
7302
7303 SplitNodeResult splitNodeResult =
7304 MaybeSplitAncestorsForInsert(aBlockTag, atCurNode.AsRaw());
7305 if (NS_WARN_IF(splitNodeResult.Failed())) {
7306 return splitNodeResult.Rv();
7307 }
7308 curBlock =
7309 htmlEditor->CreateNode(&aBlockTag, splitNodeResult.SplitPoint());
7310 NS_ENSURE_STATE(curBlock);
7311 // Remember our new block for postprocessing
7312 mNewBlock = curBlock;
7313 // Note: doesn't matter if we set mNewBlock multiple times.
7314 }
7315
7316 if (NS_WARN_IF(!atCurNode.IsSet())) {
7317 // This is possible due to mutation events, let's not assert
7318 return NS_ERROR_UNEXPECTED;
7319 }
7320
7321 // XXX If curNode is a br, replace it with a return if going to <pre>
7322
7323 // This is a continuation of some inline nodes that belong together in
7324 // the same block item. Use curBlock.
7325 nsresult rv = htmlEditor->MoveNode(curNode->AsContent(), curBlock, -1);
7326 NS_ENSURE_SUCCESS(rv, rv);
7327 }
7328 }
7329 return NS_OK;
7330 }
7331
MaybeSplitAncestorsForInsert(nsAtom & aTag,const EditorRawDOMPoint & aStartOfDeepestRightNode)7332 SplitNodeResult HTMLEditRules::MaybeSplitAncestorsForInsert(
7333 nsAtom& aTag, const EditorRawDOMPoint& aStartOfDeepestRightNode) {
7334 if (NS_WARN_IF(!aStartOfDeepestRightNode.IsSet())) {
7335 return SplitNodeResult(NS_ERROR_INVALID_ARG);
7336 }
7337 MOZ_ASSERT(aStartOfDeepestRightNode.IsSetAndValid());
7338
7339 if (NS_WARN_IF(!mHTMLEditor)) {
7340 return SplitNodeResult(NS_ERROR_NOT_AVAILABLE);
7341 }
7342 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
7343
7344 RefPtr<Element> host = htmlEditor->GetActiveEditingHost();
7345 if (NS_WARN_IF(!host)) {
7346 return SplitNodeResult(NS_ERROR_FAILURE);
7347 }
7348
7349 // The point must be descendant of editing host.
7350 if (NS_WARN_IF(aStartOfDeepestRightNode.GetContainer() != host &&
7351 !EditorUtils::IsDescendantOf(
7352 *aStartOfDeepestRightNode.GetContainer(), *host))) {
7353 return SplitNodeResult(NS_ERROR_INVALID_ARG);
7354 }
7355
7356 // Look for a node that can legally contain the tag.
7357 EditorRawDOMPoint pointToInsert(aStartOfDeepestRightNode);
7358 for (; pointToInsert.IsSet();
7359 pointToInsert.Set(pointToInsert.GetContainer())) {
7360 // We cannot split active editing host and its ancestor. So, there is
7361 // no element to contain the specified element.
7362 if (NS_WARN_IF(pointToInsert.GetChild() == host)) {
7363 return SplitNodeResult(NS_ERROR_FAILURE);
7364 }
7365
7366 if (htmlEditor->CanContainTag(*pointToInsert.GetContainer(), aTag)) {
7367 // Found an ancestor node which can contain the element.
7368 break;
7369 }
7370 }
7371
7372 MOZ_DIAGNOSTIC_ASSERT(pointToInsert.IsSet());
7373
7374 // If the point itself can contain the tag, we don't need to split any
7375 // ancestor nodes. In this case, we should return the given split point
7376 // as is.
7377 if (pointToInsert.GetContainer() == aStartOfDeepestRightNode.GetContainer()) {
7378 return SplitNodeResult(aStartOfDeepestRightNode);
7379 }
7380
7381 SplitNodeResult splitNodeResult = htmlEditor->SplitNodeDeep(
7382 *pointToInsert.GetChild(), aStartOfDeepestRightNode,
7383 SplitAtEdges::eAllowToCreateEmptyContainer);
7384 NS_WARNING_ASSERTION(splitNodeResult.Succeeded(),
7385 "Failed to split the node for insert the element");
7386 return splitNodeResult;
7387 }
7388
7389 /**
7390 * JoinNodesSmart: Join two nodes, doing whatever makes sense for their
7391 * children (which often means joining them, too). aNodeLeft & aNodeRight must
7392 * be same type of node.
7393 *
7394 * Returns the point where they're merged, or (nullptr, -1) on failure.
7395 */
JoinNodesSmart(nsIContent & aNodeLeft,nsIContent & aNodeRight)7396 EditorDOMPoint HTMLEditRules::JoinNodesSmart(nsIContent& aNodeLeft,
7397 nsIContent& aNodeRight) {
7398 // Caller responsible for left and right node being the same type
7399 nsCOMPtr<nsINode> parent = aNodeLeft.GetParentNode();
7400 if (NS_WARN_IF(!parent)) {
7401 return EditorDOMPoint();
7402 }
7403 int32_t parOffset = parent->ComputeIndexOf(&aNodeLeft);
7404 nsCOMPtr<nsINode> rightParent = aNodeRight.GetParentNode();
7405
7406 // If they don't have the same parent, first move the right node to after the
7407 // left one
7408 if (parent != rightParent) {
7409 if (NS_WARN_IF(!mHTMLEditor)) {
7410 return EditorDOMPoint();
7411 }
7412 nsresult rv = mHTMLEditor->MoveNode(&aNodeRight, parent, parOffset);
7413 if (NS_WARN_IF(NS_FAILED(rv))) {
7414 return EditorDOMPoint();
7415 }
7416 }
7417
7418 EditorDOMPoint ret(&aNodeRight, aNodeLeft.Length());
7419
7420 // Separate join rules for differing blocks
7421 if (HTMLEditUtils::IsList(&aNodeLeft) || aNodeLeft.GetAsText()) {
7422 // For lists, merge shallow (wouldn't want to combine list items)
7423 nsresult rv = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight);
7424 if (NS_WARN_IF(NS_FAILED(rv))) {
7425 return EditorDOMPoint();
7426 }
7427 return ret;
7428 }
7429
7430 // Remember the last left child, and first right child
7431 if (NS_WARN_IF(!mHTMLEditor)) {
7432 return EditorDOMPoint();
7433 }
7434 nsCOMPtr<nsIContent> lastLeft = mHTMLEditor->GetLastEditableChild(aNodeLeft);
7435 if (NS_WARN_IF(!lastLeft)) {
7436 return EditorDOMPoint();
7437 }
7438
7439 if (NS_WARN_IF(!mHTMLEditor)) {
7440 return EditorDOMPoint();
7441 }
7442 nsCOMPtr<nsIContent> firstRight =
7443 mHTMLEditor->GetFirstEditableChild(aNodeRight);
7444 if (NS_WARN_IF(!firstRight)) {
7445 return EditorDOMPoint();
7446 }
7447
7448 // For list items, divs, etc., merge smart
7449 if (NS_WARN_IF(!mHTMLEditor)) {
7450 return EditorDOMPoint();
7451 }
7452 nsresult rv = mHTMLEditor->JoinNodes(aNodeLeft, aNodeRight);
7453 if (NS_WARN_IF(NS_FAILED(rv))) {
7454 return EditorDOMPoint();
7455 }
7456
7457 if (lastLeft && firstRight && mHTMLEditor &&
7458 mHTMLEditor->AreNodesSameType(lastLeft, firstRight) &&
7459 (lastLeft->GetAsText() || !mHTMLEditor ||
7460 (lastLeft->IsElement() && firstRight->IsElement() &&
7461 CSSEditUtils::ElementsSameStyle(lastLeft->AsElement(),
7462 firstRight->AsElement())))) {
7463 if (NS_WARN_IF(!mHTMLEditor)) {
7464 return EditorDOMPoint();
7465 }
7466 return JoinNodesSmart(*lastLeft, *firstRight);
7467 }
7468 return ret;
7469 }
7470
GetTopEnclosingMailCite(nsINode & aNode)7471 Element* HTMLEditRules::GetTopEnclosingMailCite(nsINode& aNode) {
7472 nsCOMPtr<Element> ret;
7473
7474 for (nsCOMPtr<nsINode> node = &aNode; node; node = node->GetParentNode()) {
7475 if ((IsPlaintextEditor() && node->IsHTMLElement(nsGkAtoms::pre)) ||
7476 HTMLEditUtils::IsMailCite(node)) {
7477 ret = node->AsElement();
7478 }
7479 if (node->IsHTMLElement(nsGkAtoms::body)) {
7480 break;
7481 }
7482 }
7483
7484 return ret;
7485 }
7486
CacheInlineStyles(nsINode * aNode)7487 nsresult HTMLEditRules::CacheInlineStyles(nsINode* aNode) {
7488 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
7489
7490 NS_ENSURE_STATE(mHTMLEditor);
7491
7492 nsresult rv = GetInlineStyles(aNode, mCachedStyles);
7493 if (NS_WARN_IF(NS_FAILED(rv))) {
7494 return rv;
7495 }
7496 return NS_OK;
7497 }
7498
GetInlineStyles(nsINode * aNode,StyleCache aStyleCache[SIZE_STYLE_TABLE])7499 nsresult HTMLEditRules::GetInlineStyles(
7500 nsINode* aNode, StyleCache aStyleCache[SIZE_STYLE_TABLE]) {
7501 MOZ_ASSERT(aNode);
7502 MOZ_ASSERT(mHTMLEditor);
7503
7504 bool useCSS = mHTMLEditor->IsCSSEnabled();
7505
7506 for (size_t j = 0; j < SIZE_STYLE_TABLE; ++j) {
7507 // If type-in state is set, don't intervene
7508 bool typeInSet, unused;
7509 if (NS_WARN_IF(!mHTMLEditor)) {
7510 return NS_ERROR_UNEXPECTED;
7511 }
7512 mHTMLEditor->mTypeInState->GetTypingState(
7513 typeInSet, unused, aStyleCache[j].tag, aStyleCache[j].attr, nullptr);
7514 if (typeInSet) {
7515 continue;
7516 }
7517
7518 bool isSet = false;
7519 nsAutoString outValue;
7520 // Don't use CSS for <font size>, we don't support it usefully (bug 780035)
7521 if (!useCSS || (aStyleCache[j].tag == nsGkAtoms::font &&
7522 aStyleCache[j].attr == nsGkAtoms::size)) {
7523 NS_ENSURE_STATE(mHTMLEditor);
7524 isSet = mHTMLEditor->IsTextPropertySetByContent(
7525 aNode, aStyleCache[j].tag, aStyleCache[j].attr, nullptr, &outValue);
7526 } else {
7527 isSet = CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
7528 aNode, aStyleCache[j].tag, aStyleCache[j].attr, outValue,
7529 CSSEditUtils::eComputed);
7530 }
7531 if (isSet) {
7532 aStyleCache[j].mPresent = true;
7533 aStyleCache[j].value.Assign(outValue);
7534 }
7535 }
7536 return NS_OK;
7537 }
7538
ReapplyCachedStyles()7539 nsresult HTMLEditRules::ReapplyCachedStyles() {
7540 // The idea here is to examine our cached list of styles and see if any have
7541 // been removed. If so, add typeinstate for them, so that they will be
7542 // reinserted when new content is added.
7543
7544 // remember if we are in css mode
7545 NS_ENSURE_STATE(mHTMLEditor);
7546 bool useCSS = mHTMLEditor->IsCSSEnabled();
7547
7548 // get selection point; if it doesn't exist, we have nothing to do
7549 NS_ENSURE_STATE(mHTMLEditor);
7550 RefPtr<Selection> selection = mHTMLEditor->GetSelection();
7551 if (!selection) {
7552 // If the document is removed from its parent document during executing an
7553 // editor operation with DOMMutationEvent or something, there may be no
7554 // selection.
7555 return NS_OK;
7556 }
7557 if (!selection->RangeCount()) {
7558 // Nothing to do
7559 return NS_OK;
7560 }
7561 nsCOMPtr<nsIContent> selNode =
7562 do_QueryInterface(selection->GetRangeAt(0)->GetStartContainer());
7563 if (!selNode) {
7564 // Nothing to do
7565 return NS_OK;
7566 }
7567
7568 StyleCache styleAtInsertionPoint[SIZE_STYLE_TABLE];
7569 InitStyleCacheArray(styleAtInsertionPoint);
7570 nsresult rv = GetInlineStyles(selNode, styleAtInsertionPoint);
7571 if (NS_WARN_IF(NS_FAILED(rv))) {
7572 return NS_OK;
7573 }
7574
7575 for (size_t i = 0; i < SIZE_STYLE_TABLE; ++i) {
7576 if (mCachedStyles[i].mPresent) {
7577 bool bFirst, bAny, bAll;
7578 bFirst = bAny = bAll = false;
7579
7580 nsAutoString curValue;
7581 if (useCSS) {
7582 // check computed style first in css case
7583 bAny = CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
7584 selNode, mCachedStyles[i].tag, mCachedStyles[i].attr, curValue,
7585 CSSEditUtils::eComputed);
7586 }
7587 if (!bAny) {
7588 // then check typeinstate and html style
7589 NS_ENSURE_STATE(mHTMLEditor);
7590 nsresult rv = mHTMLEditor->GetInlinePropertyBase(
7591 *mCachedStyles[i].tag, mCachedStyles[i].attr,
7592 &(mCachedStyles[i].value), &bFirst, &bAny, &bAll, &curValue);
7593 NS_ENSURE_SUCCESS(rv, rv);
7594 }
7595 // This style has disappeared through deletion. Let's add the styles to
7596 // mTypeInState when same style isn't applied to the node already.
7597 if ((!bAny || IsStyleCachePreservingAction(mTheAction)) &&
7598 (!styleAtInsertionPoint[i].mPresent ||
7599 styleAtInsertionPoint[i].value != mCachedStyles[i].value)) {
7600 NS_ENSURE_STATE(mHTMLEditor);
7601 mHTMLEditor->mTypeInState->SetProp(mCachedStyles[i].tag,
7602 mCachedStyles[i].attr,
7603 mCachedStyles[i].value);
7604 }
7605 }
7606 }
7607
7608 return NS_OK;
7609 }
7610
ClearCachedStyles()7611 void HTMLEditRules::ClearCachedStyles() {
7612 // clear the mPresent bits in mCachedStyles array
7613 for (size_t j = 0; j < SIZE_STYLE_TABLE; j++) {
7614 mCachedStyles[j].mPresent = false;
7615 mCachedStyles[j].value.Truncate();
7616 }
7617 }
7618
AdjustSpecialBreaks()7619 void HTMLEditRules::AdjustSpecialBreaks() {
7620 NS_ENSURE_TRUE_VOID(mHTMLEditor);
7621
7622 // Gather list of empty nodes
7623 nsTArray<OwningNonNull<nsINode>> nodeArray;
7624 EmptyEditableFunctor functor(mHTMLEditor);
7625 DOMIterator iter;
7626 if (NS_WARN_IF(NS_FAILED(iter.Init(*mDocChangeRange)))) {
7627 return;
7628 }
7629 iter.AppendList(functor, nodeArray);
7630
7631 // Put moz-br's into these empty li's and td's
7632 for (auto& node : nodeArray) {
7633 // Need to put br at END of node. It may have empty containers in it and
7634 // still pass the "IsEmptyNode" test, and we want the br's to be after
7635 // them. Also, we want the br to be after the selection if the selection
7636 // is in this node.
7637 EditorRawDOMPoint endOfNode;
7638 endOfNode.SetToEndOf(node);
7639 RefPtr<Element> brElement = CreateMozBR(endOfNode);
7640 if (NS_WARN_IF(!brElement)) {
7641 return;
7642 }
7643 }
7644 }
7645
AdjustWhitespace(Selection * aSelection)7646 nsresult HTMLEditRules::AdjustWhitespace(Selection* aSelection) {
7647 // get selection point
7648 nsCOMPtr<nsINode> selNode;
7649 int32_t selOffset;
7650 nsresult rv = EditorBase::GetStartNodeAndOffset(
7651 aSelection, getter_AddRefs(selNode), &selOffset);
7652 NS_ENSURE_SUCCESS(rv, rv);
7653
7654 // ask whitespace object to tweak nbsp's
7655 NS_ENSURE_STATE(mHTMLEditor);
7656 return WSRunObject(mHTMLEditor, selNode, selOffset).AdjustWhitespace();
7657 }
7658
PinSelectionToNewBlock(Selection * aSelection)7659 nsresult HTMLEditRules::PinSelectionToNewBlock(Selection* aSelection) {
7660 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
7661 if (!aSelection->Collapsed()) {
7662 return NS_OK;
7663 }
7664
7665 if (NS_WARN_IF(!mNewBlock)) {
7666 return NS_ERROR_NULL_POINTER;
7667 }
7668
7669 // get the (collapsed) selection location
7670 nsCOMPtr<nsINode> selNode;
7671 int32_t selOffset;
7672 nsresult rv = EditorBase::GetStartNodeAndOffset(
7673 aSelection, getter_AddRefs(selNode), &selOffset);
7674 if (NS_WARN_IF(NS_FAILED(rv))) {
7675 return rv;
7676 }
7677 if (NS_WARN_IF(!selNode)) {
7678 return NS_ERROR_FAILURE;
7679 }
7680
7681 // use ranges and sRangeHelper to compare sel point to new block
7682 RefPtr<nsRange> range = new nsRange(selNode);
7683 rv = range->CollapseTo(selNode, selOffset);
7684 if (NS_WARN_IF(NS_FAILED(rv))) {
7685 return rv;
7686 }
7687 bool nodeBefore, nodeAfter;
7688 rv = nsRange::CompareNodeToRange(mNewBlock, range, &nodeBefore, &nodeAfter);
7689 NS_ENSURE_SUCCESS(rv, rv);
7690
7691 if (nodeBefore && nodeAfter) {
7692 return NS_OK; // selection is inside block
7693 }
7694
7695 if (nodeBefore) {
7696 // selection is after block. put at end of block.
7697 NS_ENSURE_STATE(mHTMLEditor);
7698 nsCOMPtr<nsINode> tmp = mHTMLEditor->GetLastEditableChild(*mNewBlock);
7699 if (!tmp) {
7700 tmp = mNewBlock;
7701 }
7702 EditorRawDOMPoint endPoint;
7703 if (EditorBase::IsTextNode(tmp) || mHTMLEditor->IsContainer(tmp)) {
7704 endPoint.SetToEndOf(tmp);
7705 } else {
7706 endPoint.Set(tmp);
7707 if (NS_WARN_IF(!endPoint.AdvanceOffset())) {
7708 return NS_ERROR_FAILURE;
7709 }
7710 }
7711 return aSelection->Collapse(endPoint);
7712 }
7713
7714 // selection is before block. put at start of block.
7715 NS_ENSURE_STATE(mHTMLEditor);
7716 nsCOMPtr<nsINode> tmp = mHTMLEditor->GetFirstEditableChild(*mNewBlock);
7717 if (!tmp) {
7718 tmp = mNewBlock;
7719 }
7720 EditorRawDOMPoint atStartOfBlock;
7721 if (EditorBase::IsTextNode(tmp) || mHTMLEditor->IsContainer(tmp)) {
7722 atStartOfBlock.Set(tmp);
7723 } else {
7724 atStartOfBlock.Set(tmp, 0);
7725 }
7726 return aSelection->Collapse(atStartOfBlock);
7727 }
7728
CheckInterlinePosition(Selection & aSelection)7729 void HTMLEditRules::CheckInterlinePosition(Selection& aSelection) {
7730 // If the selection isn't collapsed, do nothing.
7731 if (!aSelection.Collapsed()) {
7732 return;
7733 }
7734
7735 NS_ENSURE_TRUE_VOID(mHTMLEditor);
7736 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
7737
7738 // Get the (collapsed) selection location
7739 nsRange* firstRange = aSelection.GetRangeAt(0);
7740 if (NS_WARN_IF(!firstRange)) {
7741 return;
7742 }
7743
7744 EditorDOMPoint atStartOfSelection(firstRange->StartRef());
7745 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
7746 return;
7747 }
7748 MOZ_ASSERT(atStartOfSelection.IsSetAndValid());
7749
7750 // First, let's check to see if we are after a <br>. We take care of this
7751 // special-case first so that we don't accidentally fall through into one of
7752 // the other conditionals.
7753 nsCOMPtr<nsIContent> node = htmlEditor->GetPreviousEditableHTMLNodeInBlock(
7754 atStartOfSelection.AsRaw());
7755 if (node && node->IsHTMLElement(nsGkAtoms::br)) {
7756 aSelection.SetInterlinePosition(true);
7757 return;
7758 }
7759
7760 // Are we after a block? If so try set caret to following content
7761 if (atStartOfSelection.GetChild()) {
7762 node = htmlEditor->GetPriorHTMLSibling(atStartOfSelection.GetChild());
7763 } else {
7764 node = nullptr;
7765 }
7766 if (node && IsBlockNode(*node)) {
7767 aSelection.SetInterlinePosition(true);
7768 return;
7769 }
7770
7771 // Are we before a block? If so try set caret to prior content
7772 if (atStartOfSelection.GetChild()) {
7773 node = htmlEditor->GetNextHTMLSibling(atStartOfSelection.GetChild());
7774 } else {
7775 node = nullptr;
7776 }
7777 if (node && IsBlockNode(*node)) {
7778 aSelection.SetInterlinePosition(false);
7779 }
7780 }
7781
AdjustSelection(Selection * aSelection,nsIEditor::EDirection aAction)7782 nsresult HTMLEditRules::AdjustSelection(Selection* aSelection,
7783 nsIEditor::EDirection aAction) {
7784 if (NS_WARN_IF(!aSelection)) {
7785 return NS_ERROR_INVALID_ARG;
7786 }
7787
7788 if (NS_WARN_IF(!mHTMLEditor)) {
7789 return NS_ERROR_NOT_AVAILABLE;
7790 }
7791 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
7792
7793 // if the selection isn't collapsed, do nothing.
7794 // moose: one thing to do instead is check for the case of
7795 // only a single break selected, and collapse it. Good thing? Beats me.
7796 if (!aSelection->Collapsed()) {
7797 return NS_OK;
7798 }
7799
7800 // get the (collapsed) selection location
7801 EditorDOMPoint point(EditorBase::GetStartPoint(aSelection));
7802 if (NS_WARN_IF(!point.IsSet())) {
7803 return NS_ERROR_FAILURE;
7804 }
7805
7806 // are we in an editable node?
7807 while (!htmlEditor->IsEditable(point.GetContainer())) {
7808 // scan up the tree until we find an editable place to be
7809 point.Set(point.GetContainer());
7810 if (NS_WARN_IF(!point.IsSet())) {
7811 return NS_ERROR_FAILURE;
7812 }
7813 }
7814
7815 // make sure we aren't in an empty block - user will see no cursor. If this
7816 // is happening, put a <br> in the block if allowed.
7817 RefPtr<Element> theblock = htmlEditor->GetBlock(*point.GetContainer());
7818
7819 if (theblock && htmlEditor->IsEditable(theblock)) {
7820 bool bIsEmptyNode;
7821 nsresult rv =
7822 htmlEditor->IsEmptyNode(theblock, &bIsEmptyNode, false, false);
7823 NS_ENSURE_SUCCESS(rv, rv);
7824 // check if br can go into the destination node
7825 if (bIsEmptyNode &&
7826 htmlEditor->CanContainTag(*point.GetContainer(), *nsGkAtoms::br)) {
7827 Element* rootElement = htmlEditor->GetRoot();
7828 if (NS_WARN_IF(!rootElement)) {
7829 return NS_ERROR_FAILURE;
7830 }
7831 if (point.GetContainer() == rootElement) {
7832 // Our root node is completely empty. Don't add a <br> here.
7833 // AfterEditInner() will add one for us when it calls
7834 // CreateBogusNodeIfNeeded()!
7835 return NS_OK;
7836 }
7837
7838 // we know we can skip the rest of this routine given the cirumstance
7839 RefPtr<Element> brElement = CreateMozBR(point.AsRaw());
7840 if (NS_WARN_IF(!brElement)) {
7841 return NS_ERROR_FAILURE;
7842 }
7843 return NS_OK;
7844 }
7845 }
7846
7847 // are we in a text node?
7848 if (point.IsInTextNode()) {
7849 return NS_OK; // we LIKE it when we are in a text node. that RULZ
7850 }
7851
7852 // do we need to insert a special mozBR? We do if we are:
7853 // 1) prior node is in same block where selection is AND
7854 // 2) prior node is a br AND
7855 // 3) that br is not visible
7856
7857 nsCOMPtr<nsIContent> nearNode =
7858 htmlEditor->GetPreviousEditableHTMLNode(point.AsRaw());
7859 if (nearNode) {
7860 // is nearNode also a descendant of same block?
7861 RefPtr<Element> block = htmlEditor->GetBlock(*point.GetContainer());
7862 RefPtr<Element> nearBlock = htmlEditor->GetBlockNodeParent(nearNode);
7863 if (block && block == nearBlock) {
7864 if (nearNode && TextEditUtils::IsBreak(nearNode)) {
7865 if (!htmlEditor->IsVisibleBRElement(nearNode)) {
7866 // need to insert special moz BR. Why? Because if we don't
7867 // the user will see no new line for the break. Also, things
7868 // like table cells won't grow in height.
7869 RefPtr<Element> brElement = CreateMozBR(point.AsRaw());
7870 if (NS_WARN_IF(!brElement)) {
7871 return NS_ERROR_FAILURE;
7872 }
7873 point.Set(brElement);
7874 // selection stays *before* moz-br, sticking to it
7875 aSelection->SetInterlinePosition(true);
7876 ErrorResult error;
7877 aSelection->Collapse(point.AsRaw(), error);
7878 if (NS_WARN_IF(error.Failed())) {
7879 return error.StealNSResult();
7880 }
7881 } else {
7882 nsCOMPtr<nsIContent> nextNode =
7883 htmlEditor->GetNextEditableHTMLNodeInBlock(*nearNode);
7884 if (nextNode && TextEditUtils::IsMozBR(nextNode)) {
7885 // selection between br and mozbr. make it stick to mozbr
7886 // so that it will be on blank line.
7887 aSelection->SetInterlinePosition(true);
7888 }
7889 }
7890 }
7891 }
7892 }
7893
7894 // we aren't in a textnode: are we adjacent to text or a break or an image?
7895 nearNode = htmlEditor->GetPreviousEditableHTMLNodeInBlock(point.AsRaw());
7896 if (nearNode &&
7897 (TextEditUtils::IsBreak(nearNode) || EditorBase::IsTextNode(nearNode) ||
7898 HTMLEditUtils::IsImage(nearNode) ||
7899 nearNode->IsHTMLElement(nsGkAtoms::hr))) {
7900 // this is a good place for the caret to be
7901 return NS_OK;
7902 }
7903 nearNode = htmlEditor->GetNextEditableHTMLNodeInBlock(point.AsRaw());
7904 if (nearNode &&
7905 (TextEditUtils::IsBreak(nearNode) || EditorBase::IsTextNode(nearNode) ||
7906 nearNode->IsAnyOfHTMLElements(nsGkAtoms::img, nsGkAtoms::hr))) {
7907 return NS_OK; // this is a good place for the caret to be
7908 }
7909
7910 // look for a nearby text node.
7911 // prefer the correct direction.
7912 nearNode = FindNearEditableNode(point.AsRaw(), aAction);
7913 if (!nearNode) {
7914 return NS_OK;
7915 }
7916
7917 EditorDOMPoint pt = GetGoodSelPointForNode(*nearNode, aAction);
7918 ErrorResult error;
7919 aSelection->Collapse(pt.AsRaw(), error);
7920 if (NS_WARN_IF(error.Failed())) {
7921 return error.StealNSResult();
7922 }
7923 return NS_OK;
7924 }
7925
FindNearEditableNode(const EditorRawDOMPoint & aPoint,nsIEditor::EDirection aDirection)7926 nsIContent* HTMLEditRules::FindNearEditableNode(
7927 const EditorRawDOMPoint& aPoint, nsIEditor::EDirection aDirection) {
7928 if (NS_WARN_IF(!aPoint.IsSet()) || NS_WARN_IF(!mHTMLEditor)) {
7929 return nullptr;
7930 }
7931 MOZ_ASSERT(aPoint.IsSetAndValid());
7932
7933 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
7934
7935 nsIContent* nearNode = nullptr;
7936 if (aDirection == nsIEditor::ePrevious) {
7937 nearNode = htmlEditor->GetPreviousEditableHTMLNode(aPoint);
7938 if (!nearNode) {
7939 return nullptr; // Not illegal.
7940 }
7941 } else {
7942 nearNode = htmlEditor->GetNextEditableHTMLNode(aPoint);
7943 if (NS_WARN_IF(!nearNode)) {
7944 // Perhaps, illegal because the node pointed by aPoint isn't editable
7945 // and nobody of previous nodes is editable.
7946 return nullptr;
7947 }
7948 }
7949
7950 // scan in the right direction until we find an eligible text node,
7951 // but don't cross any breaks, images, or table elements.
7952 // XXX This comment sounds odd. |nearNode| may have already crossed breaks
7953 // and/or images.
7954 while (nearNode && !(EditorBase::IsTextNode(nearNode) ||
7955 TextEditUtils::IsBreak(nearNode) ||
7956 HTMLEditUtils::IsImage(nearNode))) {
7957 if (aDirection == nsIEditor::ePrevious) {
7958 nearNode = htmlEditor->GetPreviousEditableHTMLNode(*nearNode);
7959 if (NS_WARN_IF(!nearNode)) {
7960 return nullptr;
7961 }
7962 } else {
7963 nearNode = htmlEditor->GetNextEditableHTMLNode(*nearNode);
7964 if (NS_WARN_IF(!nearNode)) {
7965 return nullptr;
7966 }
7967 }
7968 }
7969
7970 // don't cross any table elements
7971 if (InDifferentTableElements(nearNode, aPoint.GetContainer())) {
7972 return nullptr;
7973 }
7974
7975 // otherwise, ok, we have found a good spot to put the selection
7976 return nearNode;
7977 }
7978
InDifferentTableElements(nsIDOMNode * aNode1,nsIDOMNode * aNode2)7979 bool HTMLEditRules::InDifferentTableElements(nsIDOMNode* aNode1,
7980 nsIDOMNode* aNode2) {
7981 nsCOMPtr<nsINode> node1 = do_QueryInterface(aNode1);
7982 nsCOMPtr<nsINode> node2 = do_QueryInterface(aNode2);
7983 return InDifferentTableElements(node1, node2);
7984 }
7985
InDifferentTableElements(nsINode * aNode1,nsINode * aNode2)7986 bool HTMLEditRules::InDifferentTableElements(nsINode* aNode1, nsINode* aNode2) {
7987 MOZ_ASSERT(aNode1 && aNode2);
7988
7989 while (aNode1 && !HTMLEditUtils::IsTableElement(aNode1)) {
7990 aNode1 = aNode1->GetParentNode();
7991 }
7992
7993 while (aNode2 && !HTMLEditUtils::IsTableElement(aNode2)) {
7994 aNode2 = aNode2->GetParentNode();
7995 }
7996
7997 return aNode1 != aNode2;
7998 }
7999
RemoveEmptyNodes()8000 nsresult HTMLEditRules::RemoveEmptyNodes() {
8001 NS_ENSURE_STATE(mHTMLEditor);
8002 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
8003
8004 // Some general notes on the algorithm used here: the goal is to examine all
8005 // the nodes in mDocChangeRange, and remove the empty ones. We do this by
8006 // using a content iterator to traverse all the nodes in the range, and
8007 // placing the empty nodes into an array. After finishing the iteration, we
8008 // delete the empty nodes in the array. (They cannot be deleted as we find
8009 // them because that would invalidate the iterator.)
8010 //
8011 // Since checking to see if a node is empty can be costly for nodes with many
8012 // descendants, there are some optimizations made. I rely on the fact that
8013 // the iterator is post-order: it will visit children of a node before
8014 // visiting the parent node. So if I find that a child node is not empty, I
8015 // know that its parent is not empty without even checking. So I put the
8016 // parent on a "skipList" which is just a voidArray of nodes I can skip the
8017 // empty check on. If I encounter a node on the skiplist, i skip the
8018 // processing for that node and replace its slot in the skiplist with that
8019 // node's parent.
8020 //
8021 // An interesting idea is to go ahead and regard parent nodes that are NOT on
8022 // the skiplist as being empty (without even doing the IsEmptyNode check) on
8023 // the theory that if they weren't empty, we would have encountered a
8024 // non-empty child earlier and thus put this parent node on the skiplist.
8025 //
8026 // Unfortunately I can't use that strategy here, because the range may
8027 // include some children of a node while excluding others. Thus I could find
8028 // all the _examined_ children empty, but still not have an empty parent.
8029
8030 // need an iterator
8031 nsCOMPtr<nsIContentIterator> iter = NS_NewContentIterator();
8032
8033 nsresult rv = iter->Init(mDocChangeRange);
8034 NS_ENSURE_SUCCESS(rv, rv);
8035
8036 nsTArray<OwningNonNull<nsINode>> arrayOfEmptyNodes, arrayOfEmptyCites,
8037 skipList;
8038
8039 // Check for empty nodes
8040 while (!iter->IsDone()) {
8041 OwningNonNull<nsINode> node = *iter->GetCurrentNode();
8042
8043 nsCOMPtr<nsINode> parent = node->GetParentNode();
8044
8045 size_t idx = skipList.IndexOf(node);
8046 if (idx != skipList.NoIndex) {
8047 // This node is on our skip list. Skip processing for this node, and
8048 // replace its value in the skip list with the value of its parent
8049 if (parent) {
8050 skipList[idx] = parent;
8051 }
8052 } else {
8053 bool bIsCandidate = false;
8054 bool bIsEmptyNode = false;
8055 bool bIsMailCite = false;
8056
8057 if (node->IsElement()) {
8058 if (node->IsHTMLElement(nsGkAtoms::body)) {
8059 // Don't delete the body
8060 } else if ((bIsMailCite = HTMLEditUtils::IsMailCite(node)) ||
8061 node->IsHTMLElement(nsGkAtoms::a) ||
8062 HTMLEditUtils::IsInlineStyle(node) ||
8063 HTMLEditUtils::IsList(node) ||
8064 node->IsHTMLElement(nsGkAtoms::div)) {
8065 // Only consider certain nodes to be empty for purposes of removal
8066 bIsCandidate = true;
8067 } else if (HTMLEditUtils::IsFormatNode(node) ||
8068 HTMLEditUtils::IsListItem(node) ||
8069 node->IsHTMLElement(nsGkAtoms::blockquote)) {
8070 // These node types are candidates if selection is not in them. If
8071 // it is one of these, don't delete if selection inside. This is so
8072 // we can create empty headings, etc., for the user to type into.
8073 bool bIsSelInNode;
8074 rv = SelectionEndpointInNode(node, &bIsSelInNode);
8075 NS_ENSURE_SUCCESS(rv, rv);
8076 if (!bIsSelInNode) {
8077 bIsCandidate = true;
8078 }
8079 }
8080 }
8081
8082 if (bIsCandidate) {
8083 // We delete mailcites even if they have a solo br in them. Other
8084 // nodes we require to be empty.
8085 rv = htmlEditor->IsEmptyNode(node->AsDOMNode(), &bIsEmptyNode,
8086 bIsMailCite, true);
8087 NS_ENSURE_SUCCESS(rv, rv);
8088 if (bIsEmptyNode) {
8089 if (bIsMailCite) {
8090 // mailcites go on a separate list from other empty nodes
8091 arrayOfEmptyCites.AppendElement(*node);
8092 } else {
8093 arrayOfEmptyNodes.AppendElement(*node);
8094 }
8095 }
8096 }
8097
8098 if (!bIsEmptyNode && parent) {
8099 // put parent on skip list
8100 skipList.AppendElement(*parent);
8101 }
8102 }
8103
8104 iter->Next();
8105 }
8106
8107 // now delete the empty nodes
8108 for (OwningNonNull<nsINode>& delNode : arrayOfEmptyNodes) {
8109 if (htmlEditor->IsModifiableNode(delNode)) {
8110 rv = htmlEditor->DeleteNode(delNode);
8111 NS_ENSURE_SUCCESS(rv, rv);
8112 }
8113 }
8114
8115 // Now delete the empty mailcites. This is a separate step because we want
8116 // to pull out any br's and preserve them.
8117 for (OwningNonNull<nsINode>& delNode : arrayOfEmptyCites) {
8118 bool bIsEmptyNode;
8119 rv = htmlEditor->IsEmptyNode(delNode, &bIsEmptyNode, false, true);
8120 NS_ENSURE_SUCCESS(rv, rv);
8121 if (!bIsEmptyNode) {
8122 // We are deleting a cite that has just a br. We want to delete cite,
8123 // but preserve br.
8124 RefPtr<Element> br = htmlEditor->CreateBR(EditorRawDOMPoint(delNode));
8125 if (NS_WARN_IF(!br)) {
8126 return NS_ERROR_FAILURE;
8127 }
8128 }
8129 rv = htmlEditor->DeleteNode(delNode);
8130 NS_ENSURE_SUCCESS(rv, rv);
8131 }
8132
8133 return NS_OK;
8134 }
8135
SelectionEndpointInNode(nsINode * aNode,bool * aResult)8136 nsresult HTMLEditRules::SelectionEndpointInNode(nsINode* aNode, bool* aResult) {
8137 NS_ENSURE_TRUE(aNode && aResult, NS_ERROR_NULL_POINTER);
8138
8139 *aResult = false;
8140
8141 NS_ENSURE_STATE(mHTMLEditor);
8142 RefPtr<Selection> selection = mHTMLEditor->GetSelection();
8143 NS_ENSURE_STATE(selection);
8144
8145 uint32_t rangeCount = selection->RangeCount();
8146 for (uint32_t rangeIdx = 0; rangeIdx < rangeCount; ++rangeIdx) {
8147 RefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
8148 nsINode* startContainer = range->GetStartContainer();
8149 if (startContainer) {
8150 if (aNode == startContainer) {
8151 *aResult = true;
8152 return NS_OK;
8153 }
8154 if (EditorUtils::IsDescendantOf(*startContainer, *aNode)) {
8155 *aResult = true;
8156 return NS_OK;
8157 }
8158 }
8159 nsINode* endContainer = range->GetEndContainer();
8160 if (startContainer == endContainer) {
8161 continue;
8162 }
8163 if (endContainer) {
8164 if (aNode == endContainer) {
8165 *aResult = true;
8166 return NS_OK;
8167 }
8168 if (EditorUtils::IsDescendantOf(*endContainer, *aNode)) {
8169 *aResult = true;
8170 return NS_OK;
8171 }
8172 }
8173 }
8174 return NS_OK;
8175 }
8176
8177 /**
8178 * IsEmptyInline: Return true if aNode is an empty inline container
8179 */
IsEmptyInline(nsINode & aNode)8180 bool HTMLEditRules::IsEmptyInline(nsINode& aNode) {
8181 NS_ENSURE_TRUE(mHTMLEditor, false);
8182 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
8183
8184 if (IsInlineNode(aNode) && htmlEditor->IsContainer(&aNode)) {
8185 bool isEmpty = true;
8186 htmlEditor->IsEmptyNode(&aNode, &isEmpty);
8187 return isEmpty;
8188 }
8189 return false;
8190 }
8191
ListIsEmptyLine(nsTArray<OwningNonNull<nsINode>> & aArrayOfNodes)8192 bool HTMLEditRules::ListIsEmptyLine(
8193 nsTArray<OwningNonNull<nsINode>>& aArrayOfNodes) {
8194 // We have a list of nodes which we are candidates for being moved into a new
8195 // block. Determine if it's anything more than a blank line. Look for
8196 // editable content above and beyond one single BR.
8197 NS_ENSURE_TRUE(aArrayOfNodes.Length(), true);
8198
8199 NS_ENSURE_TRUE(mHTMLEditor, false);
8200 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
8201
8202 int32_t brCount = 0;
8203
8204 for (auto& node : aArrayOfNodes) {
8205 if (!htmlEditor->IsEditable(node)) {
8206 continue;
8207 }
8208 if (TextEditUtils::IsBreak(node)) {
8209 // First break doesn't count
8210 if (brCount) {
8211 return false;
8212 }
8213 brCount++;
8214 } else if (IsEmptyInline(node)) {
8215 // Empty inline, keep looking
8216 } else {
8217 return false;
8218 }
8219 }
8220 return true;
8221 }
8222
PopListItem(nsIContent & aListItem,bool * aOutOfList)8223 nsresult HTMLEditRules::PopListItem(nsIContent& aListItem, bool* aOutOfList) {
8224 // init out params
8225 if (aOutOfList) {
8226 *aOutOfList = false;
8227 }
8228
8229 nsCOMPtr<nsIContent> kungFuDeathGrip(&aListItem);
8230 Unused << kungFuDeathGrip;
8231
8232 if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!aListItem.GetParent()) ||
8233 NS_WARN_IF(!aListItem.GetParent()->GetParentNode()) ||
8234 !HTMLEditUtils::IsListItem(&aListItem)) {
8235 return NS_ERROR_FAILURE;
8236 }
8237
8238 // if it's first or last list item, don't need to split the list
8239 // otherwise we do.
8240 bool bIsFirstListItem = mHTMLEditor->IsFirstEditableChild(&aListItem);
8241 MOZ_ASSERT(mHTMLEditor);
8242 bool bIsLastListItem = mHTMLEditor->IsLastEditableChild(&aListItem);
8243 MOZ_ASSERT(mHTMLEditor);
8244
8245 nsCOMPtr<nsIContent> leftListNode = aListItem.GetParent();
8246 if (!bIsFirstListItem && !bIsLastListItem) {
8247 if (NS_WARN_IF(!mHTMLEditor)) {
8248 return NS_ERROR_FAILURE;
8249 }
8250
8251 EditorDOMPoint atListItem(&aListItem);
8252 if (NS_WARN_IF(!atListItem.IsSet())) {
8253 return NS_ERROR_INVALID_ARG;
8254 }
8255 MOZ_ASSERT(atListItem.IsSetAndValid());
8256
8257 // split the list
8258 ErrorResult error;
8259 leftListNode = mHTMLEditor->SplitNode(atListItem.AsRaw(), error);
8260 if (NS_WARN_IF(error.Failed())) {
8261 return error.StealNSResult();
8262 }
8263 if (NS_WARN_IF(!mHTMLEditor)) {
8264 return NS_ERROR_FAILURE;
8265 }
8266 }
8267
8268 // In most cases, insert the list item into the new left list node..
8269 EditorDOMPoint pointToInsertListItem(leftListNode);
8270 if (NS_WARN_IF(!pointToInsertListItem.IsSet())) {
8271 return NS_ERROR_FAILURE;
8272 }
8273 MOZ_ASSERT(pointToInsertListItem.IsSetAndValid());
8274
8275 // But when the list item was the first child of the right list, it should
8276 // be inserted into the next sibling of the list. This allows user to hit
8277 // Enter twice at a list item breaks the parent list node.
8278 if (!bIsFirstListItem) {
8279 DebugOnly<bool> advanced = pointToInsertListItem.AdvanceOffset();
8280 NS_WARNING_ASSERTION(advanced,
8281 "Failed to advance offset to right list node");
8282 }
8283
8284 nsresult rv =
8285 mHTMLEditor->MoveNode(&aListItem, pointToInsertListItem.GetContainer(),
8286 pointToInsertListItem.Offset());
8287 NS_ENSURE_SUCCESS(rv, rv);
8288
8289 // unwrap list item contents if they are no longer in a list
8290 if (!HTMLEditUtils::IsList(pointToInsertListItem.GetContainer()) &&
8291 HTMLEditUtils::IsListItem(&aListItem)) {
8292 NS_ENSURE_STATE(mHTMLEditor);
8293 rv = mHTMLEditor->RemoveBlockContainer(*aListItem.AsElement());
8294 NS_ENSURE_SUCCESS(rv, rv);
8295 if (aOutOfList) {
8296 *aOutOfList = true;
8297 }
8298 }
8299 return NS_OK;
8300 }
8301
RemoveListStructure(Element & aList)8302 nsresult HTMLEditRules::RemoveListStructure(Element& aList) {
8303 NS_ENSURE_STATE(mHTMLEditor);
8304 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
8305 while (aList.GetFirstChild()) {
8306 OwningNonNull<nsIContent> child = *aList.GetFirstChild();
8307
8308 if (HTMLEditUtils::IsListItem(child)) {
8309 bool isOutOfList;
8310 // Keep popping it out until it's not in a list anymore
8311 do {
8312 nsresult rv = PopListItem(child, &isOutOfList);
8313 NS_ENSURE_SUCCESS(rv, rv);
8314 } while (!isOutOfList);
8315 } else if (HTMLEditUtils::IsList(child)) {
8316 nsresult rv = RemoveListStructure(*child->AsElement());
8317 NS_ENSURE_SUCCESS(rv, rv);
8318 } else {
8319 // Delete any non-list items for now
8320 nsresult rv = htmlEditor->DeleteNode(child);
8321 NS_ENSURE_SUCCESS(rv, rv);
8322 }
8323 }
8324
8325 // Delete the now-empty list
8326 nsresult rv = htmlEditor->RemoveBlockContainer(aList);
8327 NS_ENSURE_SUCCESS(rv, rv);
8328
8329 return NS_OK;
8330 }
8331
ConfirmSelectionInBody()8332 nsresult HTMLEditRules::ConfirmSelectionInBody() {
8333 // get the body
8334 NS_ENSURE_STATE(mHTMLEditor);
8335 RefPtr<Element> rootElement = mHTMLEditor->GetRoot();
8336 if (NS_WARN_IF(!rootElement)) {
8337 return NS_ERROR_UNEXPECTED;
8338 }
8339
8340 // get the selection
8341 NS_ENSURE_STATE(mHTMLEditor);
8342 RefPtr<Selection> selection = mHTMLEditor->GetSelection();
8343 if (NS_WARN_IF(!selection)) {
8344 return NS_ERROR_UNEXPECTED;
8345 }
8346
8347 // get the selection start location
8348 nsCOMPtr<nsINode> selNode;
8349 int32_t selOffset;
8350 nsresult rv = EditorBase::GetStartNodeAndOffset(
8351 selection, getter_AddRefs(selNode), &selOffset);
8352 if (NS_FAILED(rv)) {
8353 return rv;
8354 }
8355
8356 nsINode* temp = selNode;
8357
8358 // check that selNode is inside body
8359 // XXXsmaug this code is insane.
8360 while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
8361 temp = temp->GetParentOrHostNode();
8362 }
8363
8364 // if we aren't in the body, force the issue
8365 if (!temp) {
8366 // uncomment this to see when we get bad selections
8367 // NS_NOTREACHED("selection not in body");
8368 selection->Collapse(rootElement, 0);
8369 return NS_OK;
8370 }
8371
8372 // get the selection end location
8373 rv = EditorBase::GetEndNodeAndOffset(selection, getter_AddRefs(selNode),
8374 &selOffset);
8375 NS_ENSURE_SUCCESS(rv, rv);
8376 temp = selNode;
8377
8378 // check that selNode is inside body
8379 // XXXsmaug this code is insane.
8380 while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) {
8381 temp = temp->GetParentOrHostNode();
8382 }
8383
8384 // if we aren't in the body, force the issue
8385 if (!temp) {
8386 // uncomment this to see when we get bad selections
8387 // NS_NOTREACHED("selection not in body");
8388 selection->Collapse(rootElement, 0);
8389 }
8390
8391 return NS_OK;
8392 }
8393
UpdateDocChangeRange(nsRange * aRange)8394 nsresult HTMLEditRules::UpdateDocChangeRange(nsRange* aRange) {
8395 if (NS_WARN_IF(!mHTMLEditor)) {
8396 return NS_ERROR_NOT_AVAILABLE;
8397 }
8398
8399 // first make sure aRange is in the document. It might not be if
8400 // portions of our editting action involved manipulating nodes
8401 // prior to placing them in the document (e.g., populating a list item
8402 // before placing it in its list)
8403 const RangeBoundary& atStart = aRange->StartRef();
8404 if (NS_WARN_IF(!atStart.IsSet())) {
8405 return NS_ERROR_FAILURE;
8406 }
8407 if (!mHTMLEditor->IsDescendantOfRoot(atStart.Container())) {
8408 // just return - we don't need to adjust mDocChangeRange in this case
8409 return NS_OK;
8410 }
8411
8412 if (!mDocChangeRange) {
8413 // clone aRange.
8414 mDocChangeRange = aRange->CloneRange();
8415 } else {
8416 int16_t result;
8417
8418 // compare starts of ranges
8419 nsresult rv = mDocChangeRange->CompareBoundaryPoints(
8420 nsIDOMRange::START_TO_START, aRange, &result);
8421 if (rv == NS_ERROR_NOT_INITIALIZED) {
8422 // This will happen is mDocChangeRange is non-null, but the range is
8423 // uninitialized. In this case we'll set the start to aRange start.
8424 // The same test won't be needed further down since after we've set
8425 // the start the range will be collapsed to that point.
8426 result = 1;
8427 rv = NS_OK;
8428 }
8429 NS_ENSURE_SUCCESS(rv, rv);
8430 // Positive result means mDocChangeRange start is after aRange start.
8431 if (result > 0) {
8432 ErrorResult error;
8433 mDocChangeRange->SetStart(atStart.AsRaw(), error);
8434 if (NS_WARN_IF(error.Failed())) {
8435 return error.StealNSResult();
8436 }
8437 }
8438
8439 // compare ends of ranges
8440 rv = mDocChangeRange->CompareBoundaryPoints(nsIDOMRange::END_TO_END, aRange,
8441 &result);
8442 NS_ENSURE_SUCCESS(rv, rv);
8443 // Negative result means mDocChangeRange end is before aRange end.
8444 if (result < 0) {
8445 const RangeBoundary& atEnd = aRange->EndRef();
8446 if (NS_WARN_IF(!atEnd.IsSet())) {
8447 return NS_ERROR_FAILURE;
8448 }
8449 ErrorResult error;
8450 mDocChangeRange->SetEnd(atEnd.AsRaw(), error);
8451 if (NS_WARN_IF(error.Failed())) {
8452 return error.StealNSResult();
8453 }
8454 }
8455 }
8456 return NS_OK;
8457 }
8458
InsertBRIfNeededInternal(nsINode & aNode,bool aInsertMozBR)8459 nsresult HTMLEditRules::InsertBRIfNeededInternal(nsINode& aNode,
8460 bool aInsertMozBR) {
8461 if (!IsBlockNode(aNode)) {
8462 return NS_OK;
8463 }
8464
8465 if (NS_WARN_IF(!mHTMLEditor)) {
8466 return NS_ERROR_UNEXPECTED;
8467 }
8468 bool isEmpty;
8469 nsresult rv = mHTMLEditor->IsEmptyNode(&aNode, &isEmpty);
8470 if (NS_WARN_IF(NS_FAILED(rv))) {
8471 return rv;
8472 }
8473 if (!isEmpty) {
8474 return NS_OK;
8475 }
8476
8477 RefPtr<Element> brElement =
8478 CreateBRInternal(EditorRawDOMPoint(&aNode, 0), aInsertMozBR);
8479 if (NS_WARN_IF(!brElement)) {
8480 return NS_ERROR_FAILURE;
8481 }
8482 return NS_OK;
8483 }
8484
DidCreateNode(Element * aNewElement)8485 void HTMLEditRules::DidCreateNode(Element* aNewElement) {
8486 if (!mListenerEnabled) {
8487 return;
8488 }
8489 if (NS_WARN_IF(!aNewElement)) {
8490 return;
8491 }
8492 // assumption that Join keeps the righthand node
8493 IgnoredErrorResult error;
8494 mUtilRange->SelectNode(*aNewElement, error);
8495 if (NS_WARN_IF(error.Failed())) {
8496 return;
8497 }
8498 UpdateDocChangeRange(mUtilRange);
8499 }
8500
DidInsertNode(nsIContent & aContent)8501 void HTMLEditRules::DidInsertNode(nsIContent& aContent) {
8502 if (!mListenerEnabled) {
8503 return;
8504 }
8505 IgnoredErrorResult error;
8506 mUtilRange->SelectNode(aContent, error);
8507 if (NS_WARN_IF(error.Failed())) {
8508 return;
8509 }
8510 UpdateDocChangeRange(mUtilRange);
8511 }
8512
WillDeleteNode(nsINode * aChild)8513 void HTMLEditRules::WillDeleteNode(nsINode* aChild) {
8514 if (!mListenerEnabled) {
8515 return;
8516 }
8517 if (NS_WARN_IF(!aChild)) {
8518 return;
8519 }
8520 IgnoredErrorResult error;
8521 mUtilRange->SelectNode(*aChild, error);
8522 if (NS_WARN_IF(error.Failed())) {
8523 return;
8524 }
8525 UpdateDocChangeRange(mUtilRange);
8526 }
8527
DidSplitNode(nsINode * aExistingRightNode,nsINode * aNewLeftNode)8528 void HTMLEditRules::DidSplitNode(nsINode* aExistingRightNode,
8529 nsINode* aNewLeftNode) {
8530 if (!mListenerEnabled) {
8531 return;
8532 }
8533 nsresult rv =
8534 mUtilRange->SetStartAndEnd(aNewLeftNode, 0, aExistingRightNode, 0);
8535 if (NS_WARN_IF(NS_FAILED(rv))) {
8536 return;
8537 }
8538 UpdateDocChangeRange(mUtilRange);
8539 }
8540
WillJoinNodes(nsINode & aLeftNode,nsINode & aRightNode)8541 void HTMLEditRules::WillJoinNodes(nsINode& aLeftNode, nsINode& aRightNode) {
8542 if (!mListenerEnabled) {
8543 return;
8544 }
8545 // remember split point
8546 mJoinOffset = aLeftNode.Length();
8547 }
8548
DidJoinNodes(nsINode & aLeftNode,nsINode & aRightNode)8549 void HTMLEditRules::DidJoinNodes(nsINode& aLeftNode, nsINode& aRightNode) {
8550 if (!mListenerEnabled) {
8551 return;
8552 }
8553 // assumption that Join keeps the righthand node
8554 nsresult rv = mUtilRange->CollapseTo(&aRightNode, mJoinOffset);
8555 if (NS_WARN_IF(NS_FAILED(rv))) {
8556 return;
8557 }
8558 UpdateDocChangeRange(mUtilRange);
8559 }
8560
DidInsertText(nsINode * aTextNode,int32_t aOffset,const nsAString & aString)8561 void HTMLEditRules::DidInsertText(nsINode* aTextNode, int32_t aOffset,
8562 const nsAString& aString) {
8563 if (!mListenerEnabled) {
8564 return;
8565 }
8566 int32_t length = aString.Length();
8567 nsresult rv = mUtilRange->SetStartAndEnd(aTextNode, aOffset, aTextNode,
8568 aOffset + length);
8569 if (NS_WARN_IF(NS_FAILED(rv))) {
8570 return;
8571 }
8572 UpdateDocChangeRange(mUtilRange);
8573 }
8574
DidDeleteText(nsINode * aTextNode,int32_t aOffset,int32_t aLength)8575 void HTMLEditRules::DidDeleteText(nsINode* aTextNode, int32_t aOffset,
8576 int32_t aLength) {
8577 if (!mListenerEnabled) {
8578 return;
8579 }
8580 nsresult rv = mUtilRange->CollapseTo(aTextNode, aOffset);
8581 if (NS_WARN_IF(NS_FAILED(rv))) {
8582 return;
8583 }
8584 UpdateDocChangeRange(mUtilRange);
8585 }
8586
WillDeleteSelection(Selection * aSelection)8587 void HTMLEditRules::WillDeleteSelection(Selection* aSelection) {
8588 if (!mListenerEnabled) {
8589 return;
8590 }
8591 if (NS_WARN_IF(!aSelection)) {
8592 return;
8593 }
8594 EditorRawDOMPoint startPoint = EditorBase::GetStartPoint(aSelection);
8595 if (NS_WARN_IF(!startPoint.IsSet())) {
8596 return;
8597 }
8598 EditorRawDOMPoint endPoint = EditorBase::GetEndPoint(aSelection);
8599 if (NS_WARN_IF(!endPoint.IsSet())) {
8600 return;
8601 }
8602 nsresult rv = mUtilRange->SetStartAndEnd(startPoint, endPoint);
8603 if (NS_WARN_IF(NS_FAILED(rv))) {
8604 return;
8605 }
8606 UpdateDocChangeRange(mUtilRange);
8607 }
8608
8609 // Let's remove all alignment hints in the children of aNode; it can
8610 // be an ALIGN attribute (in case we just remove it) or a CENTER
8611 // element (here we have to remove the container and keep its
8612 // children). We break on tables and don't look at their children.
RemoveAlignment(nsINode & aNode,const nsAString & aAlignType,bool aChildrenOnly)8613 nsresult HTMLEditRules::RemoveAlignment(nsINode& aNode,
8614 const nsAString& aAlignType,
8615 bool aChildrenOnly) {
8616 if (EditorBase::IsTextNode(&aNode) || HTMLEditUtils::IsTable(&aNode)) {
8617 return NS_OK;
8618 }
8619
8620 nsCOMPtr<nsINode> child, tmp;
8621 if (aChildrenOnly) {
8622 child = aNode.GetFirstChild();
8623 } else {
8624 child = &aNode;
8625 }
8626 NS_ENSURE_STATE(mHTMLEditor);
8627 bool useCSS = mHTMLEditor->IsCSSEnabled();
8628
8629 while (child) {
8630 if (aChildrenOnly) {
8631 // get the next sibling right now because we could have to remove child
8632 tmp = child->GetNextSibling();
8633 } else {
8634 tmp = nullptr;
8635 }
8636
8637 if (child->IsHTMLElement(nsGkAtoms::center)) {
8638 // the current node is a CENTER element
8639 // first remove children's alignment
8640 nsresult rv = RemoveAlignment(*child, aAlignType, true);
8641 NS_ENSURE_SUCCESS(rv, rv);
8642
8643 // we may have to insert BRs in first and last position of element's
8644 // children if the nodes before/after are not blocks and not BRs
8645 rv = MakeSureElemStartsOrEndsOnCR(*child);
8646 NS_ENSURE_SUCCESS(rv, rv);
8647
8648 // now remove the CENTER container
8649 NS_ENSURE_STATE(mHTMLEditor);
8650 rv = mHTMLEditor->RemoveContainer(child->AsElement());
8651 NS_ENSURE_SUCCESS(rv, rv);
8652 } else if (IsBlockNode(*child) || child->IsHTMLElement(nsGkAtoms::hr)) {
8653 // the current node is a block element
8654 if (HTMLEditUtils::SupportsAlignAttr(*child)) {
8655 // remove the ALIGN attribute if this element can have it
8656 NS_ENSURE_STATE(mHTMLEditor);
8657 nsresult rv =
8658 mHTMLEditor->RemoveAttribute(child->AsElement(), nsGkAtoms::align);
8659 NS_ENSURE_SUCCESS(rv, rv);
8660 }
8661 if (useCSS) {
8662 if (child->IsAnyOfHTMLElements(nsGkAtoms::table, nsGkAtoms::hr)) {
8663 NS_ENSURE_STATE(mHTMLEditor);
8664 nsresult rv = mHTMLEditor->SetAttributeOrEquivalent(
8665 child->AsElement(), nsGkAtoms::align, aAlignType, false);
8666 if (NS_WARN_IF(NS_FAILED(rv))) {
8667 return rv;
8668 }
8669 } else {
8670 nsAutoString dummyCssValue;
8671 NS_ENSURE_STATE(mHTMLEditor);
8672 nsresult rv = mHTMLEditor->mCSSEditUtils->RemoveCSSInlineStyle(
8673 *child, nsGkAtoms::textAlign, dummyCssValue);
8674 if (NS_WARN_IF(NS_FAILED(rv))) {
8675 return rv;
8676 }
8677 }
8678 }
8679 if (!child->IsHTMLElement(nsGkAtoms::table)) {
8680 // unless this is a table, look at children
8681 nsresult rv = RemoveAlignment(*child, aAlignType, true);
8682 NS_ENSURE_SUCCESS(rv, rv);
8683 }
8684 }
8685 child = tmp;
8686 }
8687 return NS_OK;
8688 }
8689
8690 // Let's insert a BR as first (resp. last) child of aNode if its
8691 // first (resp. last) child is not a block nor a BR, and if the
8692 // previous (resp. next) sibling is not a block nor a BR
MakeSureElemStartsOrEndsOnCR(nsINode & aNode,bool aStarts)8693 nsresult HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsINode& aNode,
8694 bool aStarts) {
8695 if (NS_WARN_IF(!mHTMLEditor)) {
8696 return NS_ERROR_NOT_AVAILABLE;
8697 }
8698 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
8699
8700 nsINode* child = aStarts ? htmlEditor->GetFirstEditableChild(aNode)
8701 : htmlEditor->GetLastEditableChild(aNode);
8702 if (NS_WARN_IF(!child)) {
8703 return NS_OK;
8704 }
8705
8706 bool foundCR = false;
8707 if (IsBlockNode(*child) || child->IsHTMLElement(nsGkAtoms::br)) {
8708 foundCR = true;
8709 } else {
8710 nsINode* sibling = aStarts ? htmlEditor->GetPriorHTMLSibling(&aNode)
8711 : htmlEditor->GetNextHTMLSibling(&aNode);
8712 if (sibling) {
8713 if (IsBlockNode(*sibling) || sibling->IsHTMLElement(nsGkAtoms::br)) {
8714 foundCR = true;
8715 }
8716 } else {
8717 foundCR = true;
8718 }
8719 }
8720 if (!foundCR) {
8721 EditorRawDOMPoint pointToInsert;
8722 if (!aStarts) {
8723 pointToInsert.SetToEndOf(&aNode);
8724 } else {
8725 pointToInsert.Set(&aNode, 0);
8726 }
8727 RefPtr<Element> brNode = htmlEditor->CreateBR(pointToInsert);
8728 if (NS_WARN_IF(!brNode)) {
8729 return NS_ERROR_FAILURE;
8730 }
8731 }
8732 return NS_OK;
8733 }
8734
MakeSureElemStartsOrEndsOnCR(nsINode & aNode)8735 nsresult HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsINode& aNode) {
8736 nsresult rv = MakeSureElemStartsOrEndsOnCR(aNode, false);
8737 NS_ENSURE_SUCCESS(rv, rv);
8738 return MakeSureElemStartsOrEndsOnCR(aNode, true);
8739 }
8740
AlignBlock(Element & aElement,const nsAString & aAlignType,ContentsOnly aContentsOnly)8741 nsresult HTMLEditRules::AlignBlock(Element& aElement,
8742 const nsAString& aAlignType,
8743 ContentsOnly aContentsOnly) {
8744 if (!IsBlockNode(aElement) && !aElement.IsHTMLElement(nsGkAtoms::hr)) {
8745 // We deal only with blocks; early way out
8746 return NS_OK;
8747 }
8748
8749 NS_ENSURE_STATE(mHTMLEditor);
8750 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
8751
8752 nsresult rv =
8753 RemoveAlignment(aElement, aAlignType, aContentsOnly == ContentsOnly::yes);
8754 NS_ENSURE_SUCCESS(rv, rv);
8755 if (htmlEditor->IsCSSEnabled()) {
8756 // Let's use CSS alignment; we use margin-left and margin-right for tables
8757 // and text-align for other block-level elements
8758 return htmlEditor->SetAttributeOrEquivalent(&aElement, nsGkAtoms::align,
8759 aAlignType, false);
8760 }
8761
8762 // HTML case; this code is supposed to be called ONLY if the element
8763 // supports the align attribute but we'll never know...
8764 if (NS_WARN_IF(!HTMLEditUtils::SupportsAlignAttr(aElement))) {
8765 // XXX error?
8766 return NS_OK;
8767 }
8768
8769 return htmlEditor->SetAttributeOrEquivalent(&aElement, nsGkAtoms::align,
8770 aAlignType, false);
8771 }
8772
ChangeIndentation(Element & aElement,Change aChange)8773 nsresult HTMLEditRules::ChangeIndentation(Element& aElement, Change aChange) {
8774 NS_ENSURE_STATE(mHTMLEditor);
8775 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
8776
8777 nsAtom& marginProperty = MarginPropertyAtomForIndent(aElement);
8778 nsAutoString value;
8779 CSSEditUtils::GetSpecifiedProperty(aElement, marginProperty, value);
8780 float f;
8781 RefPtr<nsAtom> unit;
8782 CSSEditUtils::ParseLength(value, &f, getter_AddRefs(unit));
8783 if (!f) {
8784 nsAutoString defaultLengthUnit;
8785 CSSEditUtils::GetDefaultLengthUnit(defaultLengthUnit);
8786 unit = NS_Atomize(defaultLengthUnit);
8787 }
8788 int8_t multiplier = aChange == Change::plus ? +1 : -1;
8789 if (nsGkAtoms::in == unit) {
8790 f += NS_EDITOR_INDENT_INCREMENT_IN * multiplier;
8791 } else if (nsGkAtoms::cm == unit) {
8792 f += NS_EDITOR_INDENT_INCREMENT_CM * multiplier;
8793 } else if (nsGkAtoms::mm == unit) {
8794 f += NS_EDITOR_INDENT_INCREMENT_MM * multiplier;
8795 } else if (nsGkAtoms::pt == unit) {
8796 f += NS_EDITOR_INDENT_INCREMENT_PT * multiplier;
8797 } else if (nsGkAtoms::pc == unit) {
8798 f += NS_EDITOR_INDENT_INCREMENT_PC * multiplier;
8799 } else if (nsGkAtoms::em == unit) {
8800 f += NS_EDITOR_INDENT_INCREMENT_EM * multiplier;
8801 } else if (nsGkAtoms::ex == unit) {
8802 f += NS_EDITOR_INDENT_INCREMENT_EX * multiplier;
8803 } else if (nsGkAtoms::px == unit) {
8804 f += NS_EDITOR_INDENT_INCREMENT_PX * multiplier;
8805 } else if (nsGkAtoms::percentage == unit) {
8806 f += NS_EDITOR_INDENT_INCREMENT_PERCENT * multiplier;
8807 }
8808
8809 if (0 < f) {
8810 nsAutoString newValue;
8811 newValue.AppendFloat(f);
8812 newValue.Append(nsDependentAtomString(unit));
8813 htmlEditor->mCSSEditUtils->SetCSSProperty(aElement, marginProperty,
8814 newValue);
8815 return NS_OK;
8816 }
8817
8818 htmlEditor->mCSSEditUtils->RemoveCSSProperty(aElement, marginProperty, value);
8819
8820 // Remove unnecessary divs
8821 if (!aElement.IsHTMLElement(nsGkAtoms::div) ||
8822 &aElement == htmlEditor->GetActiveEditingHost() ||
8823 !htmlEditor->IsDescendantOfEditorRoot(&aElement) ||
8824 HTMLEditor::HasAttributes(&aElement)) {
8825 return NS_OK;
8826 }
8827
8828 nsresult rv = htmlEditor->RemoveContainer(&aElement);
8829 NS_ENSURE_SUCCESS(rv, rv);
8830
8831 return NS_OK;
8832 }
8833
WillAbsolutePosition(Selection & aSelection,bool * aCancel,bool * aHandled)8834 nsresult HTMLEditRules::WillAbsolutePosition(Selection& aSelection,
8835 bool* aCancel, bool* aHandled) {
8836 MOZ_ASSERT(aCancel && aHandled);
8837
8838 if (NS_WARN_IF(!mHTMLEditor)) {
8839 return NS_ERROR_NOT_AVAILABLE;
8840 }
8841 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
8842
8843 WillInsert(aSelection, aCancel);
8844
8845 // We want to ignore result of WillInsert()
8846 *aCancel = false;
8847 *aHandled = true;
8848
8849 nsCOMPtr<Element> focusElement = htmlEditor->GetSelectionContainer();
8850 if (focusElement && HTMLEditUtils::IsImage(focusElement)) {
8851 mNewBlock = focusElement;
8852 return NS_OK;
8853 }
8854
8855 nsresult rv = NormalizeSelection(&aSelection);
8856 NS_ENSURE_SUCCESS(rv, rv);
8857 AutoSelectionRestorer selectionRestorer(&aSelection, htmlEditor);
8858
8859 // Convert the selection ranges into "promoted" selection ranges: this
8860 // basically just expands the range to include the immediate block parent,
8861 // and then further expands to include any ancestors whose children are all
8862 // in the range.
8863
8864 nsTArray<RefPtr<nsRange>> arrayOfRanges;
8865 GetPromotedRanges(aSelection, arrayOfRanges, EditAction::setAbsolutePosition);
8866
8867 // Use these ranges to contruct a list of nodes to act on.
8868 nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
8869 rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
8870 EditAction::setAbsolutePosition);
8871 NS_ENSURE_SUCCESS(rv, rv);
8872
8873 // If nothing visible in list, make an empty block
8874 if (ListIsEmptyLine(arrayOfNodes)) {
8875 nsRange* firstRange = aSelection.GetRangeAt(0);
8876 if (NS_WARN_IF(!firstRange)) {
8877 return NS_ERROR_FAILURE;
8878 }
8879
8880 EditorDOMPoint atStartOfSelection(firstRange->StartRef());
8881 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
8882 return NS_ERROR_FAILURE;
8883 }
8884
8885 // Make sure we can put a block here.
8886 SplitNodeResult splitNodeResult = MaybeSplitAncestorsForInsert(
8887 *nsGkAtoms::div, atStartOfSelection.AsRaw());
8888 if (NS_WARN_IF(splitNodeResult.Failed())) {
8889 return splitNodeResult.Rv();
8890 }
8891 RefPtr<Element> positionedDiv =
8892 htmlEditor->CreateNode(nsGkAtoms::div, splitNodeResult.SplitPoint());
8893 NS_ENSURE_STATE(positionedDiv);
8894 // Remember our new block for postprocessing
8895 mNewBlock = positionedDiv;
8896 // Delete anything that was in the list of nodes
8897 while (!arrayOfNodes.IsEmpty()) {
8898 OwningNonNull<nsINode> curNode = arrayOfNodes[0];
8899 rv = htmlEditor->DeleteNode(curNode);
8900 NS_ENSURE_SUCCESS(rv, rv);
8901 arrayOfNodes.RemoveElementAt(0);
8902 }
8903 // Put selection in new block
8904 *aHandled = true;
8905 rv = aSelection.Collapse(positionedDiv, 0);
8906 // Don't restore the selection
8907 selectionRestorer.Abort();
8908 NS_ENSURE_SUCCESS(rv, rv);
8909 return NS_OK;
8910 }
8911
8912 // Okay, now go through all the nodes and put them in a blockquote, or
8913 // whatever is appropriate. Woohoo!
8914 nsCOMPtr<Element> curList, curPositionedDiv, indentedLI;
8915 for (OwningNonNull<nsINode>& curNode : arrayOfNodes) {
8916 // Here's where we actually figure out what to do.
8917 EditorDOMPoint atCurNode(curNode);
8918 if (NS_WARN_IF(!atCurNode.IsSet())) {
8919 return NS_ERROR_FAILURE; // XXX not continue??
8920 }
8921
8922 // Ignore all non-editable nodes. Leave them be.
8923 if (!htmlEditor->IsEditable(curNode)) {
8924 continue;
8925 }
8926
8927 nsCOMPtr<nsIContent> sibling;
8928
8929 // Some logic for putting list items into nested lists...
8930 if (HTMLEditUtils::IsList(atCurNode.GetContainer())) {
8931 // Check to see if curList is still appropriate. Which it is if curNode
8932 // is still right after it in the same list.
8933 if (curList) {
8934 sibling = htmlEditor->GetPriorHTMLSibling(curNode);
8935 }
8936
8937 if (!curList || (sibling && sibling != curList)) {
8938 nsAtom* containerName =
8939 atCurNode.GetContainer()->NodeInfo()->NameAtom();
8940 // Create a new nested list of correct type.
8941 SplitNodeResult splitNodeResult =
8942 MaybeSplitAncestorsForInsert(*containerName, atCurNode.AsRaw());
8943 if (NS_WARN_IF(splitNodeResult.Failed())) {
8944 return splitNodeResult.Rv();
8945 }
8946 if (!curPositionedDiv) {
8947 curPositionedDiv = htmlEditor->CreateNode(
8948 nsGkAtoms::div, splitNodeResult.SplitPoint());
8949 mNewBlock = curPositionedDiv;
8950 }
8951 EditorRawDOMPoint atEndOfCurPositionedDiv;
8952 atEndOfCurPositionedDiv.SetToEndOf(curPositionedDiv);
8953 curList =
8954 htmlEditor->CreateNode(containerName, atEndOfCurPositionedDiv);
8955 NS_ENSURE_STATE(curList);
8956 // curList is now the correct thing to put curNode in. Remember our
8957 // new block for postprocessing.
8958 }
8959 // Tuck the node into the end of the active list
8960 rv = htmlEditor->MoveNode(curNode->AsContent(), curList, -1);
8961 NS_ENSURE_SUCCESS(rv, rv);
8962 continue;
8963 }
8964
8965 // Not a list item, use blockquote? If we are inside a list item, we
8966 // don't want to blockquote, we want to sublist the list item. We may
8967 // have several nodes listed in the array of nodes to act on, that are in
8968 // the same list item. Since we only want to indent that li once, we
8969 // must keep track of the most recent indented list item, and not indent
8970 // it if we find another node to act on that is still inside the same li.
8971 RefPtr<Element> listItem = IsInListItem(curNode);
8972 if (listItem) {
8973 if (indentedLI == listItem) {
8974 // Already indented this list item
8975 continue;
8976 }
8977 // Check to see if curList is still appropriate. Which it is if
8978 // curNode is still right after it in the same list.
8979 if (curList) {
8980 sibling = htmlEditor->GetPriorHTMLSibling(listItem);
8981 }
8982
8983 if (!curList || (sibling && sibling != curList)) {
8984 EditorDOMPoint atListItem(listItem);
8985 if (NS_WARN_IF(!atListItem.IsSet())) {
8986 return NS_ERROR_FAILURE;
8987 }
8988 nsAtom* containerName =
8989 atListItem.GetContainer()->NodeInfo()->NameAtom();
8990 // Create a new nested list of correct type
8991 SplitNodeResult splitNodeResult =
8992 MaybeSplitAncestorsForInsert(*containerName, atListItem.AsRaw());
8993 if (NS_WARN_IF(splitNodeResult.Failed())) {
8994 return splitNodeResult.Rv();
8995 }
8996 if (!curPositionedDiv) {
8997 EditorRawDOMPoint atListItemParent(atListItem.GetContainer());
8998 curPositionedDiv =
8999 htmlEditor->CreateNode(nsGkAtoms::div, atListItemParent);
9000 mNewBlock = curPositionedDiv;
9001 }
9002 EditorRawDOMPoint atEndOfCurPositionedDiv;
9003 atEndOfCurPositionedDiv.SetToEndOf(curPositionedDiv);
9004 curList =
9005 htmlEditor->CreateNode(containerName, atEndOfCurPositionedDiv);
9006 NS_ENSURE_STATE(curList);
9007 }
9008 rv = htmlEditor->MoveNode(listItem, curList, -1);
9009 NS_ENSURE_SUCCESS(rv, rv);
9010 // Remember we indented this li
9011 indentedLI = listItem;
9012 continue;
9013 }
9014
9015 // Need to make a div to put things in if we haven't already
9016 if (!curPositionedDiv) {
9017 if (curNode->IsHTMLElement(nsGkAtoms::div)) {
9018 curPositionedDiv = curNode->AsElement();
9019 mNewBlock = curPositionedDiv;
9020 curList = nullptr;
9021 continue;
9022 }
9023 SplitNodeResult splitNodeResult =
9024 MaybeSplitAncestorsForInsert(*nsGkAtoms::div, atCurNode.AsRaw());
9025 if (NS_WARN_IF(splitNodeResult.Failed())) {
9026 return splitNodeResult.Rv();
9027 }
9028 curPositionedDiv =
9029 htmlEditor->CreateNode(nsGkAtoms::div, splitNodeResult.SplitPoint());
9030 NS_ENSURE_STATE(curPositionedDiv);
9031 // Remember our new block for postprocessing
9032 mNewBlock = curPositionedDiv;
9033 // curPositionedDiv is now the correct thing to put curNode in
9034 }
9035
9036 // Tuck the node into the end of the active blockquote
9037 rv = htmlEditor->MoveNode(curNode->AsContent(), curPositionedDiv, -1);
9038 NS_ENSURE_SUCCESS(rv, rv);
9039 // Forget curList, if any
9040 curList = nullptr;
9041 }
9042 return NS_OK;
9043 }
9044
DidAbsolutePosition()9045 nsresult HTMLEditRules::DidAbsolutePosition() {
9046 if (!mNewBlock) {
9047 return NS_OK;
9048 }
9049 if (NS_WARN_IF(!mHTMLEditor)) {
9050 return NS_ERROR_NOT_AVAILABLE;
9051 }
9052 RefPtr<HTMLEditor> htmlEditor = mHTMLEditor;
9053 return htmlEditor->SetPositionToAbsoluteOrStatic(*mNewBlock, true);
9054 }
9055
WillRemoveAbsolutePosition(Selection * aSelection,bool * aCancel,bool * aHandled)9056 nsresult HTMLEditRules::WillRemoveAbsolutePosition(Selection* aSelection,
9057 bool* aCancel,
9058 bool* aHandled) {
9059 if (!aSelection || !aCancel || !aHandled) {
9060 return NS_ERROR_NULL_POINTER;
9061 }
9062
9063 if (NS_WARN_IF(!mHTMLEditor)) {
9064 return NS_ERROR_NOT_AVAILABLE;
9065 }
9066 RefPtr<HTMLEditor> htmlEditor = mHTMLEditor;
9067
9068 WillInsert(*aSelection, aCancel);
9069
9070 // initialize out param
9071 // we want to ignore aCancel from WillInsert()
9072 *aCancel = false;
9073 *aHandled = true;
9074
9075 RefPtr<Element> element =
9076 htmlEditor->GetAbsolutelyPositionedSelectionContainer();
9077 if (NS_WARN_IF(!element)) {
9078 return NS_ERROR_FAILURE;
9079 }
9080
9081 AutoSelectionRestorer selectionRestorer(aSelection, htmlEditor);
9082
9083 return htmlEditor->SetPositionToAbsoluteOrStatic(*element, false);
9084 }
9085
WillRelativeChangeZIndex(Selection * aSelection,int32_t aChange,bool * aCancel,bool * aHandled)9086 nsresult HTMLEditRules::WillRelativeChangeZIndex(Selection* aSelection,
9087 int32_t aChange, bool* aCancel,
9088 bool* aHandled) {
9089 if (!aSelection || !aCancel || !aHandled) {
9090 return NS_ERROR_NULL_POINTER;
9091 }
9092
9093 if (NS_WARN_IF(!mHTMLEditor)) {
9094 return NS_ERROR_NOT_AVAILABLE;
9095 }
9096 RefPtr<HTMLEditor> htmlEditor = mHTMLEditor;
9097
9098 WillInsert(*aSelection, aCancel);
9099
9100 // initialize out param
9101 // we want to ignore aCancel from WillInsert()
9102 *aCancel = false;
9103 *aHandled = true;
9104
9105 RefPtr<Element> element =
9106 htmlEditor->GetAbsolutelyPositionedSelectionContainer();
9107 if (NS_WARN_IF(!element)) {
9108 return NS_ERROR_FAILURE;
9109 }
9110
9111 AutoSelectionRestorer selectionRestorer(aSelection, htmlEditor);
9112
9113 int32_t zIndex;
9114 return htmlEditor->RelativeChangeElementZIndex(*element, aChange, &zIndex);
9115 }
9116
DocumentModified()9117 nsresult HTMLEditRules::DocumentModified() {
9118 nsContentUtils::AddScriptRunner(
9119 NewRunnableMethod("HTMLEditRules::DocumentModifiedWorker", this,
9120 &HTMLEditRules::DocumentModifiedWorker));
9121 return NS_OK;
9122 }
9123
DocumentModifiedWorker()9124 void HTMLEditRules::DocumentModifiedWorker() {
9125 if (!mHTMLEditor) {
9126 return;
9127 }
9128
9129 // DeleteNode below may cause a flush, which could destroy the editor
9130 nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
9131
9132 RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
9133 RefPtr<Selection> selection = htmlEditor->GetSelection();
9134 if (!selection) {
9135 return;
9136 }
9137
9138 // Delete our bogus node, if we have one, since the document might not be
9139 // empty any more.
9140 if (mBogusNode) {
9141 htmlEditor->DeleteNode(mBogusNode);
9142 mBogusNode = nullptr;
9143 }
9144
9145 // Try to recreate the bogus node if needed.
9146 CreateBogusNodeIfNeeded(selection);
9147 }
9148
9149 } // namespace mozilla
9150