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