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           &paraAtom == nsGkAtoms::br ? nsGkAtoms::p : &paraAtom, 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           &paraAtom == nsGkAtoms::br ? nsGkAtoms::p : &paraAtom,
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