Bug 1669129 - [devtools] Enable devtools.overflow.debugging.enabled. r=jdescottes
[gecko.git] / editor / libeditor / HTMLEditor.cpp
blobcf9acf79d53bf4bdfe6b96e096053b964c282d81
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "HTMLEditor.h"
8 #include "HTMLEditorEventListener.h"
9 #include "HTMLEditUtils.h"
10 #include "JoinNodeTransaction.h"
11 #include "ReplaceTextTransaction.h"
12 #include "SplitNodeTransaction.h"
13 #include "TypeInState.h"
14 #include "WSRunObject.h"
16 #include "mozilla/ComposerCommandsUpdater.h"
17 #include "mozilla/ContentIterator.h"
18 #include "mozilla/DebugOnly.h"
19 #include "mozilla/EditAction.h"
20 #include "mozilla/EditorDOMPoint.h"
21 #include "mozilla/EditorUtils.h"
22 #include "mozilla/EventStates.h"
23 #include "mozilla/InternalMutationEvent.h"
24 #include "mozilla/mozInlineSpellChecker.h"
25 #include "mozilla/PresShell.h"
26 #include "mozilla/StaticPrefs_editor.h"
27 #include "mozilla/StyleSheet.h"
28 #include "mozilla/StyleSheetInlines.h"
29 #include "mozilla/TextEvents.h"
30 #include "mozilla/TextServicesDocument.h"
31 #include "mozilla/css/Loader.h"
32 #include "mozilla/dom/AncestorIterator.h"
33 #include "mozilla/dom/Attr.h"
34 #include "mozilla/dom/DocumentFragment.h"
35 #include "mozilla/dom/DocumentInlines.h"
36 #include "mozilla/dom/Element.h"
37 #include "mozilla/dom/Event.h"
38 #include "mozilla/dom/EventTarget.h"
39 #include "mozilla/dom/HTMLAnchorElement.h"
40 #include "mozilla/dom/HTMLBodyElement.h"
41 #include "mozilla/dom/Selection.h"
43 #include "nsContentList.h"
44 #include "nsContentUtils.h"
45 #include "nsCRT.h"
46 #include "nsElementTable.h"
47 #include "nsFocusManager.h"
48 #include "nsGenericHTMLElement.h"
49 #include "nsGkAtoms.h"
50 #include "nsHTMLDocument.h"
51 #include "nsIContent.h"
52 #include "nsIEditActionListener.h"
53 #include "nsIFrame.h"
54 #include "nsIPrincipal.h"
55 #include "nsISelectionController.h"
56 #include "nsIURI.h"
57 #include "nsIWidget.h"
58 #include "nsNetUtil.h"
59 #include "nsPresContext.h"
60 #include "nsPIDOMWindow.h"
61 #include "nsStyledElement.h"
62 #include "nsTextFragment.h"
63 #include "nsUnicharUtils.h"
65 namespace mozilla {
67 using namespace dom;
68 using namespace widget;
70 using ChildBlockBoundary = HTMLEditUtils::ChildBlockBoundary;
72 const char16_t kNBSP = 160;
74 // Some utilities to handle overloading of "A" tag for link and named anchor.
75 static bool IsLinkTag(const nsAtom& aTagName) {
76 return &aTagName == nsGkAtoms::href;
79 static bool IsNamedAnchorTag(const nsAtom& aTagName) {
80 return &aTagName == nsGkAtoms::anchor;
83 HTMLEditor::HTMLEditor()
84 : mCRInParagraphCreatesParagraph(false),
85 mCSSAware(false),
86 mSelectedCellIndex(0),
87 mIsObjectResizingEnabled(
88 StaticPrefs::editor_resizing_enabled_by_default()),
89 mIsResizing(false),
90 mPreserveRatio(false),
91 mResizedObjectIsAnImage(false),
92 mIsAbsolutelyPositioningEnabled(
93 StaticPrefs::editor_positioning_enabled_by_default()),
94 mResizedObjectIsAbsolutelyPositioned(false),
95 mGrabberClicked(false),
96 mIsMoving(false),
97 mSnapToGridEnabled(false),
98 mIsInlineTableEditingEnabled(
99 StaticPrefs::editor_inline_table_editing_enabled_by_default()),
100 mOriginalX(0),
101 mOriginalY(0),
102 mResizedObjectX(0),
103 mResizedObjectY(0),
104 mResizedObjectWidth(0),
105 mResizedObjectHeight(0),
106 mResizedObjectMarginLeft(0),
107 mResizedObjectMarginTop(0),
108 mResizedObjectBorderLeft(0),
109 mResizedObjectBorderTop(0),
110 mXIncrementFactor(0),
111 mYIncrementFactor(0),
112 mWidthIncrementFactor(0),
113 mHeightIncrementFactor(0),
114 mInfoXIncrement(20),
115 mInfoYIncrement(20),
116 mPositionedObjectX(0),
117 mPositionedObjectY(0),
118 mPositionedObjectWidth(0),
119 mPositionedObjectHeight(0),
120 mPositionedObjectMarginLeft(0),
121 mPositionedObjectMarginTop(0),
122 mPositionedObjectBorderLeft(0),
123 mPositionedObjectBorderTop(0),
124 mGridSize(0),
125 mDefaultParagraphSeparator(
126 Preferences::GetBool("editor.use_div_for_default_newlines", true)
127 ? ParagraphSeparator::div
128 : ParagraphSeparator::br) {
129 mIsHTMLEditorClass = true;
132 HTMLEditor::~HTMLEditor() {
133 mTypeInState = nullptr;
135 if (mDisabledLinkHandling) {
136 if (Document* doc = GetDocument()) {
137 doc->SetLinkHandlingEnabled(mOldLinkHandlingEnabled);
141 RemoveEventListeners();
143 HideAnonymousEditingUIs();
146 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLEditor)
148 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLEditor, TextEditor)
149 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTypeInState)
150 NS_IMPL_CYCLE_COLLECTION_UNLINK(mComposerCommandsUpdater)
151 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChangedRangeForTopLevelEditSubAction)
152 tmp->HideAnonymousEditingUIs();
153 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
155 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLEditor, TextEditor)
156 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTypeInState)
157 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mComposerCommandsUpdater)
158 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChangedRangeForTopLevelEditSubAction)
160 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopLeftHandle)
161 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopHandle)
162 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopRightHandle)
163 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLeftHandle)
164 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRightHandle)
165 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomLeftHandle)
166 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomHandle)
167 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomRightHandle)
168 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActivatedHandle)
169 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingShadow)
170 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingInfo)
171 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizedObject)
173 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbsolutelyPositionedObject)
174 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGrabber)
175 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPositioningShadow)
177 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineEditedCell)
178 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnBeforeButton)
179 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveColumnButton)
180 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnAfterButton)
181 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowBeforeButton)
182 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveRowButton)
183 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowAfterButton)
184 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
186 NS_IMPL_ADDREF_INHERITED(HTMLEditor, EditorBase)
187 NS_IMPL_RELEASE_INHERITED(HTMLEditor, EditorBase)
189 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLEditor)
190 NS_INTERFACE_MAP_ENTRY(nsIHTMLEditor)
191 NS_INTERFACE_MAP_ENTRY(nsIHTMLObjectResizer)
192 NS_INTERFACE_MAP_ENTRY(nsIHTMLAbsPosEditor)
193 NS_INTERFACE_MAP_ENTRY(nsIHTMLInlineTableEditor)
194 NS_INTERFACE_MAP_ENTRY(nsITableEditor)
195 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
196 NS_INTERFACE_MAP_ENTRY(nsIEditorMailSupport)
197 NS_INTERFACE_MAP_END_INHERITING(TextEditor)
199 nsresult HTMLEditor::Init(Document& aDoc, Element* aRoot,
200 nsISelectionController* aSelCon, uint32_t aFlags,
201 const nsAString& aInitialValue) {
202 MOZ_ASSERT(!mInitSucceeded,
203 "HTMLEditor::Init() called again without calling PreDestroy()?");
204 MOZ_ASSERT(aInitialValue.IsEmpty(), "Non-empty initial values not supported");
206 nsresult rv = EditorBase::Init(aDoc, aRoot, nullptr, aFlags, aInitialValue);
207 if (NS_FAILED(rv)) {
208 NS_WARNING("EditorBase::Init() failed");
209 return rv;
212 // Init mutation observer
213 aDoc.AddMutationObserverUnlessExists(this);
215 if (!mRootElement) {
216 UpdateRootElement();
219 // disable Composer-only features
220 if (IsMailEditor()) {
221 DebugOnly<nsresult> rvIgnored = SetAbsolutePositioningEnabled(false);
222 NS_WARNING_ASSERTION(
223 NS_SUCCEEDED(rvIgnored),
224 "HTMLEditor::SetAbsolutePositioningEnabled(false) failed, but ignored");
225 rvIgnored = SetSnapToGridEnabled(false);
226 NS_WARNING_ASSERTION(
227 NS_SUCCEEDED(rvIgnored),
228 "HTMLEditor::SetSnapToGridEnabled(false) failed, but ignored");
231 // Init the HTML-CSS utils
232 mCSSEditUtils = MakeUnique<CSSEditUtils>(this);
234 // disable links
235 Document* document = GetDocument();
236 if (NS_WARN_IF(!document)) {
237 return NS_ERROR_FAILURE;
239 if (!IsPlaintextEditor() && !IsInteractionAllowed()) {
240 mDisabledLinkHandling = true;
241 mOldLinkHandlingEnabled = document->LinkHandlingEnabled();
242 document->SetLinkHandlingEnabled(false);
245 // init the type-in state
246 mTypeInState = new TypeInState();
248 if (!IsInteractionAllowed()) {
249 nsCOMPtr<nsIURI> uaURI;
250 rv = NS_NewURI(getter_AddRefs(uaURI),
251 "resource://gre/res/EditorOverride.css");
252 NS_ENSURE_SUCCESS(rv, rv);
254 rv = document->LoadAdditionalStyleSheet(Document::eAgentSheet, uaURI);
255 NS_ENSURE_SUCCESS(rv, rv);
258 // XXX `eNotEditing` is a lie since InitEditorContentAndSelection() may
259 // insert padding `<br>`.
260 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
261 if (NS_WARN_IF(!editActionData.CanHandle())) {
262 return NS_ERROR_FAILURE;
265 rv = InitEditorContentAndSelection();
266 if (NS_FAILED(rv)) {
267 NS_WARNING("HTMLEditor::InitEditorContentAndSelection() failed");
268 // XXX Sholdn't we expose `NS_ERROR_EDITOR_DESTROYED` even though this
269 // is a public method?
270 return EditorBase::ToGenericNSResult(rv);
273 // Throw away the old transaction manager if this is not the first time that
274 // we're initializing the editor.
275 ClearUndoRedo();
276 EnableUndoRedo();
277 MOZ_ASSERT(!mInitSucceeded, "HTMLEditor::Init() shouldn't be nested");
278 mInitSucceeded = true;
279 return NS_OK;
282 void HTMLEditor::PreDestroy(bool aDestroyingFrames) {
283 if (mDidPreDestroy) {
284 return;
287 mInitSucceeded = false;
289 // FYI: Cannot create AutoEditActionDataSetter here. However, it does not
290 // necessary for the methods called by the following code.
292 RefPtr<Document> document = GetDocument();
293 if (document) {
294 document->RemoveMutationObserver(this);
296 if (!IsInteractionAllowed()) {
297 nsCOMPtr<nsIURI> uaURI;
298 nsresult rv = NS_NewURI(getter_AddRefs(uaURI),
299 "resource://gre/res/EditorOverride.css");
300 if (NS_SUCCEEDED(rv)) {
301 document->RemoveAdditionalStyleSheet(Document::eAgentSheet, uaURI);
306 // Clean up after our anonymous content -- we don't want these nodes to
307 // stay around (which they would, since the frames have an owning reference).
308 PresShell* presShell = GetPresShell();
309 if (presShell && presShell->IsDestroying()) {
310 // Just destroying PresShell now.
311 // We have to keep UI elements of anonymous content until PresShell
312 // is destroyed.
313 RefPtr<HTMLEditor> self = this;
314 nsContentUtils::AddScriptRunner(
315 NS_NewRunnableFunction("HTMLEditor::PreDestroy",
316 [self]() { self->HideAnonymousEditingUIs(); }));
317 } else {
318 // PresShell is alive or already gone.
319 HideAnonymousEditingUIs();
322 EditorBase::PreDestroy(aDestroyingFrames);
325 NS_IMETHODIMP HTMLEditor::NotifySelectionChanged(Document* aDocument,
326 Selection* aSelection,
327 int16_t aReason) {
328 if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) {
329 return NS_ERROR_INVALID_ARG;
332 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
333 if (NS_WARN_IF(!editActionData.CanHandle())) {
334 return NS_ERROR_NOT_INITIALIZED;
337 if (mTypeInState) {
338 RefPtr<TypeInState> typeInState = mTypeInState;
339 typeInState->OnSelectionChange(*aSelection, aReason);
341 // We used a class which derived from nsISelectionListener to call
342 // HTMLEditor::RefreshEditingUI(). The lifetime of the class was
343 // exactly same as mTypeInState. So, call it only when mTypeInState
344 // is not nullptr.
345 if ((aReason & (nsISelectionListener::MOUSEDOWN_REASON |
346 nsISelectionListener::KEYPRESS_REASON |
347 nsISelectionListener::SELECTALL_REASON)) &&
348 aSelection) {
349 // the selection changed and we need to check if we have to
350 // hide and/or redisplay resizing handles
351 // FYI: This is an XPCOM method. So, the caller, Selection, guarantees
352 // the lifetime of this instance. So, don't need to grab this with
353 // local variable.
354 DebugOnly<nsresult> rv = RefreshEditingUI();
355 NS_WARNING_ASSERTION(
356 NS_SUCCEEDED(rv),
357 "HTMLEditor::RefreshEditingUI() failed, but ignored");
361 if (mComposerCommandsUpdater) {
362 RefPtr<ComposerCommandsUpdater> updater = mComposerCommandsUpdater;
363 updater->OnSelectionChange();
366 nsresult rv =
367 EditorBase::NotifySelectionChanged(aDocument, aSelection, aReason);
368 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
369 "EditorBase::NotifySelectionChanged() failed");
370 return rv;
373 void HTMLEditor::UpdateRootElement() {
374 // Use the HTML documents body element as the editor root if we didn't
375 // get a root element during initialization.
377 mRootElement = GetBodyElement();
378 if (!mRootElement) {
379 RefPtr<Document> doc = GetDocument();
380 if (doc) {
381 // If there is no HTML body element,
382 // we should use the document root element instead.
383 mRootElement = doc->GetDocumentElement();
385 // else leave it null, for lack of anything better.
389 Element* HTMLEditor::FindSelectionRoot(nsINode* aNode) const {
390 if (NS_WARN_IF(!aNode)) {
391 return nullptr;
394 MOZ_ASSERT(aNode->IsDocument() || aNode->IsContent(),
395 "aNode must be content or document node");
397 Document* document = aNode->GetComposedDoc();
398 if (NS_WARN_IF(!document)) {
399 return nullptr;
402 if (aNode->IsInUncomposedDoc() &&
403 (document->HasFlag(NODE_IS_EDITABLE) || !aNode->IsContent())) {
404 return document->GetRootElement();
407 // XXX If we have readonly flag, shouldn't return the element which has
408 // contenteditable="true"? However, such case isn't there without chrome
409 // permission script.
410 if (IsReadonly()) {
411 // We still want to allow selection in a readonly editor.
412 return GetRoot();
415 nsIContent* content = aNode->AsContent();
416 if (!content->HasFlag(NODE_IS_EDITABLE)) {
417 // If the content is in read-write state but is not editable itself,
418 // return it as the selection root.
419 if (content->IsElement() &&
420 content->AsElement()->State().HasState(NS_EVENT_STATE_READWRITE)) {
421 return content->AsElement();
423 return nullptr;
426 // For non-readonly editors we want to find the root of the editable subtree
427 // containing aContent.
428 return content->GetEditingHost();
431 void HTMLEditor::CreateEventListeners() {
432 // Don't create the handler twice
433 if (!mEventListener) {
434 mEventListener = new HTMLEditorEventListener();
438 nsresult HTMLEditor::InstallEventListeners() {
439 if (NS_WARN_IF(!IsInitialized()) || NS_WARN_IF(!mEventListener)) {
440 return NS_ERROR_NOT_INITIALIZED;
443 // NOTE: HTMLEditor doesn't need to initialize mEventTarget here because
444 // the target must be document node and it must be referenced as weak pointer.
446 HTMLEditorEventListener* listener =
447 reinterpret_cast<HTMLEditorEventListener*>(mEventListener.get());
448 nsresult rv = listener->Connect(this);
449 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
450 "HTMLEditorEventListener::Connect() failed");
451 return rv;
454 void HTMLEditor::RemoveEventListeners() {
455 if (!IsInitialized()) {
456 return;
459 TextEditor::RemoveEventListeners();
462 NS_IMETHODIMP HTMLEditor::SetFlags(uint32_t aFlags) {
463 nsresult rv = TextEditor::SetFlags(aFlags);
464 if (NS_FAILED(rv)) {
465 NS_WARNING("TextEditor::SetFlags() failed");
466 return rv;
469 // Sets mCSSAware to correspond to aFlags. This toggles whether CSS is
470 // used to style elements in the editor. Note that the editor is only CSS
471 // aware by default in Composer and in the mail editor.
472 mCSSAware = !NoCSS() && !IsMailEditor();
474 return NS_OK;
477 NS_IMETHODIMP HTMLEditor::BeginningOfDocument() {
478 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
479 if (NS_WARN_IF(!editActionData.CanHandle())) {
480 return NS_ERROR_NOT_INITIALIZED;
483 nsresult rv = MaybeCollapseSelectionAtFirstEditableNode(false);
484 NS_WARNING_ASSERTION(
485 NS_SUCCEEDED(rv),
486 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) failed");
487 return rv;
490 void HTMLEditor::InitializeSelectionAncestorLimit(
491 nsIContent& aAncestorLimit) const {
492 MOZ_ASSERT(IsEditActionDataAvailable());
494 // Hack for initializing selection.
495 // HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode() will try to
496 // collapse selection at first editable text node or inline element which
497 // cannot have text nodes as its children. However, selection has already
498 // set into the new editing host by user, we should not change it. For
499 // solving this issue, we should do nothing if selection range is in active
500 // editing host except it's not collapsed at start of the editing host since
501 // aSelection.SetAncestorLimiter(aAncestorLimit) will collapse selection
502 // at start of the new limiter if focus node of aSelection is outside of the
503 // editing host. However, we need to check here if selection is already
504 // collapsed at start of the editing host because it's possible JS to do it.
505 // In such case, we should not modify selection with calling
506 // MaybeCollapseSelectionAtFirstEditableNode().
508 // Basically, we should try to collapse selection at first editable node
509 // in HTMLEditor.
510 bool tryToCollapseSelectionAtFirstEditableNode = true;
511 if (SelectionRefPtr()->RangeCount() == 1 &&
512 SelectionRefPtr()->IsCollapsed()) {
513 Element* editingHost = GetActiveEditingHost();
514 const nsRange* range = SelectionRefPtr()->GetRangeAt(0);
515 if (range->GetStartContainer() == editingHost && !range->StartOffset()) {
516 // JS or user operation has already collapsed selection at start of
517 // the editing host. So, we don't need to try to change selection
518 // in this case.
519 tryToCollapseSelectionAtFirstEditableNode = false;
523 EditorBase::InitializeSelectionAncestorLimit(aAncestorLimit);
525 // XXX Do we need to check if we still need to change selection? E.g.,
526 // we could have already lost focus while we're changing the ancestor
527 // limiter because it may causes "selectionchange" event.
528 if (tryToCollapseSelectionAtFirstEditableNode) {
529 DebugOnly<nsresult> rvIgnored =
530 MaybeCollapseSelectionAtFirstEditableNode(true);
531 NS_WARNING_ASSERTION(
532 NS_SUCCEEDED(rvIgnored),
533 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(true) failed, "
534 "but ignored");
538 nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(
539 bool aIgnoreIfSelectionInEditingHost) const {
540 MOZ_ASSERT(IsEditActionDataAvailable());
542 // Use editing host. If you use root element here, selection may be
543 // moved to <head> element, e.g., if there is a text node in <script>
544 // element. So, we should use active editing host.
545 RefPtr<Element> editingHost = GetActiveEditingHost();
546 if (NS_WARN_IF(!editingHost)) {
547 return NS_OK;
550 // If selection range is already in the editing host and the range is not
551 // start of the editing host, we shouldn't reset selection. E.g., window
552 // is activated when the editor had focus before inactivated.
553 if (aIgnoreIfSelectionInEditingHost && SelectionRefPtr()->RangeCount() == 1) {
554 const nsRange* range = SelectionRefPtr()->GetRangeAt(0);
555 if (!range->Collapsed() ||
556 range->GetStartContainer() != editingHost.get() ||
557 range->StartOffset()) {
558 return NS_OK;
562 // Find first editable and visible node.
563 EditorRawDOMPoint pointToPutCaret(editingHost, 0);
564 for (;;) {
565 WSScanResult forwardScanFromPointToPutCaretResult =
566 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this,
567 pointToPutCaret);
568 // If we meet a non-editable node first, we should move caret to start of
569 // the editing host (perhaps, user may want to insert something before
570 // the first non-editable node? Chromium behaves so).
571 if (forwardScanFromPointToPutCaretResult.GetContent() &&
572 !forwardScanFromPointToPutCaretResult.IsContentEditable()) {
573 pointToPutCaret.Set(editingHost, 0);
574 break;
577 // WSRunScanner::ScanNextVisibleNodeOrBlockBoundary() reaches "special
578 // content" when it meets empty inline element. In this case, we should go
579 // to next sibling. For example, if current editor is: <div
580 // contenteditable><span></span><b><br></b></div> then, we should put caret
581 // at the <br> element. So, let's check if found node is an empty inline
582 // container element.
583 if (forwardScanFromPointToPutCaretResult.ReachedSpecialContent() &&
584 forwardScanFromPointToPutCaretResult.GetContent() &&
585 HTMLEditUtils::CanNodeContain(
586 *forwardScanFromPointToPutCaretResult.GetContent()
587 ->NodeInfo()
588 ->NameAtom(),
589 *nsGkAtoms::textTagName)) {
590 pointToPutCaret =
591 forwardScanFromPointToPutCaretResult.RawPointAfterContent();
592 continue;
595 // If there is editable and visible text node, move caret at start of it.
596 if (forwardScanFromPointToPutCaretResult.InNormalWhiteSpacesOrText()) {
597 pointToPutCaret = forwardScanFromPointToPutCaretResult.RawPoint();
598 break;
601 // If there is editable <br> or something inline special element like
602 // <img>, <input>, etc, move caret before it.
603 if (forwardScanFromPointToPutCaretResult.ReachedBRElement() ||
604 forwardScanFromPointToPutCaretResult.ReachedSpecialContent()) {
605 pointToPutCaret =
606 forwardScanFromPointToPutCaretResult.RawPointAtContent();
607 break;
610 // If there is no visible/editable node except another block element in
611 // current editing host, we should move caret to very first of the editing
612 // host.
613 // XXX This may not make sense, but Chromium behaves so. Therefore, the
614 // reason why we do this is just compatibility with Chromium.
615 if (!forwardScanFromPointToPutCaretResult.ReachedOtherBlockElement()) {
616 pointToPutCaret.Set(editingHost, 0);
617 break;
620 // By definition of WSRunScanner, a block element terminates a white-space
621 // run. That is, although we are calling a method that is named
622 // "ScanNextVisibleNodeOrBlockBoundary", the node returned might not
623 // be visible/editable!
625 // However, we were given a block that is not a container. Since the
626 // block can not contain anything that's visible, such a block only
627 // makes sense if it is visible by itself, like a <hr>. We want to
628 // place the caret in front of that block.
629 if (!forwardScanFromPointToPutCaretResult.GetContent() ||
630 !HTMLEditUtils::IsContainerNode(
631 *forwardScanFromPointToPutCaretResult.GetContent())) {
632 pointToPutCaret =
633 forwardScanFromPointToPutCaretResult.RawPointAtContent();
634 break;
637 // If the given block does not contain any visible/editable items, we want
638 // to skip it and continue our search.
639 if (IsEmptyNode(*forwardScanFromPointToPutCaretResult.GetContent())) {
640 // Skip the empty block
641 pointToPutCaret =
642 forwardScanFromPointToPutCaretResult.RawPointAfterContent();
643 } else {
644 pointToPutCaret.Set(forwardScanFromPointToPutCaretResult.GetContent(), 0);
647 nsresult rv = CollapseSelectionTo(pointToPutCaret);
648 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
649 "HTMLEditor::CollapseSelectionTo() failed");
650 return rv;
653 nsresult HTMLEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) {
654 // NOTE: When you change this method, you should also change:
655 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
657 if (IsReadonly()) {
658 // When we're not editable, the events are handled on EditorBase, so, we can
659 // bypass TextEditor.
660 nsresult rv = EditorBase::HandleKeyPressEvent(aKeyboardEvent);
661 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
662 "EditorBase::HandleKeyPressEvent() failed");
663 return rv;
666 if (NS_WARN_IF(!aKeyboardEvent)) {
667 return NS_ERROR_UNEXPECTED;
669 MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress,
670 "HandleKeyPressEvent gets non-keypress event");
672 switch (aKeyboardEvent->mKeyCode) {
673 case NS_VK_META:
674 case NS_VK_WIN:
675 case NS_VK_SHIFT:
676 case NS_VK_CONTROL:
677 case NS_VK_ALT: {
678 // These keys are handled on EditorBase, so, we can bypass
679 // TextEditor.
680 nsresult rv = EditorBase::HandleKeyPressEvent(aKeyboardEvent);
681 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
682 "EditorBase::HandleKeyPressEvent() failed");
683 return rv;
685 case NS_VK_BACK:
686 case NS_VK_DELETE: {
687 // These keys are handled on TextEditor.
688 nsresult rv = TextEditor::HandleKeyPressEvent(aKeyboardEvent);
689 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
690 "TextEditor::HandleKeyPressEvent() failed");
691 return rv;
693 case NS_VK_TAB: {
694 if (IsPlaintextEditor()) {
695 // If this works as plain text editor, e.g., mail editor for plain
696 // text, should be handled on TextEditor.
697 nsresult rv = TextEditor::HandleKeyPressEvent(aKeyboardEvent);
698 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
699 "TextEditor::HandleKeyPressEvent() failed");
700 return rv;
703 // If we're a `contenteditable` element or in `designMode`, "Tab" key
704 // be used only for focus navigation.
705 if (IsTabbable()) {
706 return NS_OK;
709 // Otherwise, e.g., we're an embedding editor in chrome, we can handle
710 // "Tab" key as an input.
712 if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() ||
713 aKeyboardEvent->IsMeta() || aKeyboardEvent->IsOS()) {
714 return NS_OK;
717 RefPtr<Selection> selection = GetSelection();
718 if (NS_WARN_IF(!selection) || NS_WARN_IF(!selection->RangeCount())) {
719 return NS_ERROR_FAILURE;
722 nsINode* startContainer = selection->GetRangeAt(0)->GetStartContainer();
723 MOZ_ASSERT(startContainer);
724 if (!startContainer->IsContent()) {
725 break;
728 Element* blockParent = HTMLEditUtils::GetInclusiveAncestorBlockElement(
729 *startContainer->AsContent());
730 if (!blockParent) {
731 break;
734 // If selection is in a table element, we need special handling.
735 if (HTMLEditUtils::IsAnyTableElement(blockParent)) {
736 EditActionResult result = HandleTabKeyPressInTable(aKeyboardEvent);
737 if (result.Failed()) {
738 NS_WARNING("HTMLEditor::HandleTabKeyPressInTable() failed");
739 return EditorBase::ToGenericNSResult(result.Rv());
741 if (!result.Handled()) {
742 return NS_OK;
744 nsresult rv = ScrollSelectionFocusIntoView();
745 NS_WARNING_ASSERTION(
746 NS_SUCCEEDED(rv),
747 "EditorBase::ScrollSelectionFocusIntoView() failed");
748 return EditorBase::ToGenericNSResult(rv);
751 // If selection is in an list item element, treat it as indent or outdent.
752 if (HTMLEditUtils::IsListItem(blockParent)) {
753 aKeyboardEvent->PreventDefault();
754 if (!aKeyboardEvent->IsShift()) {
755 nsresult rv = IndentAsAction();
756 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
757 "HTMLEditor::IndentAsAction() failed");
758 return EditorBase::ToGenericNSResult(rv);
760 nsresult rv = OutdentAsAction();
761 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
762 "HTMLEditor::OutdentAsAction() failed");
763 return EditorBase::ToGenericNSResult(rv);
766 // If only "Tab" key is pressed in normal context, just treat it as
767 // horizontal tab character input.
768 if (aKeyboardEvent->IsShift()) {
769 return NS_OK;
771 aKeyboardEvent->PreventDefault();
772 nsresult rv = OnInputText(u"\t"_ns);
773 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
774 "TextEditor::OnInputText(\\t) failed");
775 return EditorBase::ToGenericNSResult(rv);
777 case NS_VK_RETURN:
778 if (!aKeyboardEvent->IsInputtingLineBreak()) {
779 return NS_OK;
781 aKeyboardEvent->PreventDefault(); // consumed
782 if (aKeyboardEvent->IsShift()) {
783 // Only inserts a <br> element.
784 nsresult rv = InsertLineBreakAsAction();
785 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
786 "HTMLEditor::InsertLineBreakAsAction() failed");
787 return EditorBase::ToGenericNSResult(rv);
789 // uses rules to figure out what to insert
790 nsresult rv = InsertParagraphSeparatorAsAction();
791 NS_WARNING_ASSERTION(
792 NS_SUCCEEDED(rv),
793 "HTMLEditor::InsertParagraphSeparatorAsAction() failed");
794 return EditorBase::ToGenericNSResult(rv);
797 if (!aKeyboardEvent->IsInputtingText()) {
798 // we don't PreventDefault() here or keybindings like control-x won't work
799 return NS_OK;
801 aKeyboardEvent->PreventDefault();
802 nsAutoString str(aKeyboardEvent->mCharCode);
803 nsresult rv = OnInputText(str);
804 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "TextEditor::OnInputText() failed");
805 return rv;
808 NS_IMETHODIMP HTMLEditor::NodeIsBlock(nsINode* aNode, bool* aIsBlock) {
809 *aIsBlock = aNode && aNode->IsContent() &&
810 HTMLEditUtils::IsBlockElement(*aNode->AsContent());
811 return NS_OK;
814 bool HTMLEditor::IsEmptyInlineNode(nsIContent& aContent) const {
815 MOZ_ASSERT(IsEditActionDataAvailable());
817 if (!HTMLEditUtils::IsInlineElement(aContent) ||
818 !HTMLEditUtils::IsContainerNode(aContent)) {
819 return false;
821 return IsEmptyNode(aContent);
825 * IsNextCharInNodeWhiteSpace() checks the adjacent content in the same node to
826 * see if following selection is white-space or nbsp.
828 void HTMLEditor::IsNextCharInNodeWhiteSpace(nsIContent* aContent,
829 int32_t aOffset, bool* outIsSpace,
830 bool* outIsNBSP,
831 nsIContent** outNode,
832 int32_t* outOffset) {
833 MOZ_ASSERT(aContent && outIsSpace && outIsNBSP);
834 MOZ_ASSERT((outNode && outOffset) || (!outNode && !outOffset));
835 *outIsSpace = false;
836 *outIsNBSP = false;
837 if (outNode && outOffset) {
838 *outNode = nullptr;
839 *outOffset = -1;
842 if (aContent->IsText() && (uint32_t)aOffset < aContent->Length()) {
843 char16_t ch = aContent->GetText()->CharAt(aOffset);
844 *outIsSpace = nsCRT::IsAsciiSpace(ch);
845 *outIsNBSP = (ch == kNBSP);
846 if (outNode && outOffset) {
847 NS_IF_ADDREF(*outNode = aContent);
848 // yes, this is _past_ the character
849 *outOffset = aOffset + 1;
855 * IsPrevCharInNodeWhiteSpace() checks the adjacent content in the same node to
856 * see if following selection is white-space.
858 void HTMLEditor::IsPrevCharInNodeWhiteSpace(nsIContent* aContent,
859 int32_t aOffset, bool* outIsSpace,
860 bool* outIsNBSP,
861 nsIContent** outNode,
862 int32_t* outOffset) {
863 MOZ_ASSERT(aContent && outIsSpace && outIsNBSP);
864 MOZ_ASSERT((outNode && outOffset) || (!outNode && !outOffset));
865 *outIsSpace = false;
866 *outIsNBSP = false;
867 if (outNode && outOffset) {
868 *outNode = nullptr;
869 *outOffset = -1;
872 if (aContent->IsText() && aOffset > 0) {
873 char16_t ch = aContent->GetText()->CharAt(aOffset - 1);
874 *outIsSpace = nsCRT::IsAsciiSpace(ch);
875 *outIsNBSP = (ch == kNBSP);
876 if (outNode && outOffset) {
877 NS_IF_ADDREF(*outNode = aContent);
878 *outOffset = aOffset - 1;
883 bool HTMLEditor::IsVisibleBRElement(const nsINode* aNode) const {
884 MOZ_ASSERT(aNode);
885 if (!aNode->IsHTMLElement(nsGkAtoms::br)) {
886 return false;
888 // Check if there is another element or text node in block after current
889 // <br> element.
890 // Note that even if following node is non-editable, it may make the
891 // <br> element visible if it just exists.
892 // E.g., foo<br><button contenteditable="false">button</button>
893 // However, we need to ignore invisible data nodes like comment node.
894 nsIContent* nextContent = GetNextHTMLElementOrTextInBlock(*aNode);
895 if (nextContent && nextContent->IsHTMLElement(nsGkAtoms::br)) {
896 return true;
899 // A single line break before a block boundary is not displayed, so e.g.
900 // foo<p>bar<br></p> and foo<br><p>bar</p> display the same as foo<p>bar</p>.
901 // But if there are multiple <br>s in a row, all but the last are visible.
902 if (!nextContent) {
903 // This break is trailer in block, it's not visible
904 return false;
906 if (HTMLEditUtils::IsBlockElement(*nextContent)) {
907 // Break is right before a block, it's not visible
908 return false;
911 // If there's an inline node after this one that's not a break, and also a
912 // prior break, this break must be visible.
913 // Note that even if previous node is non-editable, it may make the
914 // <br> element visible if it just exists.
915 // E.g., <button contenteditable="false"><br>foo
916 // However, we need to ignore invisible data nodes like comment node.
917 nsCOMPtr<nsINode> priorNode = GetPreviousHTMLElementOrTextInBlock(*aNode);
918 if (priorNode && priorNode->IsHTMLElement(nsGkAtoms::br)) {
919 return true;
922 // Sigh. We have to use expensive white-space calculation code to
923 // determine what is going on
924 EditorRawDOMPoint afterBRElement(EditorRawDOMPoint::After(*aNode));
925 if (NS_WARN_IF(!afterBRElement.IsSet())) {
926 return false;
928 return !WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(*this,
929 afterBRElement)
930 .ReachedBlockBoundary();
933 NS_IMETHODIMP HTMLEditor::UpdateBaseURL() {
934 RefPtr<Document> document = GetDocument();
935 if (NS_WARN_IF(!document)) {
936 return NS_ERROR_FAILURE;
939 // Look for an HTML <base> tag
940 RefPtr<nsContentList> baseElementList =
941 document->GetElementsByTagName(u"base"_ns);
943 // If no base tag, then set baseURL to the document's URL. This is very
944 // important, else relative URLs for links and images are wrong
945 if (!baseElementList || !baseElementList->Item(0)) {
946 document->SetBaseURI(document->GetDocumentURI());
948 return NS_OK;
951 NS_IMETHODIMP HTMLEditor::InsertLineBreak() {
952 MOZ_ASSERT(!IsSingleLineEditor());
954 // XPCOM method's InsertLineBreak() should insert paragraph separator in
955 // HTMLEditor.
956 AutoEditActionDataSetter editActionData(
957 *this, EditAction::eInsertParagraphSeparator);
958 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
959 if (NS_FAILED(rv)) {
960 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
961 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
962 return EditorBase::ToGenericNSResult(rv);
965 EditActionResult result = InsertParagraphSeparatorAsSubAction();
966 NS_WARNING_ASSERTION(
967 result.Succeeded(),
968 "HTMLEditor::InsertParagraphSeparatorAsSubAction() failed");
969 return EditorBase::ToGenericNSResult(result.Rv());
972 nsresult HTMLEditor::InsertLineBreakAsAction(nsIPrincipal* aPrincipal) {
973 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLineBreak,
974 aPrincipal);
975 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
976 if (NS_FAILED(rv)) {
977 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
978 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
979 return EditorBase::ToGenericNSResult(rv);
982 if (IsSelectionRangeContainerNotContent()) {
983 return NS_SUCCESS_DOM_NO_OPERATION;
986 // XXX This method may be called by "insertLineBreak" command. So, using
987 // TypingTxnName here is odd in such case.
988 AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
989 ScrollSelectionIntoView::Yes);
990 rv = InsertBRElementAtSelectionWithTransaction();
991 NS_WARNING_ASSERTION(
992 NS_SUCCEEDED(rv),
993 "HTMLEditor::InsertBRElementAtSelectionWithTransaction() failed");
994 // Don't return NS_SUCCESS_DOM_NO_OPERATION for compatibility of `execCommand`
995 // result of Chrome.
996 return NS_FAILED(rv) ? rv : NS_OK;
999 nsresult HTMLEditor::InsertParagraphSeparatorAsAction(
1000 nsIPrincipal* aPrincipal) {
1001 AutoEditActionDataSetter editActionData(
1002 *this, EditAction::eInsertParagraphSeparator, aPrincipal);
1003 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1004 if (NS_FAILED(rv)) {
1005 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1006 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1007 return EditorBase::ToGenericNSResult(rv);
1010 EditActionResult result = InsertParagraphSeparatorAsSubAction();
1011 NS_WARNING_ASSERTION(
1012 result.Succeeded(),
1013 "HTMLEditor::InsertParagraphSeparatorAsSubAction() failed");
1014 return EditorBase::ToGenericNSResult(result.Rv());
1017 EditActionResult HTMLEditor::HandleTabKeyPressInTable(
1018 WidgetKeyboardEvent* aKeyboardEvent) {
1019 MOZ_ASSERT(aKeyboardEvent);
1021 AutoEditActionDataSetter dummyEditActionData(*this, EditAction::eNotEditing);
1022 if (NS_WARN_IF(!dummyEditActionData.CanHandle())) {
1023 // Do nothing if we didn't find a table cell.
1024 return EditActionIgnored();
1027 // Find enclosing table cell from selection (cell may be selected element)
1028 Element* cellElement =
1029 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td);
1030 if (!cellElement) {
1031 NS_WARNING(
1032 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td) "
1033 "returned nullptr");
1034 // Do nothing if we didn't find a table cell.
1035 return EditActionIgnored();
1038 // find enclosing table
1039 RefPtr<Element> table =
1040 HTMLEditUtils::GetClosestAncestorTableElement(*cellElement);
1041 if (!table) {
1042 NS_WARNING("HTMLEditor::GetClosestAncestorTableElement() failed");
1043 return EditActionIgnored();
1046 // advance to next cell
1047 // first create an iterator over the table
1048 PostContentIterator postOrderIter;
1049 nsresult rv = postOrderIter.Init(table);
1050 if (NS_FAILED(rv)) {
1051 NS_WARNING("PostContentIterator::Init() failed");
1052 return EditActionResult(rv);
1054 // position postOrderIter at block
1055 rv = postOrderIter.PositionAt(cellElement);
1056 if (NS_FAILED(rv)) {
1057 NS_WARNING("PostContentIterator::PositionAt() failed");
1058 return EditActionResult(rv);
1061 do {
1062 if (aKeyboardEvent->IsShift()) {
1063 postOrderIter.Prev();
1064 } else {
1065 postOrderIter.Next();
1068 nsCOMPtr<nsINode> node = postOrderIter.GetCurrentNode();
1069 if (node && HTMLEditUtils::IsTableCell(node) &&
1070 HTMLEditUtils::GetClosestAncestorTableElement(*node->AsElement()) ==
1071 table) {
1072 aKeyboardEvent->PreventDefault();
1073 CollapseSelectionToDeepestNonTableFirstChild(node);
1074 return EditActionHandled(
1075 NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK);
1077 } while (!postOrderIter.IsDone());
1079 if (aKeyboardEvent->IsShift()) {
1080 return EditActionIgnored();
1083 // If we haven't handled it yet, then we must have run off the end of the
1084 // table. Insert a new row.
1085 // XXX We should investigate whether this behavior is supported by other
1086 // browsers later.
1087 AutoEditActionDataSetter editActionData(*this,
1088 EditAction::eInsertTableRowElement);
1089 rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1090 if (NS_FAILED(rv)) {
1091 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1092 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1093 return EditActionHandled(rv);
1095 rv = InsertTableRowsWithTransaction(1, InsertPosition::eAfterSelectedCell);
1096 if (NS_WARN_IF(Destroyed())) {
1097 return EditActionHandled(NS_ERROR_EDITOR_DESTROYED);
1099 if (NS_FAILED(rv)) {
1100 NS_WARNING(
1101 "HTMLEditor::InsertTableRowsWithTransaction(1, "
1102 "InsertPosition::eAfterSelectedCell) failed");
1103 return EditActionHandled(rv);
1105 aKeyboardEvent->PreventDefault();
1106 // Put selection in right place. Use table code to get selection and index
1107 // to new row...
1108 RefPtr<Element> tblElement, cell;
1109 int32_t row;
1110 rv = GetCellContext(getter_AddRefs(tblElement), getter_AddRefs(cell), nullptr,
1111 nullptr, &row, nullptr);
1112 if (NS_FAILED(rv)) {
1113 NS_WARNING("HTMLEditor::GetCellContext() failed");
1114 return EditActionHandled(rv);
1116 if (!tblElement) {
1117 NS_WARNING("HTMLEditor::GetCellContext() didn't return table element");
1118 return EditActionHandled(NS_ERROR_FAILURE);
1120 // ...so that we can ask for first cell in that row...
1121 cell = GetTableCellElementAt(*tblElement, row, 0);
1122 // ...and then set selection there. (Note that normally you should use
1123 // CollapseSelectionToDeepestNonTableFirstChild(), but we know cell is an
1124 // empty new cell, so this works fine)
1125 if (cell) {
1126 DebugOnly<nsresult> rvIgnored = CollapseSelectionToStartOf(*cell);
1127 NS_WARNING_ASSERTION(
1128 NS_SUCCEEDED(rvIgnored),
1129 "HTMLEditor::CollapseSelectionToStartOf() failed, but ignored");
1131 return EditActionHandled(NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED
1132 : NS_OK);
1135 nsresult HTMLEditor::InsertBRElementAtSelectionWithTransaction() {
1136 MOZ_ASSERT(IsEditActionDataAvailable());
1137 MOZ_ASSERT(!IsSelectionRangeContainerNotContent());
1139 // calling it text insertion to trigger moz br treatment by rules
1140 // XXX Why do we use EditSubAction::eInsertText here? Looks like
1141 // EditSubAction::eInsertLineBreak or EditSubAction::eInsertNode
1142 // is better.
1143 IgnoredErrorResult ignoredError;
1144 AutoEditSubActionNotifier startToHandleEditSubAction(
1145 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
1146 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1147 return ignoredError.StealNSResult();
1149 NS_WARNING_ASSERTION(
1150 !ignoredError.Failed(),
1151 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1153 if (!SelectionRefPtr()->IsCollapsed()) {
1154 nsresult rv = DeleteSelectionAsSubAction(eNone, eStrip);
1155 if (NS_FAILED(rv)) {
1156 NS_WARNING(
1157 "EditorBase::DeleteSelectionAsSubAction(eNone, eStrip) failed");
1158 return rv;
1162 EditorDOMPoint atStartOfSelection(
1163 EditorBase::GetStartPoint(*SelectionRefPtr()));
1164 if (NS_WARN_IF(!atStartOfSelection.IsSet())) {
1165 return NS_ERROR_FAILURE;
1168 // InsertBRElementWithTransaction() will set selection after the new <br>
1169 // element.
1170 RefPtr<Element> newBRElement =
1171 InsertBRElementWithTransaction(atStartOfSelection, eNext);
1172 NS_WARNING_ASSERTION(newBRElement,
1173 "HTMLEditor::InsertBRElementWithTransaction() failed");
1174 return newBRElement ? NS_OK : NS_ERROR_FAILURE;
1177 void HTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(nsINode* aNode) {
1178 MOZ_ASSERT(IsEditActionDataAvailable());
1180 MOZ_ASSERT(aNode);
1182 nsCOMPtr<nsINode> node = aNode;
1184 for (nsIContent* child = node->GetFirstChild(); child;
1185 child = child->GetFirstChild()) {
1186 // Stop if we find a table, don't want to go into nested tables
1187 if (HTMLEditUtils::IsTable(child) ||
1188 !HTMLEditUtils::IsContainerNode(*child)) {
1189 break;
1191 node = child;
1194 DebugOnly<nsresult> rvIgnored = CollapseSelectionToStartOf(*node);
1195 NS_WARNING_ASSERTION(
1196 NS_SUCCEEDED(rvIgnored),
1197 "HTMLEditor::CollapseSelectionToStartOf() failed, but ignored");
1200 nsresult HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction(
1201 const nsAString& aSourceToInsert) {
1202 MOZ_ASSERT(IsEditActionDataAvailable());
1204 // don't do any post processing, rules get confused
1205 IgnoredErrorResult ignoredError;
1206 AutoEditSubActionNotifier startToHandleEditSubAction(
1207 *this, EditSubAction::eReplaceHeadWithHTMLSource, nsIEditor::eNone,
1208 ignoredError);
1209 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1210 return ignoredError.StealNSResult();
1212 NS_WARNING_ASSERTION(
1213 !ignoredError.Failed(),
1214 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1216 CommitComposition();
1218 // Do not use AutoTopLevelEditSubActionNotifier -- rules code won't let us
1219 // insert in <head>. Use the head node as a parent and delete/insert
1220 // directly.
1221 // XXX We're using AutoTopLevelEditSubActionNotifier above...
1222 RefPtr<Document> document = GetDocument();
1223 if (NS_WARN_IF(!document)) {
1224 return NS_ERROR_NOT_INITIALIZED;
1227 RefPtr<nsContentList> headElementList =
1228 document->GetElementsByTagName(u"head"_ns);
1229 if (NS_WARN_IF(!headElementList)) {
1230 return NS_ERROR_FAILURE;
1233 RefPtr<Element> primaryHeadElement = headElementList->Item(0)->AsElement();
1234 if (NS_WARN_IF(!primaryHeadElement)) {
1235 return NS_ERROR_FAILURE;
1238 // First, make sure there are no return chars in the source. Bad things
1239 // happen if you insert returns (instead of dom newlines, \n) into an editor
1240 // document.
1241 nsAutoString inputString(aSourceToInsert);
1243 // Windows linebreaks: Map CRLF to LF:
1244 inputString.ReplaceSubstring(u"\r\n"_ns, u"\n"_ns);
1246 // Mac linebreaks: Map any remaining CR to LF:
1247 inputString.ReplaceSubstring(u"\r"_ns, u"\n"_ns);
1249 AutoPlaceholderBatch treatAsOneTransaction(*this,
1250 ScrollSelectionIntoView::Yes);
1252 // Get the first range in the selection, for context:
1253 RefPtr<const nsRange> range = SelectionRefPtr()->GetRangeAt(0);
1254 if (NS_WARN_IF(!range)) {
1255 return NS_ERROR_FAILURE;
1258 ErrorResult error;
1259 RefPtr<DocumentFragment> documentFragment =
1260 range->CreateContextualFragment(inputString, error);
1262 // XXXX BUG 50965: This is not returning the text between <title>...</title>
1263 // Special code is needed in JS to handle title anyway, so it doesn't matter!
1265 if (error.Failed()) {
1266 NS_WARNING("nsRange::CreateContextualFragment() failed");
1267 return error.StealNSResult();
1269 if (NS_WARN_IF(!documentFragment)) {
1270 NS_WARNING(
1271 "nsRange::CreateContextualFragment() didn't create DocumentFragment");
1272 return NS_ERROR_FAILURE;
1275 // First delete all children in head
1276 while (nsCOMPtr<nsIContent> child = primaryHeadElement->GetFirstChild()) {
1277 nsresult rv = DeleteNodeWithTransaction(*child);
1278 if (NS_FAILED(rv)) {
1279 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
1280 return rv;
1284 // Now insert the new nodes
1285 int32_t offsetOfNewNode = 0;
1287 // Loop over the contents of the fragment and move into the document
1288 while (nsCOMPtr<nsIContent> child = documentFragment->GetFirstChild()) {
1289 nsresult rv = InsertNodeWithTransaction(
1290 *child, EditorDOMPoint(primaryHeadElement, offsetOfNewNode++));
1291 if (NS_FAILED(rv)) {
1292 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
1293 return rv;
1297 return NS_OK;
1300 NS_IMETHODIMP HTMLEditor::RebuildDocumentFromSource(
1301 const nsAString& aSourceString) {
1302 CommitComposition();
1304 AutoEditActionDataSetter editActionData(*this, EditAction::eSetHTML);
1305 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1306 if (NS_FAILED(rv)) {
1307 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1308 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1309 return EditorBase::ToGenericNSResult(rv);
1312 RefPtr<Element> rootElement = GetRoot();
1313 if (NS_WARN_IF(!rootElement)) {
1314 return NS_ERROR_NULL_POINTER;
1317 // Find where the <body> tag starts.
1318 nsReadingIterator<char16_t> beginbody;
1319 nsReadingIterator<char16_t> endbody;
1320 aSourceString.BeginReading(beginbody);
1321 aSourceString.EndReading(endbody);
1322 bool foundbody =
1323 CaseInsensitiveFindInReadable(u"<body"_ns, beginbody, endbody);
1325 nsReadingIterator<char16_t> beginhead;
1326 nsReadingIterator<char16_t> endhead;
1327 aSourceString.BeginReading(beginhead);
1328 aSourceString.EndReading(endhead);
1329 bool foundhead =
1330 CaseInsensitiveFindInReadable(u"<head"_ns, beginhead, endhead);
1331 // a valid head appears before the body
1332 if (foundbody && beginhead.get() > beginbody.get()) {
1333 foundhead = false;
1336 nsReadingIterator<char16_t> beginclosehead;
1337 nsReadingIterator<char16_t> endclosehead;
1338 aSourceString.BeginReading(beginclosehead);
1339 aSourceString.EndReading(endclosehead);
1341 // Find the index after "<head>"
1342 bool foundclosehead = CaseInsensitiveFindInReadable(
1343 u"</head>"_ns, beginclosehead, endclosehead);
1344 // a valid close head appears after a found head
1345 if (foundhead && beginhead.get() > beginclosehead.get()) {
1346 foundclosehead = false;
1348 // a valid close head appears before a found body
1349 if (foundbody && beginclosehead.get() > beginbody.get()) {
1350 foundclosehead = false;
1353 // Time to change the document
1354 AutoPlaceholderBatch treatAsOneTransaction(*this,
1355 ScrollSelectionIntoView::Yes);
1357 nsReadingIterator<char16_t> endtotal;
1358 aSourceString.EndReading(endtotal);
1360 if (foundhead) {
1361 if (foundclosehead) {
1362 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(
1363 Substring(beginhead, beginclosehead));
1364 if (NS_FAILED(rv)) {
1365 NS_WARNING(
1366 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1367 "failed");
1368 return rv;
1370 } else if (foundbody) {
1371 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(
1372 Substring(beginhead, beginbody));
1373 if (NS_FAILED(rv)) {
1374 NS_WARNING(
1375 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1376 "failed");
1377 return rv;
1379 } else {
1380 // XXX Without recourse to some parser/content sink/docshell hackery we
1381 // don't really know where the head ends and the body begins so we assume
1382 // that there is no body
1383 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(
1384 Substring(beginhead, endtotal));
1385 if (NS_FAILED(rv)) {
1386 NS_WARNING(
1387 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1388 "failed");
1389 return rv;
1392 } else {
1393 nsReadingIterator<char16_t> begintotal;
1394 aSourceString.BeginReading(begintotal);
1395 constexpr auto head = u"<head>"_ns;
1396 if (foundclosehead) {
1397 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(
1398 head + Substring(begintotal, beginclosehead));
1399 if (NS_FAILED(rv)) {
1400 NS_WARNING(
1401 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1402 "failed");
1403 return rv;
1405 } else if (foundbody) {
1406 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(
1407 head + Substring(begintotal, beginbody));
1408 if (NS_FAILED(rv)) {
1409 NS_WARNING(
1410 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1411 "failed");
1412 return rv;
1414 } else {
1415 // XXX Without recourse to some parser/content sink/docshell hackery we
1416 // don't really know where the head ends and the body begins so we assume
1417 // that there is no head
1418 nsresult rv = ReplaceHeadContentsWithSourceWithTransaction(head);
1419 if (NS_FAILED(rv)) {
1420 NS_WARNING(
1421 "HTMLEditor::ReplaceHeadContentsWithSourceWithTransaction() "
1422 "failed");
1423 return rv;
1428 rv = SelectAll();
1429 if (NS_FAILED(rv)) {
1430 NS_WARNING("EditorBase::SelectAll() failed");
1431 return rv;
1434 if (!foundbody) {
1435 constexpr auto body = u"<body>"_ns;
1436 // XXX Without recourse to some parser/content sink/docshell hackery we
1437 // don't really know where the head ends and the body begins
1438 if (foundclosehead) {
1439 // assume body starts after the head ends
1440 nsresult rv = LoadHTML(body + Substring(endclosehead, endtotal));
1441 if (NS_FAILED(rv)) {
1442 NS_WARNING("HTMLEditor::LoadHTML() failed");
1443 return rv;
1445 } else if (foundhead) {
1446 // assume there is no body
1447 nsresult rv = LoadHTML(body);
1448 if (NS_FAILED(rv)) {
1449 NS_WARNING("HTMLEditor::LoadHTML() failed");
1450 return rv;
1452 } else {
1453 // assume there is no head, the entire source is body
1454 nsresult rv = LoadHTML(body + aSourceString);
1455 if (NS_FAILED(rv)) {
1456 NS_WARNING("HTMLEditor::LoadHTML() failed");
1457 return rv;
1461 RefPtr<Element> divElement = CreateElementWithDefaults(*nsGkAtoms::div);
1462 if (!divElement) {
1463 NS_WARNING(
1464 "HTMLEditor::CreateElementWithDefaults(nsGkAtoms::div) failed");
1465 return NS_ERROR_FAILURE;
1467 CloneAttributesWithTransaction(*rootElement, *divElement);
1469 nsresult rv = MaybeCollapseSelectionAtFirstEditableNode(false);
1470 NS_WARNING_ASSERTION(
1471 NS_SUCCEEDED(rv),
1472 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) failed");
1473 return rv;
1476 rv = LoadHTML(Substring(beginbody, endtotal));
1477 if (NS_FAILED(rv)) {
1478 NS_WARNING("HTMLEditor::LoadHTML() failed");
1479 return rv;
1482 // Now we must copy attributes user might have edited on the <body> tag
1483 // because InsertHTML (actually, CreateContextualFragment()) will never
1484 // return a body node in the DOM fragment
1486 // We already know where "<body" begins
1487 nsReadingIterator<char16_t> beginclosebody = beginbody;
1488 nsReadingIterator<char16_t> endclosebody;
1489 aSourceString.EndReading(endclosebody);
1490 if (!FindInReadable(u">"_ns, beginclosebody, endclosebody)) {
1491 NS_WARNING("'>' was not found");
1492 return NS_ERROR_FAILURE;
1495 // Truncate at the end of the body tag. Kludge of the year: fool the parser
1496 // by replacing "body" with "div" so we get a node
1497 nsAutoString bodyTag;
1498 bodyTag.AssignLiteral("<div ");
1499 bodyTag.Append(Substring(endbody, endclosebody));
1501 RefPtr<const nsRange> range = SelectionRefPtr()->GetRangeAt(0);
1502 if (NS_WARN_IF(!range)) {
1503 return NS_ERROR_FAILURE;
1506 ErrorResult error;
1507 RefPtr<DocumentFragment> documentFragment =
1508 range->CreateContextualFragment(bodyTag, error);
1509 if (error.Failed()) {
1510 NS_WARNING("nsRange::CreateContextualFragment() failed");
1511 return error.StealNSResult();
1513 if (!documentFragment) {
1514 NS_WARNING(
1515 "nsRange::CreateContextualFragment() didn't create DocumentFagement");
1516 return NS_ERROR_FAILURE;
1519 nsCOMPtr<nsIContent> firstChild = documentFragment->GetFirstChild();
1520 if (!firstChild || !firstChild->IsElement()) {
1521 NS_WARNING("First child of DocumentFragment was not an Element node");
1522 return NS_ERROR_FAILURE;
1525 // Copy all attributes from the div child to current body element
1526 CloneAttributesWithTransaction(*rootElement,
1527 MOZ_KnownLive(*firstChild->AsElement()));
1529 // place selection at first editable content
1530 rv = MaybeCollapseSelectionAtFirstEditableNode(false);
1531 NS_WARNING_ASSERTION(
1532 NS_SUCCEEDED(rv),
1533 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) failed");
1534 return rv;
1537 EditorRawDOMPoint HTMLEditor::GetBetterInsertionPointFor(
1538 nsIContent& aContentToInsert,
1539 const EditorRawDOMPoint& aPointToInsert) const {
1540 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
1541 return aPointToInsert;
1544 EditorRawDOMPoint pointToInsert(aPointToInsert.GetNonAnonymousSubtreePoint());
1545 if (NS_WARN_IF(!pointToInsert.IsSet())) {
1546 // Cannot insert aContentToInsert into this DOM tree.
1547 return EditorRawDOMPoint();
1550 // If the node to insert is not a block level element, we can insert it
1551 // at any point.
1552 if (!HTMLEditUtils::IsBlockElement(aContentToInsert)) {
1553 return pointToInsert;
1556 WSRunScanner wsScannerForPointToInsert(*this, pointToInsert);
1558 // If the insertion position is after the last visible item in a line,
1559 // i.e., the insertion position is just before a visible line break <br>,
1560 // we want to skip to the position just after the line break (see bug 68767).
1561 WSScanResult forwardScanFromPointToInsertResult =
1562 wsScannerForPointToInsert.ScanNextVisibleNodeOrBlockBoundaryFrom(
1563 pointToInsert);
1564 // So, if the next visible node isn't a <br> element, we can insert the block
1565 // level element to the point.
1566 if (!forwardScanFromPointToInsertResult.GetContent() ||
1567 !forwardScanFromPointToInsertResult.ReachedBRElement()) {
1568 return pointToInsert;
1571 // However, we must not skip next <br> element when the caret appears to be
1572 // positioned at the beginning of a block, in that case skipping the <br>
1573 // would not insert the <br> at the caret position, but after the current
1574 // empty line.
1575 WSScanResult backwardScanFromPointToInsertResult =
1576 wsScannerForPointToInsert.ScanPreviousVisibleNodeOrBlockBoundaryFrom(
1577 pointToInsert);
1578 // So, if there is no previous visible node,
1579 // or, if both nodes of the insertion point is <br> elements,
1580 // or, if the previous visible node is different block,
1581 // we need to skip the following <br>. So, otherwise, we can insert the
1582 // block at the insertion point.
1583 if (!backwardScanFromPointToInsertResult.GetContent() ||
1584 backwardScanFromPointToInsertResult.ReachedBRElement() ||
1585 backwardScanFromPointToInsertResult.ReachedCurrentBlockBoundary()) {
1586 return pointToInsert;
1589 return forwardScanFromPointToInsertResult.RawPointAfterContent();
1592 NS_IMETHODIMP HTMLEditor::InsertElementAtSelection(Element* aElement,
1593 bool aDeleteSelection) {
1594 nsresult rv = InsertElementAtSelectionAsAction(aElement, aDeleteSelection);
1595 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1596 "HTMLEditor::InsertElementAtSelectionAsAction() failed");
1597 return rv;
1600 nsresult HTMLEditor::InsertElementAtSelectionAsAction(
1601 Element* aElement, bool aDeleteSelection, nsIPrincipal* aPrincipal) {
1602 if (NS_WARN_IF(!aElement)) {
1603 return NS_ERROR_INVALID_ARG;
1606 AutoEditActionDataSetter editActionData(
1607 *this, HTMLEditUtils::GetEditActionForInsert(*aElement), aPrincipal);
1608 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1609 if (NS_FAILED(rv)) {
1610 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1611 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1612 return EditorBase::ToGenericNSResult(rv);
1615 DebugOnly<nsresult> rvIgnored = CommitComposition();
1616 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
1617 "EditorBase::CommitComposition() failed, but ignored");
1619 // XXX Oh, this should be done before dispatching `beforeinput` event.
1620 if (IsReadonly()) {
1621 return NS_OK;
1624 EditActionResult result = CanHandleHTMLEditSubAction();
1625 if (result.Failed() || result.Canceled()) {
1626 NS_WARNING_ASSERTION(result.Succeeded(),
1627 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
1628 return EditorBase::ToGenericNSResult(result.Rv());
1631 UndefineCaretBidiLevel();
1633 AutoPlaceholderBatch treatAsOneTransaction(*this,
1634 ScrollSelectionIntoView::Yes);
1635 IgnoredErrorResult ignoredError;
1636 AutoEditSubActionNotifier startToHandleEditSubAction(
1637 *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError);
1638 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
1639 return ignoredError.StealNSResult();
1641 NS_WARNING_ASSERTION(
1642 !ignoredError.Failed(),
1643 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
1645 rv = EnsureNoPaddingBRElementForEmptyEditor();
1646 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1647 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
1649 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1650 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
1651 "failed, but ignored");
1653 if (NS_SUCCEEDED(rv) && SelectionRefPtr()->IsCollapsed()) {
1654 nsresult rv = EnsureCaretNotAfterPaddingBRElement();
1655 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1656 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
1658 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1659 "HTMLEditor::EnsureCaretNotAfterPaddingBRElement() "
1660 "failed, but ignored");
1661 if (NS_SUCCEEDED(rv)) {
1662 nsresult rv = PrepareInlineStylesForCaret();
1663 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
1664 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
1666 NS_WARNING_ASSERTION(
1667 NS_SUCCEEDED(rv),
1668 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
1672 if (aDeleteSelection) {
1673 if (!HTMLEditUtils::IsBlockElement(*aElement)) {
1674 // E.g., inserting an image. In this case we don't need to delete any
1675 // inline wrappers before we do the insertion. Otherwise we let
1676 // DeleteSelectionAndPrepareToCreateNode do the deletion for us, which
1677 // calls DeleteSelection with aStripWrappers = eStrip.
1678 nsresult rv = DeleteSelectionAsSubAction(eNone, eNoStrip);
1679 if (NS_FAILED(rv)) {
1680 NS_WARNING(
1681 "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed");
1682 return EditorBase::ToGenericNSResult(rv);
1686 nsresult rv = DeleteSelectionAndPrepareToCreateNode();
1687 if (NS_FAILED(rv)) {
1688 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed");
1689 return rv;
1692 // If deleting, selection will be collapsed.
1693 // so if not, we collapse it
1694 else {
1695 // Named Anchor is a special case,
1696 // We collapse to insert element BEFORE the selection
1697 // For all other tags, we insert AFTER the selection
1698 if (HTMLEditUtils::IsNamedAnchor(aElement)) {
1699 IgnoredErrorResult ignoredError;
1700 SelectionRefPtr()->CollapseToStart(ignoredError);
1701 if (NS_WARN_IF(Destroyed())) {
1702 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
1704 NS_WARNING_ASSERTION(!ignoredError.Failed(),
1705 "Selection::CollapseToStart() failed, but ignored");
1706 } else {
1707 IgnoredErrorResult ignoredError;
1708 SelectionRefPtr()->CollapseToEnd(ignoredError);
1709 if (NS_WARN_IF(Destroyed())) {
1710 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
1712 NS_WARNING_ASSERTION(!ignoredError.Failed(),
1713 "Selection::CollapseToEnd() failed, but ignored");
1717 if (!SelectionRefPtr()->GetAnchorNode()) {
1718 return NS_OK;
1721 EditorRawDOMPoint atAnchor(SelectionRefPtr()->AnchorRef());
1722 // Adjust position based on the node we are going to insert.
1723 EditorDOMPoint pointToInsert =
1724 GetBetterInsertionPointFor(*aElement, atAnchor);
1725 if (!pointToInsert.IsSet()) {
1726 NS_WARNING("HTMLEditor::GetBetterInsertionPointFor() failed");
1727 return NS_ERROR_FAILURE;
1730 EditorDOMPoint insertedPoint = InsertNodeIntoProperAncestorWithTransaction(
1731 *aElement, pointToInsert, SplitAtEdges::eAllowToCreateEmptyContainer);
1732 if (!insertedPoint.IsSet()) {
1733 NS_WARNING(
1734 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(SplitAtEdges::"
1735 "eAllowToCreateEmptyContainer) failed");
1736 return NS_ERROR_FAILURE;
1738 // Set caret after element, but check for special case
1739 // of inserting table-related elements: set in first cell instead
1740 if (!SetCaretInTableCell(aElement)) {
1741 if (NS_WARN_IF(Destroyed())) {
1742 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
1744 nsresult rv = CollapseSelectionAfter(*aElement);
1745 if (NS_WARN_IF(Destroyed())) {
1746 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
1748 if (NS_FAILED(rv)) {
1749 NS_WARNING("HTMLEditor::CollapseSelectionAfter() failed");
1750 return rv;
1754 if (NS_WARN_IF(Destroyed())) {
1755 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
1758 // check for inserting a whole table at the end of a block. If so insert
1759 // a br after it.
1760 if (HTMLEditUtils::IsTable(aElement) && IsLastEditableChild(aElement)) {
1761 DebugOnly<bool> advanced = insertedPoint.AdvanceOffset();
1762 NS_WARNING_ASSERTION(advanced,
1763 "Failed to advance offset from inserted point");
1764 // Collapse selection to the new `<br>` element node after creating it.
1765 RefPtr<Element> newBRElement =
1766 InsertBRElementWithTransaction(insertedPoint, ePrevious);
1767 if (NS_WARN_IF(Destroyed())) {
1768 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED);
1770 if (!newBRElement) {
1771 NS_WARNING(
1772 "HTMLEditor::InsertBRElementWithTransaction(ePrevious) failed");
1773 return NS_ERROR_FAILURE;
1777 return NS_OK;
1780 EditorDOMPoint HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(
1781 nsIContent& aNode, const EditorDOMPoint& aPointToInsert,
1782 SplitAtEdges aSplitAtEdges) {
1783 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
1784 return EditorDOMPoint();
1786 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
1788 // Search up the parent chain to find a suitable container.
1789 EditorDOMPoint pointToInsert(aPointToInsert);
1790 MOZ_ASSERT(pointToInsert.IsSet());
1791 while (!HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(), aNode)) {
1792 // If the current parent is a root (body or table element)
1793 // then go no further - we can't insert.
1794 if (pointToInsert.IsContainerHTMLElement(nsGkAtoms::body) ||
1795 HTMLEditUtils::IsAnyTableElement(pointToInsert.GetContainer())) {
1796 return EditorDOMPoint();
1799 // Get the next point.
1800 pointToInsert.Set(pointToInsert.GetContainer());
1801 if (NS_WARN_IF(!pointToInsert.IsSet())) {
1802 return EditorDOMPoint();
1805 if (!pointToInsert.IsInContentNode() ||
1806 !EditorUtils::IsEditableContent(*pointToInsert.ContainerAsContent(),
1807 EditorType::HTML)) {
1808 // There's no suitable place to put the node in this editing host. Maybe
1809 // someone is trying to put block content in a span. So just put it
1810 // where we were originally asked.
1811 pointToInsert = aPointToInsert;
1812 break;
1816 if (pointToInsert != aPointToInsert) {
1817 // We need to split some levels above the original selection parent.
1818 MOZ_ASSERT(pointToInsert.GetChild());
1819 SplitNodeResult splitNodeResult =
1820 SplitNodeDeepWithTransaction(MOZ_KnownLive(*pointToInsert.GetChild()),
1821 aPointToInsert, aSplitAtEdges);
1822 if (splitNodeResult.Failed()) {
1823 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed");
1824 return EditorDOMPoint();
1826 pointToInsert = splitNodeResult.SplitPoint();
1827 MOZ_ASSERT(pointToInsert.IsSet());
1831 // After inserting a node, pointToInsert will refer next sibling of
1832 // the new node but keep referring the new node's offset.
1833 // This method's result should be the point at insertion, it's useful
1834 // even if the new node is moved by mutation observer immediately.
1835 // So, let's lock only the offset and child node should be recomputed
1836 // when it's necessary.
1837 AutoEditorDOMPointChildInvalidator lockOffset(pointToInsert);
1838 // Now we can insert the new node.
1839 nsresult rv = InsertNodeWithTransaction(aNode, pointToInsert);
1840 if (NS_FAILED(rv)) {
1841 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
1842 return EditorDOMPoint();
1845 return pointToInsert;
1848 NS_IMETHODIMP HTMLEditor::SelectElement(Element* aElement) {
1849 if (NS_WARN_IF(!aElement)) {
1850 return NS_ERROR_INVALID_ARG;
1853 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1854 if (NS_WARN_IF(!editActionData.CanHandle())) {
1855 return NS_ERROR_NOT_INITIALIZED;
1858 nsresult rv = SelectContentInternal(MOZ_KnownLive(*aElement));
1859 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1860 "HTMLEditor::SelectContentInternal() failed");
1861 return rv;
1864 nsresult HTMLEditor::SelectContentInternal(nsIContent& aContentToSelect) {
1865 MOZ_ASSERT(IsEditActionDataAvailable());
1867 // Must be sure that element is contained in the document body
1868 if (NS_WARN_IF(!IsDescendantOfEditorRoot(&aContentToSelect))) {
1869 return NS_ERROR_FAILURE;
1872 EditorRawDOMPoint newSelectionStart(&aContentToSelect);
1873 if (NS_WARN_IF(!newSelectionStart.IsSet())) {
1874 return NS_ERROR_FAILURE;
1876 EditorRawDOMPoint newSelectionEnd(EditorRawDOMPoint::After(aContentToSelect));
1877 MOZ_ASSERT(newSelectionEnd.IsSet());
1878 ErrorResult error;
1879 MOZ_KnownLive(SelectionRefPtr())
1880 ->SetStartAndEndInLimiter(newSelectionStart, newSelectionEnd, error);
1881 NS_WARNING_ASSERTION(!error.Failed(),
1882 "Selection::SetStartAndEndInLimiter() failed");
1883 return error.StealNSResult();
1886 MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
1887 HTMLEditor::SetCaretAfterElement(Element* aElement) {
1888 if (NS_WARN_IF(!aElement)) {
1889 return NS_ERROR_INVALID_ARG;
1892 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1893 if (NS_WARN_IF(!editActionData.CanHandle())) {
1894 return NS_ERROR_NOT_INITIALIZED;
1897 nsresult rv = CollapseSelectionAfter(*aElement);
1898 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1899 "HTMLEditor::CollapseSelectionAfter() failed");
1900 return rv;
1903 nsresult HTMLEditor::CollapseSelectionAfter(Element& aElement) {
1904 MOZ_ASSERT(IsEditActionDataAvailable());
1906 // Be sure the element is contained in the document body
1907 if (NS_WARN_IF(!IsDescendantOfEditorRoot(&aElement))) {
1908 return NS_ERROR_INVALID_ARG;
1910 if (NS_WARN_IF(!aElement.GetParentNode())) {
1911 return NS_ERROR_FAILURE;
1913 // Collapse selection to just after desired element,
1914 EditorRawDOMPoint afterElement(EditorRawDOMPoint::After(aElement));
1915 if (NS_WARN_IF(!afterElement.IsSet())) {
1916 return NS_ERROR_FAILURE;
1918 nsresult rv = CollapseSelectionTo(afterElement);
1919 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1920 "HTMLEditor::CollapseSelectionTo() failed");
1921 return rv;
1924 nsresult HTMLEditor::SetParagraphFormatAsAction(
1925 const nsAString& aParagraphFormat, nsIPrincipal* aPrincipal) {
1926 AutoEditActionDataSetter editActionData(
1927 *this, EditAction::eInsertBlockElement, aPrincipal);
1928 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
1929 if (NS_FAILED(rv)) {
1930 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
1931 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
1932 return EditorBase::ToGenericNSResult(rv);
1935 nsAutoString lowerCaseTagName(aParagraphFormat);
1936 ToLowerCase(lowerCaseTagName);
1937 RefPtr<nsAtom> tagName = NS_Atomize(lowerCaseTagName);
1938 MOZ_ASSERT(tagName);
1939 if (tagName == nsGkAtoms::dd || tagName == nsGkAtoms::dt) {
1940 EditActionResult result = MakeOrChangeListAndListItemAsSubAction(
1941 *tagName, u""_ns, SelectAllOfCurrentList::No);
1942 NS_WARNING_ASSERTION(result.Succeeded(),
1943 "HTMLEditor::MakeOrChangeListAndListItemAsSubAction("
1944 "SelectAllOfCurrentList::No) failed");
1945 return EditorBase::ToGenericNSResult(result.Rv());
1947 rv = FormatBlockContainerAsSubAction(*tagName);
1948 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1949 "HTMLEditor::FormatBlockContainerAsSubAction() failed");
1950 return EditorBase::ToGenericNSResult(rv);
1953 NS_IMETHODIMP HTMLEditor::GetParagraphState(bool* aMixed,
1954 nsAString& aFirstParagraphState) {
1955 if (NS_WARN_IF(!aMixed)) {
1956 return NS_ERROR_INVALID_ARG;
1958 if (!mInitSucceeded) {
1959 return NS_ERROR_NOT_INITIALIZED;
1962 ErrorResult error;
1963 ParagraphStateAtSelection paragraphState(*this, error);
1964 if (error.Failed()) {
1965 NS_WARNING("ParagraphStateAtSelection failed");
1966 return error.StealNSResult();
1969 *aMixed = paragraphState.IsMixed();
1970 if (NS_WARN_IF(!paragraphState.GetFirstParagraphStateAtSelection())) {
1971 // XXX Odd result, but keep this behavior for now...
1972 aFirstParagraphState.AssignASCII("x");
1973 } else {
1974 paragraphState.GetFirstParagraphStateAtSelection()->ToString(
1975 aFirstParagraphState);
1977 return NS_OK;
1980 nsresult HTMLEditor::GetBackgroundColorState(bool* aMixed,
1981 nsAString& aOutColor) {
1982 if (NS_WARN_IF(!aMixed)) {
1983 return NS_ERROR_INVALID_ARG;
1986 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
1987 if (NS_WARN_IF(!editActionData.CanHandle())) {
1988 return NS_ERROR_NOT_INITIALIZED;
1991 if (IsCSSEnabled()) {
1992 // if we are in CSS mode, we have to check if the containing block defines
1993 // a background color
1994 nsresult rv = GetCSSBackgroundColorState(aMixed, aOutColor, true);
1995 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
1996 "HTMLEditor::GetCSSBackgroundColorState() failed");
1997 return EditorBase::ToGenericNSResult(rv);
1999 // in HTML mode, we look only at page's background
2000 nsresult rv = GetHTMLBackgroundColorState(aMixed, aOutColor);
2001 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2002 "HTMLEditor::GetCSSBackgroundColorState() failed");
2003 return EditorBase::ToGenericNSResult(rv);
2006 NS_IMETHODIMP HTMLEditor::GetHighlightColorState(bool* aMixed,
2007 nsAString& aOutColor) {
2008 if (NS_WARN_IF(!aMixed)) {
2009 return NS_ERROR_INVALID_ARG;
2012 *aMixed = false;
2013 aOutColor.AssignLiteral("transparent");
2014 if (!IsCSSEnabled()) {
2015 return NS_OK;
2018 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2019 if (NS_WARN_IF(!editActionData.CanHandle())) {
2020 return NS_ERROR_NOT_INITIALIZED;
2023 // in CSS mode, text background can be added by the Text Highlight button
2024 // we need to query the background of the selection without looking for
2025 // the block container of the ranges in the selection
2026 nsresult rv = GetCSSBackgroundColorState(aMixed, aOutColor, false);
2027 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2028 "HTMLEditor::GetCSSBackgroundColorState() failed");
2029 return rv;
2032 nsresult HTMLEditor::GetCSSBackgroundColorState(bool* aMixed,
2033 nsAString& aOutColor,
2034 bool aBlockLevel) {
2035 MOZ_ASSERT(IsEditActionDataAvailable());
2037 if (NS_WARN_IF(!aMixed)) {
2038 return NS_ERROR_INVALID_ARG;
2041 *aMixed = false;
2042 // the default background color is transparent
2043 aOutColor.AssignLiteral("transparent");
2045 RefPtr<const nsRange> firstRange = SelectionRefPtr()->GetRangeAt(0);
2046 if (NS_WARN_IF(!firstRange)) {
2047 return NS_ERROR_FAILURE;
2050 nsCOMPtr<nsINode> startContainer = firstRange->GetStartContainer();
2051 if (NS_WARN_IF(!startContainer) || NS_WARN_IF(!startContainer->IsContent())) {
2052 return NS_ERROR_FAILURE;
2055 // is the selection collapsed?
2056 nsIContent* contentToExamine;
2057 if (SelectionRefPtr()->IsCollapsed() || startContainer->IsText()) {
2058 if (NS_WARN_IF(!startContainer->IsContent())) {
2059 return NS_ERROR_FAILURE;
2061 // we want to look at the startContainer and ancestors
2062 contentToExamine = startContainer->AsContent();
2063 } else {
2064 // otherwise we want to look at the first editable node after
2065 // {startContainer,offset} and its ancestors for divs with alignment on them
2066 contentToExamine = firstRange->GetChildAtStartOffset();
2067 // GetNextNode(startContainer, offset, true, address_of(contentToExamine));
2070 if (NS_WARN_IF(!contentToExamine)) {
2071 return NS_ERROR_FAILURE;
2074 if (aBlockLevel) {
2075 // we are querying the block background (and not the text background), let's
2076 // climb to the block container
2077 Element* blockParent =
2078 HTMLEditUtils::GetInclusiveAncestorBlockElement(*contentToExamine);
2079 if (NS_WARN_IF(!blockParent)) {
2080 return NS_OK;
2083 for (RefPtr<Element> element = blockParent; element;
2084 element = element->GetParentElement()) {
2085 nsCOMPtr<nsINode> parentNode = element->GetParentNode();
2086 // retrieve the computed style of background-color for blockParent
2087 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
2088 *element, *nsGkAtoms::backgroundColor, aOutColor);
2089 if (NS_WARN_IF(Destroyed())) {
2090 return NS_ERROR_EDITOR_DESTROYED;
2092 if (NS_WARN_IF(parentNode != element->GetParentNode())) {
2093 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
2095 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2096 "CSSEditUtils::GetComputedProperty(nsGkAtoms::"
2097 "backgroundColor) failed, but ignored");
2098 // look at parent if the queried color is transparent and if the node to
2099 // examine is not the root of the document
2100 if (!aOutColor.EqualsLiteral("transparent")) {
2101 break;
2105 if (aOutColor.EqualsLiteral("transparent")) {
2106 // we have hit the root of the document and the color is still transparent
2107 // ! Grumble... Let's look at the default background color because that's
2108 // the color we are looking for
2109 CSSEditUtils::GetDefaultBackgroundColor(aOutColor);
2111 } else {
2112 // no, we are querying the text background for the Text Highlight button
2113 if (contentToExamine->IsText()) {
2114 // if the node of interest is a text node, let's climb a level
2115 contentToExamine = contentToExamine->GetParent();
2117 // Return default value due to no parent node
2118 if (!contentToExamine) {
2119 return NS_OK;
2122 for (RefPtr<Element> element =
2123 contentToExamine->GetAsElementOrParentElement();
2124 element; element = element->GetParentElement()) {
2125 // is the node to examine a block ?
2126 if (HTMLEditUtils::IsBlockElement(*element)) {
2127 // yes it is a block; in that case, the text background color is
2128 // transparent
2129 aOutColor.AssignLiteral("transparent");
2130 break;
2133 // no, it's not; let's retrieve the computed style of background-color
2134 // for the node to examine
2135 nsCOMPtr<nsINode> parentNode = element->GetParentNode();
2136 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty(
2137 *element, *nsGkAtoms::backgroundColor, aOutColor);
2138 if (NS_WARN_IF(Destroyed())) {
2139 return NS_ERROR_EDITOR_DESTROYED;
2141 if (NS_WARN_IF(parentNode != element->GetParentNode())) {
2142 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE;
2144 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
2145 "CSSEditUtils::GetComputedProperty(nsGkAtoms::"
2146 "backgroundColor) failed, but ignored");
2147 if (!aOutColor.EqualsLiteral("transparent")) {
2148 break;
2152 return NS_OK;
2155 nsresult HTMLEditor::GetHTMLBackgroundColorState(bool* aMixed,
2156 nsAString& aOutColor) {
2157 MOZ_ASSERT(IsEditActionDataAvailable());
2159 // TODO: We don't handle "mixed" correctly!
2160 if (NS_WARN_IF(!aMixed)) {
2161 return NS_ERROR_INVALID_ARG;
2164 *aMixed = false;
2165 aOutColor.Truncate();
2167 ErrorResult error;
2168 RefPtr<Element> cellOrRowOrTableElement =
2169 GetSelectedOrParentTableElement(error);
2170 if (error.Failed()) {
2171 NS_WARNING(
2172 "HTMLEditor::GetSelectedOrParentTableElement() returned nullptr");
2173 return error.StealNSResult();
2176 for (RefPtr<Element> element = std::move(cellOrRowOrTableElement); element;
2177 element = element->GetParentElement()) {
2178 // We are in a cell or selected table
2179 element->GetAttr(kNameSpaceID_None, nsGkAtoms::bgcolor, aOutColor);
2181 // Done if we have a color explicitly set
2182 if (!aOutColor.IsEmpty()) {
2183 return NS_OK;
2186 // Once we hit the body, we're done
2187 if (element->IsHTMLElement(nsGkAtoms::body)) {
2188 return NS_OK;
2191 // No color is set, but we need to report visible color inherited
2192 // from nested cells/tables, so search up parent chain so that
2193 // let's keep checking the ancestors.
2196 // If no table or cell found, get page body
2197 Element* rootElement = GetRoot();
2198 if (NS_WARN_IF(!rootElement)) {
2199 return NS_ERROR_FAILURE;
2202 rootElement->GetAttr(kNameSpaceID_None, nsGkAtoms::bgcolor, aOutColor);
2203 return NS_OK;
2206 NS_IMETHODIMP HTMLEditor::GetListState(bool* aMixed, bool* aOL, bool* aUL,
2207 bool* aDL) {
2208 if (NS_WARN_IF(!aMixed) || NS_WARN_IF(!aOL) || NS_WARN_IF(!aUL) ||
2209 NS_WARN_IF(!aDL)) {
2210 return NS_ERROR_INVALID_ARG;
2212 if (!mInitSucceeded) {
2213 return NS_ERROR_NOT_INITIALIZED;
2216 ErrorResult error;
2217 ListElementSelectionState state(*this, error);
2218 if (error.Failed()) {
2219 NS_WARNING("ListElementSelectionState failed");
2220 return error.StealNSResult();
2223 *aMixed = state.IsNotOneTypeListElementSelected();
2224 *aOL = state.IsOLElementSelected();
2225 *aUL = state.IsULElementSelected();
2226 *aDL = state.IsDLElementSelected();
2227 return NS_OK;
2230 NS_IMETHODIMP HTMLEditor::GetListItemState(bool* aMixed, bool* aLI, bool* aDT,
2231 bool* aDD) {
2232 if (NS_WARN_IF(!aMixed) || NS_WARN_IF(!aLI) || NS_WARN_IF(!aDT) ||
2233 NS_WARN_IF(!aDD)) {
2234 return NS_ERROR_INVALID_ARG;
2236 if (!mInitSucceeded) {
2237 return NS_ERROR_NOT_INITIALIZED;
2240 ErrorResult error;
2241 ListItemElementSelectionState state(*this, error);
2242 if (error.Failed()) {
2243 NS_WARNING("ListItemElementSelectionState failed");
2244 return error.StealNSResult();
2247 // XXX Why do we ignore `<li>` element selected state?
2248 *aMixed = state.IsNotOneTypeDefinitionListItemElementSelected();
2249 *aLI = state.IsLIElementSelected();
2250 *aDT = state.IsDTElementSelected();
2251 *aDD = state.IsDDElementSelected();
2252 return NS_OK;
2255 NS_IMETHODIMP HTMLEditor::GetAlignment(bool* aMixed,
2256 nsIHTMLEditor::EAlignment* aAlign) {
2257 if (NS_WARN_IF(!aMixed) || NS_WARN_IF(!aAlign)) {
2258 return NS_ERROR_INVALID_ARG;
2260 if (!mInitSucceeded) {
2261 return NS_ERROR_NOT_INITIALIZED;
2264 ErrorResult error;
2265 AlignStateAtSelection state(*this, error);
2266 if (error.Failed()) {
2267 NS_WARNING("AlignStateAtSelection failed");
2268 return error.StealNSResult();
2271 *aMixed = false;
2272 *aAlign = state.AlignmentAtSelectionStart();
2273 return NS_OK;
2276 NS_IMETHODIMP HTMLEditor::MakeOrChangeList(const nsAString& aListType,
2277 bool aEntireList,
2278 const nsAString& aBulletType) {
2279 RefPtr<nsAtom> listTagName = NS_Atomize(aListType);
2280 if (NS_WARN_IF(!listTagName)) {
2281 return NS_ERROR_INVALID_ARG;
2283 nsresult rv = MakeOrChangeListAsAction(
2284 *listTagName, aBulletType,
2285 aEntireList ? SelectAllOfCurrentList::Yes : SelectAllOfCurrentList::No);
2286 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2287 "HTMLEditor::MakeOrChangeListAsAction() failed");
2288 return rv;
2291 nsresult HTMLEditor::MakeOrChangeListAsAction(
2292 nsAtom& aListTagName, const nsAString& aBulletType,
2293 SelectAllOfCurrentList aSelectAllOfCurrentList, nsIPrincipal* aPrincipal) {
2294 if (NS_WARN_IF(!mInitSucceeded)) {
2295 return NS_ERROR_NOT_INITIALIZED;
2298 AutoEditActionDataSetter editActionData(
2299 *this, HTMLEditUtils::GetEditActionForInsert(aListTagName), aPrincipal);
2300 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2301 if (NS_FAILED(rv)) {
2302 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2303 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2304 return EditorBase::ToGenericNSResult(rv);
2307 EditActionResult result = MakeOrChangeListAndListItemAsSubAction(
2308 aListTagName, aBulletType, aSelectAllOfCurrentList);
2309 NS_WARNING_ASSERTION(
2310 result.Succeeded(),
2311 "HTMLEditor::MakeOrChangeListAndListItemAsSubAction() failed");
2312 return EditorBase::ToGenericNSResult(result.Rv());
2315 NS_IMETHODIMP HTMLEditor::RemoveList(const nsAString& aListType) {
2316 nsresult rv = RemoveListAsAction(aListType);
2317 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2318 "HTMLEditor::RemoveListAsAction() failed");
2319 return rv;
2322 nsresult HTMLEditor::RemoveListAsAction(const nsAString& aListType,
2323 nsIPrincipal* aPrincipal) {
2324 if (NS_WARN_IF(!mInitSucceeded)) {
2325 return NS_ERROR_NOT_INITIALIZED;
2328 // Note that we ignore aListType when we actually remove parent list elements.
2329 // However, we need to set InputEvent.inputType to "insertOrderedList" or
2330 // "insertedUnorderedList" when this is called for
2331 // execCommand("insertorderedlist") or execCommand("insertunorderedlist").
2332 // Otherwise, comm-central UI may call this methods with "dl" or "".
2333 // So, it's okay to use mismatched EditAction here if this is called in
2334 // comm-central.
2336 RefPtr<nsAtom> listAtom = NS_Atomize(aListType);
2337 if (NS_WARN_IF(!listAtom)) {
2338 return NS_ERROR_INVALID_ARG;
2340 AutoEditActionDataSetter editActionData(
2341 *this, HTMLEditUtils::GetEditActionForRemoveList(*listAtom), aPrincipal);
2342 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2343 if (NS_FAILED(rv)) {
2344 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2345 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2346 return EditorBase::ToGenericNSResult(rv);
2349 rv = RemoveListAtSelectionAsSubAction();
2350 NS_WARNING_ASSERTION(NS_FAILED(rv),
2351 "HTMLEditor::RemoveListAtSelectionAsSubAction() failed");
2352 return rv;
2355 nsresult HTMLEditor::FormatBlockContainerAsSubAction(nsAtom& aTagName) {
2356 MOZ_ASSERT(IsEditActionDataAvailable());
2358 if (NS_WARN_IF(!mInitSucceeded)) {
2359 return NS_ERROR_NOT_INITIALIZED;
2362 MOZ_ASSERT(&aTagName != nsGkAtoms::dd && &aTagName != nsGkAtoms::dt);
2364 AutoPlaceholderBatch treatAsOneTransaction(*this,
2365 ScrollSelectionIntoView::Yes);
2366 IgnoredErrorResult ignoredError;
2367 AutoEditSubActionNotifier startToHandleEditSubAction(
2368 *this, EditSubAction::eCreateOrRemoveBlock, nsIEditor::eNext,
2369 ignoredError);
2370 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
2371 return ignoredError.StealNSResult();
2373 NS_WARNING_ASSERTION(
2374 !ignoredError.Failed(),
2375 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
2377 EditActionResult result = CanHandleHTMLEditSubAction();
2378 if (result.Failed() || result.Canceled()) {
2379 NS_WARNING_ASSERTION(result.Succeeded(),
2380 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
2381 return result.Rv();
2384 if (IsSelectionRangeContainerNotContent()) {
2385 return NS_SUCCESS_DOM_NO_OPERATION;
2388 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
2389 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2390 return NS_ERROR_EDITOR_DESTROYED;
2392 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2393 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
2394 "failed, but ignored");
2396 if (NS_SUCCEEDED(rv) && SelectionRefPtr()->IsCollapsed()) {
2397 nsresult rv = EnsureCaretNotAfterPaddingBRElement();
2398 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2399 return NS_ERROR_EDITOR_DESTROYED;
2401 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
2402 "HTMLEditor::EnsureCaretNotAfterPaddingBRElement() "
2403 "failed, but ignored");
2404 if (NS_SUCCEEDED(rv)) {
2405 nsresult rv = PrepareInlineStylesForCaret();
2406 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
2407 return NS_ERROR_EDITOR_DESTROYED;
2409 NS_WARNING_ASSERTION(
2410 NS_SUCCEEDED(rv),
2411 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored");
2415 // FormatBlockContainerWithTransaction() creates AutoSelectionRestorer.
2416 // Therefore, even if it returns NS_OK, editor might have been destroyed
2417 // at restoring Selection.
2418 rv = FormatBlockContainerWithTransaction(aTagName);
2419 if (NS_WARN_IF(Destroyed())) {
2420 return NS_ERROR_EDITOR_DESTROYED;
2422 if (NS_FAILED(rv)) {
2423 NS_WARNING("HTMLEditor::FormatBlockContainerWithTransaction() failed");
2424 return rv;
2427 rv = MaybeInsertPaddingBRElementForEmptyLastLineAtSelection();
2428 NS_WARNING_ASSERTION(
2429 NS_SUCCEEDED(rv),
2430 "HTMLEditor::MaybeInsertPaddingBRElementForEmptyLastLineAtSelection() "
2431 "failed");
2432 return rv;
2435 nsresult HTMLEditor::IndentAsAction(nsIPrincipal* aPrincipal) {
2436 if (NS_WARN_IF(!mInitSucceeded)) {
2437 return NS_ERROR_NOT_INITIALIZED;
2440 AutoEditActionDataSetter editActionData(*this, EditAction::eIndent,
2441 aPrincipal);
2442 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2443 if (NS_FAILED(rv)) {
2444 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2445 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2446 return EditorBase::ToGenericNSResult(rv);
2449 EditActionResult result = IndentAsSubAction();
2450 NS_WARNING_ASSERTION(result.Succeeded(),
2451 "HTMLEditor::IndentAsSubAction() failed");
2452 return EditorBase::ToGenericNSResult(result.Rv());
2455 nsresult HTMLEditor::OutdentAsAction(nsIPrincipal* aPrincipal) {
2456 if (NS_WARN_IF(!mInitSucceeded)) {
2457 return NS_ERROR_NOT_INITIALIZED;
2460 AutoEditActionDataSetter editActionData(*this, EditAction::eOutdent,
2461 aPrincipal);
2462 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2463 if (NS_FAILED(rv)) {
2464 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2465 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2466 return EditorBase::ToGenericNSResult(rv);
2469 EditActionResult result = OutdentAsSubAction();
2470 NS_WARNING_ASSERTION(result.Succeeded(),
2471 "HTMLEditor::OutdentAsSubAction() failed");
2472 return EditorBase::ToGenericNSResult(result.Rv());
2475 // TODO: IMPLEMENT ALIGNMENT!
2477 nsresult HTMLEditor::AlignAsAction(const nsAString& aAlignType,
2478 nsIPrincipal* aPrincipal) {
2479 AutoEditActionDataSetter editActionData(
2480 *this, HTMLEditUtils::GetEditActionForAlignment(aAlignType), aPrincipal);
2481 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
2482 if (NS_FAILED(rv)) {
2483 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2484 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
2485 return EditorBase::ToGenericNSResult(rv);
2488 EditActionResult result = AlignAsSubAction(aAlignType);
2489 NS_WARNING_ASSERTION(result.Succeeded(),
2490 "HTMLEditor::AlignAsSubAction() failed");
2491 return EditorBase::ToGenericNSResult(result.Rv());
2494 Element* HTMLEditor::GetInclusiveAncestorByTagName(const nsStaticAtom& aTagName,
2495 nsIContent& aContent) const {
2496 MOZ_ASSERT(&aTagName != nsGkAtoms::_empty);
2498 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2499 if (NS_WARN_IF(!editActionData.CanHandle())) {
2500 return nullptr;
2503 return GetInclusiveAncestorByTagNameInternal(aTagName, aContent);
2506 Element* HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(
2507 const nsStaticAtom& aTagName) const {
2508 MOZ_ASSERT(IsEditActionDataAvailable());
2509 MOZ_ASSERT(&aTagName != nsGkAtoms::_empty);
2511 // If no node supplied, get it from anchor node of current selection
2512 const EditorRawDOMPoint atAnchor(SelectionRefPtr()->AnchorRef());
2513 if (NS_WARN_IF(!atAnchor.IsSet()) ||
2514 NS_WARN_IF(!atAnchor.GetContainerAsContent())) {
2515 return nullptr;
2518 // Try to get the actual selected node
2519 nsIContent* content = nullptr;
2520 if (atAnchor.GetContainer()->HasChildNodes() &&
2521 atAnchor.GetContainerAsContent()) {
2522 content = atAnchor.GetChild();
2524 // Anchor node is probably a text node - just use that
2525 if (!content) {
2526 content = atAnchor.GetContainerAsContent();
2527 if (NS_WARN_IF(!content)) {
2528 return nullptr;
2531 return GetInclusiveAncestorByTagNameInternal(aTagName, *content);
2534 Element* HTMLEditor::GetInclusiveAncestorByTagNameInternal(
2535 const nsStaticAtom& aTagName, nsIContent& aContent) const {
2536 MOZ_ASSERT(&aTagName != nsGkAtoms::_empty);
2538 Element* currentElement = aContent.GetAsElementOrParentElement();
2539 if (NS_WARN_IF(!currentElement)) {
2540 MOZ_ASSERT(!aContent.GetParentNode());
2541 return nullptr;
2544 bool lookForLink = IsLinkTag(aTagName);
2545 bool lookForNamedAnchor = IsNamedAnchorTag(aTagName);
2546 for (Element* element : currentElement->InclusiveAncestorsOfType<Element>()) {
2547 // Stop searching if parent is a body element. Note: Originally used
2548 // IsRoot() to/ stop at table cells, but that's too messy when you are
2549 // trying to find the parent table.
2550 if (element->IsHTMLElement(nsGkAtoms::body)) {
2551 return nullptr;
2553 if (lookForLink) {
2554 // Test if we have a link (an anchor with href set)
2555 if (HTMLEditUtils::IsLink(element)) {
2556 return element;
2558 } else if (lookForNamedAnchor) {
2559 // Test if we have a named anchor (an anchor with name set)
2560 if (HTMLEditUtils::IsNamedAnchor(element)) {
2561 return element;
2563 } else if (&aTagName == nsGkAtoms::list_) {
2564 // Match "ol", "ul", or "dl" for lists
2565 if (HTMLEditUtils::IsAnyListElement(element)) {
2566 return element;
2568 } else if (&aTagName == nsGkAtoms::td) {
2569 // Table cells are another special case: match either "td" or "th"
2570 if (HTMLEditUtils::IsTableCell(element)) {
2571 return element;
2573 } else if (&aTagName == element->NodeInfo()->NameAtom()) {
2574 return element;
2577 return nullptr;
2580 NS_IMETHODIMP HTMLEditor::GetElementOrParentByTagName(const nsAString& aTagName,
2581 nsINode* aNode,
2582 Element** aReturn) {
2583 if (NS_WARN_IF(aTagName.IsEmpty()) || NS_WARN_IF(!aReturn)) {
2584 return NS_ERROR_INVALID_ARG;
2587 nsStaticAtom* tagName = EditorUtils::GetTagNameAtom(aTagName);
2588 if (NS_WARN_IF(!tagName)) {
2589 // We don't need to support custom elements since this is an internal API.
2590 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2592 if (NS_WARN_IF(tagName == nsGkAtoms::_empty)) {
2593 return NS_ERROR_INVALID_ARG;
2596 if (!aNode) {
2597 AutoEditActionDataSetter dummyEditAction(*this, EditAction::eNotEditing);
2598 if (NS_WARN_IF(!dummyEditAction.CanHandle())) {
2599 return NS_ERROR_NOT_AVAILABLE;
2601 RefPtr<Element> parentElement =
2602 GetInclusiveAncestorByTagNameAtSelection(*tagName);
2603 if (!parentElement) {
2604 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2606 parentElement.forget(aReturn);
2607 return NS_OK;
2610 if (!aNode->IsContent() || !aNode->GetAsElementOrParentElement()) {
2611 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2614 RefPtr<Element> parentElement =
2615 GetInclusiveAncestorByTagName(*tagName, *aNode->AsContent());
2616 if (!parentElement) {
2617 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND;
2619 parentElement.forget(aReturn);
2620 return NS_OK;
2623 NS_IMETHODIMP HTMLEditor::GetSelectedElement(const nsAString& aTagName,
2624 nsISupports** aReturn) {
2625 if (NS_WARN_IF(!aReturn)) {
2626 return NS_ERROR_INVALID_ARG;
2628 *aReturn = nullptr;
2630 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2631 if (NS_WARN_IF(!editActionData.CanHandle())) {
2632 return NS_ERROR_NOT_INITIALIZED;
2635 ErrorResult error;
2636 nsStaticAtom* tagName = EditorUtils::GetTagNameAtom(aTagName);
2637 if (!aTagName.IsEmpty() && !tagName) {
2638 // We don't need to support custom elements becaus of internal API.
2639 return NS_OK;
2641 RefPtr<nsINode> selectedNode = GetSelectedElement(tagName, error);
2642 NS_WARNING_ASSERTION(!error.Failed(),
2643 "HTMLEditor::GetSelectedElement() failed");
2644 selectedNode.forget(aReturn);
2645 return error.StealNSResult();
2648 already_AddRefed<Element> HTMLEditor::GetSelectedElement(const nsAtom* aTagName,
2649 ErrorResult& aRv) {
2650 MOZ_ASSERT(IsEditActionDataAvailable());
2652 MOZ_ASSERT(!aRv.Failed());
2654 // If there is no Selection or two or more selection ranges, that means that
2655 // not only one element is selected so that return nullptr.
2656 if (SelectionRefPtr()->RangeCount() != 1) {
2657 return nullptr;
2660 bool isLinkTag = aTagName && IsLinkTag(*aTagName);
2661 bool isNamedAnchorTag = aTagName && IsNamedAnchorTag(*aTagName);
2663 RefPtr<nsRange> firstRange = SelectionRefPtr()->GetRangeAt(0);
2664 MOZ_ASSERT(firstRange);
2666 const RangeBoundary& startRef = firstRange->StartRef();
2667 if (NS_WARN_IF(!startRef.IsSet())) {
2668 aRv.Throw(NS_ERROR_FAILURE);
2669 return nullptr;
2671 const RangeBoundary& endRef = firstRange->EndRef();
2672 if (NS_WARN_IF(!endRef.IsSet())) {
2673 aRv.Throw(NS_ERROR_FAILURE);
2674 return nullptr;
2677 // Optimization for a single selected element
2678 if (startRef.Container() == endRef.Container()) {
2679 nsIContent* startContent = startRef.GetChildAtOffset();
2680 nsIContent* endContent = endRef.GetChildAtOffset();
2681 if (startContent && endContent &&
2682 startContent->GetNextSibling() == endContent) {
2683 if (!aTagName) {
2684 if (!startContent->IsElement()) {
2685 // This means only a text node or something is selected. We should
2686 // return nullptr in this case since no other elements are selected.
2687 return nullptr;
2689 return do_AddRef(startContent->AsElement());
2691 // Test for appropriate node type requested
2692 if (aTagName == startContent->NodeInfo()->NameAtom() ||
2693 (isLinkTag && HTMLEditUtils::IsLink(startContent)) ||
2694 (isNamedAnchorTag && HTMLEditUtils::IsNamedAnchor(startContent))) {
2695 MOZ_ASSERT(startContent->IsElement());
2696 return do_AddRef(startContent->AsElement());
2701 if (isLinkTag && startRef.Container()->IsContent() &&
2702 endRef.Container()->IsContent()) {
2703 // Link node must be the same for both ends of selection.
2704 Element* parentLinkOfStart = GetInclusiveAncestorByTagNameInternal(
2705 *nsGkAtoms::href, *startRef.Container()->AsContent());
2706 if (parentLinkOfStart) {
2707 if (SelectionRefPtr()->IsCollapsed()) {
2708 // We have just a caret in the link.
2709 return do_AddRef(parentLinkOfStart);
2711 // Link node must be the same for both ends of selection.
2712 Element* parentLinkOfEnd = GetInclusiveAncestorByTagNameInternal(
2713 *nsGkAtoms::href, *endRef.Container()->AsContent());
2714 if (parentLinkOfStart == parentLinkOfEnd) {
2715 return do_AddRef(parentLinkOfStart);
2720 if (SelectionRefPtr()->IsCollapsed()) {
2721 return nullptr;
2724 PostContentIterator postOrderIter;
2725 postOrderIter.Init(firstRange);
2727 RefPtr<Element> lastElementInRange;
2728 for (nsINode* lastNodeInRange = nullptr; !postOrderIter.IsDone();
2729 postOrderIter.Next()) {
2730 if (lastElementInRange) {
2731 // When any node follows an element node, not only one element is
2732 // selected so that return nullptr.
2733 return nullptr;
2736 // This loop ignors any non-element nodes before first element node.
2737 // Its purpose must be that this method treats this case as selecting
2738 // the <b> element:
2739 // - <p>abc <b>d[ef</b>}</p>
2740 // because children of an element node is listed up before the element.
2741 // However, this case must not be expected by the initial developer:
2742 // - <p>a[bc <b>def</b>}</p>
2743 // When we meet non-parent and non-next-sibling node of previous node,
2744 // it means that the range across element boundary (open tag in HTML
2745 // source). So, in this case, we should not say only the following
2746 // element is selected.
2747 nsINode* currentNode = postOrderIter.GetCurrentNode();
2748 MOZ_ASSERT(currentNode);
2749 if (lastNodeInRange && lastNodeInRange->GetParentNode() != currentNode &&
2750 lastNodeInRange->GetNextSibling() != currentNode) {
2751 return nullptr;
2754 lastNodeInRange = currentNode;
2756 lastElementInRange = Element::FromNodeOrNull(lastNodeInRange);
2757 if (!lastElementInRange) {
2758 continue;
2761 // And also, if it's followed by a <br> element, we shouldn't treat the
2762 // the element is selected like this case:
2763 // - <p><b>[def</b>}<br></p>
2764 // Note that we don't need special handling for <a href> because double
2765 // clicking it selects the element and we use the first path to handle it.
2766 // Additionally, we have this case too:
2767 // - <p><b>[def</b><b>}<br></b></p>
2768 // In these cases, the <br> element is not listed up by PostContentIterator.
2769 // So, we should return nullptr if next sibling is a `<br>` element or
2770 // next sibling starts with `<br>` element.
2771 if (nsIContent* nextSibling = lastElementInRange->GetNextSibling()) {
2772 if (nextSibling->IsHTMLElement(nsGkAtoms::br)) {
2773 return nullptr;
2775 nsIContent* firstEditableLeaf = HTMLEditUtils::GetFirstLeafChild(
2776 *nextSibling, ChildBlockBoundary::Ignore);
2777 if (firstEditableLeaf &&
2778 firstEditableLeaf->IsHTMLElement(nsGkAtoms::br)) {
2779 return nullptr;
2783 if (!aTagName) {
2784 continue;
2787 if (isLinkTag && HTMLEditUtils::IsLink(lastElementInRange)) {
2788 continue;
2791 if (isNamedAnchorTag && HTMLEditUtils::IsNamedAnchor(lastElementInRange)) {
2792 continue;
2795 if (aTagName == lastElementInRange->NodeInfo()->NameAtom()) {
2796 continue;
2799 // First element in the range does not match what the caller is looking
2800 // for.
2801 return nullptr;
2803 return lastElementInRange.forget();
2806 already_AddRefed<Element> HTMLEditor::CreateElementWithDefaults(
2807 const nsAtom& aTagName) {
2808 // NOTE: Despite of public method, this can be called for internal use.
2810 // Although this creates an element, but won't change the DOM tree nor
2811 // transaction. So, EditAtion::eNotEditing is proper value here. If
2812 // this is called for internal when there is already AutoEditActionDataSetter
2813 // instance, this would be initialized with its EditAction value.
2814 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
2815 if (NS_WARN_IF(!editActionData.CanHandle())) {
2816 return nullptr;
2819 const nsAtom* realTagName = IsLinkTag(aTagName) || IsNamedAnchorTag(aTagName)
2820 ? nsGkAtoms::a
2821 : &aTagName;
2823 // We don't use editor's CreateElement because we don't want to go through
2824 // the transaction system
2826 // New call to use instead to get proper HTML element, bug 39919
2827 RefPtr<Element> newElement = CreateHTMLContent(realTagName);
2828 if (!newElement) {
2829 return nullptr;
2832 // Mark the new element dirty, so it will be formatted
2833 // XXX Don't we need to check the error result of setting _moz_dirty attr?
2834 IgnoredErrorResult ignoredError;
2835 newElement->SetAttribute(u"_moz_dirty"_ns, u""_ns, ignoredError);
2836 NS_WARNING_ASSERTION(!ignoredError.Failed(),
2837 "Element::SetAttribute(_moz_dirty) failed, but ignored");
2838 ignoredError.SuppressException();
2840 // Set default values for new elements
2841 if (realTagName == nsGkAtoms::table) {
2842 newElement->SetAttr(nsGkAtoms::cellpadding, u"2"_ns, ignoredError);
2843 if (ignoredError.Failed()) {
2844 NS_WARNING("Element::SetAttr(nsGkAtoms::cellpadding, 2) failed");
2845 return nullptr;
2847 ignoredError.SuppressException();
2849 newElement->SetAttr(nsGkAtoms::cellspacing, u"2"_ns, ignoredError);
2850 if (ignoredError.Failed()) {
2851 NS_WARNING("Element::SetAttr(nsGkAtoms::cellspacing, 2) failed");
2852 return nullptr;
2854 ignoredError.SuppressException();
2856 newElement->SetAttr(nsGkAtoms::border, u"1"_ns, ignoredError);
2857 if (ignoredError.Failed()) {
2858 NS_WARNING("Element::SetAttr(nsGkAtoms::border, 1) failed");
2859 return nullptr;
2861 } else if (realTagName == nsGkAtoms::td) {
2862 nsresult rv = SetAttributeOrEquivalent(newElement, nsGkAtoms::valign,
2863 u"top"_ns, true);
2864 if (NS_FAILED(rv)) {
2865 NS_WARNING(
2866 "HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::valign, top) "
2867 "failed");
2868 return nullptr;
2871 // ADD OTHER TAGS HERE
2873 return newElement.forget();
2876 NS_IMETHODIMP HTMLEditor::CreateElementWithDefaults(const nsAString& aTagName,
2877 Element** aReturn) {
2878 if (NS_WARN_IF(aTagName.IsEmpty()) || NS_WARN_IF(!aReturn)) {
2879 return NS_ERROR_INVALID_ARG;
2882 *aReturn = nullptr;
2884 nsStaticAtom* tagName = EditorUtils::GetTagNameAtom(aTagName);
2885 if (NS_WARN_IF(!tagName)) {
2886 return NS_ERROR_INVALID_ARG;
2888 RefPtr<Element> newElement =
2889 CreateElementWithDefaults(MOZ_KnownLive(*tagName));
2890 if (!newElement) {
2891 NS_WARNING("HTMLEditor::CreateElementWithDefaults() failed");
2892 return NS_ERROR_FAILURE;
2894 newElement.forget(aReturn);
2895 return NS_OK;
2898 NS_IMETHODIMP HTMLEditor::InsertLinkAroundSelection(Element* aAnchorElement) {
2899 nsresult rv = InsertLinkAroundSelectionAsAction(aAnchorElement);
2900 NS_WARNING_ASSERTION(
2901 NS_SUCCEEDED(rv),
2902 "HTMLEditor::InsertLinkAroundSelectionAsAction() failed");
2903 return rv;
2906 nsresult HTMLEditor::InsertLinkAroundSelectionAsAction(
2907 Element* aAnchorElement, nsIPrincipal* aPrincipal) {
2908 if (NS_WARN_IF(!aAnchorElement)) {
2909 return NS_ERROR_INVALID_ARG;
2912 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLinkElement,
2913 aPrincipal);
2914 if (NS_WARN_IF(!editActionData.CanHandle())) {
2915 return NS_ERROR_NOT_INITIALIZED;
2918 if (SelectionRefPtr()->IsCollapsed()) {
2919 NS_WARNING("Selection was collapsed");
2920 return NS_OK;
2923 // Be sure we were given an anchor element
2924 RefPtr<HTMLAnchorElement> anchor =
2925 HTMLAnchorElement::FromNodeOrNull(aAnchorElement);
2926 if (!anchor) {
2927 return NS_OK;
2930 nsAutoString rawHref;
2931 anchor->GetAttr(kNameSpaceID_None, nsGkAtoms::href, rawHref);
2932 editActionData.SetData(rawHref);
2934 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent();
2935 if (NS_FAILED(rv)) {
2936 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
2937 "MaybeDispatchBeforeInputEvent(), failed");
2938 return EditorBase::ToGenericNSResult(rv);
2941 nsAutoString href;
2942 anchor->GetHref(href);
2943 if (href.IsEmpty()) {
2944 return NS_OK;
2947 AutoPlaceholderBatch treatAsOneTransaction(*this,
2948 ScrollSelectionIntoView::Yes);
2950 // Set all attributes found on the supplied anchor element
2951 RefPtr<nsDOMAttributeMap> attributeMap = anchor->Attributes();
2952 if (NS_WARN_IF(!attributeMap)) {
2953 return NS_ERROR_FAILURE;
2956 uint32_t count = attributeMap->Length();
2957 nsAutoString value;
2958 for (uint32_t i = 0; i < count; ++i) {
2959 // XXX nsDOMAttributeMap::Item() accesses current attribute at the index.
2960 // Therefore, if `SetInlinePropertyInternal()` changed the attributes,
2961 // this may fail to scan some attributes. Perhaps, we need to cache
2962 // all attributes first.
2963 RefPtr<Attr> attribute = attributeMap->Item(i);
2964 if (!attribute) {
2965 continue;
2968 // We must clear the string buffers
2969 // because GetValue appends to previous string!
2970 value.Truncate();
2972 nsAtom* attributeName = attribute->NodeInfo()->NameAtom();
2974 attribute->GetValue(value);
2976 nsresult rv = SetInlinePropertyInternal(
2977 *nsGkAtoms::a, MOZ_KnownLive(attributeName), value);
2978 if (NS_FAILED(rv)) {
2979 NS_WARNING("SetInlinePropertyInternal(nsGkAtoms::a) failed");
2980 return rv;
2983 return NS_OK;
2986 nsresult HTMLEditor::SetHTMLBackgroundColorWithTransaction(
2987 const nsAString& aColor) {
2988 MOZ_ASSERT(IsEditActionDataAvailable());
2990 // Find a selected or enclosing table element to set background on
2991 ErrorResult error;
2992 bool isCellSelected = false;
2993 RefPtr<Element> cellOrRowOrTableElement =
2994 GetSelectedOrParentTableElement(error, &isCellSelected);
2995 if (error.Failed()) {
2996 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed");
2997 return error.StealNSResult();
3000 bool setColor = !aColor.IsEmpty();
3001 RefPtr<Element> rootElementOfBackgroundColor;
3002 if (cellOrRowOrTableElement) {
3003 rootElementOfBackgroundColor = std::move(cellOrRowOrTableElement);
3004 // Needs to set or remove background color of each selected cell elements.
3005 // Therefore, just the cell contains selection range, we don't need to
3006 // do this. Note that users can select each cell, but with Selection API,
3007 // web apps can select <tr> and <td> at same time. With <table>, looks
3008 // odd, though.
3009 if (isCellSelected || rootElementOfBackgroundColor->IsAnyOfHTMLElements(
3010 nsGkAtoms::table, nsGkAtoms::tr)) {
3011 IgnoredErrorResult ignoredError;
3012 RefPtr<Element> cellElement =
3013 GetFirstSelectedTableCellElement(ignoredError);
3014 if (cellElement) {
3015 if (setColor) {
3016 while (cellElement) {
3017 nsresult rv = SetAttributeWithTransaction(
3018 *cellElement, *nsGkAtoms::bgcolor, aColor);
3019 if (NS_FAILED(rv)) {
3020 NS_WARNING(
3021 "EditorBase::::SetAttributeWithTransaction(nsGkAtoms::"
3022 "bgcolor) failed");
3023 return rv;
3025 cellElement = GetNextSelectedTableCellElement(ignoredError);
3027 return NS_OK;
3029 while (cellElement) {
3030 nsresult rv =
3031 RemoveAttributeWithTransaction(*cellElement, *nsGkAtoms::bgcolor);
3032 if (NS_FAILED(rv)) {
3033 NS_WARNING(
3034 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::bgcolor)"
3035 " failed");
3036 return rv;
3038 cellElement = GetNextSelectedTableCellElement(ignoredError);
3040 return NS_OK;
3043 // If we failed to find a cell, fall through to use originally-found element
3044 } else {
3045 // No table element -- set the background color on the body tag
3046 rootElementOfBackgroundColor = GetRoot();
3047 if (NS_WARN_IF(!rootElementOfBackgroundColor)) {
3048 return NS_ERROR_FAILURE;
3051 // Use the editor method that goes through the transaction system
3052 if (setColor) {
3053 nsresult rv = SetAttributeWithTransaction(*rootElementOfBackgroundColor,
3054 *nsGkAtoms::bgcolor, aColor);
3055 NS_WARNING_ASSERTION(
3056 NS_SUCCEEDED(rv),
3057 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::bgcolor) failed");
3058 return rv;
3060 nsresult rv = RemoveAttributeWithTransaction(*rootElementOfBackgroundColor,
3061 *nsGkAtoms::bgcolor);
3062 NS_WARNING_ASSERTION(
3063 NS_SUCCEEDED(rv),
3064 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::bgcolor) failed");
3065 return rv;
3068 nsresult HTMLEditor::RemoveEmptyInclusiveAncestorInlineElements(
3069 nsIContent& aContent) {
3070 MOZ_ASSERT(IsEditActionDataAvailable());
3071 MOZ_ASSERT(!aContent.Length());
3073 Element* editingHost = aContent.GetEditingHost();
3074 if (NS_WARN_IF(!editingHost)) {
3075 return NS_ERROR_FAILURE;
3078 if (&aContent == editingHost || HTMLEditUtils::IsBlockElement(aContent) ||
3079 !HTMLEditUtils::IsSimplyEditableNode(aContent) || !aContent.GetParent()) {
3080 return NS_OK;
3083 // Don't strip wrappers if this is the only wrapper in the block. Then we'll
3084 // add a <br> later, so it won't be an empty wrapper in the end.
3085 Element* blockElement =
3086 HTMLEditUtils::GetAncestorBlockElement(aContent, editingHost);
3087 if (!blockElement || IsEmptyNode(*blockElement)) {
3088 return NS_OK;
3091 OwningNonNull<nsIContent> content = aContent;
3092 for (nsIContent* parentContent : aContent.AncestorsOfType<nsIContent>()) {
3093 if (HTMLEditUtils::IsBlockElement(*parentContent) ||
3094 parentContent->Length() != 1 ||
3095 !HTMLEditUtils::IsSimplyEditableNode(*parentContent) ||
3096 parentContent == editingHost) {
3097 break;
3099 content = *parentContent;
3102 nsresult rv = DeleteNodeWithTransaction(content);
3103 if (NS_WARN_IF(Destroyed())) {
3104 return NS_ERROR_EDITOR_DESTROYED;
3106 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3107 "HTMLEditor::DeleteNodeWithTransaction() failed");
3108 return rv;
3111 nsresult HTMLEditor::DeleteNodeWithTransaction(nsIContent& aContent) {
3112 // Do nothing if the node is read-only.
3113 // XXX This is not a override method of EditorBase's method. This might
3114 // cause not called accidentally. We need to investigate this issue.
3115 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aContent) &&
3116 !EditorUtils::IsPaddingBRElementForEmptyEditor(aContent))) {
3117 return NS_ERROR_FAILURE;
3119 nsresult rv = EditorBase::DeleteNodeWithTransaction(aContent);
3120 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3121 "EditorBase::DeleteNodeWithTransaction() failed");
3122 return rv;
3125 nsresult HTMLEditor::DeleteAllChildrenWithTransaction(Element& aElement) {
3126 MOZ_ASSERT(IsEditActionDataAvailable());
3128 // Prevent rules testing until we're done
3129 IgnoredErrorResult ignoredError;
3130 AutoEditSubActionNotifier startToHandleEditSubAction(
3131 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError);
3132 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3133 return ignoredError.StealNSResult();
3135 NS_WARNING_ASSERTION(
3136 !ignoredError.Failed(),
3137 "OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3139 while (nsCOMPtr<nsIContent> child = aElement.GetLastChild()) {
3140 nsresult rv = DeleteNodeWithTransaction(*child);
3141 if (NS_FAILED(rv)) {
3142 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
3143 return rv;
3146 return NS_OK;
3149 NS_IMETHODIMP HTMLEditor::DeleteNode(nsINode* aNode) {
3150 if (NS_WARN_IF(!aNode) || NS_WARN_IF(!aNode->IsContent())) {
3151 return NS_ERROR_INVALID_ARG;
3154 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveNode);
3155 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
3156 if (NS_FAILED(rv)) {
3157 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
3158 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
3159 return EditorBase::ToGenericNSResult(rv);
3162 rv = DeleteNodeWithTransaction(MOZ_KnownLive(*aNode->AsContent()));
3163 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3164 "HTMLEditor::DeleteNodeWithTransaction() failed");
3165 return rv;
3168 nsresult HTMLEditor::DeleteTextWithTransaction(Text& aTextNode,
3169 uint32_t aOffset,
3170 uint32_t aLength) {
3171 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aTextNode))) {
3172 return NS_ERROR_FAILURE;
3175 nsresult rv =
3176 EditorBase::DeleteTextWithTransaction(aTextNode, aOffset, aLength);
3177 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3178 "EditorBase::DeleteTextWithTransaction() failed");
3179 return rv;
3182 nsresult HTMLEditor::ReplaceTextWithTransaction(
3183 Text& aTextNode, uint32_t aOffset, uint32_t aLength,
3184 const nsAString& aStringToInsert) {
3185 MOZ_ASSERT(IsEditActionDataAvailable());
3186 MOZ_ASSERT(aLength > 0 || !aStringToInsert.IsEmpty());
3188 if (aStringToInsert.IsEmpty()) {
3189 nsresult rv = DeleteTextWithTransaction(aTextNode, aOffset, aLength);
3190 if (NS_WARN_IF(Destroyed())) {
3191 return NS_ERROR_EDITOR_DESTROYED;
3193 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3194 "HTMLEditor::DeleteTextWithTransaction() failed");
3195 return rv;
3198 if (!aLength) {
3199 RefPtr<Document> document = GetDocument();
3200 if (NS_WARN_IF(!document)) {
3201 return NS_ERROR_NOT_INITIALIZED;
3203 nsresult rv = InsertTextWithTransaction(
3204 *document, aStringToInsert, EditorRawDOMPoint(&aTextNode, aOffset));
3205 if (NS_WARN_IF(Destroyed())) {
3206 return NS_ERROR_EDITOR_DESTROYED;
3208 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3209 "HTMLEditor::InsertTextWithTransaction() failed");
3210 return rv;
3213 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aTextNode))) {
3214 return NS_ERROR_FAILURE;
3217 // This should emulates inserting text for better undo/redo behavior.
3218 IgnoredErrorResult ignoredError;
3219 AutoEditSubActionNotifier startToHandleEditSubAction(
3220 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError);
3221 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3222 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult());
3224 NS_WARNING_ASSERTION(
3225 !ignoredError.Failed(),
3226 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
3228 // FYI: Create the insertion point before changing the DOM tree because
3229 // the point may become invalid offset after that.
3230 EditorDOMPointInText pointToInsert(&aTextNode, aOffset);
3232 // `ReplaceTextTransaction()` removes the replaced text first, then,
3233 // insert new text. Therefore, if selection is in the text node, the
3234 // range is moved to start of the range and deletion and never adjusted
3235 // for the inserting text since the change occurs after the range.
3236 // Therefore, we might need to save/restore selection here.
3237 Maybe<AutoSelectionRestorer> restoreSelection;
3238 if (!AllowsTransactionsToChangeSelection() && !ArePreservingSelection()) {
3239 for (uint32_t i = 0; i < SelectionRefPtr()->RangeCount(); i++) {
3240 const nsRange* range = SelectionRefPtr()->GetRangeAt(i);
3241 if (!range) {
3242 continue;
3244 if ((range->GetStartContainer() == &aTextNode &&
3245 range->StartOffset() >= aOffset) ||
3246 (range->GetEndContainer() == &aTextNode &&
3247 range->EndOffset() >= aOffset)) {
3248 restoreSelection.emplace(*this);
3249 break;
3254 RefPtr<ReplaceTextTransaction> transaction = ReplaceTextTransaction::Create(
3255 *this, aStringToInsert, aTextNode, aOffset, aLength);
3256 MOZ_ASSERT(transaction);
3258 if (aLength && !mActionListeners.IsEmpty()) {
3259 for (auto& listener : mActionListeners.Clone()) {
3260 DebugOnly<nsresult> rvIgnored =
3261 listener->WillDeleteText(&aTextNode, aOffset, aLength);
3262 NS_WARNING_ASSERTION(
3263 NS_SUCCEEDED(rvIgnored),
3264 "nsIEditActionListener::WillDeleteText() failed, but ignored");
3268 nsresult rv = DoTransactionInternal(transaction);
3269 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3270 "EditorBase::DoTransactionInternal() failed");
3272 if (pointToInsert.IsSet()) {
3273 EditorDOMPointInText begin, end;
3274 Tie(begin, end) = ComputeInsertedRange(pointToInsert, aStringToInsert);
3275 if (begin.IsSet() && end.IsSet()) {
3276 TopLevelEditSubActionDataRef().DidDeleteText(*this, begin);
3277 TopLevelEditSubActionDataRef().DidInsertText(*this, begin, end);
3281 // Now, restores selection for allowing the following listeners to modify
3282 // selection.
3283 restoreSelection.reset();
3285 if (!mActionListeners.IsEmpty()) {
3286 for (auto& listener : mActionListeners.Clone()) {
3287 DebugOnly<nsresult> rvIgnored =
3288 listener->DidInsertText(&aTextNode, aOffset, aStringToInsert, rv);
3289 NS_WARNING_ASSERTION(
3290 NS_SUCCEEDED(rvIgnored),
3291 "nsIEditActionListener::DidInsertText() failed, but ignored");
3295 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : rv;
3298 nsresult HTMLEditor::InsertTextWithTransaction(
3299 Document& aDocument, const nsAString& aStringToInsert,
3300 const EditorRawDOMPoint& aPointToInsert,
3301 EditorRawDOMPoint* aPointAfterInsertedString) {
3302 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
3303 return NS_ERROR_INVALID_ARG;
3306 // Do nothing if the node is read-only
3307 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(
3308 *aPointToInsert.GetContainer()))) {
3309 return NS_ERROR_FAILURE;
3312 nsresult rv = EditorBase::InsertTextWithTransaction(
3313 aDocument, aStringToInsert, aPointToInsert, aPointAfterInsertedString);
3314 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3315 "EditorBase::InsertTextWithTransaction() failed");
3316 return rv;
3319 EditorDOMPoint HTMLEditor::PrepareToInsertBRElement(
3320 const EditorDOMPoint& aPointToInsert) {
3321 MOZ_ASSERT(IsEditActionDataAvailable());
3323 if (NS_WARN_IF(!aPointToInsert.IsSet())) {
3324 return EditorDOMPoint();
3327 if (!aPointToInsert.IsInTextNode()) {
3328 return aPointToInsert;
3331 if (aPointToInsert.IsStartOfContainer()) {
3332 // Insert before the text node.
3333 EditorDOMPoint pointInContainer(aPointToInsert.GetContainer());
3334 NS_WARNING_ASSERTION(pointInContainer.IsSet(),
3335 "Failed to climb up the DOM tree from text node");
3336 return pointInContainer;
3339 if (aPointToInsert.IsEndOfContainer()) {
3340 // Insert after the text node.
3341 EditorDOMPoint pointInContainer(aPointToInsert.GetContainer());
3342 if (NS_WARN_IF(!pointInContainer.IsSet())) {
3343 return pointInContainer;
3345 DebugOnly<bool> advanced = pointInContainer.AdvanceOffset();
3346 NS_WARNING_ASSERTION(advanced,
3347 "Failed to advance offset to after the text node");
3348 return pointInContainer;
3351 MOZ_DIAGNOSTIC_ASSERT(aPointToInsert.IsSetAndValid());
3353 // Unfortunately, we need to split the text node at the offset.
3354 IgnoredErrorResult ignoredError;
3355 nsCOMPtr<nsIContent> newLeftNode =
3356 SplitNodeWithTransaction(aPointToInsert, ignoredError);
3357 NS_WARNING_ASSERTION(!ignoredError.Failed(),
3358 "HTMLEditor::SplitNodeWithTransaction() failed");
3359 if (ignoredError.Failed()) {
3360 return EditorDOMPoint();
3362 Unused << newLeftNode;
3363 // Insert new <br> before the right node.
3364 EditorDOMPoint pointInContainer(aPointToInsert.GetContainer());
3365 NS_WARNING_ASSERTION(pointInContainer.IsSet(),
3366 "Failed to split the text node");
3367 return pointInContainer;
3370 already_AddRefed<Element> HTMLEditor::InsertBRElementWithTransaction(
3371 const EditorDOMPoint& aPointToInsert, EDirection aSelect /* = eNone */) {
3372 MOZ_ASSERT(IsEditActionDataAvailable());
3374 EditorDOMPoint pointToInsert = PrepareToInsertBRElement(aPointToInsert);
3375 if (NS_WARN_IF(!pointToInsert.IsSet())) {
3376 return nullptr;
3379 RefPtr<Element> newBRElement =
3380 CreateNodeWithTransaction(*nsGkAtoms::br, pointToInsert);
3381 if (NS_WARN_IF(!newBRElement)) {
3382 return nullptr;
3385 switch (aSelect) {
3386 case eNone:
3387 break;
3388 case eNext: {
3389 IgnoredErrorResult ignoredError;
3390 SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
3391 NS_WARNING_ASSERTION(
3392 !ignoredError.Failed(),
3393 "Selection::SetInterlinePosition(true) failed, but ignored");
3394 // Collapse selection after the <br> node.
3395 EditorRawDOMPoint afterBRElement(EditorRawDOMPoint::After(*newBRElement));
3396 NS_WARNING_ASSERTION(afterBRElement.IsSet(),
3397 "Setting after <br> element failed, but ignored");
3398 if (afterBRElement.IsSet()) {
3399 ignoredError.SuppressException();
3400 CollapseSelectionTo(afterBRElement, ignoredError);
3401 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3402 return nullptr;
3404 NS_WARNING_ASSERTION(
3405 !ignoredError.Failed(),
3406 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
3408 break;
3410 case ePrevious: {
3411 IgnoredErrorResult ignoredError;
3412 SelectionRefPtr()->SetInterlinePosition(true, ignoredError);
3413 NS_WARNING_ASSERTION(
3414 !ignoredError.Failed(),
3415 "Selection::SetInterlinePosition(true) failed, but ignored");
3416 // Collapse selection at the <br> node.
3417 EditorRawDOMPoint atBRElement(newBRElement);
3418 NS_WARNING_ASSERTION(atBRElement.IsSet(),
3419 "Setting at <br> element failed, but ignored");
3420 if (atBRElement.IsSet()) {
3421 ignoredError.SuppressException();
3422 CollapseSelectionTo(atBRElement, ignoredError);
3423 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
3424 return nullptr;
3426 NS_WARNING_ASSERTION(
3427 !ignoredError.Failed(),
3428 "HTMLEditor::CollapseSelectionTo() failed, but ignored");
3430 break;
3432 default:
3433 NS_WARNING(
3434 "aSelect has invalid value, the caller need to set selection "
3435 "by itself");
3436 break;
3439 return newBRElement.forget();
3442 already_AddRefed<Element> HTMLEditor::InsertContainerWithTransactionInternal(
3443 nsIContent& aContent, nsAtom& aTagName, nsAtom& aAttribute,
3444 const nsAString& aAttributeValue) {
3445 EditorDOMPoint pointToInsertNewContainer(&aContent);
3446 if (NS_WARN_IF(!pointToInsertNewContainer.IsSet())) {
3447 return nullptr;
3449 // aContent will be moved to the new container before inserting the new
3450 // container. So, when we insert the container, the insertion point
3451 // is before the next sibling of aContent.
3452 // XXX If pointerToInsertNewContainer stores offset here, the offset and
3453 // referring child node become mismatched. Although, currently this
3454 // is not a problem since InsertNodeTransaction refers only child node.
3455 DebugOnly<bool> advanced = pointToInsertNewContainer.AdvanceOffset();
3456 NS_WARNING_ASSERTION(advanced, "Failed to advance offset to after aContent");
3458 // Create new container.
3459 RefPtr<Element> newContainer = CreateHTMLContent(&aTagName);
3460 if (NS_WARN_IF(!newContainer)) {
3461 return nullptr;
3464 // Set attribute if needed.
3465 if (&aAttribute != nsGkAtoms::_empty) {
3466 nsresult rv = newContainer->SetAttr(kNameSpaceID_None, &aAttribute,
3467 aAttributeValue, true);
3468 if (NS_FAILED(rv)) {
3469 NS_WARNING("Element::SetAttr() failed");
3470 return nullptr;
3474 // Notify our internal selection state listener
3475 AutoInsertContainerSelNotify selNotify(RangeUpdaterRef());
3477 // Put aNode in the new container, first.
3478 // XXX Perhaps, we should not remove the container if it's not editable.
3479 nsresult rv = EditorBase::DeleteNodeWithTransaction(aContent);
3480 if (NS_FAILED(rv)) {
3481 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3482 return nullptr;
3486 AutoTransactionsConserveSelection conserveSelection(*this);
3487 rv = InsertNodeWithTransaction(aContent, EditorDOMPoint(newContainer, 0));
3488 if (NS_FAILED(rv)) {
3489 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
3490 return nullptr;
3494 // Put the new container where aNode was.
3495 rv = InsertNodeWithTransaction(*newContainer, pointToInsertNewContainer);
3496 if (NS_FAILED(rv)) {
3497 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
3498 return nullptr;
3501 return newContainer.forget();
3504 already_AddRefed<Element> HTMLEditor::ReplaceContainerWithTransactionInternal(
3505 Element& aOldContainer, nsAtom& aTagName, nsAtom& aAttribute,
3506 const nsAString& aAttributeValue, bool aCloneAllAttributes) {
3507 MOZ_ASSERT(IsEditActionDataAvailable());
3509 EditorDOMPoint atOldContainer(&aOldContainer);
3510 if (NS_WARN_IF(!atOldContainer.IsSet())) {
3511 return nullptr;
3514 RefPtr<Element> newContainer = CreateHTMLContent(&aTagName);
3515 if (NS_WARN_IF(!newContainer)) {
3516 return nullptr;
3519 // Set or clone attribute if needed.
3520 if (aCloneAllAttributes) {
3521 MOZ_ASSERT(&aAttribute == nsGkAtoms::_empty);
3522 CloneAttributesWithTransaction(*newContainer, aOldContainer);
3523 } else if (&aAttribute != nsGkAtoms::_empty) {
3524 nsresult rv = newContainer->SetAttr(kNameSpaceID_None, &aAttribute,
3525 aAttributeValue, true);
3526 if (NS_FAILED(rv)) {
3527 NS_WARNING("Element::SetAttr() failed");
3528 return nullptr;
3532 // Notify our internal selection state listener.
3533 // Note: An AutoSelectionRestorer object must be created before calling this
3534 // to initialize RangeUpdaterRef().
3535 AutoReplaceContainerSelNotify selStateNotify(RangeUpdaterRef(), aOldContainer,
3536 *newContainer);
3538 AutoTransactionsConserveSelection conserveSelection(*this);
3539 // Move all children from the old container to the new container.
3540 while (aOldContainer.HasChildren()) {
3541 nsCOMPtr<nsIContent> child = aOldContainer.GetFirstChild();
3542 if (NS_WARN_IF(!child)) {
3543 return nullptr;
3545 // HTMLEditor::DeleteNodeWithTransaction() does not move non-editable
3546 // node, but we need to move non-editable nodes too. Therefore, call
3547 // EditorBase's method directly.
3548 nsresult rv = EditorBase::DeleteNodeWithTransaction(*child);
3549 if (NS_FAILED(rv)) {
3550 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3551 return nullptr;
3554 rv = InsertNodeWithTransaction(
3555 *child, EditorDOMPoint(newContainer, newContainer->Length()));
3556 if (NS_FAILED(rv)) {
3557 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
3558 return nullptr;
3563 // Insert new container into tree.
3564 NS_WARNING_ASSERTION(atOldContainer.IsSetAndValid(),
3565 "The old container might be moved by mutation observer");
3566 nsresult rv = InsertNodeWithTransaction(*newContainer, atOldContainer);
3567 if (NS_FAILED(rv)) {
3568 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
3569 return nullptr;
3572 // Delete old container.
3573 // XXX Perhaps, we should not remove the container if it's not editable.
3574 rv = EditorBase::DeleteNodeWithTransaction(aOldContainer);
3575 if (NS_FAILED(rv)) {
3576 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3577 return nullptr;
3580 return newContainer.forget();
3583 nsresult HTMLEditor::RemoveContainerWithTransaction(Element& aElement) {
3584 MOZ_ASSERT(IsEditActionDataAvailable());
3586 EditorDOMPoint pointToInsertChildren(&aElement);
3587 if (NS_WARN_IF(!pointToInsertChildren.IsSet())) {
3588 return NS_ERROR_FAILURE;
3591 // Notify our internal selection state listener.
3592 AutoRemoveContainerSelNotify selNotify(RangeUpdaterRef(),
3593 pointToInsertChildren);
3595 // Move all children from aNode to its parent.
3596 while (aElement.HasChildren()) {
3597 nsCOMPtr<nsIContent> child = aElement.GetLastChild();
3598 if (NS_WARN_IF(!child)) {
3599 return NS_ERROR_FAILURE;
3601 // HTMLEditor::DeleteNodeWithTransaction() does not move non-editable
3602 // node, but we need to move non-editable nodes too. Therefore, call
3603 // EditorBase's method directly.
3604 nsresult rv = EditorBase::DeleteNodeWithTransaction(*child);
3605 if (NS_FAILED(rv)) {
3606 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
3607 return rv;
3610 // Insert the last child before the previous last child. So, we need to
3611 // use offset here because previous child might have been moved to
3612 // container.
3613 rv = InsertNodeWithTransaction(
3614 *child, EditorDOMPoint(pointToInsertChildren.GetContainer(),
3615 pointToInsertChildren.Offset()));
3616 if (NS_FAILED(rv)) {
3617 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed");
3618 return rv;
3622 // XXX Perhaps, we should not remove the container if it's not editable.
3623 nsresult rv = EditorBase::DeleteNodeWithTransaction(aElement);
3624 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3625 "EditorBase::DeleteNodeWithTransaction() failed");
3626 return rv;
3629 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentAppended(
3630 nsIContent* aFirstNewContent) {
3631 DoContentInserted(aFirstNewContent, eAppended);
3634 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentInserted(
3635 nsIContent* aChild) {
3636 DoContentInserted(aChild, eInserted);
3639 bool HTMLEditor::IsInObservedSubtree(nsIContent* aChild) {
3640 if (!aChild) {
3641 return false;
3644 // FIXME(emilio, bug 1596856): This should probably work if the root is in the
3645 // same shadow tree as the child, probably? I don't know what the
3646 // contenteditable-in-shadow-dom situation is.
3647 if (Element* root = GetRoot()) {
3648 // To be super safe here, check both ChromeOnlyAccess and NAC / Shadow DOM.
3649 // That catches (also unbound) native anonymous content and ShadowDOM.
3650 if (root->ChromeOnlyAccess() != aChild->ChromeOnlyAccess() ||
3651 root->IsInNativeAnonymousSubtree() !=
3652 aChild->IsInNativeAnonymousSubtree() ||
3653 root->IsInShadowTree() != aChild->IsInShadowTree()) {
3654 return false;
3658 return !aChild->ChromeOnlyAccess() && !aChild->IsInShadowTree() &&
3659 !aChild->IsInNativeAnonymousSubtree();
3662 void HTMLEditor::DoContentInserted(nsIContent* aChild,
3663 InsertedOrAppended aInsertedOrAppended) {
3664 MOZ_ASSERT(aChild);
3665 nsINode* container = aChild->GetParentNode();
3666 MOZ_ASSERT(container);
3668 if (!IsInObservedSubtree(aChild)) {
3669 return;
3672 // XXX Why do we need this? This method is a helper of mutation observer.
3673 // So, the callers of mutation observer should guarantee that this won't
3674 // be deleted at least during the call.
3675 RefPtr<HTMLEditor> kungFuDeathGrip(this);
3677 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
3678 if (NS_WARN_IF(!editActionData.CanHandle())) {
3679 return;
3682 if (ShouldReplaceRootElement()) {
3683 UpdateRootElement();
3684 if (mPendingRootElementUpdatedRunner) {
3685 return;
3687 mPendingRootElementUpdatedRunner = NewRunnableMethod(
3688 "HTMLEditor::NotifyRootChanged", this, &HTMLEditor::NotifyRootChanged);
3689 nsContentUtils::AddScriptRunner(
3690 do_AddRef(mPendingRootElementUpdatedRunner));
3691 return;
3694 // We don't need to handle our own modifications
3695 if (!GetTopLevelEditSubAction() && container->IsEditable()) {
3696 if (EditorUtils::IsPaddingBRElementForEmptyEditor(*aChild)) {
3697 // Ignore insertion of the padding <br> element.
3698 return;
3700 nsresult rv = OnDocumentModified();
3701 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3702 return;
3704 NS_WARNING_ASSERTION(
3705 NS_SUCCEEDED(rv),
3706 "HTMLEditor::OnDocumentModified() failed, but ignored");
3708 // Update spellcheck for only the newly-inserted node (bug 743819)
3709 if (mInlineSpellChecker) {
3710 RefPtr<nsRange> range = nsRange::Create(aChild);
3711 nsIContent* endContent = aChild;
3712 if (aInsertedOrAppended == eAppended) {
3713 // Maybe more than 1 child was appended.
3714 endContent = container->GetLastChild();
3716 range->SelectNodesInContainer(container, aChild, endContent);
3717 DebugOnly<nsresult> rvIgnored =
3718 mInlineSpellChecker->SpellCheckRange(range);
3719 NS_WARNING_ASSERTION(
3720 rvIgnored == NS_ERROR_NOT_INITIALIZED || NS_SUCCEEDED(rvIgnored),
3721 "mozInlineSpellChecker::SpellCheckRange() failed, but ignored");
3726 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentRemoved(
3727 nsIContent* aChild, nsIContent* aPreviousSibling) {
3728 if (!IsInObservedSubtree(aChild)) {
3729 return;
3732 // XXX Why do we need to do this? This method is a mutation observer's
3733 // method. Therefore, the caller should guarantee that this won't be
3734 // deleted during the call.
3735 RefPtr<HTMLEditor> kungFuDeathGrip(this);
3737 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
3738 if (NS_WARN_IF(!editActionData.CanHandle())) {
3739 return;
3742 if (SameCOMIdentity(aChild, mRootElement)) {
3743 mRootElement = nullptr;
3744 if (mPendingRootElementUpdatedRunner) {
3745 return;
3747 mPendingRootElementUpdatedRunner = NewRunnableMethod(
3748 "HTMLEditor::NotifyRootChanged", this, &HTMLEditor::NotifyRootChanged);
3749 nsContentUtils::AddScriptRunner(
3750 do_AddRef(mPendingRootElementUpdatedRunner));
3751 return;
3754 // We don't need to handle our own modifications
3755 if (!GetTopLevelEditSubAction() && aChild->GetParentNode()->IsEditable()) {
3756 if (aChild && EditorUtils::IsPaddingBRElementForEmptyEditor(*aChild)) {
3757 // Ignore removal of the padding <br> element for empty editor.
3758 return;
3761 nsresult rv = OnDocumentModified();
3762 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
3763 return;
3765 NS_WARNING_ASSERTION(
3766 NS_SUCCEEDED(rv),
3767 "HTMLEditor::OnDocumentModified() failed, but ignored");
3771 nsresult HTMLEditor::SelectEntireDocument() {
3772 MOZ_ASSERT(IsEditActionDataAvailable());
3774 if (!mInitSucceeded) {
3775 return NS_ERROR_NOT_INITIALIZED;
3778 // XXX It's odd to select all of the document body if an contenteditable
3779 // element has focus.
3780 RefPtr<Element> bodyOrDocumentElement = GetRoot();
3781 if (NS_WARN_IF(!bodyOrDocumentElement)) {
3782 return NS_ERROR_NOT_INITIALIZED;
3785 // If we're empty, don't select all children because that would select the
3786 // padding <br> element for empty editor.
3787 if (IsEmpty()) {
3788 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement);
3789 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3790 "HTMLEditor::CollapseSelectionToStartOf() failed");
3791 return rv;
3794 // Otherwise, select all children.
3795 ErrorResult error;
3796 SelectionRefPtr()->SelectAllChildren(*bodyOrDocumentElement, error);
3797 if (NS_WARN_IF(Destroyed())) {
3798 error.SuppressException();
3799 return NS_ERROR_EDITOR_DESTROYED;
3801 NS_WARNING_ASSERTION(!error.Failed(),
3802 "Selection::SelectAllChildren() failed");
3803 return error.StealNSResult();
3806 nsresult HTMLEditor::SelectAllInternal() {
3807 MOZ_ASSERT(IsEditActionDataAvailable());
3809 CommitComposition();
3810 if (NS_WARN_IF(Destroyed())) {
3811 return NS_ERROR_EDITOR_DESTROYED;
3814 // XXX Perhaps, we should check whether we still have focus since composition
3815 // event listener may have already moved focus to different editing
3816 // host or other element. So, perhaps, we need to retrieve anchor node
3817 // before committing composition and check if selection is still in
3818 // same editing host.
3820 nsINode* anchorNode = SelectionRefPtr()->GetAnchorNode();
3821 if (NS_WARN_IF(!anchorNode) || NS_WARN_IF(!anchorNode->IsContent())) {
3822 return NS_ERROR_FAILURE;
3825 nsIContent* anchorContent = anchorNode->AsContent();
3826 nsIContent* rootContent;
3827 if (anchorContent->HasIndependentSelection()) {
3828 SelectionRefPtr()->SetAncestorLimiter(nullptr);
3829 rootContent = mRootElement;
3830 } else {
3831 RefPtr<PresShell> presShell = GetPresShell();
3832 rootContent = anchorContent->GetSelectionRootContent(presShell);
3835 if (NS_WARN_IF(!rootContent)) {
3836 return NS_ERROR_UNEXPECTED;
3839 Maybe<Selection::AutoUserInitiated> userSelection;
3840 if (!rootContent->IsEditable()) {
3841 userSelection.emplace(SelectionRefPtr());
3843 ErrorResult error;
3844 SelectionRefPtr()->SelectAllChildren(*rootContent, error);
3845 NS_WARNING_ASSERTION(!error.Failed(),
3846 "Selection::SelectAllChildren() failed");
3847 return error.StealNSResult();
3850 // this will NOT find aAttribute unless aAttribute has a non-null value
3851 // so singleton attributes like <Table border> will not be matched!
3852 bool HTMLEditor::IsTextPropertySetByContent(nsINode* aNode, nsAtom* aProperty,
3853 nsAtom* aAttribute,
3854 const nsAString* aValue,
3855 nsAString* outValue) {
3856 MOZ_ASSERT(aNode && aProperty);
3858 for (Element* element = aNode->GetAsElementOrParentElement(); element;
3859 element = element->GetParentElement()) {
3860 if (aProperty != element->NodeInfo()->NameAtom()) {
3861 continue;
3863 if (!aAttribute) {
3864 return true;
3866 nsAutoString value;
3867 element->GetAttr(kNameSpaceID_None, aAttribute, value);
3868 if (outValue) {
3869 *outValue = value;
3871 if (!value.IsEmpty()) {
3872 if (!aValue) {
3873 return true;
3875 if (aValue->Equals(value, nsCaseInsensitiveStringComparator)) {
3876 return true;
3878 // We found the prop with the attribute, but the value doesn't match.
3879 return false;
3882 return false;
3885 bool HTMLEditor::SetCaretInTableCell(Element* aElement) {
3886 MOZ_ASSERT(IsEditActionDataAvailable());
3888 if (!aElement || !aElement->IsHTMLElement() ||
3889 !HTMLEditUtils::IsAnyTableElement(aElement) ||
3890 !IsDescendantOfEditorRoot(aElement)) {
3891 return false;
3894 nsCOMPtr<nsIContent> deepestFirstChild = aElement;
3895 while (deepestFirstChild->HasChildren()) {
3896 deepestFirstChild = deepestFirstChild->GetFirstChild();
3899 // Set selection at beginning of the found node
3900 nsresult rv = CollapseSelectionToStartOf(*deepestFirstChild);
3901 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3902 "HTMLEditor::CollapseSelectionToStartOf() failed");
3903 return NS_SUCCEEDED(rv);
3907 * This method scans the selection for adjacent text nodes
3908 * and collapses them into a single text node.
3909 * "adjacent" means literally adjacent siblings of the same parent.
3910 * Uses HTMLEditor::JoinNodesWithTransaction() so action is undoable.
3911 * Should be called within the context of a batch transaction.
3913 nsresult HTMLEditor::CollapseAdjacentTextNodes(nsRange& aInRange) {
3914 AutoTransactionsConserveSelection dontChangeMySelection(*this);
3916 // we can't actually do anything during iteration, so store the text nodes in
3917 // an array first.
3918 DOMSubtreeIterator subtreeIter;
3919 if (NS_FAILED(subtreeIter.Init(aInRange))) {
3920 NS_WARNING("DOMSubtreeIterator::Init() failed");
3921 return NS_ERROR_FAILURE;
3923 AutoTArray<OwningNonNull<Text>, 8> textNodes;
3924 subtreeIter.AppendNodesToArray(
3925 +[](nsINode& aNode, void*) -> bool {
3926 return EditorUtils::IsEditableContent(*aNode.AsText(),
3927 EditorType::HTML);
3929 textNodes);
3931 // now that I have a list of text nodes, collapse adjacent text nodes
3932 // NOTE: assumption that JoinNodes keeps the righthand node
3933 while (textNodes.Length() > 1) {
3934 // we assume a textNodes entry can't be nullptr
3935 Text* leftTextNode = textNodes[0];
3936 Text* rightTextNode = textNodes[1];
3937 NS_ASSERTION(leftTextNode && rightTextNode,
3938 "left or rightTextNode null in CollapseAdjacentTextNodes");
3940 // get the prev sibling of the right node, and see if its leftTextNode
3941 nsIContent* previousSiblingOfRightTextNode =
3942 rightTextNode->GetPreviousSibling();
3943 if (previousSiblingOfRightTextNode &&
3944 previousSiblingOfRightTextNode == leftTextNode) {
3945 nsresult rv = JoinNodesWithTransaction(MOZ_KnownLive(*leftTextNode),
3946 MOZ_KnownLive(*rightTextNode));
3947 if (NS_FAILED(rv)) {
3948 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed");
3949 return rv;
3953 // remove the leftmost text node from the list
3954 textNodes.RemoveElementAt(0);
3957 return NS_OK;
3960 nsresult HTMLEditor::SetSelectionAtDocumentStart() {
3961 MOZ_ASSERT(IsEditActionDataAvailable());
3963 RefPtr<Element> rootElement = GetRoot();
3964 if (NS_WARN_IF(!rootElement)) {
3965 return NS_ERROR_FAILURE;
3968 nsresult rv = CollapseSelectionToStartOf(*rootElement);
3969 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
3970 "HTMLEditor::CollapseSelectionToStartOf() failed");
3971 return rv;
3975 * Remove aNode, reparenting any children into the parent of aNode. In
3976 * addition, insert any br's needed to preserve identity of removed block.
3978 nsresult HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) {
3979 MOZ_ASSERT(IsEditActionDataAvailable());
3981 // Two possibilities: the container could be empty of editable content. If
3982 // that is the case, we need to compare what is before and after aNode to
3983 // determine if we need a br.
3985 // Or it could be not empty, in which case we have to compare previous
3986 // sibling and first child to determine if we need a leading br, and compare
3987 // following sibling and last child to determine if we need a trailing br.
3989 nsCOMPtr<nsIContent> child = GetFirstEditableChild(aElement);
3991 if (child) {
3992 // The case of aNode not being empty. We need a br at start unless:
3993 // 1) previous sibling of aNode is a block, OR
3994 // 2) previous sibling of aNode is a br, OR
3995 // 3) first child of aNode is a block OR
3996 // 4) either is null
3998 if (nsIContent* previousSibling = GetPriorHTMLSibling(&aElement)) {
3999 if (!HTMLEditUtils::IsBlockElement(*previousSibling) &&
4000 !previousSibling->IsHTMLElement(nsGkAtoms::br) &&
4001 !HTMLEditUtils::IsBlockElement(*child)) {
4002 // Insert br node
4003 RefPtr<Element> brElement =
4004 InsertBRElementWithTransaction(EditorDOMPoint(&aElement, 0));
4005 if (NS_WARN_IF(Destroyed())) {
4006 return NS_ERROR_EDITOR_DESTROYED;
4008 if (!brElement) {
4009 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
4010 return NS_ERROR_FAILURE;
4015 // We need a br at end unless:
4016 // 1) following sibling of aNode is a block, OR
4017 // 2) last child of aNode is a block, OR
4018 // 3) last child of aNode is a br OR
4019 // 4) either is null
4021 if (nsIContent* nextSibling = GetNextHTMLSibling(&aElement)) {
4022 if (nextSibling && !HTMLEditUtils::IsBlockElement(*nextSibling)) {
4023 if (nsIContent* lastChild = GetLastEditableChild(aElement)) {
4024 if (!HTMLEditUtils::IsBlockElement(*lastChild) &&
4025 !lastChild->IsHTMLElement(nsGkAtoms::br)) {
4026 RefPtr<Element> brElement = InsertBRElementWithTransaction(
4027 EditorDOMPoint::AtEndOf(aElement));
4028 if (NS_WARN_IF(Destroyed())) {
4029 return NS_ERROR_EDITOR_DESTROYED;
4031 if (!brElement) {
4032 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
4033 return NS_ERROR_FAILURE;
4039 } else if (nsIContent* previousSibling = GetPriorHTMLSibling(&aElement)) {
4040 // The case of aNode being empty. We need a br at start unless:
4041 // 1) previous sibling of aNode is a block, OR
4042 // 2) previous sibling of aNode is a br, OR
4043 // 3) following sibling of aNode is a block, OR
4044 // 4) following sibling of aNode is a br OR
4045 // 5) either is null
4046 if (!HTMLEditUtils::IsBlockElement(*previousSibling) &&
4047 !previousSibling->IsHTMLElement(nsGkAtoms::br)) {
4048 if (nsIContent* nextSibling = GetNextHTMLSibling(&aElement)) {
4049 if (!HTMLEditUtils::IsBlockElement(*nextSibling) &&
4050 !nextSibling->IsHTMLElement(nsGkAtoms::br)) {
4051 RefPtr<Element> brElement =
4052 InsertBRElementWithTransaction(EditorDOMPoint(&aElement, 0));
4053 if (NS_WARN_IF(Destroyed())) {
4054 return NS_ERROR_EDITOR_DESTROYED;
4056 if (!brElement) {
4057 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
4058 return NS_ERROR_FAILURE;
4065 // Now remove container
4066 nsresult rv = RemoveContainerWithTransaction(aElement);
4067 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4068 "HTMLEditor::RemoveContainerWithTransaction() failed");
4069 return rv;
4072 already_AddRefed<nsIContent> HTMLEditor::SplitNodeWithTransaction(
4073 const EditorDOMPoint& aStartOfRightNode, ErrorResult& aError) {
4074 MOZ_ASSERT(IsEditActionDataAvailable());
4076 if (NS_WARN_IF(!aStartOfRightNode.IsInContentNode())) {
4077 aError.Throw(NS_ERROR_INVALID_ARG);
4078 return nullptr;
4080 MOZ_ASSERT(aStartOfRightNode.IsSetAndValid());
4082 AutoEditSubActionNotifier startToHandleEditSubAction(
4083 *this, EditSubAction::eSplitNode, nsIEditor::eNext, aError);
4084 if (NS_WARN_IF(aError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4085 return nullptr;
4087 NS_WARNING_ASSERTION(
4088 !aError.Failed(),
4089 "OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4090 aError.SuppressException();
4092 // XXX Unfortunately, storing offset of the split point in
4093 // SplitNodeTransaction is necessary for now. We should fix this
4094 // in a follow up bug.
4095 Unused << aStartOfRightNode.Offset();
4097 RefPtr<SplitNodeTransaction> transaction =
4098 SplitNodeTransaction::Create(*this, aStartOfRightNode);
4099 aError = DoTransactionInternal(transaction);
4100 NS_WARNING_ASSERTION(!aError.Failed(),
4101 "EditorBase::DoTransactionInternal() failed");
4103 nsCOMPtr<nsIContent> newLeftContent = transaction->GetNewLeftContent();
4104 NS_WARNING_ASSERTION(newLeftContent, "Failed to create a new left node");
4106 if (newLeftContent) {
4107 // XXX Some other transactions manage range updater by themselves.
4108 // Why doesn't SplitNodeTransaction do it?
4109 DebugOnly<nsresult> rvIgnored = RangeUpdaterRef().SelAdjSplitNode(
4110 *aStartOfRightNode.GetContainerAsContent(), *newLeftContent);
4111 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4112 "RangeUpdater::SelAdjSplitNode() failed, but ignored");
4114 if (AsHTMLEditor() && newLeftContent) {
4115 TopLevelEditSubActionDataRef().DidSplitContent(
4116 *this, *aStartOfRightNode.GetContainerAsContent(), *newLeftContent);
4119 if (mInlineSpellChecker) {
4120 RefPtr<mozInlineSpellChecker> spellChecker = mInlineSpellChecker;
4121 spellChecker->DidSplitNode(aStartOfRightNode.GetContainer(),
4122 newLeftContent);
4125 if (aError.Failed()) {
4126 return nullptr;
4129 return newLeftContent.forget();
4132 SplitNodeResult HTMLEditor::SplitNodeDeepWithTransaction(
4133 nsIContent& aMostAncestorToSplit,
4134 const EditorDOMPoint& aStartOfDeepestRightNode,
4135 SplitAtEdges aSplitAtEdges) {
4136 MOZ_ASSERT(aStartOfDeepestRightNode.IsSetAndValid());
4137 MOZ_ASSERT(
4138 aStartOfDeepestRightNode.GetContainer() == &aMostAncestorToSplit ||
4139 EditorUtils::IsDescendantOf(*aStartOfDeepestRightNode.GetContainer(),
4140 aMostAncestorToSplit));
4142 if (NS_WARN_IF(!aStartOfDeepestRightNode.IsSet())) {
4143 return SplitNodeResult(NS_ERROR_INVALID_ARG);
4146 nsCOMPtr<nsIContent> newLeftNodeOfMostAncestor;
4147 EditorDOMPoint atStartOfRightNode(aStartOfDeepestRightNode);
4148 while (true) {
4149 // Need to insert rules code call here to do things like not split a list
4150 // if you are after the last <li> or before the first, etc. For now we
4151 // just have some smarts about unneccessarily splitting text nodes, which
4152 // should be universal enough to put straight in this EditorBase routine.
4153 if (NS_WARN_IF(!atStartOfRightNode.GetContainerAsContent())) {
4154 return SplitNodeResult(NS_ERROR_FAILURE);
4156 // If we meet an orphan node before meeting aMostAncestorToSplit, we need
4157 // to stop splitting. This is a bug of the caller.
4158 if (NS_WARN_IF(atStartOfRightNode.GetContainer() != &aMostAncestorToSplit &&
4159 !atStartOfRightNode.GetContainerParentAsContent())) {
4160 return SplitNodeResult(NS_ERROR_FAILURE);
4163 nsIContent* currentRightNode = atStartOfRightNode.GetContainerAsContent();
4165 // If the split point is middle of the node or the node is not a text node
4166 // and we're allowed to create empty element node, split it.
4167 if ((aSplitAtEdges == SplitAtEdges::eAllowToCreateEmptyContainer &&
4168 !atStartOfRightNode.GetContainerAsText()) ||
4169 (!atStartOfRightNode.IsStartOfContainer() &&
4170 !atStartOfRightNode.IsEndOfContainer())) {
4171 ErrorResult error;
4172 nsCOMPtr<nsIContent> newLeftNode =
4173 SplitNodeWithTransaction(atStartOfRightNode, error);
4174 if (error.Failed()) {
4175 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
4176 return SplitNodeResult(error.StealNSResult());
4179 if (currentRightNode == &aMostAncestorToSplit) {
4180 // Actually, we split aMostAncestorToSplit.
4181 return SplitNodeResult(newLeftNode, &aMostAncestorToSplit);
4184 // Then, try to split its parent before current node.
4185 atStartOfRightNode.Set(currentRightNode);
4187 // If the split point is end of the node and it is a text node or we're not
4188 // allowed to create empty container node, try to split its parent after it.
4189 else if (!atStartOfRightNode.IsStartOfContainer()) {
4190 if (currentRightNode == &aMostAncestorToSplit) {
4191 return SplitNodeResult(&aMostAncestorToSplit, nullptr);
4194 // Try to split its parent after current node.
4195 atStartOfRightNode.Set(currentRightNode);
4196 DebugOnly<bool> advanced = atStartOfRightNode.AdvanceOffset();
4197 NS_WARNING_ASSERTION(advanced,
4198 "Failed to advance offset after current node");
4200 // If the split point is start of the node and it is a text node or we're
4201 // not allowed to create empty container node, try to split its parent.
4202 else {
4203 if (currentRightNode == &aMostAncestorToSplit) {
4204 return SplitNodeResult(nullptr, &aMostAncestorToSplit);
4207 // Try to split its parent before current node.
4208 atStartOfRightNode.Set(currentRightNode);
4212 return SplitNodeResult(NS_ERROR_FAILURE);
4215 void HTMLEditor::DoSplitNode(const EditorDOMPoint& aStartOfRightNode,
4216 nsIContent& aNewLeftNode, ErrorResult& aError) {
4217 if (NS_WARN_IF(aError.Failed())) {
4218 return;
4221 // XXX Perhaps, aStartOfRightNode may be invalid if this is a redo
4222 // operation after modifying DOM node with JS.
4223 if (NS_WARN_IF(!aStartOfRightNode.IsSet())) {
4224 aError.Throw(NS_ERROR_INVALID_ARG);
4225 return;
4227 MOZ_ASSERT(aStartOfRightNode.IsSetAndValid());
4229 // Remember all selection points.
4230 AutoTArray<SavedRange, 10> savedRanges;
4231 for (SelectionType selectionType : kPresentSelectionTypes) {
4232 SavedRange range;
4233 range.mSelection = GetSelection(selectionType);
4234 if (NS_WARN_IF(!range.mSelection &&
4235 selectionType == SelectionType::eNormal)) {
4236 aError.Throw(NS_ERROR_FAILURE);
4237 return;
4239 if (!range.mSelection) {
4240 // For non-normal selections, skip over the non-existing ones.
4241 continue;
4244 for (uint32_t j = 0; j < range.mSelection->RangeCount(); ++j) {
4245 RefPtr<const nsRange> r = range.mSelection->GetRangeAt(j);
4246 MOZ_ASSERT(r->IsPositioned());
4247 // XXX Looks like that SavedRange should have mStart and mEnd which
4248 // are RangeBoundary. Then, we can avoid to compute offset here.
4249 range.mStartContainer = r->GetStartContainer();
4250 range.mStartOffset = r->StartOffset();
4251 range.mEndContainer = r->GetEndContainer();
4252 range.mEndOffset = r->EndOffset();
4254 savedRanges.AppendElement(range);
4258 nsCOMPtr<nsINode> parent = aStartOfRightNode.GetContainerParent();
4259 if (NS_WARN_IF(!parent)) {
4260 aError.Throw(NS_ERROR_FAILURE);
4261 return;
4264 // Fix the child before mutation observer may touch the DOM tree.
4265 nsIContent* firstChildOfRightNode = aStartOfRightNode.GetChild();
4266 parent->InsertBefore(aNewLeftNode, aStartOfRightNode.GetContainer(), aError);
4267 if (aError.Failed()) {
4268 NS_WARNING("nsINode::InsertBefore() failed");
4269 return;
4272 // At this point, the existing right node has all the children. Move all
4273 // the children which are before aStartOfRightNode.
4274 if (!aStartOfRightNode.IsStartOfContainer()) {
4275 // If it's a text node, just shuffle around some text
4276 Text* rightAsText = aStartOfRightNode.GetContainerAsText();
4277 Text* leftAsText = aNewLeftNode.GetAsText();
4278 if (rightAsText && leftAsText) {
4279 MOZ_DIAGNOSTIC_ASSERT(AsHTMLEditor(),
4280 "Text node in TextEditor shouldn't be split");
4281 // Fix right node
4282 nsAutoString leftText;
4283 IgnoredErrorResult ignoredError;
4284 rightAsText->SubstringData(0, aStartOfRightNode.Offset(), leftText,
4285 ignoredError);
4286 NS_WARNING_ASSERTION(!ignoredError.Failed(),
4287 "Text::SubstringData() failed, but ignored");
4288 ignoredError.SuppressException();
4290 // XXX This call may destroy us.
4291 DoDeleteText(MOZ_KnownLive(*rightAsText), 0, aStartOfRightNode.Offset(),
4292 ignoredError);
4293 NS_WARNING_ASSERTION(!ignoredError.Failed(),
4294 "EditorBase::DoDeleteText() failed, but ignored");
4295 ignoredError.SuppressException();
4297 // Fix left node
4298 // XXX This call may destroy us.
4299 DoSetText(MOZ_KnownLive(*leftAsText), leftText, ignoredError);
4300 NS_WARNING_ASSERTION(!ignoredError.Failed(),
4301 "EditorBase::DoSetText() failed, but ignored");
4302 } else {
4303 MOZ_DIAGNOSTIC_ASSERT(!rightAsText && !leftAsText);
4304 // Otherwise it's an interior node, so shuffle around the children. Go
4305 // through list backwards so deletes don't interfere with the iteration.
4306 if (!firstChildOfRightNode) {
4307 MoveAllChildren(*aStartOfRightNode.GetContainer(),
4308 EditorRawDOMPoint(&aNewLeftNode, 0), aError);
4309 NS_WARNING_ASSERTION(!aError.Failed(),
4310 "HTMLEditor::MoveAllChildren() failed");
4311 } else if (NS_WARN_IF(aStartOfRightNode.GetContainer() !=
4312 firstChildOfRightNode->GetParentNode())) {
4313 // firstChildOfRightNode has been moved by mutation observer.
4314 // In this case, we what should we do? Use offset? But we cannot
4315 // check if the offset is still expected.
4316 } else {
4317 MovePreviousSiblings(*firstChildOfRightNode,
4318 EditorRawDOMPoint(&aNewLeftNode, 0), aError);
4319 NS_WARNING_ASSERTION(!aError.Failed(),
4320 "HTMLEditor::MovePreviousSiblings() failed");
4325 // XXX Why do we ignore an error while moving nodes from the right node to
4326 // the left node?
4327 NS_WARNING_ASSERTION(!aError.Failed(), "The previous error is ignored");
4328 aError.SuppressException();
4330 // Handle selection
4331 if (RefPtr<PresShell> presShell = GetPresShell()) {
4332 presShell->FlushPendingNotifications(FlushType::Frames);
4334 NS_WARNING_ASSERTION(!Destroyed(),
4335 "The editor is destroyed during splitting a node");
4337 bool allowedTransactionsToChangeSelection =
4338 AllowsTransactionsToChangeSelection();
4340 RefPtr<Selection> previousSelection;
4341 for (size_t i = 0; i < savedRanges.Length(); ++i) {
4342 // Adjust the selection if needed.
4343 SavedRange& range = savedRanges[i];
4345 // If we have not seen the selection yet, clear all of its ranges.
4346 if (range.mSelection != previousSelection) {
4347 range.mSelection->RemoveAllRanges(aError);
4348 if (aError.Failed()) {
4349 NS_WARNING("Selection::RemoveAllRanges() failed");
4350 return;
4352 previousSelection = range.mSelection;
4355 // XXX Looks like that we don't need to modify normal selection here
4356 // because selection will be modified by the caller if
4357 // AllowsTransactionsToChangeSelection() will return true.
4358 if (allowedTransactionsToChangeSelection &&
4359 range.mSelection->Type() == SelectionType::eNormal) {
4360 // If the editor should adjust the selection, don't bother restoring
4361 // the ranges for the normal selection here.
4362 continue;
4365 // Split the selection into existing node and new node.
4366 if (range.mStartContainer == aStartOfRightNode.GetContainer()) {
4367 if (static_cast<uint32_t>(range.mStartOffset) <
4368 aStartOfRightNode.Offset()) {
4369 range.mStartContainer = &aNewLeftNode;
4370 } else {
4371 range.mStartOffset -= aStartOfRightNode.Offset();
4375 if (range.mEndContainer == aStartOfRightNode.GetContainer()) {
4376 if (static_cast<uint32_t>(range.mEndOffset) <
4377 aStartOfRightNode.Offset()) {
4378 range.mEndContainer = &aNewLeftNode;
4379 } else {
4380 range.mEndOffset -= aStartOfRightNode.Offset();
4384 RefPtr<nsRange> newRange =
4385 nsRange::Create(range.mStartContainer, range.mStartOffset,
4386 range.mEndContainer, range.mEndOffset, aError);
4387 if (aError.Failed()) {
4388 NS_WARNING("nsRange::Create() failed");
4389 return;
4391 // The `MOZ_KnownLive` annotation is only necessary because of a bug
4392 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253) in the
4393 // static analyzer.
4394 MOZ_KnownLive(range.mSelection)
4395 ->AddRangeAndSelectFramesAndNotifyListeners(*newRange, aError);
4396 if (aError.Failed()) {
4397 NS_WARNING(
4398 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed");
4399 return;
4403 // We don't need to set selection here because the caller should do that
4404 // in any case.
4406 // If splitting the node causes running mutation event listener and we've
4407 // got unexpected result, we should return error because callers will
4408 // continue to do their work without complicated DOM tree result.
4409 // NOTE: Perhaps, we shouldn't do this immediately after each DOM tree change
4410 // because stopping handling it causes some data loss. E.g., user
4411 // may loose the text which is moved to the new text node.
4412 // XXX We cannot check all descendants in the right node and the new left
4413 // node for performance reason. I think that if caller needs to access
4414 // some of the descendants, they should check by themselves.
4415 if (NS_WARN_IF(parent != aStartOfRightNode.GetContainer()->GetParentNode()) ||
4416 NS_WARN_IF(parent != aNewLeftNode.GetParentNode()) ||
4417 NS_WARN_IF(aNewLeftNode.GetNextSibling() !=
4418 aStartOfRightNode.GetContainer())) {
4419 aError.Throw(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE);
4423 nsresult HTMLEditor::JoinNodesWithTransaction(nsINode& aLeftNode,
4424 nsINode& aRightNode) {
4425 MOZ_ASSERT(IsEditActionDataAvailable());
4426 MOZ_ASSERT(aLeftNode.IsContent());
4427 MOZ_ASSERT(aRightNode.IsContent());
4429 nsCOMPtr<nsINode> parent = aLeftNode.GetParentNode();
4430 MOZ_ASSERT(parent);
4432 IgnoredErrorResult ignoredError;
4433 AutoEditSubActionNotifier startToHandleEditSubAction(
4434 *this, EditSubAction::eJoinNodes, nsIEditor::ePrevious, ignoredError);
4435 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
4436 return ignoredError.StealNSResult();
4438 NS_WARNING_ASSERTION(
4439 !ignoredError.Failed(),
4440 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
4442 // Remember some values; later used for saved selection updating.
4443 // Find the offset between the nodes to be joined.
4444 int32_t offset = parent->ComputeIndexOf(&aRightNode);
4445 // Find the number of children of the lefthand node
4446 uint32_t oldLeftNodeLen = aLeftNode.Length();
4448 if (AsHTMLEditor()) {
4449 TopLevelEditSubActionDataRef().WillJoinContents(
4450 *this, *aLeftNode.AsContent(), *aRightNode.AsContent());
4453 RefPtr<JoinNodeTransaction> transaction = JoinNodeTransaction::MaybeCreate(
4454 *this, *aLeftNode.AsContent(), *aRightNode.AsContent());
4455 NS_WARNING_ASSERTION(
4456 transaction, "JoinNodeTransaction::MaybeCreate() failed, but ignored");
4458 nsresult rv = NS_OK;
4459 if (transaction) {
4460 rv = DoTransactionInternal(transaction);
4461 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4462 "EditorBase::DoTransactionInternal() failed");
4465 // XXX Some other transactions manage range updater by themselves.
4466 // Why doesn't JoinNodeTransaction do it?
4467 DebugOnly<nsresult> rvIgnored =
4468 RangeUpdaterRef().SelAdjJoinNodes(aLeftNode, aRightNode, *parent, offset,
4469 static_cast<int32_t>(oldLeftNodeLen));
4470 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4471 "RangeUpdater::SelAdjJoinNodes() failed, but ignored");
4473 if (AsHTMLEditor()) {
4474 TopLevelEditSubActionDataRef().DidJoinContents(
4475 *this, *aLeftNode.AsContent(), *aRightNode.AsContent());
4478 if (mInlineSpellChecker) {
4479 RefPtr<mozInlineSpellChecker> spellChecker = mInlineSpellChecker;
4480 spellChecker->DidJoinNodes(aLeftNode, aRightNode);
4483 if (mTextServicesDocument && NS_SUCCEEDED(rv)) {
4484 RefPtr<TextServicesDocument> textServicesDocument = mTextServicesDocument;
4485 textServicesDocument->DidJoinNodes(aLeftNode, aRightNode);
4488 if (!mActionListeners.IsEmpty()) {
4489 for (auto& listener : mActionListeners.Clone()) {
4490 DebugOnly<nsresult> rvIgnored =
4491 listener->DidJoinNodes(&aLeftNode, &aRightNode, parent, rv);
4492 NS_WARNING_ASSERTION(
4493 NS_SUCCEEDED(rvIgnored),
4494 "nsIEditActionListener::DidJoinNodes() failed, but ignored");
4498 return rv;
4501 nsresult HTMLEditor::DoJoinNodes(nsIContent& aContentToKeep,
4502 nsIContent& aContentToJoin) {
4503 MOZ_ASSERT(IsEditActionDataAvailable());
4504 MOZ_DIAGNOSTIC_ASSERT(AsHTMLEditor());
4506 uint32_t firstNodeLength = aContentToJoin.Length();
4508 EditorRawDOMPoint atNodeToJoin(&aContentToJoin);
4509 EditorRawDOMPoint atNodeToKeep(&aContentToKeep);
4511 // Remember all selection points.
4512 // XXX Do we need to restore all types of selections by ourselves? Normal
4513 // selection should be modified later as result of handling edit action.
4514 // IME selections shouldn't be there when nodes are joined. Spellcheck
4515 // selections should be recreated with newer text. URL selections
4516 // shouldn't be there because of used only by the URL bar.
4517 AutoTArray<SavedRange, 10> savedRanges;
4518 for (SelectionType selectionType : kPresentSelectionTypes) {
4519 SavedRange range;
4520 range.mSelection = GetSelection(selectionType);
4521 if (selectionType == SelectionType::eNormal) {
4522 if (NS_WARN_IF(!range.mSelection)) {
4523 return NS_ERROR_FAILURE;
4525 } else if (!range.mSelection) {
4526 // For non-normal selections, skip over the non-existing ones.
4527 continue;
4530 for (uint32_t j = 0; j < range.mSelection->RangeCount(); ++j) {
4531 const RefPtr<nsRange> r = range.mSelection->GetRangeAt(j);
4532 MOZ_ASSERT(r->IsPositioned());
4533 range.mStartContainer = r->GetStartContainer();
4534 range.mStartOffset = r->StartOffset();
4535 range.mEndContainer = r->GetEndContainer();
4536 range.mEndOffset = r->EndOffset();
4538 // If selection endpoint is between the nodes, remember it as being
4539 // in the one that is going away instead. This simplifies later selection
4540 // adjustment logic at end of this method.
4541 if (range.mStartContainer) {
4542 if (range.mStartContainer == atNodeToKeep.GetContainer() &&
4543 atNodeToJoin.Offset() < static_cast<uint32_t>(range.mStartOffset) &&
4544 static_cast<uint32_t>(range.mStartOffset) <=
4545 atNodeToKeep.Offset()) {
4546 range.mStartContainer = &aContentToJoin;
4547 range.mStartOffset = firstNodeLength;
4549 if (range.mEndContainer == atNodeToKeep.GetContainer() &&
4550 atNodeToJoin.Offset() < static_cast<uint32_t>(range.mEndOffset) &&
4551 static_cast<uint32_t>(range.mEndOffset) <= atNodeToKeep.Offset()) {
4552 range.mEndContainer = &aContentToJoin;
4553 range.mEndOffset = firstNodeLength;
4557 savedRanges.AppendElement(range);
4561 // OK, ready to do join now.
4562 // If it's a text node, just shuffle around some text.
4563 if (aContentToKeep.IsText() && aContentToJoin.IsText()) {
4564 nsAutoString rightText;
4565 nsAutoString leftText;
4566 aContentToKeep.AsText()->GetData(rightText);
4567 aContentToJoin.AsText()->GetData(leftText);
4568 leftText += rightText;
4569 IgnoredErrorResult ignoredError;
4570 DoSetText(MOZ_KnownLive(*aContentToKeep.AsText()), leftText, ignoredError);
4571 if (NS_WARN_IF(Destroyed())) {
4572 return NS_ERROR_EDITOR_DESTROYED;
4574 NS_WARNING_ASSERTION(!ignoredError.Failed(),
4575 "EditorBase::DoSetText() failed, but ignored");
4576 } else {
4577 // Otherwise it's an interior node, so shuffle around the children.
4578 nsCOMPtr<nsINodeList> childNodes = aContentToJoin.ChildNodes();
4579 MOZ_ASSERT(childNodes);
4581 // Remember the first child in aContentToKeep, we'll insert all the children
4582 // of aContentToJoin in front of it GetFirstChild returns nullptr firstNode
4583 // if aContentToKeep has no children, that's OK.
4584 nsCOMPtr<nsIContent> firstNode = aContentToKeep.GetFirstChild();
4586 // Have to go through the list backwards to keep deletes from interfering
4587 // with iteration.
4588 for (uint32_t i = childNodes->Length(); i; --i) {
4589 nsCOMPtr<nsIContent> childNode = childNodes->Item(i - 1);
4590 if (childNode) {
4591 // prepend children of aContentToJoin
4592 ErrorResult error;
4593 aContentToKeep.InsertBefore(*childNode, firstNode, error);
4594 if (NS_WARN_IF(Destroyed())) {
4595 error.SuppressException();
4596 return NS_ERROR_EDITOR_DESTROYED;
4598 if (error.Failed()) {
4599 NS_WARNING("nsINode::InsertBefore() failed");
4600 return error.StealNSResult();
4602 firstNode = std::move(childNode);
4607 // Delete the extra node.
4608 aContentToJoin.Remove();
4609 if (NS_WARN_IF(Destroyed())) {
4610 return NS_ERROR_EDITOR_DESTROYED;
4613 bool allowedTransactionsToChangeSelection =
4614 AllowsTransactionsToChangeSelection();
4616 RefPtr<Selection> previousSelection;
4617 for (size_t i = 0; i < savedRanges.Length(); ++i) {
4618 // And adjust the selection if needed.
4619 SavedRange& range = savedRanges[i];
4621 // If we have not seen the selection yet, clear all of its ranges.
4622 if (range.mSelection != previousSelection) {
4623 ErrorResult error;
4624 range.mSelection->RemoveAllRanges(error);
4625 if (NS_WARN_IF(Destroyed())) {
4626 error.SuppressException();
4627 return NS_ERROR_EDITOR_DESTROYED;
4629 if (error.Failed()) {
4630 NS_WARNING("Selection::RemoveAllRanges() failed");
4631 return error.StealNSResult();
4633 previousSelection = range.mSelection;
4636 if (allowedTransactionsToChangeSelection &&
4637 range.mSelection->Type() == SelectionType::eNormal) {
4638 // If the editor should adjust the selection, don't bother restoring
4639 // the ranges for the normal selection here.
4640 continue;
4643 // Check to see if we joined nodes where selection starts.
4644 if (range.mStartContainer == &aContentToJoin) {
4645 range.mStartContainer = &aContentToKeep;
4646 } else if (range.mStartContainer == &aContentToKeep) {
4647 range.mStartOffset += firstNodeLength;
4650 // Check to see if we joined nodes where selection ends.
4651 if (range.mEndContainer == &aContentToJoin) {
4652 range.mEndContainer = &aContentToKeep;
4653 } else if (range.mEndContainer == &aContentToKeep) {
4654 range.mEndOffset += firstNodeLength;
4657 RefPtr<nsRange> newRange =
4658 nsRange::Create(range.mStartContainer, range.mStartOffset,
4659 range.mEndContainer, range.mEndOffset, IgnoreErrors());
4660 if (!newRange) {
4661 NS_WARNING("nsRange::Create() failed");
4662 return NS_ERROR_FAILURE;
4665 ErrorResult error;
4666 // The `MOZ_KnownLive` annotation is only necessary because of a bug
4667 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253) in the
4668 // static analyzer.
4669 MOZ_KnownLive(range.mSelection)
4670 ->AddRangeAndSelectFramesAndNotifyListeners(*newRange, error);
4671 if (NS_WARN_IF(Destroyed())) {
4672 error.SuppressException();
4673 return NS_ERROR_EDITOR_DESTROYED;
4675 if (NS_WARN_IF(error.Failed())) {
4676 return error.StealNSResult();
4680 if (allowedTransactionsToChangeSelection) {
4681 // Editor wants us to set selection at join point.
4682 DebugOnly<nsresult> rvIgnored = SelectionRefPtr()->CollapseInLimiter(
4683 &aContentToKeep, AssertedCast<int32_t>(firstNodeLength));
4684 if (NS_WARN_IF(Destroyed())) {
4685 return NS_ERROR_EDITOR_DESTROYED;
4687 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
4688 "Selection::CollapseInLimiter() failed, but ignored");
4691 return NS_OK;
4694 nsresult HTMLEditor::MoveNodeWithTransaction(
4695 nsIContent& aContent, const EditorDOMPoint& aPointToInsert) {
4696 MOZ_ASSERT(aPointToInsert.IsSetAndValid());
4698 EditorDOMPoint oldPoint(&aContent);
4699 if (NS_WARN_IF(!oldPoint.IsSet())) {
4700 return NS_ERROR_FAILURE;
4703 // Don't do anything if it's already in right place.
4704 if (aPointToInsert == oldPoint) {
4705 return NS_OK;
4708 // Notify our internal selection state listener
4709 AutoMoveNodeSelNotify selNotify(RangeUpdaterRef(), oldPoint, aPointToInsert);
4711 // Hold a reference so aNode doesn't go away when we remove it (bug 772282)
4712 // HTMLEditor::DeleteNodeWithTransaction() does not move non-editable
4713 // node, but we need to move non-editable nodes too. Therefore, call
4714 // EditorBase's method directly.
4715 // XXX Perhaps, this method and DeleteNodeWithTransaction() should take
4716 // new argument for making callers specify whether non-editable nodes
4717 // should be moved or not.
4718 nsresult rv = EditorBase::DeleteNodeWithTransaction(aContent);
4719 if (NS_FAILED(rv)) {
4720 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed");
4721 return rv;
4724 // Mutation event listener could break insertion point. Let's check it.
4725 EditorDOMPoint pointToInsert(selNotify.ComputeInsertionPoint());
4726 if (NS_WARN_IF(!pointToInsert.IsSet())) {
4727 return NS_ERROR_FAILURE;
4729 // If some children have removed from the container, let's append to the
4730 // container.
4731 // XXX Perhaps, if mutation event listener inserts or removes some children
4732 // but the child node referring with aPointToInsert is still available,
4733 // we should insert aContent before it. However, we should keep
4734 // traditional behavior for now.
4735 if (NS_WARN_IF(!pointToInsert.IsSetAndValid())) {
4736 pointToInsert.SetToEndOf(pointToInsert.GetContainer());
4738 rv = InsertNodeWithTransaction(aContent, pointToInsert);
4739 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
4740 "EditorBase::InsertNodeWithTransaction() failed");
4741 return rv;
4744 already_AddRefed<Element> HTMLEditor::DeleteSelectionAndCreateElement(
4745 nsAtom& aTag) {
4746 MOZ_ASSERT(IsEditActionDataAvailable());
4748 nsresult rv = DeleteSelectionAndPrepareToCreateNode();
4749 if (NS_FAILED(rv)) {
4750 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed");
4751 return nullptr;
4754 EditorDOMPoint pointToInsert(SelectionRefPtr()->AnchorRef());
4755 if (!pointToInsert.IsSet()) {
4756 return nullptr;
4758 RefPtr<Element> newElement = CreateNodeWithTransaction(aTag, pointToInsert);
4759 if (!newElement) {
4760 NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
4761 return nullptr;
4764 // We want the selection to be just after the new node
4765 EditorRawDOMPoint afterNewElement(EditorRawDOMPoint::After(newElement));
4766 MOZ_ASSERT(afterNewElement.IsSetAndValid());
4767 IgnoredErrorResult ignoredError;
4768 SelectionRefPtr()->CollapseInLimiter(afterNewElement, ignoredError);
4769 if (ignoredError.Failed()) {
4770 NS_WARNING("Selection::CollapseInLimiter() failed");
4771 // XXX Even if it succeeded to create new element, this returns error
4772 // when Selection.Collapse() fails something. This could occur with
4773 // mutation observer or mutation event listener.
4774 return nullptr;
4776 return newElement.forget();
4779 nsresult HTMLEditor::DeleteSelectionAndPrepareToCreateNode() {
4780 MOZ_ASSERT(IsEditActionDataAvailable());
4781 MOZ_ASSERT(IsHTMLEditor()); // TODO: Move this method to `HTMLEditor`
4783 if (NS_WARN_IF(!SelectionRefPtr()->GetAnchorFocusRange())) {
4784 return NS_OK;
4787 if (!SelectionRefPtr()->GetAnchorFocusRange()->Collapsed()) {
4788 nsresult rv =
4789 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip);
4790 if (NS_FAILED(rv)) {
4791 NS_WARNING("EditorBase::DeleteSelectionAsSubAction() failed");
4792 return rv;
4794 MOZ_ASSERT(SelectionRefPtr()->GetAnchorFocusRange() &&
4795 SelectionRefPtr()->GetAnchorFocusRange()->Collapsed(),
4796 "Selection not collapsed after delete");
4799 // If the selection is a chardata node, split it if necessary and compute
4800 // where to put the new node
4801 EditorDOMPoint atAnchor(SelectionRefPtr()->AnchorRef());
4802 if (NS_WARN_IF(!atAnchor.IsSet()) || !atAnchor.IsInDataNode()) {
4803 return NS_OK;
4806 if (NS_WARN_IF(!atAnchor.GetContainerParent())) {
4807 return NS_ERROR_FAILURE;
4810 if (atAnchor.IsStartOfContainer()) {
4811 EditorRawDOMPoint atAnchorContainer(atAnchor.GetContainer());
4812 if (NS_WARN_IF(!atAnchorContainer.IsSetAndValid())) {
4813 return NS_ERROR_FAILURE;
4815 ErrorResult error;
4816 SelectionRefPtr()->CollapseInLimiter(atAnchorContainer, error);
4817 NS_WARNING_ASSERTION(!error.Failed(),
4818 "Selection::CollapseInLimiter() failed");
4819 return error.StealNSResult();
4822 if (atAnchor.IsEndOfContainer()) {
4823 EditorRawDOMPoint afterAnchorContainer(atAnchor.GetContainer());
4824 if (NS_WARN_IF(!afterAnchorContainer.AdvanceOffset())) {
4825 return NS_ERROR_FAILURE;
4827 ErrorResult error;
4828 SelectionRefPtr()->CollapseInLimiter(afterAnchorContainer, error);
4829 NS_WARNING_ASSERTION(!error.Failed(),
4830 "Selection::CollapseInLimiter() failed");
4831 return error.StealNSResult();
4834 ErrorResult error;
4835 nsCOMPtr<nsIContent> newLeftNode = SplitNodeWithTransaction(atAnchor, error);
4836 if (error.Failed()) {
4837 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed");
4838 return error.StealNSResult();
4841 EditorRawDOMPoint atRightNode(atAnchor.GetContainer());
4842 if (NS_WARN_IF(!atRightNode.IsSet())) {
4843 return NS_ERROR_FAILURE;
4845 MOZ_ASSERT(atRightNode.IsSetAndValid());
4846 SelectionRefPtr()->CollapseInLimiter(atRightNode, error);
4847 NS_WARNING_ASSERTION(!error.Failed(),
4848 "Selection::CollapseInLimiter() failed");
4849 return error.StealNSResult();
4852 nsIContent* HTMLEditor::GetPriorHTMLSibling(nsINode* aNode,
4853 SkipWhiteSpace aSkipWS) const {
4854 MOZ_ASSERT(aNode);
4856 nsIContent* content = aNode->GetPreviousSibling();
4857 while (content &&
4858 (!EditorUtils::IsEditableContent(*content, EditorType::HTML) ||
4859 SkippableWhiteSpace(content, aSkipWS))) {
4860 content = content->GetPreviousSibling();
4863 return content;
4866 nsIContent* HTMLEditor::GetNextHTMLSibling(nsINode* aNode,
4867 SkipWhiteSpace aSkipWS) const {
4868 MOZ_ASSERT(aNode);
4870 nsIContent* content = aNode->GetNextSibling();
4871 while (content &&
4872 (!EditorUtils::IsEditableContent(*content, EditorType::HTML) ||
4873 SkippableWhiteSpace(content, aSkipWS))) {
4874 content = content->GetNextSibling();
4877 return content;
4880 nsIContent* HTMLEditor::GetPreviousHTMLElementOrTextInternal(
4881 const nsINode& aNode, bool aNoBlockCrossing) const {
4882 if (NS_WARN_IF(!GetActiveEditingHost())) {
4883 return nullptr;
4885 return aNoBlockCrossing ? GetPreviousElementOrTextInBlock(aNode)
4886 : GetPreviousElementOrText(aNode);
4889 template <typename PT, typename CT>
4890 nsIContent* HTMLEditor::GetPreviousHTMLElementOrTextInternal(
4891 const EditorDOMPointBase<PT, CT>& aPoint, bool aNoBlockCrossing) const {
4892 if (NS_WARN_IF(!GetActiveEditingHost())) {
4893 return nullptr;
4895 return aNoBlockCrossing ? GetPreviousElementOrTextInBlock(aPoint)
4896 : GetPreviousElementOrText(aPoint);
4899 nsIContent* HTMLEditor::GetPreviousEditableHTMLNodeInternal(
4900 nsINode& aNode, bool aNoBlockCrossing) const {
4901 if (NS_WARN_IF(!GetActiveEditingHost())) {
4902 return nullptr;
4904 return aNoBlockCrossing ? GetPreviousEditableNodeInBlock(aNode)
4905 : GetPreviousEditableNode(aNode);
4908 template <typename PT, typename CT>
4909 nsIContent* HTMLEditor::GetPreviousEditableHTMLNodeInternal(
4910 const EditorDOMPointBase<PT, CT>& aPoint, bool aNoBlockCrossing) const {
4911 if (NS_WARN_IF(!GetActiveEditingHost())) {
4912 return nullptr;
4914 return aNoBlockCrossing ? GetPreviousEditableNodeInBlock(aPoint)
4915 : GetPreviousEditableNode(aPoint);
4918 nsIContent* HTMLEditor::GetNextHTMLElementOrTextInternal(
4919 const nsINode& aNode, bool aNoBlockCrossing) const {
4920 if (NS_WARN_IF(!GetActiveEditingHost())) {
4921 return nullptr;
4923 return aNoBlockCrossing ? GetNextElementOrTextInBlock(aNode)
4924 : GetNextElementOrText(aNode);
4927 template <typename PT, typename CT>
4928 nsIContent* HTMLEditor::GetNextHTMLElementOrTextInternal(
4929 const EditorDOMPointBase<PT, CT>& aPoint, bool aNoBlockCrossing) const {
4930 if (NS_WARN_IF(!GetActiveEditingHost())) {
4931 return nullptr;
4933 return aNoBlockCrossing ? GetNextElementOrTextInBlock(aPoint)
4934 : GetNextElementOrText(aPoint);
4937 nsIContent* HTMLEditor::GetNextEditableHTMLNodeInternal(
4938 nsINode& aNode, bool aNoBlockCrossing) const {
4939 if (NS_WARN_IF(!GetActiveEditingHost())) {
4940 return nullptr;
4942 return aNoBlockCrossing ? GetNextEditableNodeInBlock(aNode)
4943 : GetNextEditableNode(aNode);
4946 template <typename PT, typename CT>
4947 nsIContent* HTMLEditor::GetNextEditableHTMLNodeInternal(
4948 const EditorDOMPointBase<PT, CT>& aPoint, bool aNoBlockCrossing) const {
4949 if (NS_WARN_IF(!GetActiveEditingHost())) {
4950 return nullptr;
4952 return aNoBlockCrossing ? GetNextEditableNodeInBlock(aPoint)
4953 : GetNextEditableNode(aPoint);
4956 bool HTMLEditor::IsFirstEditableChild(nsINode* aNode) const {
4957 MOZ_ASSERT(aNode);
4958 // find first editable child and compare it to aNode
4959 nsCOMPtr<nsINode> parentNode = aNode->GetParentNode();
4960 if (NS_WARN_IF(!parentNode)) {
4961 return false;
4963 return GetFirstEditableChild(*parentNode) == aNode;
4966 bool HTMLEditor::IsLastEditableChild(nsINode* aNode) const {
4967 MOZ_ASSERT(aNode);
4968 // find last editable child and compare it to aNode
4969 nsCOMPtr<nsINode> parentNode = aNode->GetParentNode();
4970 if (NS_WARN_IF(!parentNode)) {
4971 return false;
4973 return GetLastEditableChild(*parentNode) == aNode;
4976 nsIContent* HTMLEditor::GetFirstEditableChild(nsINode& aNode) const {
4977 nsIContent* child = aNode.GetFirstChild();
4978 while (child && !EditorUtils::IsEditableContent(*child, EditorType::HTML)) {
4979 child = child->GetNextSibling();
4981 return child;
4984 nsIContent* HTMLEditor::GetLastEditableChild(nsINode& aNode) const {
4985 nsIContent* child = aNode.GetLastChild();
4986 while (child && !EditorUtils::IsEditableContent(*child, EditorType::HTML)) {
4987 child = child->GetPreviousSibling();
4989 return child;
4992 nsIContent* HTMLEditor::GetFirstEditableLeaf(nsINode& aNode) const {
4993 nsIContent* child =
4994 HTMLEditUtils::GetFirstLeafChild(aNode, ChildBlockBoundary::Ignore);
4995 while (child && (!EditorUtils::IsEditableContent(*child, EditorType::HTML) ||
4996 child->HasChildren())) {
4997 child = GetNextEditableHTMLNode(*child);
4999 // Only accept nodes that are descendants of aNode
5000 if (!aNode.Contains(child)) {
5001 return nullptr;
5005 return child;
5008 nsIContent* HTMLEditor::GetLastEditableLeaf(nsINode& aNode) const {
5009 nsIContent* child =
5010 HTMLEditUtils::GetLastLeafChild(aNode, ChildBlockBoundary::Ignore);
5011 while (child && (!EditorUtils::IsEditableContent(*child, EditorType::HTML) ||
5012 child->HasChildren())) {
5013 child = GetPreviousEditableHTMLNode(*child);
5015 // Only accept nodes that are descendants of aNode
5016 if (!aNode.Contains(child)) {
5017 return nullptr;
5021 return child;
5024 bool HTMLEditor::IsInVisibleTextFrames(Text& aText) const {
5025 nsISelectionController* selectionController = GetSelectionController();
5026 if (NS_WARN_IF(!selectionController)) {
5027 return false;
5030 if (!aText.TextDataLength()) {
5031 return false;
5034 // Ask the selection controller for information about whether any of the
5035 // data in the node is really rendered. This is really something that
5036 // frames know about, but we aren't supposed to talk to frames. So we put
5037 // a call in the selection controller interface, since it's already in bed
5038 // with frames anyway. (This is a fix for bug 22227, and a partial fix for
5039 // bug 46209.)
5040 bool isVisible = false;
5041 nsresult rv = selectionController->CheckVisibilityContent(
5042 &aText, 0, aText.TextDataLength(), &isVisible);
5043 NS_WARNING_ASSERTION(
5044 NS_SUCCEEDED(rv),
5045 "nsISelectionController::CheckVisibilityContent() failed, but ignored");
5046 return NS_SUCCEEDED(rv) && isVisible;
5049 bool HTMLEditor::IsVisibleTextNode(Text& aText) const {
5050 if (!aText.TextDataLength()) {
5051 return false;
5054 if (!aText.TextIsOnlyWhitespace()) {
5055 return true;
5058 WSScanResult nextWSScanResult =
5059 WSRunScanner::ScanNextVisibleNodeOrBlockBoundary(
5060 *this, EditorRawDOMPoint(&aText, 0));
5061 return nextWSScanResult.InNormalWhiteSpacesOrText() &&
5062 nextWSScanResult.TextPtr() == &aText;
5065 bool HTMLEditor::IsEmpty() const {
5066 if (mPaddingBRElementForEmptyEditor) {
5067 return true;
5070 // XXX Oddly, we check body or document element's state instead of
5071 // active editing host. Must be a bug.
5072 Element* bodyOrDocumentElement = GetRoot();
5073 if (!bodyOrDocumentElement) {
5074 return true;
5077 for (nsIContent* childContent = bodyOrDocumentElement->GetFirstChild();
5078 childContent; childContent = childContent->GetNextSibling()) {
5079 if (!childContent->IsText() || childContent->Length()) {
5080 return false;
5083 return true;
5087 * IsEmptyNodeImpl() is workhorse for IsEmptyNode().
5089 bool HTMLEditor::IsEmptyNodeImpl(nsINode& aNode, bool aSingleBRDoesntCount,
5090 bool aListOrCellNotEmpty,
5091 bool aSafeToAskFrames, bool* aSeenBR) const {
5092 MOZ_ASSERT(aSeenBR);
5094 if (Text* text = aNode.GetAsText()) {
5095 return aSafeToAskFrames ? !IsInVisibleTextFrames(*text)
5096 : !IsVisibleTextNode(*text);
5099 // if it's not a text node (handled above) and it's not a container,
5100 // then we don't call it empty (it's an <hr>, or <br>, etc.).
5101 // Also, if it's an anchor then don't treat it as empty - even though
5102 // anchors are containers, named anchors are "empty" but we don't
5103 // want to treat them as such. Also, don't call ListItems or table
5104 // cells empty if caller desires. Form Widgets not empty.
5105 if (!aNode.IsContent() ||
5106 !HTMLEditUtils::IsContainerNode(*aNode.AsContent()) ||
5107 (HTMLEditUtils::IsNamedAnchor(&aNode) ||
5108 HTMLEditUtils::IsFormWidget(&aNode) ||
5109 (aListOrCellNotEmpty && (HTMLEditUtils::IsListItem(&aNode) ||
5110 HTMLEditUtils::IsTableCell(&aNode))))) {
5111 return false;
5114 // need this for later
5115 bool isListItemOrCell =
5116 HTMLEditUtils::IsListItem(&aNode) || HTMLEditUtils::IsTableCell(&aNode);
5118 // loop over children of node. if no children, or all children are either
5119 // empty text nodes or non-editable, then node qualifies as empty
5120 for (nsCOMPtr<nsIContent> child = aNode.GetFirstChild(); child;
5121 child = child->GetNextSibling()) {
5122 // Is the child editable and non-empty? if so, return false
5123 if (EditorUtils::IsEditableContent(*child, EditorType::HTML)) {
5124 if (Text* text = child->GetAsText()) {
5125 // break out if we find we aren't empty
5126 if (!(aSafeToAskFrames ? !IsInVisibleTextFrames(*text)
5127 : !IsVisibleTextNode(*text))) {
5128 return false;
5130 } else {
5131 // An editable, non-text node. We need to check its content.
5132 // Is it the node we are iterating over?
5133 if (child == &aNode) {
5134 break;
5137 if (aSingleBRDoesntCount && !*aSeenBR &&
5138 child->IsHTMLElement(nsGkAtoms::br)) {
5139 // the first br in a block doesn't count if the caller so indicated
5140 *aSeenBR = true;
5141 } else {
5142 // is it an empty node of some sort?
5143 // note: list items or table cells are not considered empty
5144 // if they contain other lists or tables
5145 if (child->IsElement()) {
5146 if (isListItemOrCell) {
5147 if (HTMLEditUtils::IsAnyListElement(child) ||
5148 child->IsHTMLElement(nsGkAtoms::table)) {
5149 // break out if we find we aren't empty
5150 return false;
5152 } else if (HTMLEditUtils::IsFormWidget(child)) {
5153 // is it a form widget?
5154 // break out if we find we aren't empty
5155 return false;
5159 if (!IsEmptyNodeImpl(*child, aSingleBRDoesntCount,
5160 aListOrCellNotEmpty, aSafeToAskFrames,
5161 aSeenBR)) {
5162 // otherwise it ain't empty
5163 return false;
5170 return true;
5173 // add to aElement the CSS inline styles corresponding to the HTML attribute
5174 // aAttribute with its value aValue
5175 nsresult HTMLEditor::SetAttributeOrEquivalent(Element* aElement,
5176 nsAtom* aAttribute,
5177 const nsAString& aValue,
5178 bool aSuppressTransaction) {
5179 MOZ_ASSERT(aElement);
5180 MOZ_ASSERT(aAttribute);
5182 nsAutoScriptBlocker scriptBlocker;
5183 nsStyledElement* styledElement = nsStyledElement::FromNodeOrNull(aElement);
5184 if (!IsCSSEnabled() || !mCSSEditUtils) {
5185 // we are not in an HTML+CSS editor; let's set the attribute the HTML way
5186 if (mCSSEditUtils && styledElement) {
5187 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
5188 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
5189 nsresult rv =
5190 aSuppressTransaction
5191 ? mCSSEditUtils->RemoveCSSEquivalentToHTMLStyleWithoutTransaction(
5192 MOZ_KnownLive(*styledElement), nullptr, aAttribute, nullptr)
5193 : mCSSEditUtils->RemoveCSSEquivalentToHTMLStyleWithTransaction(
5194 MOZ_KnownLive(*styledElement), nullptr, aAttribute,
5195 nullptr);
5196 if (rv == NS_ERROR_EDITOR_DESTROYED) {
5197 NS_WARNING(
5198 "CSSEditUtils::RemoveCSSEquivalentToHTMLStyle*Transaction() "
5199 "destroyed the editor");
5200 return NS_ERROR_EDITOR_DESTROYED;
5202 NS_WARNING_ASSERTION(
5203 NS_SUCCEEDED(rv),
5204 "CSSEditUtils::RemoveCSSEquivalentToHTMLStyle*Transaction() "
5205 "failed, but ignored");
5207 if (aSuppressTransaction) {
5208 nsresult rv =
5209 aElement->SetAttr(kNameSpaceID_None, aAttribute, aValue, true);
5210 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::SetAttr() failed");
5211 return rv;
5213 nsresult rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue);
5214 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5215 "EditorBase::SetAttributeWithTransaction() failed");
5216 return rv;
5219 if (styledElement) {
5220 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
5221 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
5222 Result<int32_t, nsresult> count =
5223 aSuppressTransaction
5224 ? mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithoutTransaction(
5225 MOZ_KnownLive(*styledElement), nullptr, aAttribute, &aValue)
5226 : mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
5227 MOZ_KnownLive(*styledElement), nullptr, aAttribute, &aValue);
5228 if (count.isErr()) {
5229 if (count.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
5230 return NS_ERROR_EDITOR_DESTROYED;
5232 NS_WARNING(
5233 "CSSEditUtils::SetCSSEquivalentToHTMLStyle*Transaction() failed, but "
5234 "ignored");
5236 if (count.inspect()) {
5237 // we found an equivalence ; let's remove the HTML attribute itself if it
5238 // is set
5239 nsAutoString existingValue;
5240 if (!aElement->GetAttr(kNameSpaceID_None, aAttribute, existingValue)) {
5241 return NS_OK;
5244 if (aSuppressTransaction) {
5245 nsresult rv = aElement->UnsetAttr(kNameSpaceID_None, aAttribute, true);
5246 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::UnsetAttr() failed");
5247 return rv;
5249 nsresult rv = RemoveAttributeWithTransaction(*aElement, *aAttribute);
5250 NS_WARNING_ASSERTION(
5251 NS_SUCCEEDED(rv),
5252 "EditorBase::RemoveAttributeWithTransaction() failed");
5253 return rv;
5257 // count is an integer that represents the number of CSS declarations
5258 // applied to the element. If it is zero, we found no equivalence in this
5259 // implementation for the attribute
5260 if (aAttribute == nsGkAtoms::style) {
5261 // if it is the style attribute, just add the new value to the existing
5262 // style attribute's value
5263 nsAutoString existingValue;
5264 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::style, existingValue);
5265 existingValue.Append(' ');
5266 existingValue.Append(aValue);
5267 if (aSuppressTransaction) {
5268 nsresult rv = aElement->SetAttr(kNameSpaceID_None, nsGkAtoms::style,
5269 existingValue, true);
5270 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5271 "Element::SetAttr(nsGkAtoms::style) failed");
5272 return rv;
5274 nsresult rv = SetAttributeWithTransaction(*aElement, *nsGkAtoms::style,
5275 existingValue);
5276 NS_WARNING_ASSERTION(
5277 NS_SUCCEEDED(rv),
5278 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::style) failed");
5279 return rv;
5282 // we have no CSS equivalence for this attribute and it is not the style
5283 // attribute; let's set it the good'n'old HTML way
5284 if (aSuppressTransaction) {
5285 nsresult rv =
5286 aElement->SetAttr(kNameSpaceID_None, aAttribute, aValue, true);
5287 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::SetAttr() failed");
5288 return rv;
5290 nsresult rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue);
5291 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5292 "EditorBase::SetAttributeWithTransaction() failed");
5293 return rv;
5296 nsresult HTMLEditor::RemoveAttributeOrEquivalent(Element* aElement,
5297 nsAtom* aAttribute,
5298 bool aSuppressTransaction) {
5299 MOZ_ASSERT(aElement);
5300 MOZ_ASSERT(aAttribute);
5302 if (IsCSSEnabled() && mCSSEditUtils &&
5303 CSSEditUtils::IsCSSEditableProperty(aElement, nullptr, aAttribute)) {
5304 // XXX It might be keep handling attribute even if aElement is not
5305 // an nsStyledElement instance.
5306 nsStyledElement* styledElement = nsStyledElement::FromNodeOrNull(aElement);
5307 if (NS_WARN_IF(!styledElement)) {
5308 return NS_ERROR_INVALID_ARG;
5310 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must
5311 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method.
5312 nsresult rv =
5313 aSuppressTransaction
5314 ? mCSSEditUtils->RemoveCSSEquivalentToHTMLStyleWithoutTransaction(
5315 MOZ_KnownLive(*styledElement), nullptr, aAttribute, nullptr)
5316 : mCSSEditUtils->RemoveCSSEquivalentToHTMLStyleWithTransaction(
5317 MOZ_KnownLive(*styledElement), nullptr, aAttribute, nullptr);
5318 if (NS_FAILED(rv)) {
5319 NS_WARNING(
5320 "CSSEditUtils::RemoveCSSEquivalentToHTMLStyle*Transaction() failed");
5321 return rv;
5325 if (!aElement->HasAttr(kNameSpaceID_None, aAttribute)) {
5326 return NS_OK;
5329 if (aSuppressTransaction) {
5330 nsresult rv = aElement->UnsetAttr(kNameSpaceID_None, aAttribute,
5331 /* aNotify = */ true);
5332 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::UnsetAttr() failed");
5333 return rv;
5335 nsresult rv = RemoveAttributeWithTransaction(*aElement, *aAttribute);
5336 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5337 "EditorBase::RemoveAttributeWithTransaction() failed");
5338 return rv;
5341 NS_IMETHODIMP HTMLEditor::SetIsCSSEnabled(bool aIsCSSPrefChecked) {
5342 if (!mCSSEditUtils) {
5343 return NS_ERROR_NOT_INITIALIZED;
5346 AutoEditActionDataSetter editActionData(*this,
5347 EditAction::eEnableOrDisableCSS);
5348 if (NS_WARN_IF(!editActionData.CanHandle())) {
5349 return NS_ERROR_NOT_INITIALIZED;
5352 mCSSEditUtils->SetCSSEnabled(aIsCSSPrefChecked);
5354 // Disable the eEditorNoCSSMask flag if we're enabling StyleWithCSS.
5355 uint32_t flags = mFlags;
5356 if (aIsCSSPrefChecked) {
5357 // Turn off NoCSS as we're enabling CSS
5358 flags &= ~eEditorNoCSSMask;
5359 } else {
5360 // Turn on NoCSS, as we're disabling CSS.
5361 flags |= eEditorNoCSSMask;
5364 nsresult rv = SetFlags(flags);
5365 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::SetFlags() failed");
5366 return rv;
5369 // Set the block background color
5370 nsresult HTMLEditor::SetCSSBackgroundColorWithTransaction(
5371 const nsAString& aColor) {
5372 MOZ_ASSERT(IsEditActionDataAvailable());
5374 if (NS_WARN_IF(!mInitSucceeded)) {
5375 return NS_ERROR_NOT_INITIALIZED;
5378 CommitComposition();
5380 // XXX Shouldn't we do this before calling `CommitComposition()`?
5381 if (IsPlaintextEditor()) {
5382 return NS_OK;
5385 EditActionResult result = CanHandleHTMLEditSubAction();
5386 if (result.Failed() || result.Canceled()) {
5387 NS_WARNING_ASSERTION(result.Succeeded(),
5388 "HTMLEditor::CanHandleHTMLEditSubAction() failed");
5389 return result.Rv();
5392 bool selectionIsCollapsed = SelectionRefPtr()->IsCollapsed();
5394 AutoPlaceholderBatch treatAsOneTransaction(*this,
5395 ScrollSelectionIntoView::Yes);
5396 IgnoredErrorResult ignoredError;
5397 AutoEditSubActionNotifier startToHandleEditSubAction(
5398 *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError);
5399 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) {
5400 return ignoredError.StealNSResult();
5402 NS_WARNING_ASSERTION(!ignoredError.Failed(),
5403 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() "
5404 "failed, but ignored");
5407 AutoSelectionRestorer restoreSelectionLater(*this);
5408 AutoTransactionsConserveSelection dontChangeMySelection(*this);
5410 // Loop through the ranges in the selection
5411 // XXX This is different from `SetInlinePropertyInternal()`. It uses
5412 // AutoSelectionRangeArray to store all ranges first. The result may be
5413 // different if mutation event listener changes the `Selection`.
5414 for (uint32_t i = 0; i < SelectionRefPtr()->RangeCount(); i++) {
5415 RefPtr<nsRange> range = SelectionRefPtr()->GetRangeAt(i);
5416 if (NS_WARN_IF(!range)) {
5417 return NS_ERROR_FAILURE;
5420 EditorDOMPoint startOfRange(range->StartRef());
5421 EditorDOMPoint endOfRange(range->EndRef());
5422 if (NS_WARN_IF(!startOfRange.IsSet()) ||
5423 NS_WARN_IF(!endOfRange.IsSet())) {
5424 continue;
5427 if (startOfRange.GetContainer() == endOfRange.GetContainer()) {
5428 // If the range is in a text node, set background color of its parent
5429 // block.
5430 if (startOfRange.IsInTextNode()) {
5431 if (RefPtr<nsStyledElement> blockStyledElement =
5432 nsStyledElement::FromNodeOrNull(
5433 HTMLEditUtils::GetAncestorBlockElement(
5434 *startOfRange.ContainerAsText()))) {
5435 Result<int32_t, nsresult> result =
5436 mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
5437 *blockStyledElement, nullptr, nsGkAtoms::bgcolor, &aColor);
5438 if (result.isErr()) {
5439 if (result.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
5440 NS_WARNING(
5441 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5442 "nsGkAtoms::bgcolor) destroyed the editor");
5443 return NS_ERROR_EDITOR_DESTROYED;
5445 NS_WARNING(
5446 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5447 "nsGkAtoms::bgcolor) failed, but ignored");
5450 continue;
5453 // If `Selection` is collapsed in a `<body>` element, set background
5454 // color of the `<body>` element.
5455 // XXX Why do we refer whether the `Selection` is collapsed rather
5456 // than the `nsRange` is collapsed?
5457 if (startOfRange.GetContainer()->IsHTMLElement(nsGkAtoms::body) &&
5458 selectionIsCollapsed) {
5459 if (RefPtr<nsStyledElement> styledElement =
5460 startOfRange.GetContainerAsStyledElement()) {
5461 Result<int32_t, nsresult> result =
5462 mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
5463 *styledElement, nullptr, nsGkAtoms::bgcolor, &aColor);
5464 if (result.isErr()) {
5465 if (result.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
5466 NS_WARNING(
5467 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5468 "nsGkAtoms::bgcolor) destroyed the editor");
5469 return NS_ERROR_EDITOR_DESTROYED;
5471 NS_WARNING(
5472 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5473 "nsGkAtoms::bgcolor) failed, but ignored");
5476 continue;
5478 // If one node is selected, set background color of it if it's a
5479 // block, or of its parent block otherwise.
5480 if ((startOfRange.IsStartOfContainer() &&
5481 endOfRange.IsStartOfContainer()) ||
5482 startOfRange.Offset() + 1 == endOfRange.Offset()) {
5483 if (NS_WARN_IF(startOfRange.IsInDataNode())) {
5484 continue;
5486 if (RefPtr<nsStyledElement> blockStyledElement =
5487 nsStyledElement::FromNodeOrNull(
5488 HTMLEditUtils::GetInclusiveAncestorBlockElement(
5489 *startOfRange.GetChild()))) {
5490 Result<int32_t, nsresult> result =
5491 mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
5492 *blockStyledElement, nullptr, nsGkAtoms::bgcolor, &aColor);
5493 if (result.isErr()) {
5494 if (result.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
5495 NS_WARNING(
5496 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5497 "nsGkAtoms::bgcolor) destroyed the editor");
5498 return NS_ERROR_EDITOR_DESTROYED;
5500 NS_WARNING(
5501 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5502 "nsGkAtoms::bgcolor) failed, but ignored");
5505 continue;
5509 // Collect editable nodes which are entirely contained in the range.
5510 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents;
5511 ContentSubtreeIterator subtreeIter;
5512 // If there is no node which is entirely in the range,
5513 // `ContentSubtreeIterator::Init()` fails, but this is possible case,
5514 // don't warn it.
5515 nsresult rv = subtreeIter.Init(range);
5516 NS_WARNING_ASSERTION(
5517 NS_SUCCEEDED(rv),
5518 "ContentSubtreeIterator::Init() failed, but ignored");
5519 if (NS_SUCCEEDED(rv)) {
5520 for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
5521 nsINode* node = subtreeIter.GetCurrentNode();
5522 if (NS_WARN_IF(!node)) {
5523 return NS_ERROR_FAILURE;
5525 if (node->IsContent() && EditorUtils::IsEditableContent(
5526 *node->AsContent(), EditorType::HTML)) {
5527 arrayOfContents.AppendElement(*node->AsContent());
5532 // This caches block parent if we set its background color.
5533 RefPtr<Element> handledBlockParent;
5535 // If start node is a text node, set background color of its parent
5536 // block.
5537 if (startOfRange.IsInTextNode() &&
5538 EditorUtils::IsEditableContent(*startOfRange.ContainerAsText(),
5539 EditorType::HTML)) {
5540 RefPtr<Element> blockElement = HTMLEditUtils::GetAncestorBlockElement(
5541 *startOfRange.ContainerAsText());
5542 if (blockElement && handledBlockParent != blockElement) {
5543 handledBlockParent = blockElement;
5544 if (nsStyledElement* blockStyledElement =
5545 nsStyledElement::FromNode(blockElement)) {
5546 // MOZ_KnownLive(*blockStyledElement): It's blockElement whose
5547 // type is RefPtr.
5548 Result<int32_t, nsresult> result =
5549 mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
5550 MOZ_KnownLive(*blockStyledElement), nullptr,
5551 nsGkAtoms::bgcolor, &aColor);
5552 if (result.isErr()) {
5553 if (result.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
5554 NS_WARNING(
5555 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5556 "nsGkAtoms::bgcolor) destroyed the editor");
5557 return NS_ERROR_EDITOR_DESTROYED;
5559 NS_WARNING(
5560 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5561 "nsGkAtoms::bgcolor) failed, but ignored");
5567 // Then, set background color of each block or block parent of all nodes
5568 // in the range entirely.
5569 for (OwningNonNull<nsIContent>& content : arrayOfContents) {
5570 RefPtr<Element> blockElement =
5571 HTMLEditUtils::GetInclusiveAncestorBlockElement(content);
5572 if (blockElement && handledBlockParent != blockElement) {
5573 handledBlockParent = blockElement;
5574 if (nsStyledElement* blockStyledElement =
5575 nsStyledElement::FromNode(blockElement)) {
5576 // MOZ_KnownLive(*blockStyledElement): It's blockElement whose
5577 // type is RefPtr.
5578 Result<int32_t, nsresult> result =
5579 mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
5580 MOZ_KnownLive(*blockStyledElement), nullptr,
5581 nsGkAtoms::bgcolor, &aColor);
5582 if (result.isErr()) {
5583 if (result.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
5584 NS_WARNING(
5585 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5586 "nsGkAtoms::bgcolor) destroyed the editor");
5587 return NS_ERROR_EDITOR_DESTROYED;
5589 NS_WARNING(
5590 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5591 "nsGkAtoms::bgcolor) failed, but ignored");
5597 // Finally, if end node is a text node, set background color of its
5598 // parent block.
5599 if (endOfRange.IsInTextNode() &&
5600 EditorUtils::IsEditableContent(*endOfRange.ContainerAsText(),
5601 EditorType::HTML)) {
5602 RefPtr<Element> blockElement = HTMLEditUtils::GetAncestorBlockElement(
5603 *endOfRange.ContainerAsText());
5604 if (blockElement && handledBlockParent != blockElement) {
5605 if (nsStyledElement* blockStyledElement =
5606 nsStyledElement::FromNode(blockElement)) {
5607 // MOZ_KnownLive(*blockStyledElement): It's blockElement whose
5608 // type is RefPtr.
5609 Result<int32_t, nsresult> result =
5610 mCSSEditUtils->SetCSSEquivalentToHTMLStyleWithTransaction(
5611 MOZ_KnownLive(*blockStyledElement), nullptr,
5612 nsGkAtoms::bgcolor, &aColor);
5613 if (result.isErr()) {
5614 if (result.inspectErr() == NS_ERROR_EDITOR_DESTROYED) {
5615 NS_WARNING(
5616 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5617 "nsGkAtoms::bgcolor) destroyed the editor");
5618 return NS_ERROR_EDITOR_DESTROYED;
5620 NS_WARNING(
5621 "CSSEditUtils::SetCSSEquivalentToHTMLStyleWithTransaction("
5622 "nsGkAtoms::bgcolor) failed, but ignored");
5630 // Restoring `Selection` may cause destroying us.
5631 return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
5634 NS_IMETHODIMP HTMLEditor::SetBackgroundColor(const nsAString& aColor) {
5635 nsresult rv = SetBackgroundColorAsAction(aColor);
5636 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
5637 "HTMLEditor::SetBackgroundColorAsAction() failed");
5638 return rv;
5641 nsresult HTMLEditor::SetBackgroundColorAsAction(const nsAString& aColor,
5642 nsIPrincipal* aPrincipal) {
5643 AutoEditActionDataSetter editActionData(
5644 *this, EditAction::eSetBackgroundColor, aPrincipal);
5645 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent();
5646 if (NS_FAILED(rv)) {
5647 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED,
5648 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed");
5649 return EditorBase::ToGenericNSResult(rv);
5652 if (IsCSSEnabled()) {
5653 // if we are in CSS mode, we have to apply the background color to the
5654 // containing block (or the body if we have no block-level element in
5655 // the document)
5656 nsresult rv = SetCSSBackgroundColorWithTransaction(aColor);
5657 NS_WARNING_ASSERTION(
5658 NS_SUCCEEDED(rv),
5659 "HTMLEditor::SetCSSBackgroundColorWithTransaction() failed");
5660 return EditorBase::ToGenericNSResult(rv);
5663 // but in HTML mode, we can only set the document's background color
5664 rv = SetHTMLBackgroundColorWithTransaction(aColor);
5665 NS_WARNING_ASSERTION(
5666 NS_SUCCEEDED(rv),
5667 "HTMLEditor::SetHTMLBackgroundColorWithTransaction() failed");
5668 return EditorBase::ToGenericNSResult(rv);
5671 nsresult HTMLEditor::CopyLastEditableChildStylesWithTransaction(
5672 Element& aPreviousBlock, Element& aNewBlock,
5673 RefPtr<Element>* aNewBRElement) {
5674 MOZ_ASSERT(IsEditActionDataAvailable());
5676 if (NS_WARN_IF(!aNewBRElement)) {
5677 return NS_ERROR_INVALID_ARG;
5679 *aNewBRElement = nullptr;
5681 RefPtr<Element> previousBlock(&aPreviousBlock);
5682 RefPtr<Element> newBlock(&aNewBlock);
5684 // First, clear out aNewBlock. Contract is that we want only the styles
5685 // from aPreviousBlock.
5686 for (nsCOMPtr<nsIContent> child = newBlock->GetFirstChild(); child;
5687 child = newBlock->GetFirstChild()) {
5688 nsresult rv = DeleteNodeWithTransaction(*child);
5689 if (NS_FAILED(rv)) {
5690 NS_WARNING("HTMLEditor::DeleteNodeWithTransaction() failed");
5691 return rv;
5695 // XXX aNewBlock may be moved or removed. Even in such case, we should
5696 // keep cloning the styles?
5698 // Look for the deepest last editable leaf node in aPreviousBlock.
5699 // Then, if found one is a <br> element, look for non-<br> element.
5700 nsIContent* deepestEditableContent = nullptr;
5701 for (nsCOMPtr<nsIContent> child = previousBlock.get(); child;
5702 child = GetLastEditableChild(*child)) {
5703 deepestEditableContent = child;
5705 while (deepestEditableContent &&
5706 deepestEditableContent->IsHTMLElement(nsGkAtoms::br)) {
5707 deepestEditableContent =
5708 GetPreviousEditableHTMLNode(*deepestEditableContent);
5710 if (!deepestEditableContent) {
5711 return NS_OK;
5714 Element* deepestVisibleEditableElement =
5715 deepestEditableContent->GetAsElementOrParentElement();
5716 if (!deepestVisibleEditableElement) {
5717 return NS_OK;
5720 // Clone inline elements to keep current style in the new block.
5721 // XXX Looks like that this is really slow if lastEditableDescendant is
5722 // far from aPreviousBlock. Probably, we should clone inline containers
5723 // from ancestor to descendants without transactions, then, insert it
5724 // after that with transaction.
5725 RefPtr<Element> lastClonedElement, firstClonsedElement;
5726 for (RefPtr<Element> elementInPreviousBlock = deepestVisibleEditableElement;
5727 elementInPreviousBlock && elementInPreviousBlock != previousBlock;
5728 elementInPreviousBlock = elementInPreviousBlock->GetParentElement()) {
5729 if (!HTMLEditUtils::IsInlineStyle(elementInPreviousBlock) &&
5730 !elementInPreviousBlock->IsHTMLElement(nsGkAtoms::span)) {
5731 continue;
5733 nsAtom* tagName = elementInPreviousBlock->NodeInfo()->NameAtom();
5734 // At first time, just create the most descendant inline container
5735 // element.
5736 if (!firstClonsedElement) {
5737 firstClonsedElement = lastClonedElement = CreateNodeWithTransaction(
5738 MOZ_KnownLive(*tagName), EditorDOMPoint(newBlock, 0));
5739 if (!firstClonsedElement) {
5740 NS_WARNING("EditorBase::CreateNodeWithTransaction() failed");
5741 return NS_ERROR_FAILURE;
5743 // Clone all attributes.
5744 // XXX Looks like that this clones id attribute too.
5745 CloneAttributesWithTransaction(*lastClonedElement,
5746 *elementInPreviousBlock);
5747 continue;
5749 // Otherwise, inserts new parent inline container to the previous inserted
5750 // inline container.
5751 lastClonedElement = InsertContainerWithTransaction(*lastClonedElement,
5752 MOZ_KnownLive(*tagName));
5753 if (!lastClonedElement) {
5754 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed");
5755 return NS_ERROR_FAILURE;
5757 CloneAttributesWithTransaction(*lastClonedElement, *elementInPreviousBlock);
5760 if (!firstClonsedElement) {
5761 // XXX Even if no inline elements are cloned, shouldn't we create new
5762 // <br> element for aNewBlock?
5763 return NS_OK;
5766 RefPtr<Element> brElement =
5767 InsertBRElementWithTransaction(EditorDOMPoint(firstClonsedElement, 0));
5768 if (!brElement) {
5769 NS_WARNING("HTMLEditor::InsertBRElementWithTransaction() failed");
5770 return NS_ERROR_FAILURE;
5772 *aNewBRElement = std::move(brElement);
5773 return NS_OK;
5776 nsresult HTMLEditor::GetElementOrigin(Element& aElement, int32_t& aX,
5777 int32_t& aY) {
5778 aX = 0;
5779 aY = 0;
5781 if (NS_WARN_IF(!IsInitialized())) {
5782 return NS_ERROR_NOT_INITIALIZED;
5784 PresShell* presShell = GetPresShell();
5785 if (NS_WARN_IF(!presShell)) {
5786 return NS_ERROR_NOT_INITIALIZED;
5789 nsIFrame* frame = aElement.GetPrimaryFrame();
5790 if (NS_WARN_IF(!frame)) {
5791 return NS_OK;
5794 nsIFrame* absoluteContainerBlockFrame =
5795 presShell->GetAbsoluteContainingBlock(frame);
5796 if (NS_WARN_IF(!absoluteContainerBlockFrame)) {
5797 return NS_OK;
5799 nsPoint off = frame->GetOffsetTo(absoluteContainerBlockFrame);
5800 aX = nsPresContext::AppUnitsToIntCSSPixels(off.x);
5801 aY = nsPresContext::AppUnitsToIntCSSPixels(off.y);
5803 return NS_OK;
5806 Element* HTMLEditor::GetSelectionContainerElement() const {
5807 MOZ_ASSERT(IsEditActionDataAvailable());
5809 nsINode* focusNode = nullptr;
5810 if (SelectionRefPtr()->IsCollapsed()) {
5811 focusNode = SelectionRefPtr()->GetFocusNode();
5812 if (NS_WARN_IF(!focusNode)) {
5813 return nullptr;
5815 } else {
5816 uint32_t rangeCount = SelectionRefPtr()->RangeCount();
5817 MOZ_ASSERT(rangeCount, "If 0, Selection::IsCollapsed() should return true");
5819 if (rangeCount == 1) {
5820 const nsRange* range = SelectionRefPtr()->GetRangeAt(0);
5822 const RangeBoundary& startRef = range->StartRef();
5823 const RangeBoundary& endRef = range->EndRef();
5825 // This method called GetSelectedElement() to retrieve proper container
5826 // when only one node is selected. However, it simply returns start
5827 // node of Selection with additional cost. So, we do not need to call
5828 // it anymore.
5829 if (startRef.Container()->IsElement() &&
5830 startRef.Container() == endRef.Container() &&
5831 startRef.GetChildAtOffset() &&
5832 startRef.GetChildAtOffset()->GetNextSibling() ==
5833 endRef.GetChildAtOffset()) {
5834 focusNode = startRef.GetChildAtOffset();
5835 MOZ_ASSERT(focusNode, "Start container must not be nullptr");
5836 } else {
5837 focusNode = range->GetClosestCommonInclusiveAncestor();
5838 if (!focusNode) {
5839 NS_WARNING(
5840 "AbstractRange::GetClosestCommonInclusiveAncestor() returned "
5841 "nullptr");
5842 return nullptr;
5845 } else {
5846 for (uint32_t i = 0; i < rangeCount; i++) {
5847 const nsRange* range = SelectionRefPtr()->GetRangeAt(i);
5848 nsINode* startContainer = range->GetStartContainer();
5849 if (!focusNode) {
5850 focusNode = startContainer;
5851 } else if (focusNode != startContainer) {
5852 // XXX Looks odd to use parent of startContainer because previous
5853 // range may not be in the parent node of current
5854 // startContainer.
5855 focusNode = startContainer->GetParentNode();
5856 // XXX Looks odd to break the for-loop here because we refer only
5857 // first range and another range which starts from different
5858 // container, and the latter range is preferred. Why?
5859 break;
5862 if (!focusNode) {
5863 NS_WARNING("Focused node of selection was not found");
5864 return nullptr;
5869 if (focusNode->IsText()) {
5870 focusNode = focusNode->GetParentNode();
5871 if (NS_WARN_IF(!focusNode)) {
5872 return nullptr;
5876 if (NS_WARN_IF(!focusNode->IsElement())) {
5877 return nullptr;
5879 return focusNode->AsElement();
5882 NS_IMETHODIMP HTMLEditor::IsAnonymousElement(Element* aElement, bool* aReturn) {
5883 if (NS_WARN_IF(!aElement)) {
5884 return NS_ERROR_INVALID_ARG;
5886 *aReturn = aElement->IsRootOfNativeAnonymousSubtree();
5887 return NS_OK;
5890 nsresult HTMLEditor::SetReturnInParagraphCreatesNewParagraph(
5891 bool aCreatesNewParagraph) {
5892 mCRInParagraphCreatesParagraph = aCreatesNewParagraph;
5893 return NS_OK;
5896 bool HTMLEditor::GetReturnInParagraphCreatesNewParagraph() {
5897 return mCRInParagraphCreatesParagraph;
5900 nsresult HTMLEditor::GetReturnInParagraphCreatesNewParagraph(
5901 bool* aCreatesNewParagraph) {
5902 *aCreatesNewParagraph = mCRInParagraphCreatesParagraph;
5903 return NS_OK;
5906 nsIContent* HTMLEditor::GetFocusedContent() const {
5907 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
5908 if (NS_WARN_IF(!focusManager)) {
5909 return nullptr;
5912 nsIContent* focusedContent = focusManager->GetFocusedElement();
5914 Document* document = GetDocument();
5915 if (NS_WARN_IF(!document)) {
5916 return nullptr;
5918 bool inDesignMode = document->HasFlag(NODE_IS_EDITABLE);
5919 if (!focusedContent) {
5920 // in designMode, nobody gets focus in most cases.
5921 if (inDesignMode && OurWindowHasFocus()) {
5922 return document->GetRootElement();
5924 return nullptr;
5927 if (inDesignMode) {
5928 return OurWindowHasFocus() &&
5929 focusedContent->IsInclusiveDescendantOf(document)
5930 ? focusedContent
5931 : nullptr;
5934 // We're HTML editor for contenteditable
5936 // If the focused content isn't editable, or it has independent selection,
5937 // we don't have focus.
5938 if (!focusedContent->HasFlag(NODE_IS_EDITABLE) ||
5939 focusedContent->HasIndependentSelection()) {
5940 return nullptr;
5942 // If our window is focused, we're focused.
5943 return OurWindowHasFocus() ? focusedContent : nullptr;
5946 nsIContent* HTMLEditor::GetFocusedContentForIME() const {
5947 nsIContent* focusedContent = GetFocusedContent();
5948 if (!focusedContent) {
5949 return nullptr;
5952 Document* document = GetDocument();
5953 if (NS_WARN_IF(!document)) {
5954 return nullptr;
5956 return document->HasFlag(NODE_IS_EDITABLE) ? nullptr : focusedContent;
5959 bool HTMLEditor::IsActiveInDOMWindow() const {
5960 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
5961 if (NS_WARN_IF(!focusManager)) {
5962 return false;
5965 Document* document = GetDocument();
5966 if (NS_WARN_IF(!document)) {
5967 return false;
5969 bool inDesignMode = document->HasFlag(NODE_IS_EDITABLE);
5971 // If we're in designMode, we're always active in the DOM window.
5972 if (inDesignMode) {
5973 return true;
5976 nsPIDOMWindowOuter* ourWindow = document->GetWindow();
5977 nsCOMPtr<nsPIDOMWindowOuter> win;
5978 nsIContent* content = nsFocusManager::GetFocusedDescendant(
5979 ourWindow, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(win));
5980 if (!content) {
5981 return false;
5984 // We're HTML editor for contenteditable
5986 // If the active content isn't editable, or it has independent selection,
5987 // we're not active).
5988 if (!content->HasFlag(NODE_IS_EDITABLE) ||
5989 content->HasIndependentSelection()) {
5990 return false;
5992 return true;
5995 Element* HTMLEditor::GetActiveEditingHost() const {
5996 Document* document = GetDocument();
5997 if (NS_WARN_IF(!document)) {
5998 return nullptr;
6000 if (document->HasFlag(NODE_IS_EDITABLE)) {
6001 return document->GetBodyElement();
6004 // We're HTML editor for contenteditable
6005 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
6006 if (NS_WARN_IF(!editActionData.CanHandle())) {
6007 return nullptr;
6010 nsINode* focusNode = SelectionRefPtr()->GetFocusNode();
6011 if (NS_WARN_IF(!focusNode) || NS_WARN_IF(!focusNode->IsContent())) {
6012 return nullptr;
6014 nsIContent* content = focusNode->AsContent();
6016 // If the active content isn't editable, or it has independent selection,
6017 // we're not active.
6018 if (!content->HasFlag(NODE_IS_EDITABLE) ||
6019 content->HasIndependentSelection()) {
6020 return nullptr;
6022 return content->GetEditingHost();
6025 void HTMLEditor::NotifyEditingHostMaybeChanged() {
6026 Document* document = GetDocument();
6027 if (NS_WARN_IF(!document) ||
6028 NS_WARN_IF(document->HasFlag(NODE_IS_EDITABLE))) {
6029 return;
6032 // We're HTML editor for contenteditable
6033 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
6034 if (NS_WARN_IF(!editActionData.CanHandle())) {
6035 return;
6038 // Get selection ancestor limit which may be old editing host.
6039 nsIContent* ancestorLimiter = SelectionRefPtr()->GetAncestorLimiter();
6040 if (!ancestorLimiter) {
6041 // If we've not initialized selection ancestor limit, we should wait focus
6042 // event to set proper limiter.
6043 return;
6046 // Compute current editing host.
6047 nsIContent* editingHost = GetActiveEditingHost();
6048 if (NS_WARN_IF(!editingHost)) {
6049 return;
6052 // Update selection ancestor limit if current editing host includes the
6053 // previous editing host.
6054 if (ancestorLimiter->IsInclusiveDescendantOf(editingHost)) {
6055 // Note that don't call HTMLEditor::InitializeSelectionAncestorLimit()
6056 // here because it may collapse selection to the first editable node.
6057 EditorBase::InitializeSelectionAncestorLimit(*editingHost);
6061 EventTarget* HTMLEditor::GetDOMEventTarget() const {
6062 // Don't use getDocument here, because we have no way of knowing
6063 // whether Init() was ever called. So we need to get the document
6064 // ourselves, if it exists.
6065 MOZ_ASSERT(IsInitialized(), "The HTMLEditor has not been initialized yet");
6066 return GetDocument();
6069 bool HTMLEditor::ShouldReplaceRootElement() const {
6070 if (!mRootElement) {
6071 // If we don't know what is our root element, we should find our root.
6072 return true;
6075 // If we temporary set document root element to mRootElement, but there is
6076 // body element now, we should replace the root element by the body element.
6077 return mRootElement != GetBodyElement();
6080 void HTMLEditor::NotifyRootChanged() {
6081 MOZ_ASSERT(mPendingRootElementUpdatedRunner,
6082 "HTMLEditor::NotifyRootChanged() should be called via a runner");
6083 mPendingRootElementUpdatedRunner = nullptr;
6085 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
6087 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
6088 if (NS_WARN_IF(!editActionData.CanHandle())) {
6089 return;
6092 RemoveEventListeners();
6093 nsresult rv = InstallEventListeners();
6094 if (NS_FAILED(rv)) {
6095 NS_WARNING("HTMLEditor::InstallEventListeners() failed, but ignored");
6096 return;
6099 UpdateRootElement();
6100 if (!mRootElement) {
6101 return;
6104 rv = MaybeCollapseSelectionAtFirstEditableNode(false);
6105 if (NS_FAILED(rv)) {
6106 NS_WARNING(
6107 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) "
6108 "failed, "
6109 "but ignored");
6110 return;
6113 // When this editor has focus, we need to reset the selection limiter to
6114 // new root. Otherwise, that is going to be done when this gets focus.
6115 nsCOMPtr<nsINode> node = GetFocusedNode();
6116 if (node) {
6117 DebugOnly<nsresult> rvIgnored = InitializeSelection(*node);
6118 NS_WARNING_ASSERTION(
6119 NS_SUCCEEDED(rvIgnored),
6120 "EditorBase::InitializeSelection() failed, but ignored");
6123 SyncRealTimeSpell();
6126 Element* HTMLEditor::GetBodyElement() const {
6127 MOZ_ASSERT(IsInitialized(), "The HTMLEditor hasn't been initialized yet");
6128 Document* document = GetDocument();
6129 if (NS_WARN_IF(!document)) {
6130 return nullptr;
6132 return document->GetBody();
6135 nsINode* HTMLEditor::GetFocusedNode() const {
6136 nsIContent* focusedContent = GetFocusedContent();
6137 if (!focusedContent) {
6138 return nullptr;
6141 // focusedContent might be non-null even focusManager->GetFocusedContent()
6142 // is null. That's the designMode case, and in that case our
6143 // FocusedContent() returns the root element, but we want to return
6144 // the document.
6146 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
6147 NS_ASSERTION(focusManager, "Focus manager is null");
6148 Element* focusedElement = focusManager->GetFocusedElement();
6149 if (focusedElement) {
6150 return focusedElement;
6153 return GetDocument();
6156 bool HTMLEditor::OurWindowHasFocus() const {
6157 nsFocusManager* focusManager = nsFocusManager::GetFocusManager();
6158 if (NS_WARN_IF(!focusManager)) {
6159 return false;
6161 nsPIDOMWindowOuter* focusedWindow = focusManager->GetFocusedWindow();
6162 if (!focusedWindow) {
6163 return false;
6165 Document* document = GetDocument();
6166 if (NS_WARN_IF(!document)) {
6167 return false;
6169 nsPIDOMWindowOuter* ourWindow = document->GetWindow();
6170 return ourWindow == focusedWindow;
6173 bool HTMLEditor::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const {
6174 if (!EditorBase::IsAcceptableInputEvent(aGUIEvent)) {
6175 return false;
6178 // While there is composition, all composition events in its top level
6179 // window are always fired on the composing editor. Therefore, if this
6180 // editor has composition, the composition events should be handled in this
6181 // editor.
6182 if (mComposition && aGUIEvent->AsCompositionEvent()) {
6183 return true;
6186 RefPtr<EventTarget> eventTarget = aGUIEvent->GetOriginalDOMEventTarget();
6187 if (NS_WARN_IF(!eventTarget)) {
6188 return false;
6190 nsCOMPtr<nsINode> eventTargetNode = do_QueryInterface(eventTarget);
6191 if (NS_WARN_IF(!eventTargetNode)) {
6192 return false;
6195 if (eventTargetNode->IsContent()) {
6196 eventTargetNode =
6197 eventTargetNode->AsContent()->FindFirstNonChromeOnlyAccessContent();
6198 if (NS_WARN_IF(!eventTargetNode)) {
6199 return false;
6203 RefPtr<Document> document = GetDocument();
6204 if (NS_WARN_IF(!document)) {
6205 return false;
6208 if (document->HasFlag(NODE_IS_EDITABLE)) {
6209 // If this editor is in designMode and the event target is the document,
6210 // the event is for this editor.
6211 if (eventTargetNode->IsDocument()) {
6212 return eventTargetNode == document;
6214 // Otherwise, check whether the event target is in this document or not.
6215 if (NS_WARN_IF(!eventTargetNode->IsContent())) {
6216 return false;
6218 return document == eventTargetNode->GetUncomposedDoc();
6221 // This HTML editor is for contenteditable. We need to check the validity
6222 // of the target.
6223 if (NS_WARN_IF(!eventTargetNode->IsContent())) {
6224 return false;
6227 // If the event is a mouse event, we need to check if the target content is
6228 // the focused editing host or its descendant.
6229 if (aGUIEvent->AsMouseEventBase()) {
6230 nsIContent* editingHost = GetActiveEditingHost();
6231 // If there is no active editing host, we cannot handle the mouse event
6232 // correctly.
6233 if (!editingHost) {
6234 return false;
6236 // If clicked on non-editable root element but the body element is the
6237 // active editing host, we should assume that the click event is
6238 // targetted.
6239 if (eventTargetNode == document->GetRootElement() &&
6240 !eventTargetNode->HasFlag(NODE_IS_EDITABLE) &&
6241 editingHost == document->GetBodyElement()) {
6242 eventTargetNode = editingHost;
6244 // If the target element is neither the active editing host nor a
6245 // descendant of it, we may not be able to handle the event.
6246 if (!eventTargetNode->IsInclusiveDescendantOf(editingHost)) {
6247 return false;
6249 // If the clicked element has an independent selection, we shouldn't
6250 // handle this click event.
6251 if (eventTargetNode->AsContent()->HasIndependentSelection()) {
6252 return false;
6254 // If the target content is editable, we should handle this event.
6255 return eventTargetNode->HasFlag(NODE_IS_EDITABLE);
6258 // If the target of the other events which target focused element isn't
6259 // editable or has an independent selection, this editor shouldn't handle
6260 // the event.
6261 if (!eventTargetNode->HasFlag(NODE_IS_EDITABLE) ||
6262 eventTargetNode->AsContent()->HasIndependentSelection()) {
6263 return false;
6266 // Finally, check whether we're actually focused or not. When we're not
6267 // focused, we should ignore the dispatched event by script (or something)
6268 // because content editable element needs selection in itself for editing.
6269 // However, when we're not focused, it's not guaranteed.
6270 return IsActiveInDOMWindow();
6273 nsresult HTMLEditor::GetPreferredIMEState(IMEState* aState) {
6274 // HTML editor don't prefer the CSS ime-mode because IE didn't do so too.
6275 aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE;
6276 if (IsReadonly()) {
6277 aState->mEnabled = IMEState::DISABLED;
6278 } else {
6279 aState->mEnabled = IMEState::ENABLED;
6281 return NS_OK;
6284 already_AddRefed<Element> HTMLEditor::GetInputEventTargetElement() const {
6285 RefPtr<Element> target = GetActiveEditingHost();
6286 if (target) {
6287 return target.forget();
6290 // When there is no active editing host due to focus node is a
6291 // non-editable node, we should look for its editable parent to
6292 // dispatch `beforeinput` event.
6293 nsIContent* focusContent =
6294 nsIContent::FromNodeOrNull(SelectionRefPtr()->GetFocusNode());
6295 if (!focusContent || focusContent->IsEditable()) {
6296 return nullptr;
6298 for (Element* element : focusContent->AncestorsOfType<Element>()) {
6299 if (element->IsEditable()) {
6300 target = element->GetEditingHost();
6301 return target.forget();
6304 return nullptr;
6307 Element* HTMLEditor::GetEditorRoot() const { return GetActiveEditingHost(); }
6309 nsresult HTMLEditor::OnModifyDocument() {
6310 MOZ_ASSERT(mPendingDocumentModifiedRunner,
6311 "HTMLEditor::OnModifyDocument() should be called via a runner");
6312 mPendingDocumentModifiedRunner = nullptr;
6314 if (IsEditActionDataAvailable()) {
6315 return OnModifyDocumentInternal();
6318 AutoEditActionDataSetter editActionData(
6319 *this, EditAction::eCreatePaddingBRElementForEmptyEditor);
6320 if (NS_WARN_IF(!editActionData.CanHandle())) {
6321 return NS_ERROR_NOT_AVAILABLE;
6324 nsresult rv = OnModifyDocumentInternal();
6325 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6326 "HTMLEditor::OnModifyDocumentInternal() failed");
6327 return rv;
6330 nsresult HTMLEditor::OnModifyDocumentInternal() {
6331 MOZ_ASSERT(IsEditActionDataAvailable());
6332 MOZ_ASSERT(!mPendingDocumentModifiedRunner);
6334 // EnsureNoPaddingBRElementForEmptyEditor() below may cause a flush, which
6335 // could destroy the editor
6336 nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
6338 // Delete our padding <br> element for empty editor, if we have one, since
6339 // the document might not be empty any more.
6340 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
6341 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6342 return rv;
6344 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
6345 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
6346 "failed, but ignored");
6348 // Try to recreate the padding <br> element for empty editor if needed.
6349 rv = MaybeCreatePaddingBRElementForEmptyEditor();
6350 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
6351 return NS_ERROR_EDITOR_DESTROYED;
6353 NS_WARNING_ASSERTION(
6354 NS_SUCCEEDED(rv),
6355 "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
6357 return rv;
6360 } // namespace mozilla