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