HTMLEditor.cpp (294046B)
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/. */ 5 6 #include "HTMLEditor.h" 7 #include "HTMLEditHelpers.h" 8 #include "HTMLEditorInlines.h" 9 #include "HTMLEditorNestedClasses.h" 10 11 #include "AutoClonedRangeArray.h" 12 #include "AutoSelectionRestorer.h" 13 #include "CSSEditUtils.h" 14 #include "EditAction.h" 15 #include "EditorBase.h" 16 #include "EditorDOMAPIWrapper.h" 17 #include "EditorDOMPoint.h" 18 #include "EditorLineBreak.h" 19 #include "EditorUtils.h" 20 #include "ErrorList.h" 21 #include "HTMLEditorEventListener.h" 22 #include "HTMLEditUtils.h" 23 #include "InsertNodeTransaction.h" 24 #include "JoinNodesTransaction.h" 25 #include "MoveNodeTransaction.h" 26 #include "PendingStyles.h" 27 #include "ReplaceTextTransaction.h" 28 #include "SplitNodeTransaction.h" 29 #include "WhiteSpaceVisibilityKeeper.h" 30 #include "WSRunScanner.h" 31 32 #include "mozilla/ComposerCommandsUpdater.h" 33 #include "mozilla/ContentIterator.h" 34 #include "mozilla/DebugOnly.h" 35 #include "mozilla/EditorForwards.h" 36 #include "mozilla/Encoding.h" // for Encoding 37 #include "mozilla/FlushType.h" 38 #include "mozilla/IMEStateManager.h" 39 #include "mozilla/IntegerRange.h" // for IntegerRange 40 #include "mozilla/mozInlineSpellChecker.h" 41 #include "mozilla/Preferences.h" 42 #include "mozilla/PresShell.h" 43 #include "mozilla/StaticPrefs_editor.h" 44 #include "mozilla/StyleSheet.h" 45 #include "mozilla/StyleSheetInlines.h" 46 #include "mozilla/glean/EditorLibeditorMetrics.h" 47 #include "mozilla/TextControlElement.h" 48 #include "mozilla/TextEditor.h" 49 #include "mozilla/TextEvents.h" 50 #include "mozilla/TextServicesDocument.h" 51 #include "mozilla/ToString.h" 52 #include "mozilla/css/Loader.h" 53 #include "mozilla/dom/AncestorIterator.h" 54 #include "mozilla/dom/Attr.h" 55 #include "mozilla/dom/BorrowedAttrInfo.h" 56 #include "mozilla/dom/CharacterDataBuffer.h" 57 #include "mozilla/dom/DocumentFragment.h" 58 #include "mozilla/dom/DocumentInlines.h" 59 #include "mozilla/dom/Element.h" 60 #include "mozilla/dom/ElementInlines.h" 61 #include "mozilla/dom/Event.h" 62 #include "mozilla/dom/EventTarget.h" 63 #include "mozilla/dom/HTMLAnchorElement.h" 64 #include "mozilla/dom/HTMLBodyElement.h" 65 #include "mozilla/dom/HTMLBRElement.h" 66 #include "mozilla/dom/HTMLButtonElement.h" 67 #include "mozilla/dom/HTMLSummaryElement.h" 68 #include "mozilla/dom/NameSpaceConstants.h" 69 #include "mozilla/dom/Selection.h" 70 71 #include "nsContentList.h" 72 #include "nsContentUtils.h" 73 #include "nsCRT.h" 74 #include "nsDebug.h" 75 #include "nsElementTable.h" 76 #include "nsFocusManager.h" 77 #include "nsGenericHTMLElement.h" 78 #include "nsGkAtoms.h" 79 #include "nsHTMLDocument.h" 80 #include "nsIContent.h" 81 #include "nsIContentInlines.h" // for nsINode::IsInDesignMode() 82 #include "nsIEditActionListener.h" 83 #include "nsIFrame.h" 84 #include "nsIPrincipal.h" 85 #include "nsISelectionController.h" 86 #include "nsIURI.h" 87 #include "nsIWidget.h" 88 #include "nsNetUtil.h" 89 #include "nsPresContext.h" 90 #include "nsPrintfCString.h" 91 #include "nsPIDOMWindow.h" 92 #include "nsStyledElement.h" 93 #include "nsUnicharUtils.h" 94 95 namespace mozilla { 96 97 using namespace dom; 98 using namespace widget; 99 100 LazyLogModule gHTMLEditorFocusLog("HTMLEditorFocus"); 101 102 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; 103 using LeafNodeType = HTMLEditUtils::LeafNodeType; 104 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; 105 using WalkTreeOption = HTMLEditUtils::WalkTreeOption; 106 107 // Some utilities to handle overloading of "A" tag for link and named anchor. 108 static bool IsLinkTag(const nsAtom& aTagName) { 109 return &aTagName == nsGkAtoms::href; 110 } 111 112 static bool IsNamedAnchorTag(const nsAtom& aTagName) { 113 return &aTagName == nsGkAtoms::anchor; 114 } 115 116 // Helper struct for DoJoinNodes() and DoSplitNode(). 117 struct MOZ_STACK_CLASS SavedRange final { 118 RefPtr<Selection> mSelection; 119 nsCOMPtr<nsINode> mStartContainer; 120 nsCOMPtr<nsINode> mEndContainer; 121 uint32_t mStartOffset = 0; 122 uint32_t mEndOffset = 0; 123 }; 124 125 /****************************************************************************** 126 * HTMLEditor 127 *****************************************************************************/ 128 129 template Result<CreateContentResult, nsresult> 130 HTMLEditor::InsertNodeIntoProperAncestorWithTransaction( 131 nsIContent& aContentToInsert, const EditorDOMPoint& aPointToInsert, 132 SplitAtEdges aSplitAtEdges); 133 template Result<CreateElementResult, nsresult> 134 HTMLEditor::InsertNodeIntoProperAncestorWithTransaction( 135 Element& aContentToInsert, const EditorDOMPoint& aPointToInsert, 136 SplitAtEdges aSplitAtEdges); 137 template Result<CreateTextResult, nsresult> 138 HTMLEditor::InsertNodeIntoProperAncestorWithTransaction( 139 Text& aContentToInsert, const EditorDOMPoint& aPointToInsert, 140 SplitAtEdges aSplitAtEdges); 141 142 MOZ_RUNINIT HTMLEditor::InitializeInsertingElement 143 HTMLEditor::DoNothingForNewElement = 144 [](HTMLEditor&, Element&, const EditorDOMPoint&) { return NS_OK; }; 145 146 MOZ_RUNINIT HTMLEditor::InitializeInsertingElement 147 HTMLEditor::InsertNewBRElement = 148 [](HTMLEditor& aHTMLEditor, Element& aNewElement, 149 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY { 150 MOZ_ASSERT(!aNewElement.IsInComposedDoc()); 151 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 152 aHTMLEditor.InsertLineBreak(WithTransaction::No, 153 LineBreakType::BRElement, 154 EditorDOMPoint(&aNewElement, 0u)); 155 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 156 NS_WARNING( 157 "HTMLEditor::InsertLineBreak(WithTransaction::No, " 158 "LineBreakType::BRElement) failed"); 159 return insertBRElementResultOrError.unwrapErr(); 160 } 161 insertBRElementResultOrError.unwrap().IgnoreCaretPointSuggestion(); 162 return NS_OK; 163 }; 164 165 // static 166 Result<CreateElementResult, nsresult> 167 HTMLEditor::AppendNewElementToInsertingElement( 168 HTMLEditor& aHTMLEditor, const nsStaticAtom& aTagName, Element& aNewElement, 169 const InitializeInsertingElement& aInitializer) { 170 MOZ_ASSERT(!aNewElement.IsInComposedDoc()); 171 Result<CreateElementResult, nsresult> createNewElementResult = 172 aHTMLEditor.CreateAndInsertElement( 173 WithTransaction::No, const_cast<nsStaticAtom&>(aTagName), 174 EditorDOMPoint(&aNewElement, 0u), aInitializer); 175 NS_WARNING_ASSERTION( 176 createNewElementResult.isOk(), 177 "HTMLEditor::CreateAndInsertElement(WithTransaction::No) failed"); 178 return createNewElementResult; 179 } 180 181 // static 182 Result<CreateElementResult, nsresult> 183 HTMLEditor::AppendNewElementWithBRToInsertingElement( 184 HTMLEditor& aHTMLEditor, const nsStaticAtom& aTagName, 185 Element& aNewElement) { 186 MOZ_ASSERT(!aNewElement.IsInComposedDoc()); 187 Result<CreateElementResult, nsresult> createNewElementWithBRResult = 188 HTMLEditor::AppendNewElementToInsertingElement( 189 aHTMLEditor, aTagName, aNewElement, HTMLEditor::InsertNewBRElement); 190 NS_WARNING_ASSERTION( 191 createNewElementWithBRResult.isOk(), 192 "HTMLEditor::AppendNewElementToInsertingElement() failed"); 193 return createNewElementWithBRResult; 194 } 195 196 MOZ_RUNINIT HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributes = 197 [](HTMLEditor&, const Element&, const Element&, int32_t, const nsAtom&, 198 nsString&) { return true; }; 199 MOZ_RUNINIT HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributesExceptId = 200 [](HTMLEditor&, const Element&, const Element&, int32_t aNamespaceID, 201 const nsAtom& aAttrName, nsString&) { 202 return aNamespaceID != kNameSpaceID_None || &aAttrName != nsGkAtoms::id; 203 }; 204 MOZ_RUNINIT HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributesExceptDir = 205 [](HTMLEditor&, const Element&, const Element&, int32_t aNamespaceID, 206 const nsAtom& aAttrName, nsString&) { 207 return aNamespaceID != kNameSpaceID_None || &aAttrName != nsGkAtoms::dir; 208 }; 209 MOZ_RUNINIT HTMLEditor::AttributeFilter 210 HTMLEditor::CopyAllAttributesExceptIdAndDir = 211 [](HTMLEditor&, const Element&, const Element&, int32_t aNamespaceID, 212 const nsAtom& aAttrName, nsString&) { 213 return !( 214 aNamespaceID == kNameSpaceID_None && 215 (&aAttrName == nsGkAtoms::id || &aAttrName == nsGkAtoms::dir)); 216 }; 217 218 HTMLEditor::HTMLEditor(const Document& aDocument) 219 : EditorBase(EditorBase::EditorType::HTML), 220 mCRInParagraphCreatesParagraph(false), 221 mIsObjectResizingEnabled( 222 StaticPrefs::editor_resizing_enabled_by_default()), 223 mIsResizing(false), 224 mPreserveRatio(false), 225 mResizedObjectIsAnImage(false), 226 mIsAbsolutelyPositioningEnabled( 227 StaticPrefs::editor_positioning_enabled_by_default()), 228 mResizedObjectIsAbsolutelyPositioned(false), 229 mGrabberClicked(false), 230 mIsMoving(false), 231 mSnapToGridEnabled(false), 232 mIsInlineTableEditingEnabled( 233 StaticPrefs::editor_inline_table_editing_enabled_by_default()), 234 mIsCSSPrefChecked(StaticPrefs::editor_use_css()), 235 mOriginalX(0), 236 mOriginalY(0), 237 mResizedObjectX(0), 238 mResizedObjectY(0), 239 mResizedObjectWidth(0), 240 mResizedObjectHeight(0), 241 mResizedObjectMarginLeft(0), 242 mResizedObjectMarginTop(0), 243 mResizedObjectBorderLeft(0), 244 mResizedObjectBorderTop(0), 245 mXIncrementFactor(0), 246 mYIncrementFactor(0), 247 mWidthIncrementFactor(0), 248 mHeightIncrementFactor(0), 249 mInfoXIncrement(20), 250 mInfoYIncrement(20), 251 mPositionedObjectX(0), 252 mPositionedObjectY(0), 253 mPositionedObjectWidth(0), 254 mPositionedObjectHeight(0), 255 mPositionedObjectMarginLeft(0), 256 mPositionedObjectMarginTop(0), 257 mPositionedObjectBorderLeft(0), 258 mPositionedObjectBorderTop(0), 259 mGridSize(0), 260 mDefaultParagraphSeparator(ParagraphSeparator::div) {} 261 262 HTMLEditor::~HTMLEditor() { 263 glean::htmleditors::with_beforeinput_listeners 264 .EnumGet(static_cast<glean::htmleditors::WithBeforeinputListenersLabel>( 265 MayHaveBeforeInputEventListenersForTelemetry() ? 1 : 0)) 266 .Add(); 267 glean::htmleditors::overridden_by_beforeinput_listeners 268 .EnumGet(static_cast< 269 glean::htmleditors::OverriddenByBeforeinputListenersLabel>( 270 mHasBeforeInputBeenCanceled ? 1 : 0)) 271 .Add(); 272 glean::htmleditors::with_mutation_observers_without_beforeinput_listeners 273 .EnumGet(static_cast< 274 glean::htmleditors:: 275 WithMutationObserversWithoutBeforeinputListenersLabel>( 276 !MayHaveBeforeInputEventListenersForTelemetry() && 277 MutationObserverHasObservedNodeForTelemetry() 278 ? 1 279 : 0)) 280 .Add(); 281 282 mPendingStylesToApplyToNewContent = nullptr; 283 284 if (mDisabledLinkHandling) { 285 if (Document* doc = GetDocument()) { 286 doc->SetLinkHandlingEnabled(mOldLinkHandlingEnabled); 287 } 288 } 289 290 RemoveEventListeners(); 291 292 HideAnonymousEditingUIs(); 293 } 294 295 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLEditor) 296 297 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLEditor, EditorBase) 298 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStylesToApplyToNewContent) 299 NS_IMPL_CYCLE_COLLECTION_UNLINK(mComposerCommandsUpdater) 300 NS_IMPL_CYCLE_COLLECTION_UNLINK(mChangedRangeForTopLevelEditSubAction) 301 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPaddingBRElementForEmptyEditor) 302 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLastCollapsibleWhiteSpaceAppendedTextNode) 303 tmp->HideAnonymousEditingUIs(); 304 NS_IMPL_CYCLE_COLLECTION_UNLINK_END 305 306 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLEditor, EditorBase) 307 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStylesToApplyToNewContent) 308 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mComposerCommandsUpdater) 309 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChangedRangeForTopLevelEditSubAction) 310 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPaddingBRElementForEmptyEditor) 311 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLastCollapsibleWhiteSpaceAppendedTextNode) 312 313 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopLeftHandle) 314 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopHandle) 315 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopRightHandle) 316 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLeftHandle) 317 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRightHandle) 318 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomLeftHandle) 319 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomHandle) 320 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBottomRightHandle) 321 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActivatedHandle) 322 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingShadow) 323 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizingInfo) 324 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResizedObject) 325 326 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAbsolutelyPositionedObject) 327 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGrabber) 328 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPositioningShadow) 329 330 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineEditedCell) 331 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnBeforeButton) 332 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveColumnButton) 333 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddColumnAfterButton) 334 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowBeforeButton) 335 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRemoveRowButton) 336 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAddRowAfterButton) 337 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END 338 339 NS_IMPL_ADDREF_INHERITED(HTMLEditor, EditorBase) 340 NS_IMPL_RELEASE_INHERITED(HTMLEditor, EditorBase) 341 342 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLEditor) 343 NS_INTERFACE_MAP_ENTRY(nsIHTMLEditor) 344 NS_INTERFACE_MAP_ENTRY(nsIHTMLObjectResizer) 345 NS_INTERFACE_MAP_ENTRY(nsIHTMLAbsPosEditor) 346 NS_INTERFACE_MAP_ENTRY(nsIHTMLInlineTableEditor) 347 NS_INTERFACE_MAP_ENTRY(nsITableEditor) 348 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) 349 NS_INTERFACE_MAP_ENTRY(nsIEditorMailSupport) 350 NS_INTERFACE_MAP_END_INHERITING(EditorBase) 351 352 nsresult HTMLEditor::Init(Document& aDocument, 353 ComposerCommandsUpdater& aComposerCommandsUpdater, 354 uint32_t aFlags) { 355 MOZ_ASSERT(!mInitSucceeded, 356 "HTMLEditor::Init() called again without calling PreDestroy()?"); 357 358 MOZ_DIAGNOSTIC_ASSERT(!mComposerCommandsUpdater || 359 mComposerCommandsUpdater == &aComposerCommandsUpdater); 360 mComposerCommandsUpdater = &aComposerCommandsUpdater; 361 362 RefPtr<PresShell> presShell = aDocument.GetPresShell(); 363 if (NS_WARN_IF(!presShell)) { 364 return NS_ERROR_FAILURE; 365 } 366 nsresult rv = InitInternal(aDocument, nullptr, *presShell, aFlags); 367 if (NS_FAILED(rv)) { 368 NS_WARNING("EditorBase::InitInternal() failed"); 369 return rv; 370 } 371 372 // Init mutation observer 373 aDocument.AddMutationObserverUnlessExists(this); 374 375 if (!mRootElement) { 376 UpdateRootElement(); 377 } 378 379 // disable Composer-only features 380 if (IsMailEditor()) { 381 DebugOnly<nsresult> rvIgnored = SetAbsolutePositioningEnabled(false); 382 NS_WARNING_ASSERTION( 383 NS_SUCCEEDED(rvIgnored), 384 "HTMLEditor::SetAbsolutePositioningEnabled(false) failed, but ignored"); 385 rvIgnored = SetSnapToGridEnabled(false); 386 NS_WARNING_ASSERTION( 387 NS_SUCCEEDED(rvIgnored), 388 "HTMLEditor::SetSnapToGridEnabled(false) failed, but ignored"); 389 } 390 391 // disable links 392 Document* document = GetDocument(); 393 if (NS_WARN_IF(!document)) { 394 return NS_ERROR_FAILURE; 395 } 396 if (!IsPlaintextMailComposer() && !IsInteractionAllowed()) { 397 mDisabledLinkHandling = true; 398 mOldLinkHandlingEnabled = document->LinkHandlingEnabled(); 399 document->SetLinkHandlingEnabled(false); 400 } 401 402 // init the type-in state 403 mPendingStylesToApplyToNewContent = new PendingStyles(); 404 405 AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing); 406 if (NS_WARN_IF(!editActionData.CanHandle())) { 407 return NS_ERROR_FAILURE; 408 } 409 410 rv = InitEditorContentAndSelection(); 411 if (NS_FAILED(rv)) { 412 NS_WARNING("HTMLEditor::InitEditorContentAndSelection() failed"); 413 // XXX Sholdn't we expose `NS_ERROR_EDITOR_DESTROYED` even though this 414 // is a public method? 415 return EditorBase::ToGenericNSResult(rv); 416 } 417 418 // Throw away the old transaction manager if this is not the first time that 419 // we're initializing the editor. 420 ClearUndoRedo(); 421 EnableUndoRedo(); // FYI: Creating mTransactionManager in this call 422 423 if (mTransactionManager) { 424 mTransactionManager->Attach(*this); 425 } 426 427 MOZ_ASSERT(!mInitSucceeded, "HTMLEditor::Init() shouldn't be nested"); 428 mInitSucceeded = true; 429 editActionData.OnEditorInitialized(); 430 return NS_OK; 431 } 432 433 nsresult HTMLEditor::PostCreate() { 434 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 435 if (NS_WARN_IF(!editActionData.CanHandle())) { 436 return NS_ERROR_NOT_INITIALIZED; 437 } 438 439 nsresult rv = PostCreateInternal(); 440 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 441 "EditorBase::PostCreatInternal() failed"); 442 return rv; 443 } 444 445 void HTMLEditor::PreDestroy() { 446 if (mDidPreDestroy) { 447 return; 448 } 449 450 mInitSucceeded = false; 451 452 // FYI: Cannot create AutoEditActionDataSetter here. However, it does not 453 // necessary for the methods called by the following code. 454 455 RefPtr<Document> document = GetDocument(); 456 if (document) { 457 document->RemoveMutationObserver(this); 458 } 459 460 // Clean up after our anonymous content -- we don't want these nodes to 461 // stay around (which they would, since the frames have an owning reference). 462 PresShell* presShell = GetPresShell(); 463 if (presShell && presShell->IsDestroying()) { 464 // Just destroying PresShell now. 465 // We have to keep UI elements of anonymous content until PresShell 466 // is destroyed. 467 RefPtr<HTMLEditor> self = this; 468 nsContentUtils::AddScriptRunner(NS_NewRunnableFunction( 469 "HTMLEditor::PreDestroy", [self]() MOZ_CAN_RUN_SCRIPT_BOUNDARY_LAMBDA { 470 self->HideAnonymousEditingUIs(); 471 })); 472 } else { 473 // PresShell is alive or already gone. 474 HideAnonymousEditingUIs(); 475 } 476 477 mPaddingBRElementForEmptyEditor = nullptr; 478 479 PreDestroyInternal(); 480 } 481 482 bool HTMLEditor::IsStyleEditable() const { 483 if (IsInDesignMode()) { 484 return true; 485 } 486 if (IsPlaintextMailComposer()) { 487 return false; 488 } 489 const Element* const editingHost = ComputeEditingHost(LimitInBodyElement::No); 490 // Let's return true if there is no focused editing host for the backward 491 // compatibility. 492 return !editingHost || !editingHost->IsContentEditablePlainTextOnly(); 493 } 494 495 NS_IMETHODIMP HTMLEditor::GetDocumentCharacterSet(nsACString& aCharacterSet) { 496 nsresult rv = GetDocumentCharsetInternal(aCharacterSet); 497 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 498 "HTMLEditor::GetDocumentCharsetInternal() failed"); 499 return rv; 500 } 501 502 NS_IMETHODIMP HTMLEditor::SetDocumentCharacterSet( 503 const nsACString& aCharacterSet) { 504 AutoEditActionDataSetter editActionData(*this, EditAction::eSetCharacterSet); 505 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 506 if (NS_FAILED(rv)) { 507 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 508 "CanHandleAndMaybeDispatchBeforeInputEvent() failed"); 509 return EditorBase::ToGenericNSResult(rv); 510 } 511 512 RefPtr<Document> document = GetDocument(); 513 if (NS_WARN_IF(!document)) { 514 return EditorBase::ToGenericNSResult(NS_ERROR_NOT_INITIALIZED); 515 } 516 // This method is scriptable, so add-ons could pass in something other 517 // than a canonical name. 518 const Encoding* encoding = Encoding::ForLabelNoReplacement(aCharacterSet); 519 if (!encoding) { 520 NS_WARNING("Encoding::ForLabelNoReplacement() failed"); 521 return EditorBase::ToGenericNSResult(NS_ERROR_INVALID_ARG); 522 } 523 document->SetDocumentCharacterSet(WrapNotNull(encoding)); 524 525 // Update META charset element. 526 if (UpdateMetaCharsetWithTransaction(*document, aCharacterSet)) { 527 return NS_OK; 528 } 529 530 // Set attributes to the created element 531 if (aCharacterSet.IsEmpty()) { 532 return NS_OK; 533 } 534 535 RefPtr<nsContentList> headElementList = 536 document->GetElementsByTagName(u"head"_ns); 537 if (NS_WARN_IF(!headElementList)) { 538 return NS_OK; 539 } 540 541 nsCOMPtr<nsIContent> primaryHeadElement = headElementList->Item(0); 542 if (NS_WARN_IF(!primaryHeadElement)) { 543 return NS_OK; 544 } 545 546 // Create a new meta charset tag 547 Result<CreateElementResult, nsresult> createNewMetaElementResult = 548 CreateAndInsertElement( 549 WithTransaction::Yes, *nsGkAtoms::meta, 550 EditorDOMPoint(primaryHeadElement, 0), 551 [&aCharacterSet](HTMLEditor&, Element& aMetaElement, 552 const EditorDOMPoint&) { 553 MOZ_ASSERT(!aMetaElement.IsInComposedDoc()); 554 DebugOnly<nsresult> rvIgnored = 555 aMetaElement.SetAttr(kNameSpaceID_None, nsGkAtoms::httpEquiv, 556 u"Content-Type"_ns, false); 557 NS_WARNING_ASSERTION( 558 NS_SUCCEEDED(rvIgnored), 559 "Element::SetAttr(nsGkAtoms::httpEquiv, \"Content-Type\", " 560 "false) failed, but ignored"); 561 rvIgnored = 562 aMetaElement.SetAttr(kNameSpaceID_None, nsGkAtoms::content, 563 u"text/html;charset="_ns + 564 NS_ConvertASCIItoUTF16(aCharacterSet), 565 false); 566 NS_WARNING_ASSERTION( 567 NS_SUCCEEDED(rvIgnored), 568 nsPrintfCString( 569 "Element::SetAttr(nsGkAtoms::content, " 570 "\"text/html;charset=%s\", false) failed, but ignored", 571 nsPromiseFlatCString(aCharacterSet).get()) 572 .get()); 573 return NS_OK; 574 }); 575 NS_WARNING_ASSERTION(createNewMetaElementResult.isOk(), 576 "HTMLEditor::CreateAndInsertElement(WithTransaction::" 577 "Yes, nsGkAtoms::meta) failed, but ignored"); 578 // Probably, we don't need to update selection in this case since we should 579 // not put selection into <head> element. 580 createNewMetaElementResult.inspect().IgnoreCaretPointSuggestion(); 581 return NS_OK; 582 } 583 584 bool HTMLEditor::UpdateMetaCharsetWithTransaction( 585 Document& aDocument, const nsACString& aCharacterSet) { 586 // get a list of META tags 587 RefPtr<nsContentList> metaElementList = 588 aDocument.GetElementsByTagName(u"meta"_ns); 589 if (NS_WARN_IF(!metaElementList)) { 590 return false; 591 } 592 593 for (uint32_t i = 0; i < metaElementList->Length(true); ++i) { 594 RefPtr<Element> metaElement = metaElementList->Item(i)->AsElement(); 595 MOZ_ASSERT(metaElement); 596 597 nsAutoString currentValue; 598 metaElement->GetAttr(nsGkAtoms::httpEquiv, currentValue); 599 600 if (!FindInReadable(u"content-type"_ns, currentValue, 601 nsCaseInsensitiveStringComparator)) { 602 continue; 603 } 604 605 metaElement->GetAttr(nsGkAtoms::content, currentValue); 606 607 constexpr auto charsetEquals = u"charset="_ns; 608 nsAString::const_iterator originalStart, start, end; 609 originalStart = currentValue.BeginReading(start); 610 currentValue.EndReading(end); 611 if (!FindInReadable(charsetEquals, start, end, 612 nsCaseInsensitiveStringComparator)) { 613 continue; 614 } 615 616 // set attribute to <original prefix> charset=text/html 617 nsresult rv = SetAttributeWithTransaction( 618 *metaElement, *nsGkAtoms::content, 619 Substring(originalStart, start) + charsetEquals + 620 NS_ConvertASCIItoUTF16(aCharacterSet)); 621 NS_WARNING_ASSERTION( 622 NS_SUCCEEDED(rv), 623 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::content) failed"); 624 return NS_SUCCEEDED(rv); 625 } 626 return false; 627 } 628 629 NS_IMETHODIMP HTMLEditor::NotifySelectionChanged(Document* aDocument, 630 Selection* aSelection, 631 int16_t aReason, 632 int32_t aAmount) { 633 if (NS_WARN_IF(!aDocument) || NS_WARN_IF(!aSelection)) { 634 return NS_ERROR_INVALID_ARG; 635 } 636 637 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 638 if (NS_WARN_IF(!editActionData.CanHandle())) { 639 return NS_ERROR_NOT_INITIALIZED; 640 } 641 642 if (mPendingStylesToApplyToNewContent) { 643 RefPtr<PendingStyles> pendingStyles = mPendingStylesToApplyToNewContent; 644 pendingStyles->OnSelectionChange(*this, aReason); 645 646 // We used a class which derived from nsISelectionListener to call 647 // HTMLEditor::RefreshEditingUI(). The lifetime of the class was 648 // exactly same as mPendingStylesToApplyToNewContent. So, call it only when 649 // mPendingStylesToApplyToNewContent is not nullptr. 650 if ((aReason & (nsISelectionListener::MOUSEDOWN_REASON | 651 nsISelectionListener::KEYPRESS_REASON | 652 nsISelectionListener::SELECTALL_REASON)) && 653 aSelection) { 654 // the selection changed and we need to check if we have to 655 // hide and/or redisplay resizing handles 656 // FYI: This is an XPCOM method. So, the caller, Selection, guarantees 657 // the lifetime of this instance. So, don't need to grab this with 658 // local variable. 659 DebugOnly<nsresult> rv = RefreshEditingUI(); 660 NS_WARNING_ASSERTION( 661 NS_SUCCEEDED(rv), 662 "HTMLEditor::RefreshEditingUI() failed, but ignored"); 663 } 664 } 665 666 if (mComposerCommandsUpdater) { 667 RefPtr<ComposerCommandsUpdater> updater = mComposerCommandsUpdater; 668 updater->OnSelectionChange(); 669 } 670 671 nsresult rv = EditorBase::NotifySelectionChanged(aDocument, aSelection, 672 aReason, aAmount); 673 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 674 "EditorBase::NotifySelectionChanged() failed"); 675 return rv; 676 } 677 678 void HTMLEditor::UpdateRootElement() { 679 // Use the HTML documents body element as the editor root if we didn't 680 // get a root element during initialization. 681 682 mRootElement = GetBodyElement(); 683 if (!mRootElement) { 684 RefPtr<Document> doc = GetDocument(); 685 if (doc) { 686 // If there is no HTML body element, 687 // we should use the document root element instead. 688 mRootElement = doc->GetDocumentElement(); 689 } 690 // else leave it null, for lack of anything better. 691 } 692 } 693 694 nsresult HTMLEditor::FocusedElementOrDocumentBecomesEditable( 695 Document& aDocument, Element* aElement) { 696 MOZ_LOG(gHTMLEditorFocusLog, LogLevel::Info, 697 ("%s(aDocument=%p, aElement=%s): mHasFocus=%s, mIsInDesignMode=%s, " 698 "aDocument.IsInDesignMode()=%s, aElement->IsInDesignMode()=%s", 699 __FUNCTION__, &aDocument, ToString(RefPtr{aElement}).c_str(), 700 mHasFocus ? "true" : "false", mIsInDesignMode ? "true" : "false", 701 aDocument.IsInDesignMode() ? "true" : "false", 702 aElement ? (aElement->IsInDesignMode() ? "true" : "false") : "N/A")); 703 704 const bool enteringInDesignMode = 705 (aDocument.IsInDesignMode() && (!aElement || aElement->IsInDesignMode())); 706 707 // If we should've already handled focus event, selection limiter should not 708 // be set. However, IMEStateManager is not notified the pseudo focus change 709 // in this case. Therefore, we need to notify IMEStateManager of this. 710 if (mHasFocus) { 711 if (enteringInDesignMode) { 712 mIsInDesignMode = true; 713 return NS_OK; 714 } 715 // Although editor is already initialized due to re-used, ISM may not 716 // create IME content observer yet. So we have to create it. 717 Result<IMEState, nsresult> newStateOrError = GetPreferredIMEState(); 718 if (MOZ_UNLIKELY(newStateOrError.isErr())) { 719 NS_WARNING("HTMLEditor::GetPreferredIMEState() failed"); 720 mIsInDesignMode = false; 721 return NS_OK; 722 } 723 const RefPtr<Element> focusedElement = GetFocusedElement(); 724 if (focusedElement) { 725 MOZ_ASSERT(focusedElement == aElement); 726 TextControlElement* const textControlElement = 727 TextControlElement::FromNode(focusedElement); 728 if (textControlElement && 729 textControlElement->IsSingleLineTextControlOrTextArea()) { 730 // Let's emulate blur first. 731 DebugOnly<nsresult> rv = FinalizeSelection(); 732 NS_WARNING_ASSERTION( 733 NS_SUCCEEDED(rv), 734 "HTMLEditor::FinalizeSelection() failed, but ignored"); 735 mHasFocus = false; 736 mIsInDesignMode = false; 737 } 738 IMEStateManager::UpdateIMEState(newStateOrError.unwrap(), focusedElement, 739 *this); 740 // XXX Do we need to notify focused TextEditor of focus? In theory, 741 // the TextEditor should get focus event in this case. 742 } 743 mIsInDesignMode = false; 744 return NS_OK; 745 } 746 747 // If we should be in the design mode, we want to handle focus event fired 748 // on the document node. Therefore, we should emulate it here. 749 if (enteringInDesignMode) { 750 MOZ_ASSERT(&aDocument == GetDocument()); 751 nsresult rv = OnFocus(aDocument); 752 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnFocus() failed"); 753 return rv; 754 } 755 756 if (NS_WARN_IF(!aElement)) { 757 return NS_ERROR_INVALID_ARG; 758 } 759 760 // Otherwise, we should've already handled focus event on the element, 761 // therefore, we need to emulate it here. 762 MOZ_ASSERT(nsFocusManager::GetFocusedElementStatic() == aElement); 763 nsresult rv = OnFocus(*aElement); 764 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::OnFocus() failed"); 765 766 // Note that we don't need to call 767 // IMEStateManager::MaybeOnEditableStateDisabled here because 768 // EditorBase::OnFocus must have already been called IMEStateManager::OnFocus 769 // if succeeded. And perhaps, it's okay that IME is not enabled when 770 // HTMLEditor fails to start handling since nobody can handle composition 771 // events anyway... 772 773 return rv; 774 } 775 776 nsresult HTMLEditor::OnFocus(const nsINode& aOriginalEventTargetNode) { 777 MOZ_LOG(gHTMLEditorFocusLog, LogLevel::Info, 778 ("%s(aOriginalEventTarget=%s): mIsInDesignMode=%s, " 779 "aOriginalEventTargetNode.IsInDesignMode()=%s", 780 __FUNCTION__, ToString(RefPtr{&aOriginalEventTargetNode}).c_str(), 781 mIsInDesignMode ? "true" : "false", 782 aOriginalEventTargetNode.IsInDesignMode() ? "true" : "false")); 783 784 // Before doing anything, we should check whether the original target is still 785 // valid focus event target because it may have already lost focus. 786 if (!CanKeepHandlingFocusEvent(aOriginalEventTargetNode)) { 787 return NS_OK; 788 } 789 790 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 791 if (NS_WARN_IF(!editActionData.CanHandle())) { 792 return NS_ERROR_FAILURE; 793 } 794 795 nsresult rv = EditorBase::OnFocus(aOriginalEventTargetNode); 796 if (NS_FAILED(rv)) { 797 NS_WARNING("EditorBase::OnFocus() failed"); 798 return rv; 799 } 800 mHasFocus = true; 801 mIsInDesignMode = aOriginalEventTargetNode.IsInDesignMode(); 802 return NS_OK; 803 } 804 805 nsresult HTMLEditor::FocusedElementOrDocumentBecomesNotEditable( 806 HTMLEditor* aHTMLEditor, Document& aDocument, Element* aElement) { 807 MOZ_LOG( 808 gHTMLEditorFocusLog, LogLevel::Info, 809 ("%s(aHTMLEditor=%p, aDocument=%p, aElement=%s): " 810 "aHTMLEditor->HasFocus()=%s, aHTMLEditor->IsInDesignMode()=%s, " 811 "aDocument.IsInDesignMode()=%s, aElement->IsInDesignMode()=%s, " 812 "nsFocusManager::GetFocusedElementStatic()=%s", 813 __FUNCTION__, aHTMLEditor, &aDocument, 814 ToString(RefPtr{aElement}).c_str(), 815 aHTMLEditor ? (aHTMLEditor->HasFocus() ? "true" : "false") : "N/A", 816 aHTMLEditor ? (aHTMLEditor->IsInDesignMode() ? "true" : "false") : "N/A", 817 aDocument.IsInDesignMode() ? "true" : "false", 818 aElement ? (aElement->IsInDesignMode() ? "true" : "false") : "N/A", 819 ToString(RefPtr{nsFocusManager::GetFocusedElementStatic()}).c_str())); 820 821 nsresult rv = [&]() MOZ_CAN_RUN_SCRIPT { 822 // If HTMLEditor has not been created yet, we just need to adjust 823 // IMEStateManager. So, don't return error. 824 if (!aHTMLEditor || !aHTMLEditor->HasFocus()) { 825 return NS_OK; 826 } 827 828 // Let's emulate blur first. 829 nsresult rv = aHTMLEditor->FinalizeSelection(); 830 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 831 "HTMLEditor::FinalizeSelection() failed"); 832 aHTMLEditor->mHasFocus = false; 833 aHTMLEditor->mIsInDesignMode = false; 834 835 RefPtr<Element> focusedElement = nsFocusManager::GetFocusedElementStatic(); 836 if (focusedElement && !focusedElement->IsInComposedDoc()) { 837 // nsFocusManager may keep storing the focused element even after 838 // disconnected from the tree, but HTMLEditor cannot work with editable 839 // nodes not in a composed document. Therefore, we should treat no 840 // focused element in the case. 841 focusedElement = nullptr; 842 } 843 TextControlElement* const focusedTextControlElement = 844 TextControlElement::FromNodeOrNull(focusedElement); 845 if ((focusedElement && focusedElement->IsEditable() && 846 focusedElement->OwnerDoc() == aHTMLEditor->GetDocument() && 847 (!focusedTextControlElement || 848 !focusedTextControlElement->IsSingleLineTextControlOrTextArea())) || 849 (!focusedElement && aDocument.IsInDesignMode())) { 850 // Then, the focused element is still editable, let's emulate focus to 851 // make the editor be ready to handle input. 852 DebugOnly<nsresult> rvIgnored = aHTMLEditor->OnFocus( 853 focusedElement ? static_cast<nsINode&>(*focusedElement) 854 : static_cast<nsINode&>(aDocument)); 855 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 856 "HTMLEditor::OnFocus() failed, but ignored"); 857 } else if (focusedTextControlElement && 858 focusedTextControlElement->IsSingleLineTextControlOrTextArea()) { 859 if (const RefPtr<TextEditor> textEditor = 860 focusedTextControlElement->GetExtantTextEditor()) { 861 textEditor->OnFocus(*focusedElement); 862 } 863 } 864 return rv; 865 }(); 866 867 // If the element becomes not editable without focus change, IMEStateManager 868 // does not have a chance to disable IME. Therefore, (even if we fail to 869 // handle the emulated blur/focus above,) we should notify IMEStateManager of 870 // the editing state change. Note that if the window of the HTMLEditor has 871 // already lost focus, we don't need to do that and we should not touch the 872 // other windows. 873 if (const RefPtr<nsPresContext> presContext = aDocument.GetPresContext()) { 874 const RefPtr<Element> focusedElementInDocument = 875 Element::FromNodeOrNull(aDocument.GetUnretargetedFocusedContent()); 876 MOZ_ASSERT_IF(focusedElementInDocument, 877 focusedElementInDocument->GetPresContext( 878 Element::PresContextFor::eForComposedDoc)); 879 IMEStateManager::MaybeOnEditableStateDisabled(*presContext, 880 focusedElementInDocument); 881 } 882 883 return rv; 884 } 885 886 nsresult HTMLEditor::OnBlur(const EventTarget* aEventTarget) { 887 MOZ_LOG(gHTMLEditorFocusLog, LogLevel::Info, 888 ("%s(aEventTarget=%s): mHasFocus=%s, mIsInDesignMode=%s, " 889 "aEventTarget->IsInDesignMode()=%s", 890 __FUNCTION__, ToString(RefPtr{aEventTarget}).c_str(), 891 mHasFocus ? "true" : "false", mIsInDesignMode ? "true" : "false", 892 nsINode::FromEventTargetOrNull(aEventTarget) 893 ? (nsINode::FromEventTarget(aEventTarget)->IsInDesignMode() 894 ? "true" 895 : "false") 896 : "N/A")); 897 const Element* eventTargetAsElement = 898 Element::FromEventTargetOrNull(aEventTarget); 899 900 // If another element already has focus, we should not maintain the selection 901 // because we may not have the rights doing it. 902 const Element* focusedElement = nsFocusManager::GetFocusedElementStatic(); 903 if (focusedElement && focusedElement != eventTargetAsElement) { 904 // XXX If we had focus and new focused element is a text control, we may 905 // need to notify focus of its TextEditor... 906 mIsInDesignMode = false; 907 mHasFocus = false; 908 return NS_OK; 909 } 910 911 // If we're in the designMode and blur occurs, the target must be the document 912 // node. If a blur event is fired and the target is an element, it must be 913 // delayed blur event at initializing the `HTMLEditor`. 914 if (mIsInDesignMode && eventTargetAsElement && 915 eventTargetAsElement->IsInComposedDoc()) { 916 return NS_OK; 917 } 918 919 nsresult rv = FinalizeSelection(); 920 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 921 "EditorBase::FinalizeSelection() failed"); 922 mIsInDesignMode = false; 923 mHasFocus = false; 924 return rv; 925 } 926 927 Element* HTMLEditor::FindSelectionRoot(const nsINode& aNode) const { 928 MOZ_ASSERT(aNode.IsDocument() || aNode.IsContent(), 929 "aNode must be content or document node"); 930 931 if (NS_WARN_IF(!aNode.IsInComposedDoc())) { 932 return nullptr; 933 } 934 935 if (aNode.IsInDesignMode()) { 936 return GetDocument()->GetRootElement(); 937 } 938 939 nsIContent* content = const_cast<nsIContent*>(aNode.AsContent()); 940 if (!content->HasFlag(NODE_IS_EDITABLE)) { 941 // If the content is in read-write state but is not editable itself, 942 // return it as the selection root. 943 if (content->IsElement() && 944 content->AsElement()->State().HasState(ElementState::READWRITE)) { 945 return content->AsElement(); 946 } 947 return nullptr; 948 } 949 950 // For non-readonly editors we want to find the root of the editable subtree 951 // containing aContent. 952 return content->GetEditingHost(); 953 } 954 955 bool HTMLEditor::EntireDocumentIsEditable() const { 956 Document* document = GetDocument(); 957 return document && document->GetDocumentElement() && 958 (document->GetDocumentElement()->IsEditable() || 959 (document->GetBody() && document->GetBody()->IsEditable())); 960 } 961 962 void HTMLEditor::CreateEventListeners() { 963 // Don't create the handler twice 964 if (!mEventListener) { 965 mEventListener = new HTMLEditorEventListener(); 966 } 967 } 968 969 nsresult HTMLEditor::InstallEventListeners() { 970 // FIXME InstallEventListeners() should not be called if we failed to set 971 // document or create an event listener. So, these checks should be 972 // MOZ_DIAGNOSTIC_ASSERT instead. 973 MOZ_ASSERT(GetDocument()); 974 if (MOZ_UNLIKELY(!GetDocument()) || NS_WARN_IF(!mEventListener)) { 975 return NS_ERROR_NOT_INITIALIZED; 976 } 977 978 // NOTE: HTMLEditor doesn't need to initialize mEventTarget here because 979 // the target must be document node and it must be referenced as weak pointer. 980 981 HTMLEditorEventListener* listener = 982 reinterpret_cast<HTMLEditorEventListener*>(mEventListener.get()); 983 nsresult rv = listener->Connect(this); 984 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 985 "HTMLEditorEventListener::Connect() failed"); 986 return rv; 987 } 988 989 void HTMLEditor::Detach( 990 const ComposerCommandsUpdater& aComposerCommandsUpdater) { 991 MOZ_DIAGNOSTIC_ASSERT_IF( 992 mComposerCommandsUpdater, 993 &aComposerCommandsUpdater == mComposerCommandsUpdater); 994 if (mComposerCommandsUpdater == &aComposerCommandsUpdater) { 995 mComposerCommandsUpdater = nullptr; 996 if (mTransactionManager) { 997 mTransactionManager->Detach(*this); 998 } 999 } 1000 } 1001 1002 NS_IMETHODIMP HTMLEditor::BeginningOfDocument() { 1003 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 1004 if (NS_WARN_IF(!editActionData.CanHandle())) { 1005 return NS_ERROR_NOT_INITIALIZED; 1006 } 1007 1008 nsresult rv = MaybeCollapseSelectionAtFirstEditableNode(false); 1009 NS_WARNING_ASSERTION( 1010 NS_SUCCEEDED(rv), 1011 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) failed"); 1012 return rv; 1013 } 1014 1015 NS_IMETHODIMP HTMLEditor::EndOfDocument() { 1016 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 1017 if (NS_WARN_IF(!editActionData.CanHandle())) { 1018 return NS_ERROR_NOT_INITIALIZED; 1019 } 1020 nsresult rv = CollapseSelectionToEndOfLastLeafNodeOfDocument(); 1021 NS_WARNING_ASSERTION( 1022 NS_SUCCEEDED(rv), 1023 "HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() failed"); 1024 // This is low level API for embedders and chrome script so that we can return 1025 // raw error code here. 1026 return rv; 1027 } 1028 1029 nsresult HTMLEditor::CollapseSelectionToEndOfLastLeafNodeOfDocument() const { 1030 MOZ_ASSERT(IsEditActionDataAvailable()); 1031 1032 // We should do nothing with the result of GetRoot() if only a part of the 1033 // document is editable. 1034 if (!EntireDocumentIsEditable()) { 1035 return NS_OK; 1036 } 1037 1038 RefPtr<Element> bodyOrDocumentElement = GetRoot(); 1039 if (NS_WARN_IF(!bodyOrDocumentElement)) { 1040 return NS_ERROR_NULL_POINTER; 1041 } 1042 1043 auto pointToPutCaret = [&]() -> EditorRawDOMPoint { 1044 nsCOMPtr<nsIContent> lastLeafContent = HTMLEditUtils::GetLastLeafContent( 1045 *bodyOrDocumentElement, {LeafNodeType::OnlyLeafNode}); 1046 if (!lastLeafContent) { 1047 return EditorRawDOMPoint::AtEndOf(*bodyOrDocumentElement); 1048 } 1049 // TODO: We should put caret into text node if it's visible. 1050 return lastLeafContent->IsText() || 1051 HTMLEditUtils::IsContainerNode(*lastLeafContent) 1052 ? EditorRawDOMPoint::AtEndOf(*lastLeafContent) 1053 : EditorRawDOMPoint(lastLeafContent); 1054 }(); 1055 nsresult rv = CollapseSelectionTo(pointToPutCaret); 1056 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1057 "EditorBase::CollapseSelectionTo() failed"); 1058 return rv; 1059 } 1060 1061 void HTMLEditor::InitializeSelectionAncestorLimit( 1062 Element& aAncestorLimit) const { 1063 MOZ_ASSERT(IsEditActionDataAvailable()); 1064 1065 // Hack for initializing selection. 1066 // HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode() will try to 1067 // collapse selection at first editable text node or inline element which 1068 // cannot have text nodes as its children. However, selection has already 1069 // set into the new editing host by user, we should not change it. For 1070 // solving this issue, we should do nothing if selection range is in active 1071 // editing host except it's not collapsed at start of the editing host since 1072 // aSelection.SetAncestorLimiter(aAncestorLimit) will collapse selection 1073 // at start of the new limiter if focus node of aSelection is outside of the 1074 // editing host. However, we need to check here if selection is already 1075 // collapsed at start of the editing host because it's possible JS to do it. 1076 // In such case, we should not modify selection with calling 1077 // MaybeCollapseSelectionAtFirstEditableNode(). 1078 1079 // Basically, we should try to collapse selection at first editable node 1080 // in HTMLEditor. 1081 bool tryToCollapseSelectionAtFirstEditableNode = true; 1082 if (SelectionRef().RangeCount() == 1 && SelectionRef().IsCollapsed()) { 1083 Element* editingHost = ComputeEditingHost(); 1084 const nsRange* range = SelectionRef().GetRangeAt(0); 1085 if (range->GetStartContainer() == editingHost && !range->StartOffset()) { 1086 // JS or user operation has already collapsed selection at start of 1087 // the editing host. So, we don't need to try to change selection 1088 // in this case. 1089 tryToCollapseSelectionAtFirstEditableNode = false; 1090 } 1091 } 1092 1093 EditorBase::InitializeSelectionAncestorLimit(aAncestorLimit); 1094 1095 // XXX Do we need to check if we still need to change selection? E.g., 1096 // we could have already lost focus while we're changing the ancestor 1097 // limiter because it may causes "selectionchange" event. 1098 if (tryToCollapseSelectionAtFirstEditableNode) { 1099 DebugOnly<nsresult> rvIgnored = 1100 MaybeCollapseSelectionAtFirstEditableNode(true); 1101 NS_WARNING_ASSERTION( 1102 NS_SUCCEEDED(rvIgnored), 1103 "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(true) failed, " 1104 "but ignored"); 1105 } 1106 1107 // If the target is a text control element, we won't handle user input 1108 // for the `TextEditor` in it. However, we need to be open for `execCommand`. 1109 // Therefore, we shouldn't set ancestor limit in this case. 1110 // Note that we should do this once setting ancestor limiter for backward 1111 // compatiblity of select events, etc. (Selection should be collapsed into 1112 // the text control element.) 1113 if (aAncestorLimit.HasIndependentSelection()) { 1114 SelectionRef().SetAncestorLimiter(nullptr); 1115 } 1116 } 1117 1118 nsresult HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode( 1119 bool aIgnoreIfSelectionInEditingHost) const { 1120 MOZ_ASSERT(IsEditActionDataAvailable()); 1121 1122 RefPtr<Element> editingHost = ComputeEditingHost(LimitInBodyElement::No); 1123 if (NS_WARN_IF(!editingHost)) { 1124 return NS_OK; 1125 } 1126 1127 // If selection range is already in the editing host and the range is not 1128 // start of the editing host, we shouldn't reset selection. E.g., window 1129 // is activated when the editor had focus before inactivated. 1130 if (aIgnoreIfSelectionInEditingHost && SelectionRef().RangeCount() == 1) { 1131 const nsRange* range = SelectionRef().GetRangeAt(0); 1132 if (!range->Collapsed() || 1133 range->GetStartContainer() != editingHost.get() || 1134 range->StartOffset()) { 1135 return NS_OK; 1136 } 1137 } 1138 1139 for (nsIContent* leafContent = HTMLEditUtils::GetFirstLeafContent( 1140 *editingHost, 1141 {LeafNodeType::LeafNodeOrNonEditableNode, 1142 LeafNodeType::LeafNodeOrChildBlock}, 1143 BlockInlineCheck::UseComputedDisplayStyle, editingHost); 1144 leafContent;) { 1145 // If we meet a non-editable node first, we should move caret to start 1146 // of the container block or editing host. 1147 if (!EditorUtils::IsEditableContent(*leafContent, EditorType::HTML)) { 1148 MOZ_ASSERT(leafContent->GetParent()); 1149 MOZ_ASSERT(EditorUtils::IsEditableContent(*leafContent->GetParent(), 1150 EditorType::HTML)); 1151 if (const Element* editableBlockElementOrInlineEditingHost = 1152 HTMLEditUtils::GetAncestorElement( 1153 *leafContent, 1154 HTMLEditUtils::ClosestEditableBlockElementOrInlineEditingHost, 1155 BlockInlineCheck::UseComputedDisplayStyle)) { 1156 nsresult rv = CollapseSelectionTo( 1157 EditorDOMPoint(editableBlockElementOrInlineEditingHost, 0)); 1158 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1159 "EditorBase::CollapseSelectionTo() failed"); 1160 return rv; 1161 } 1162 NS_WARNING("Found leaf content did not have editable parent, why?"); 1163 return NS_ERROR_FAILURE; 1164 } 1165 1166 // When we meet an empty inline element, we should look for a next sibling. 1167 // For example, if current editor is: 1168 // <div contenteditable><span></span><b><br></b></div> 1169 // then, we should put caret at the <br> element. So, let's check if found 1170 // node is an empty inline container element. 1171 if (Element* leafElement = Element::FromNode(leafContent)) { 1172 if (HTMLEditUtils::IsInlineContent( 1173 *leafElement, BlockInlineCheck::UseComputedDisplayStyle) && 1174 !HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafElement) && 1175 HTMLEditUtils::CanNodeContain(*leafElement, 1176 *nsGkAtoms::textTagName)) { 1177 // Chromium collapses selection to start of the editing host when this 1178 // is the last leaf content. So, we don't need special handling here. 1179 leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 1180 *leafElement, 1181 {LeafNodeType::LeafNodeOrNonEditableNode, 1182 LeafNodeType::LeafNodeOrChildBlock}, 1183 BlockInlineCheck::UseComputedDisplayStyle, editingHost); 1184 continue; 1185 } 1186 } 1187 1188 if (Text* text = leafContent->GetAsText()) { 1189 // If there is editable and visible text node, move caret at first of 1190 // the visible character. 1191 const WSScanResult scanResultInTextNode = 1192 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1193 {WSRunScanner::Option::OnlyEditableNodes}, 1194 EditorRawDOMPoint(text, 0)); 1195 if ((scanResultInTextNode.InVisibleOrCollapsibleCharacters() || 1196 scanResultInTextNode.ReachedPreformattedLineBreak()) && 1197 scanResultInTextNode.TextPtr() == text) { 1198 nsresult rv = CollapseSelectionTo( 1199 scanResultInTextNode.PointAtReachedContent<EditorRawDOMPoint>()); 1200 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1201 "EditorBase::CollapseSelectionTo() failed"); 1202 return rv; 1203 } 1204 // If it's an invisible text node, keep scanning next leaf. 1205 leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 1206 *leafContent, 1207 {LeafNodeType::LeafNodeOrNonEditableNode, 1208 LeafNodeType::LeafNodeOrChildBlock}, 1209 BlockInlineCheck::UseComputedDisplayStyle, editingHost); 1210 continue; 1211 } 1212 1213 // If there is editable <br> or something void element like <img>, <input>, 1214 // <hr> etc, move caret before it. 1215 if (!HTMLEditUtils::CanNodeContain(*leafContent, *nsGkAtoms::textTagName) || 1216 HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafContent)) { 1217 MOZ_ASSERT(leafContent->GetParent()); 1218 if (EditorUtils::IsEditableContent(*leafContent, EditorType::HTML)) { 1219 nsresult rv = CollapseSelectionTo(EditorDOMPoint(leafContent)); 1220 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1221 "EditorBase::CollapseSelectionTo() failed"); 1222 return rv; 1223 } 1224 MOZ_ASSERT_UNREACHABLE( 1225 "How do we reach editable leaf in non-editable element?"); 1226 // But if it's not editable, let's put caret at start of editing host 1227 // for now. 1228 nsresult rv = CollapseSelectionTo(EditorDOMPoint(editingHost, 0)); 1229 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1230 "EditorBase::CollapseSelectionTo() failed"); 1231 return rv; 1232 } 1233 1234 // If we meet non-empty block element, we need to scan its child too. 1235 if (HTMLEditUtils::IsBlockElement( 1236 *leafContent, BlockInlineCheck::UseComputedDisplayStyle) && 1237 !HTMLEditUtils::IsEmptyNode( 1238 *leafContent, 1239 {EmptyCheckOption::TreatSingleBRElementAsVisible, 1240 EmptyCheckOption::TreatNonEditableContentAsInvisible}) && 1241 !HTMLEditUtils::IsNeverElementContentsEditableByUser(*leafContent)) { 1242 leafContent = HTMLEditUtils::GetFirstLeafContent( 1243 *leafContent, 1244 {LeafNodeType::LeafNodeOrNonEditableNode, 1245 LeafNodeType::LeafNodeOrChildBlock}, 1246 BlockInlineCheck::UseComputedDisplayStyle, editingHost); 1247 continue; 1248 } 1249 1250 // Otherwise, we must meet an empty block element or a data node like 1251 // comment node. Let's ignore it. 1252 leafContent = HTMLEditUtils::GetNextLeafContentOrNextBlockElement( 1253 *leafContent, 1254 {LeafNodeType::LeafNodeOrNonEditableNode, 1255 LeafNodeType::LeafNodeOrChildBlock}, 1256 BlockInlineCheck::UseComputedDisplayStyle, editingHost); 1257 } 1258 1259 // If there is no visible/editable node except another block element in 1260 // current editing host, we should move caret to very first of the editing 1261 // host. 1262 // XXX This may not make sense, but Chromium behaves so. Therefore, the 1263 // reason why we do this is just compatibility with Chromium. 1264 nsresult rv = CollapseSelectionTo(EditorDOMPoint(editingHost, 0)); 1265 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1266 "EditorBase::CollapseSelectionTo() failed"); 1267 return rv; 1268 } 1269 1270 void HTMLEditor::PreHandleMouseDown(const MouseEvent& aMouseDownEvent) { 1271 if (mPendingStylesToApplyToNewContent) { 1272 // mPendingStylesToApplyToNewContent will be notified of selection change 1273 // even if aMouseDownEvent is not an acceptable event for this editor. 1274 // Therefore, we need to notify it of this event too. 1275 mPendingStylesToApplyToNewContent->PreHandleMouseEvent(aMouseDownEvent); 1276 } 1277 } 1278 1279 void HTMLEditor::PreHandleMouseUp(const MouseEvent& aMouseUpEvent) { 1280 if (mPendingStylesToApplyToNewContent) { 1281 // mPendingStylesToApplyToNewContent will be notified of selection change 1282 // even if aMouseUpEvent is not an acceptable event for this editor. 1283 // Therefore, we need to notify it of this event too. 1284 mPendingStylesToApplyToNewContent->PreHandleMouseEvent(aMouseUpEvent); 1285 } 1286 } 1287 1288 void HTMLEditor::PreHandleSelectionChangeCommand(Command aCommand) { 1289 if (mPendingStylesToApplyToNewContent) { 1290 mPendingStylesToApplyToNewContent->PreHandleSelectionChangeCommand( 1291 aCommand); 1292 } 1293 } 1294 1295 void HTMLEditor::PostHandleSelectionChangeCommand(Command aCommand) { 1296 if (!mPendingStylesToApplyToNewContent) { 1297 return; 1298 } 1299 1300 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 1301 if (!editActionData.CanHandle()) { 1302 return; 1303 } 1304 mPendingStylesToApplyToNewContent->PostHandleSelectionChangeCommand(*this, 1305 aCommand); 1306 } 1307 1308 nsresult HTMLEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) { 1309 // NOTE: When you change this method, you should also change: 1310 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html 1311 if (NS_WARN_IF(!aKeyboardEvent)) { 1312 return NS_ERROR_UNEXPECTED; 1313 } 1314 1315 if (IsReadonly()) { 1316 HandleKeyPressEventInReadOnlyMode(*aKeyboardEvent); 1317 return NS_OK; 1318 } 1319 1320 MOZ_ASSERT(aKeyboardEvent->mMessage == eKeyPress, 1321 "HandleKeyPressEvent gets non-keypress event"); 1322 1323 switch (aKeyboardEvent->mKeyCode) { 1324 case NS_VK_META: 1325 case NS_VK_WIN: 1326 case NS_VK_SHIFT: 1327 case NS_VK_CONTROL: 1328 case NS_VK_ALT: 1329 // FYI: This shouldn't occur since modifier key shouldn't cause eKeyPress 1330 // event. 1331 aKeyboardEvent->PreventDefault(); 1332 return NS_OK; 1333 1334 case NS_VK_BACK: 1335 case NS_VK_DELETE: { 1336 nsresult rv = EditorBase::HandleKeyPressEvent(aKeyboardEvent); 1337 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1338 "EditorBase::HandleKeyPressEvent() failed"); 1339 return rv; 1340 } 1341 case NS_VK_TAB: { 1342 // Basically, "Tab" key be used only for focus navigation. 1343 // FYI: In web apps, this is always true. 1344 if (IsTabbable()) { 1345 return NS_OK; 1346 } 1347 1348 // If we're in the plaintext mode, and not tabbable editor, let's 1349 // insert a horizontal tabulation. 1350 if (IsPlaintextMailComposer()) { 1351 if (aKeyboardEvent->IsShift() || aKeyboardEvent->IsControl() || 1352 aKeyboardEvent->IsAlt() || aKeyboardEvent->IsMeta()) { 1353 return NS_OK; 1354 } 1355 1356 // else we insert the tab straight through 1357 aKeyboardEvent->PreventDefault(); 1358 nsresult rv = OnInputText(u"\t"_ns); 1359 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1360 "EditorBase::OnInputText(\\t) failed"); 1361 return rv; 1362 } 1363 1364 // Otherwise, e.g., we're an embedding editor in chrome, we can handle 1365 // "Tab" key as an input. 1366 if (aKeyboardEvent->IsControl() || aKeyboardEvent->IsAlt() || 1367 aKeyboardEvent->IsMeta()) { 1368 return NS_OK; 1369 } 1370 1371 RefPtr<Selection> selection = GetSelection(); 1372 if (NS_WARN_IF(!selection) || NS_WARN_IF(!selection->RangeCount())) { 1373 return NS_ERROR_FAILURE; 1374 } 1375 1376 nsINode* startContainer = selection->GetRangeAt(0)->GetStartContainer(); 1377 MOZ_ASSERT(startContainer); 1378 if (!startContainer->IsContent()) { 1379 break; 1380 } 1381 1382 const Element* editableBlockElement = 1383 HTMLEditUtils::GetInclusiveAncestorElement( 1384 *startContainer->AsContent(), 1385 HTMLEditUtils::ClosestEditableBlockElement, 1386 BlockInlineCheck::UseComputedDisplayOutsideStyle); 1387 if (!editableBlockElement) { 1388 break; 1389 } 1390 1391 // If selection is in a table element, we need special handling. 1392 if (HTMLEditUtils::IsAnyTableElementExceptColumnElement( 1393 *editableBlockElement)) { 1394 Result<EditActionResult, nsresult> result = 1395 HandleTabKeyPressInTable(aKeyboardEvent); 1396 if (MOZ_UNLIKELY(result.isErr())) { 1397 NS_WARNING("HTMLEditor::HandleTabKeyPressInTable() failed"); 1398 return EditorBase::ToGenericNSResult(result.unwrapErr()); 1399 } 1400 if (!result.inspect().Handled()) { 1401 return NS_OK; 1402 } 1403 nsresult rv = ScrollSelectionFocusIntoView(); 1404 NS_WARNING_ASSERTION( 1405 NS_SUCCEEDED(rv), 1406 "EditorBase::ScrollSelectionFocusIntoView() failed"); 1407 return EditorBase::ToGenericNSResult(rv); 1408 } 1409 1410 // If selection is in an list item element, treat it as indent or outdent. 1411 if (HTMLEditUtils::IsListItemElement(*editableBlockElement)) { 1412 aKeyboardEvent->PreventDefault(); 1413 if (!aKeyboardEvent->IsShift()) { 1414 nsresult rv = IndentAsAction(); 1415 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1416 "HTMLEditor::IndentAsAction() failed"); 1417 return EditorBase::ToGenericNSResult(rv); 1418 } 1419 nsresult rv = OutdentAsAction(); 1420 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1421 "HTMLEditor::OutdentAsAction() failed"); 1422 return EditorBase::ToGenericNSResult(rv); 1423 } 1424 1425 // If only "Tab" key is pressed in normal context, just treat it as 1426 // horizontal tab character input. 1427 if (aKeyboardEvent->IsShift()) { 1428 return NS_OK; 1429 } 1430 aKeyboardEvent->PreventDefault(); 1431 nsresult rv = OnInputText(u"\t"_ns); 1432 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1433 "EditorBase::OnInputText(\\t) failed"); 1434 return EditorBase::ToGenericNSResult(rv); 1435 } 1436 case NS_VK_RETURN: { 1437 if (!aKeyboardEvent->IsInputtingLineBreak()) { 1438 return NS_OK; 1439 } 1440 // Anyway consume the event even if we cannot handle it actually because 1441 // we've already checked whether the an editing host has focus. 1442 aKeyboardEvent->PreventDefault(); 1443 const RefPtr<Element> editingHost = 1444 ComputeEditingHost(LimitInBodyElement::No); 1445 if (NS_WARN_IF(!editingHost)) { 1446 return NS_ERROR_UNEXPECTED; 1447 } 1448 // Shift + Enter should insert a <br> or a LF instead of splitting current 1449 // paragraph. Additionally, if we're in plaintext-only mode, we should 1450 // do so because Chrome does so, but execCommand("insertParagraph") keeps 1451 // working as contenteditable=true. So, we cannot redirect in 1452 // InsertParagraphSeparatorAsAction(). 1453 if (aKeyboardEvent->IsShift() || 1454 editingHost->IsContentEditablePlainTextOnly()) { 1455 // Only inserts a <br> element. 1456 nsresult rv = InsertLineBreakAsAction(); 1457 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1458 "HTMLEditor::InsertLineBreakAsAction() failed"); 1459 return EditorBase::ToGenericNSResult(rv); 1460 } 1461 // uses rules to figure out what to insert 1462 nsresult rv = InsertParagraphSeparatorAsAction(); 1463 NS_WARNING_ASSERTION( 1464 NS_SUCCEEDED(rv), 1465 "HTMLEditor::InsertParagraphSeparatorAsAction() failed"); 1466 return EditorBase::ToGenericNSResult(rv); 1467 } 1468 } 1469 1470 if (!aKeyboardEvent->IsInputtingText()) { 1471 // we don't PreventDefault() here or keybindings like control-x won't work 1472 return NS_OK; 1473 } 1474 aKeyboardEvent->PreventDefault(); 1475 // If we dispatch 2 keypress events for a surrogate pair and we set only 1476 // first `.key` value to the surrogate pair, the preceding one has it and the 1477 // other has empty string. In this case, we should handle only the first one 1478 // with the key value. 1479 if (!StaticPrefs::dom_event_keypress_dispatch_once_per_surrogate_pair() && 1480 !StaticPrefs::dom_event_keypress_key_allow_lone_surrogate() && 1481 aKeyboardEvent->mKeyValue.IsEmpty() && 1482 IS_SURROGATE(aKeyboardEvent->mCharCode)) { 1483 return NS_OK; 1484 } 1485 nsAutoString str(aKeyboardEvent->mKeyValue); 1486 if (str.IsEmpty()) { 1487 str.Assign(static_cast<char16_t>(aKeyboardEvent->mCharCode)); 1488 } 1489 // FYI: DIfferent from TextEditor, we can treat \r (CR) as-is in HTMLEditor. 1490 nsresult rv = OnInputText(str); 1491 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::OnInputText() failed"); 1492 return rv; 1493 } 1494 1495 NS_IMETHODIMP HTMLEditor::NodeIsBlock(nsINode* aNode, bool* aIsBlock) { 1496 if (NS_WARN_IF(!aNode)) { 1497 return NS_ERROR_INVALID_ARG; 1498 } 1499 if (MOZ_UNLIKELY(!aNode->IsElement())) { 1500 *aIsBlock = false; 1501 return NS_OK; 1502 } 1503 // If the node is in composed doc, we'll refer its style. If we don't flush 1504 // pending style here, another API call may change the style. Therefore, 1505 // let's flush the pending style changes right now. 1506 if (aNode->IsInComposedDoc()) { 1507 if (RefPtr<PresShell> presShell = GetPresShell()) { 1508 presShell->FlushPendingNotifications(FlushType::Style); 1509 } 1510 } 1511 *aIsBlock = HTMLEditUtils::IsBlockElement( 1512 *aNode->AsElement(), BlockInlineCheck::UseComputedDisplayOutsideStyle); 1513 return NS_OK; 1514 } 1515 1516 NS_IMETHODIMP HTMLEditor::UpdateBaseURL() { 1517 RefPtr<Document> document = GetDocument(); 1518 if (NS_WARN_IF(!document)) { 1519 return NS_ERROR_FAILURE; 1520 } 1521 1522 // Look for an HTML <base> tag 1523 RefPtr<nsContentList> baseElementList = 1524 document->GetElementsByTagName(u"base"_ns); 1525 1526 // If no base tag, then set baseURL to the document's URL. This is very 1527 // important, else relative URLs for links and images are wrong 1528 if (!baseElementList || !baseElementList->Item(0)) { 1529 document->SetBaseURI(document->GetDocumentURI()); 1530 } 1531 return NS_OK; 1532 } 1533 1534 NS_IMETHODIMP HTMLEditor::InsertLineBreak() { 1535 // XPCOM method's InsertLineBreak() should insert paragraph separator in 1536 // HTMLEditor. 1537 AutoEditActionDataSetter editActionData( 1538 *this, EditAction::eInsertParagraphSeparator); 1539 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 1540 if (NS_FAILED(rv)) { 1541 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 1542 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 1543 return EditorBase::ToGenericNSResult(rv); 1544 } 1545 1546 const RefPtr<Element> editingHost = 1547 ComputeEditingHost(LimitInBodyElement::No); 1548 if (!editingHost) { 1549 return NS_SUCCESS_DOM_NO_OPERATION; 1550 } 1551 1552 Result<EditActionResult, nsresult> result = 1553 InsertParagraphSeparatorAsSubAction(*editingHost); 1554 if (MOZ_UNLIKELY(result.isErr())) { 1555 NS_WARNING("HTMLEditor::InsertParagraphSeparatorAsSubAction() failed"); 1556 return EditorBase::ToGenericNSResult(result.unwrapErr()); 1557 } 1558 return NS_OK; 1559 } 1560 1561 nsresult HTMLEditor::InsertLineBreakAsAction(nsIPrincipal* aPrincipal) { 1562 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLineBreak, 1563 aPrincipal); 1564 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 1565 if (NS_FAILED(rv)) { 1566 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 1567 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 1568 return EditorBase::ToGenericNSResult(rv); 1569 } 1570 1571 if (IsSelectionRangeContainerNotContent()) { 1572 return NS_SUCCESS_DOM_NO_OPERATION; 1573 } 1574 1575 rv = InsertLineBreakAsSubAction(); 1576 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1577 "HTMLEditor::InsertLineBreakAsSubAction() failed"); 1578 // Don't return NS_SUCCESS_DOM_NO_OPERATION for compatibility of `execCommand` 1579 // result of Chrome. 1580 return NS_FAILED(rv) ? rv : NS_OK; 1581 } 1582 1583 nsresult HTMLEditor::InsertParagraphSeparatorAsAction( 1584 nsIPrincipal* aPrincipal) { 1585 AutoEditActionDataSetter editActionData( 1586 *this, EditAction::eInsertParagraphSeparator, aPrincipal); 1587 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 1588 if (NS_FAILED(rv)) { 1589 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 1590 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 1591 return EditorBase::ToGenericNSResult(rv); 1592 } 1593 1594 const RefPtr<Element> editingHost = 1595 ComputeEditingHost(LimitInBodyElement::No); 1596 if (!editingHost) { 1597 return NS_SUCCESS_DOM_NO_OPERATION; 1598 } 1599 1600 Result<EditActionResult, nsresult> result = 1601 InsertParagraphSeparatorAsSubAction(*editingHost); 1602 if (MOZ_UNLIKELY(result.isErr())) { 1603 NS_WARNING("HTMLEditor::InsertParagraphSeparatorAsSubAction() failed"); 1604 return EditorBase::ToGenericNSResult(result.unwrapErr()); 1605 } 1606 return NS_OK; 1607 } 1608 1609 Result<EditActionResult, nsresult> HTMLEditor::HandleTabKeyPressInTable( 1610 WidgetKeyboardEvent* aKeyboardEvent) { 1611 MOZ_ASSERT(aKeyboardEvent); 1612 1613 AutoEditActionDataSetter dummyEditActionData(*this, EditAction::eNotEditing); 1614 if (NS_WARN_IF(!dummyEditActionData.CanHandle())) { 1615 // Do nothing if we didn't find a table cell. 1616 return EditActionResult::IgnoredResult(); 1617 } 1618 1619 // Find enclosing table cell from selection (cell may be selected element) 1620 const RefPtr<Element> cellElement = 1621 GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td); 1622 if (!cellElement) { 1623 NS_WARNING( 1624 "HTMLEditor::GetInclusiveAncestorByTagNameAtSelection(*nsGkAtoms::td) " 1625 "returned nullptr"); 1626 // Do nothing if we didn't find a table cell. 1627 return EditActionResult::IgnoredResult(); 1628 } 1629 1630 // find enclosing table 1631 RefPtr<Element> table = 1632 HTMLEditUtils::GetClosestAncestorTableElement(*cellElement); 1633 if (!table) { 1634 NS_WARNING("HTMLEditor::GetClosestAncestorTableElement() failed"); 1635 return EditActionResult::IgnoredResult(); 1636 } 1637 1638 // advance to next cell 1639 // first create an iterator over the table 1640 PostContentIterator postOrderIter; 1641 nsresult rv = postOrderIter.Init(table); 1642 if (NS_FAILED(rv)) { 1643 NS_WARNING("PostContentIterator::Init() failed"); 1644 return Err(rv); 1645 } 1646 // position postOrderIter at block 1647 rv = postOrderIter.PositionAt(cellElement); 1648 if (NS_FAILED(rv)) { 1649 NS_WARNING("PostContentIterator::PositionAt() failed"); 1650 return Err(rv); 1651 } 1652 1653 do { 1654 if (aKeyboardEvent->IsShift()) { 1655 postOrderIter.Prev(); 1656 } else { 1657 postOrderIter.Next(); 1658 } 1659 1660 const RefPtr<Element> element = 1661 Element::FromNodeOrNull(postOrderIter.GetCurrentNode()); 1662 if (element && HTMLEditUtils::IsTableCellElement(*element) && 1663 HTMLEditUtils::GetClosestAncestorTableElement(*element) == table) { 1664 aKeyboardEvent->PreventDefault(); 1665 CollapseSelectionToDeepestNonTableFirstChild(element); 1666 if (NS_WARN_IF(Destroyed())) { 1667 return Err(NS_ERROR_EDITOR_DESTROYED); 1668 } 1669 return EditActionResult::HandledResult(); 1670 } 1671 } while (!postOrderIter.IsDone()); 1672 1673 if (aKeyboardEvent->IsShift()) { 1674 return EditActionResult::IgnoredResult(); 1675 } 1676 1677 // If we haven't handled it yet, then we must have run off the end of the 1678 // table. Insert a new row. 1679 // XXX We should investigate whether this behavior is supported by other 1680 // browsers later. 1681 AutoEditActionDataSetter editActionData(*this, 1682 EditAction::eInsertTableRowElement); 1683 rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 1684 if (NS_FAILED(rv)) { 1685 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 1686 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 1687 return Err(rv); 1688 } 1689 rv = InsertTableRowsWithTransaction(*cellElement, 1, 1690 InsertPosition::eAfterSelectedCell); 1691 if (NS_WARN_IF(Destroyed())) { 1692 return Err(NS_ERROR_EDITOR_DESTROYED); 1693 } 1694 if (NS_FAILED(rv)) { 1695 NS_WARNING( 1696 "HTMLEditor::InsertTableRowsWithTransaction(*cellElement, 1, " 1697 "InsertPosition::eAfterSelectedCell) failed"); 1698 return Err(rv); 1699 } 1700 aKeyboardEvent->PreventDefault(); 1701 // Put selection in right place. Use table code to get selection and index 1702 // to new row... 1703 RefPtr<Element> tblElement, cell; 1704 int32_t row; 1705 rv = GetCellContext(getter_AddRefs(tblElement), getter_AddRefs(cell), nullptr, 1706 nullptr, &row, nullptr); 1707 if (NS_FAILED(rv)) { 1708 NS_WARNING("HTMLEditor::GetCellContext() failed"); 1709 return Err(rv); 1710 } 1711 if (!tblElement) { 1712 NS_WARNING("HTMLEditor::GetCellContext() didn't return table element"); 1713 return Err(NS_ERROR_FAILURE); 1714 } 1715 // ...so that we can ask for first cell in that row... 1716 cell = GetTableCellElementAt(*tblElement, row, 0); 1717 // ...and then set selection there. (Note that normally you should use 1718 // CollapseSelectionToDeepestNonTableFirstChild(), but we know cell is an 1719 // empty new cell, so this works fine) 1720 if (cell) { 1721 nsresult rv = CollapseSelectionToStartOf(*cell); 1722 if (NS_FAILED(rv)) { 1723 NS_WARNING("EditorBase::CollapseSelectionToStartOf() failed"); 1724 return Err(NS_ERROR_EDITOR_DESTROYED); 1725 } 1726 } 1727 if (NS_WARN_IF(Destroyed())) { 1728 return Err(NS_ERROR_EDITOR_DESTROYED); 1729 } 1730 return EditActionResult::HandledResult(); 1731 } 1732 1733 void HTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(nsINode* aNode) { 1734 MOZ_ASSERT(IsEditActionDataAvailable()); 1735 1736 MOZ_ASSERT(aNode); 1737 1738 nsCOMPtr<nsINode> node = aNode; 1739 1740 for (nsIContent* child = node->GetFirstChild(); child; 1741 child = child->GetFirstChild()) { 1742 // Stop if we find a table, don't want to go into nested tables 1743 if (child->IsHTMLElement(nsGkAtoms::table) || 1744 !HTMLEditUtils::IsContainerNode(*child)) { 1745 break; 1746 } 1747 node = child; 1748 } 1749 1750 DebugOnly<nsresult> rvIgnored = CollapseSelectionToStartOf(*node); 1751 NS_WARNING_ASSERTION( 1752 NS_SUCCEEDED(rvIgnored), 1753 "EditorBase::CollapseSelectionToStartOf() failed, but ignored"); 1754 } 1755 1756 NS_IMETHODIMP HTMLEditor::InsertElementAtSelection(Element* aElement, 1757 bool aDeleteSelection) { 1758 InsertElementOptions options; 1759 if (aDeleteSelection) { 1760 options += InsertElementOption::DeleteSelection; 1761 } 1762 nsresult rv = InsertElementAtSelectionAsAction(aElement, options); 1763 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1764 "HTMLEditor::InsertElementAtSelectionAsAction() failed"); 1765 return rv; 1766 } 1767 1768 nsresult HTMLEditor::InsertElementAtSelectionAsAction( 1769 Element* aElement, const InsertElementOptions aOptions, 1770 nsIPrincipal* aPrincipal) { 1771 if (NS_WARN_IF(!aElement)) { 1772 return NS_ERROR_INVALID_ARG; 1773 } 1774 1775 if (IsReadonly()) { 1776 return NS_OK; 1777 } 1778 1779 AutoEditActionDataSetter editActionData( 1780 *this, HTMLEditUtils::GetEditActionForInsert(*aElement), aPrincipal); 1781 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 1782 if (NS_FAILED(rv)) { 1783 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 1784 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 1785 return EditorBase::ToGenericNSResult(rv); 1786 } 1787 1788 DebugOnly<nsresult> rvIgnored = CommitComposition(); 1789 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 1790 "EditorBase::CommitComposition() failed, but ignored"); 1791 1792 { 1793 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 1794 if (MOZ_UNLIKELY(result.isErr())) { 1795 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 1796 return EditorBase::ToGenericNSResult(result.unwrapErr()); 1797 } 1798 if (result.inspect().Canceled()) { 1799 return NS_OK; 1800 } 1801 } 1802 1803 UndefineCaretBidiLevel(); 1804 1805 AutoPlaceholderBatch treatAsOneTransaction( 1806 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 1807 IgnoredErrorResult ignoredError; 1808 AutoEditSubActionNotifier startToHandleEditSubAction( 1809 *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError); 1810 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 1811 return ignoredError.StealNSResult(); 1812 } 1813 NS_WARNING_ASSERTION( 1814 !ignoredError.Failed(), 1815 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 1816 1817 const RefPtr<Element> editingHost = 1818 ComputeEditingHost(LimitInBodyElement::No); 1819 if (NS_WARN_IF(!editingHost)) { 1820 return EditorBase::ToGenericNSResult(NS_ERROR_FAILURE); 1821 } 1822 1823 rv = EnsureNoPaddingBRElementForEmptyEditor(); 1824 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 1825 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 1826 } 1827 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1828 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 1829 "failed, but ignored"); 1830 1831 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 1832 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(*editingHost); 1833 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 1834 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 1835 } 1836 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1837 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 1838 "failed, but ignored"); 1839 if (NS_SUCCEEDED(rv)) { 1840 nsresult rv = PrepareInlineStylesForCaret(); 1841 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 1842 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 1843 } 1844 NS_WARNING_ASSERTION( 1845 NS_SUCCEEDED(rv), 1846 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 1847 } 1848 } 1849 1850 if (aOptions.contains(InsertElementOption::DeleteSelection) && 1851 !SelectionRef().IsCollapsed()) { 1852 if (!HTMLEditUtils::IsBlockElement( 1853 *aElement, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 1854 // E.g., inserting an image. In this case we don't need to delete any 1855 // inline wrappers before we do the insertion. Otherwise we let 1856 // DeleteSelectionAndPrepareToCreateNode do the deletion for us, which 1857 // calls DeleteSelection with aStripWrappers = eStrip. 1858 nsresult rv = DeleteSelectionAsSubAction( 1859 eNone, 1860 aOptions.contains(InsertElementOption::SplitAncestorInlineElements) 1861 ? eStrip 1862 : eNoStrip); 1863 if (NS_FAILED(rv)) { 1864 NS_WARNING( 1865 "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed"); 1866 return EditorBase::ToGenericNSResult(rv); 1867 } 1868 } 1869 1870 nsresult rv = DeleteSelectionAndPrepareToCreateNode(); 1871 if (NS_FAILED(rv)) { 1872 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed"); 1873 return rv; 1874 } 1875 } 1876 // If deleting, selection will be collapsed. 1877 // so if not, we collapse it 1878 else { 1879 // Named Anchor is a special case, 1880 // We collapse to insert element BEFORE the selection 1881 // For all other tags, we insert AFTER the selection 1882 if (HTMLEditUtils::IsNamedAnchorElement(*aElement)) { 1883 IgnoredErrorResult ignoredError; 1884 SelectionRef().CollapseToStart(ignoredError); 1885 if (NS_WARN_IF(Destroyed())) { 1886 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 1887 } 1888 NS_WARNING_ASSERTION(!ignoredError.Failed(), 1889 "Selection::CollapseToStart() failed, but ignored"); 1890 } else { 1891 IgnoredErrorResult ignoredError; 1892 SelectionRef().CollapseToEnd(ignoredError); 1893 if (NS_WARN_IF(Destroyed())) { 1894 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 1895 } 1896 NS_WARNING_ASSERTION(!ignoredError.Failed(), 1897 "Selection::CollapseToEnd() failed, but ignored"); 1898 } 1899 } 1900 1901 if (!SelectionRef().GetAnchorNode()) { 1902 return NS_OK; 1903 } 1904 if (NS_WARN_IF(!SelectionRef().GetAnchorNode()->IsInclusiveDescendantOf( 1905 editingHost))) { 1906 return NS_ERROR_FAILURE; 1907 } 1908 1909 EditorRawDOMPoint atAnchor(SelectionRef().AnchorRef()); 1910 // Adjust position based on the node we are going to insert. 1911 EditorDOMPoint pointToInsert = 1912 HTMLEditUtils::GetBetterInsertionPointFor<EditorDOMPoint>(*aElement, 1913 atAnchor); 1914 if (!pointToInsert.IsSet()) { 1915 NS_WARNING("HTMLEditUtils::GetBetterInsertionPointFor() failed"); 1916 return NS_ERROR_FAILURE; 1917 } 1918 Result<EditorDOMPoint, nsresult> pointToInsertOrError = 1919 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( 1920 *this, pointToInsert, 1921 {WhiteSpaceVisibilityKeeper::NormalizeOption:: 1922 StopIfFollowingWhiteSpacesStartsWithNBSP}); 1923 if (MOZ_UNLIKELY(pointToInsertOrError.isErr())) { 1924 NS_WARNING( 1925 "WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt() failed"); 1926 return pointToInsertOrError.propagateErr(); 1927 } 1928 pointToInsert = pointToInsertOrError.unwrap(); 1929 if (NS_WARN_IF(!pointToInsert.IsSetAndValidInComposedDoc())) { 1930 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1931 } 1932 1933 if (aOptions.contains(InsertElementOption::SplitAncestorInlineElements)) { 1934 if (const RefPtr<Element> topmostInlineElement = Element::FromNodeOrNull( 1935 HTMLEditUtils::GetMostDistantAncestorInlineElement( 1936 *pointToInsert.ContainerAs<nsIContent>(), 1937 BlockInlineCheck::UseComputedDisplayOutsideStyle, 1938 editingHost))) { 1939 Result<SplitNodeResult, nsresult> splitInlinesResult = 1940 SplitNodeDeepWithTransaction( 1941 *topmostInlineElement, pointToInsert, 1942 SplitAtEdges::eDoNotCreateEmptyContainer); 1943 if (MOZ_UNLIKELY(splitInlinesResult.isErr())) { 1944 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); 1945 return splitInlinesResult.unwrapErr(); 1946 } 1947 splitInlinesResult.inspect().IgnoreCaretPointSuggestion(); 1948 auto splitPoint = 1949 splitInlinesResult.inspect().AtSplitPoint<EditorDOMPoint>(); 1950 if (MOZ_LIKELY(splitPoint.IsSet())) { 1951 pointToInsert = std::move(splitPoint); 1952 } 1953 } 1954 } 1955 { 1956 Result<CreateElementResult, nsresult> insertElementResult = 1957 InsertNodeIntoProperAncestorWithTransaction<Element>( 1958 *aElement, pointToInsert, 1959 SplitAtEdges::eAllowToCreateEmptyContainer); 1960 if (MOZ_UNLIKELY(insertElementResult.isErr())) { 1961 NS_WARNING( 1962 "HTMLEditor::InsertNodeIntoProperAncestorWithTransaction(" 1963 "SplitAtEdges::eAllowToCreateEmptyContainer) failed"); 1964 return EditorBase::ToGenericNSResult(insertElementResult.unwrapErr()); 1965 } 1966 if (MOZ_LIKELY(aElement->IsInComposedDoc())) { 1967 const auto afterElement = EditorDOMPoint::After(*aElement); 1968 if (MOZ_LIKELY(afterElement.IsInContentNode())) { 1969 nsresult rv = EnsureNoFollowingUnnecessaryLineBreak(afterElement); 1970 if (NS_FAILED(rv)) { 1971 NS_WARNING( 1972 "HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak() failed"); 1973 return EditorBase::ToGenericNSResult(rv); 1974 } 1975 } 1976 } 1977 insertElementResult.inspect().IgnoreCaretPointSuggestion(); 1978 } 1979 // Set caret after element, but check for special case 1980 // of inserting table-related elements: set in first cell instead 1981 if (!SetCaretInTableCell(aElement)) { 1982 if (NS_WARN_IF(Destroyed())) { 1983 return EditorBase::ToGenericNSResult(NS_ERROR_EDITOR_DESTROYED); 1984 } 1985 nsresult rv = CollapseSelectionTo(EditorRawDOMPoint::After(*aElement)); 1986 if (NS_FAILED(rv)) { 1987 NS_WARNING("HTMLEditor::CollapseSelectionTo() failed"); 1988 return EditorBase::ToGenericNSResult(rv); 1989 } 1990 } 1991 1992 // check for inserting a whole table at the end of a block. If so insert 1993 // a br after it. 1994 if (!aElement->IsHTMLElement(nsGkAtoms::table) || 1995 !HTMLEditUtils::IsLastChild(*aElement, 1996 {WalkTreeOption::IgnoreNonEditableNode})) { 1997 return NS_OK; 1998 } 1999 2000 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 2001 InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, 2002 EditorDOMPoint::After(*aElement), 2003 // Will collapse selection to before the new line break. 2004 ePrevious); 2005 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 2006 NS_WARNING( 2007 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 2008 "LineBreakType::BRElement, ePrevious) failed"); 2009 return EditorBase::ToGenericNSResult( 2010 insertBRElementResultOrError.unwrapErr()); 2011 } 2012 CreateLineBreakResult insertBRElementResult = 2013 insertBRElementResultOrError.unwrap(); 2014 MOZ_ASSERT(insertBRElementResult.Handled()); 2015 rv = insertBRElementResult.SuggestCaretPointTo(*this, {}); 2016 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2017 "CaretPoint::SuggestCaretPointTo() failed"); 2018 return EditorBase::ToGenericNSResult(rv); 2019 } 2020 2021 template <typename NodeType> 2022 Result<CreateNodeResultBase<NodeType>, nsresult> 2023 HTMLEditor::InsertNodeIntoProperAncestorWithTransaction( 2024 NodeType& aContentToInsert, const EditorDOMPoint& aPointToInsert, 2025 SplitAtEdges aSplitAtEdges) { 2026 MOZ_ASSERT(aPointToInsert.IsSetAndValidInComposedDoc()); 2027 if (NS_WARN_IF(!aPointToInsert.IsInContentNode())) { 2028 return Err(NS_ERROR_FAILURE); 2029 } 2030 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 2031 2032 if (aContentToInsert.NodeType() == nsINode::DOCUMENT_TYPE_NODE || 2033 aContentToInsert.NodeType() == nsINode::PROCESSING_INSTRUCTION_NODE) { 2034 return CreateNodeResultBase<NodeType>::NotHandled(); 2035 } 2036 2037 // Search up the parent chain to find a suitable container. 2038 EditorDOMPoint pointToInsert(aPointToInsert); 2039 MOZ_ASSERT(pointToInsert.IsInContentNode()); 2040 while (!HTMLEditUtils::CanNodeContain(*pointToInsert.GetContainer(), 2041 aContentToInsert)) { 2042 // If the current parent is a root (body or table element) 2043 // then go no further - we can't insert. 2044 if (MOZ_UNLIKELY(pointToInsert.IsContainerHTMLElement(nsGkAtoms::body) || 2045 HTMLEditUtils::IsAnyTableElementExceptColumnElement( 2046 *pointToInsert.ContainerAs<nsIContent>()))) { 2047 NS_WARNING( 2048 "There was no proper container element to insert the content node in " 2049 "the document"); 2050 return Err(NS_ERROR_FAILURE); 2051 } 2052 2053 // Get the next point. 2054 pointToInsert = pointToInsert.ParentPoint(); 2055 2056 if (MOZ_UNLIKELY( 2057 !pointToInsert.IsInContentNode() || 2058 !EditorUtils::IsEditableContent( 2059 *pointToInsert.ContainerAs<nsIContent>(), EditorType::HTML))) { 2060 NS_WARNING( 2061 "There was no proper container element to insert the content node in " 2062 "the editing host"); 2063 return Err(NS_ERROR_FAILURE); 2064 } 2065 } 2066 2067 if (pointToInsert != aPointToInsert) { 2068 // We need to split some levels above the original selection parent. 2069 MOZ_ASSERT(pointToInsert.GetChild()); 2070 Result<SplitNodeResult, nsresult> splitNodeResult = 2071 SplitNodeDeepWithTransaction(MOZ_KnownLive(*pointToInsert.GetChild()), 2072 aPointToInsert, aSplitAtEdges); 2073 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 2074 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); 2075 return splitNodeResult.propagateErr(); 2076 } 2077 pointToInsert = 2078 splitNodeResult.inspect().template AtSplitPoint<EditorDOMPoint>(); 2079 MOZ_ASSERT(pointToInsert.IsSetAndValidInComposedDoc()); 2080 // Caret should be set by the caller of this method so that we don't 2081 // need to handle it here. 2082 splitNodeResult.inspect().IgnoreCaretPointSuggestion(); 2083 } 2084 2085 // Now we can insert the new node. 2086 Result<CreateNodeResultBase<NodeType>, nsresult> insertContentNodeResult = 2087 InsertNodeWithTransaction<NodeType>(aContentToInsert, pointToInsert); 2088 if (MOZ_LIKELY(insertContentNodeResult.isOk()) && 2089 MOZ_UNLIKELY(NS_WARN_IF(!aContentToInsert.GetParentNode()) || 2090 NS_WARN_IF(aContentToInsert.GetParentNode() != 2091 pointToInsert.GetContainer()))) { 2092 NS_WARNING( 2093 "EditorBase::InsertNodeWithTransaction() succeeded, but the inserted " 2094 "node was moved or removed by the web app"); 2095 insertContentNodeResult.inspect().IgnoreCaretPointSuggestion(); 2096 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2097 } 2098 NS_WARNING_ASSERTION(insertContentNodeResult.isOk(), 2099 "EditorBase::InsertNodeWithTransaction() failed"); 2100 return insertContentNodeResult; 2101 } 2102 2103 NS_IMETHODIMP HTMLEditor::SelectElement(Element* aElement) { 2104 if (NS_WARN_IF(!aElement)) { 2105 return NS_ERROR_INVALID_ARG; 2106 } 2107 2108 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 2109 if (NS_WARN_IF(!editActionData.CanHandle())) { 2110 return NS_ERROR_NOT_INITIALIZED; 2111 } 2112 2113 nsresult rv = SelectContentInternal(MOZ_KnownLive(*aElement)); 2114 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2115 "HTMLEditor::SelectContentInternal() failed"); 2116 return rv; 2117 } 2118 2119 nsresult HTMLEditor::SelectContentInternal(nsIContent& aContentToSelect) { 2120 MOZ_ASSERT(IsEditActionDataAvailable()); 2121 2122 // Must be sure that element is contained in the editing host 2123 const RefPtr<Element> editingHost = ComputeEditingHost(); 2124 if (NS_WARN_IF(!editingHost) || 2125 NS_WARN_IF(!aContentToSelect.IsInclusiveDescendantOf(editingHost))) { 2126 return NS_ERROR_FAILURE; 2127 } 2128 2129 EditorRawDOMPoint newSelectionStart(&aContentToSelect); 2130 if (NS_WARN_IF(!newSelectionStart.IsSet())) { 2131 return NS_ERROR_FAILURE; 2132 } 2133 EditorRawDOMPoint newSelectionEnd(EditorRawDOMPoint::After(aContentToSelect)); 2134 MOZ_ASSERT(newSelectionEnd.IsSet()); 2135 ErrorResult error; 2136 SelectionRef().SetStartAndEndInLimiter(newSelectionStart, newSelectionEnd, 2137 error); 2138 NS_WARNING_ASSERTION(!error.Failed(), 2139 "Selection::SetStartAndEndInLimiter() failed"); 2140 return error.StealNSResult(); 2141 } 2142 2143 nsresult HTMLEditor::AppendContentToSelectionAsRange(nsIContent& aContent) { 2144 MOZ_ASSERT(IsEditActionDataAvailable()); 2145 2146 EditorRawDOMPoint atContent(&aContent); 2147 if (NS_WARN_IF(!atContent.IsSet())) { 2148 return NS_ERROR_FAILURE; 2149 } 2150 2151 RefPtr<nsRange> range = nsRange::Create( 2152 atContent.ToRawRangeBoundary(), 2153 atContent.NextPoint().ToRawRangeBoundary(), IgnoreErrors()); 2154 if (NS_WARN_IF(!range)) { 2155 NS_WARNING("nsRange::Create() failed"); 2156 return NS_ERROR_FAILURE; 2157 } 2158 2159 ErrorResult error; 2160 SelectionRef().AddRangeAndSelectFramesAndNotifyListeners(*range, error); 2161 if (NS_WARN_IF(Destroyed())) { 2162 if (error.Failed()) { 2163 error.SuppressException(); 2164 } 2165 return NS_ERROR_EDITOR_DESTROYED; 2166 } 2167 NS_WARNING_ASSERTION(!error.Failed(), "Failed to add range to Selection"); 2168 return error.StealNSResult(); 2169 } 2170 2171 nsresult HTMLEditor::ClearSelection() { 2172 MOZ_ASSERT(IsEditActionDataAvailable()); 2173 2174 ErrorResult error; 2175 SelectionRef().RemoveAllRanges(error); 2176 if (NS_WARN_IF(Destroyed())) { 2177 if (error.Failed()) { 2178 error.SuppressException(); 2179 } 2180 return NS_ERROR_EDITOR_DESTROYED; 2181 } 2182 NS_WARNING_ASSERTION(!error.Failed(), "Selection::RemoveAllRanges() failed"); 2183 return error.StealNSResult(); 2184 } 2185 2186 nsresult HTMLEditor::FormatBlockAsAction(const nsAString& aParagraphFormat, 2187 nsIPrincipal* aPrincipal) { 2188 if (NS_WARN_IF(aParagraphFormat.IsEmpty())) { 2189 return NS_ERROR_INVALID_ARG; 2190 } 2191 2192 AutoEditActionDataSetter editActionData( 2193 *this, EditAction::eInsertBlockElement, aPrincipal); 2194 if (NS_WARN_IF(!editActionData.CanHandle())) { 2195 return NS_ERROR_NOT_INITIALIZED; 2196 } 2197 2198 const RefPtr<Element> editingHost = 2199 ComputeEditingHost(LimitInBodyElement::No); 2200 if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) { 2201 return NS_SUCCESS_DOM_NO_OPERATION; 2202 } 2203 2204 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 2205 if (NS_FAILED(rv)) { 2206 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2207 "MaybeDispatchBeforeInputEvent(), failed"); 2208 return EditorBase::ToGenericNSResult(rv); 2209 } 2210 2211 RefPtr<nsAtom> tagName = NS_Atomize(aParagraphFormat); 2212 MOZ_ASSERT(tagName); 2213 if (NS_WARN_IF(!tagName->IsStatic()) || 2214 NS_WARN_IF(!HTMLEditUtils::IsFormatTagForFormatBlockCommand( 2215 *tagName->AsStatic()))) { 2216 return NS_ERROR_INVALID_ARG; 2217 } 2218 2219 if (tagName == nsGkAtoms::dd || tagName == nsGkAtoms::dt) { 2220 // MOZ_KnownLive(tagName->AsStatic()) because nsStaticAtom instances live 2221 // while the process is running. 2222 Result<EditActionResult, nsresult> result = 2223 MakeOrChangeListAndListItemAsSubAction( 2224 MOZ_KnownLive(*tagName->AsStatic()), EmptyString(), 2225 SelectAllOfCurrentList::No, *editingHost); 2226 if (MOZ_UNLIKELY(result.isErr())) { 2227 NS_WARNING( 2228 "HTMLEditor::MakeOrChangeListAndListItemAsSubAction(" 2229 "SelectAllOfCurrentList::No) failed"); 2230 return EditorBase::ToGenericNSResult(result.unwrapErr()); 2231 } 2232 return NS_OK; 2233 } 2234 2235 rv = FormatBlockContainerAsSubAction(MOZ_KnownLive(*tagName->AsStatic()), 2236 FormatBlockMode::HTMLFormatBlockCommand, 2237 *editingHost); 2238 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2239 "HTMLEditor::FormatBlockContainerAsSubAction() failed"); 2240 return EditorBase::ToGenericNSResult(rv); 2241 } 2242 2243 nsresult HTMLEditor::SetParagraphStateAsAction( 2244 const nsAString& aParagraphFormat, nsIPrincipal* aPrincipal) { 2245 AutoEditActionDataSetter editActionData( 2246 *this, EditAction::eInsertBlockElement, aPrincipal); 2247 if (NS_WARN_IF(!editActionData.CanHandle())) { 2248 return NS_ERROR_NOT_INITIALIZED; 2249 } 2250 2251 const RefPtr<Element> editingHost = 2252 ComputeEditingHost(LimitInBodyElement::No); 2253 if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) { 2254 return NS_SUCCESS_DOM_NO_OPERATION; 2255 } 2256 2257 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 2258 if (NS_FAILED(rv)) { 2259 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2260 "MaybeDispatchBeforeInputEvent(), failed"); 2261 return EditorBase::ToGenericNSResult(rv); 2262 } 2263 2264 // TODO: Computing the editing host here makes the `execCommand` in 2265 // docshell/base/crashtests/file_432114-2.xhtml cannot run 2266 // `DOMNodeRemoved` event listener with deleting the bogus <br> element. 2267 // So that it should be rewritten with different mutation event listener 2268 // since we'd like to stop using it. 2269 2270 nsAutoString lowerCaseTagName(aParagraphFormat); 2271 ToLowerCase(lowerCaseTagName); 2272 RefPtr<nsAtom> tagName = NS_Atomize(lowerCaseTagName); 2273 MOZ_ASSERT(tagName); 2274 if (NS_WARN_IF(!tagName->IsStatic())) { 2275 return NS_ERROR_INVALID_ARG; 2276 } 2277 if (tagName == nsGkAtoms::dd || tagName == nsGkAtoms::dt) { 2278 // MOZ_KnownLive(tagName->AsStatic()) because nsStaticAtom instances live 2279 // while the process is running. 2280 Result<EditActionResult, nsresult> result = 2281 MakeOrChangeListAndListItemAsSubAction( 2282 MOZ_KnownLive(*tagName->AsStatic()), EmptyString(), 2283 SelectAllOfCurrentList::No, *editingHost); 2284 if (MOZ_UNLIKELY(result.isErr())) { 2285 NS_WARNING( 2286 "HTMLEditor::MakeOrChangeListAndListItemAsSubAction(" 2287 "SelectAllOfCurrentList::No) failed"); 2288 return EditorBase::ToGenericNSResult(result.unwrapErr()); 2289 } 2290 return NS_OK; 2291 } 2292 2293 rv = FormatBlockContainerAsSubAction( 2294 MOZ_KnownLive(*tagName->AsStatic()), 2295 FormatBlockMode::XULParagraphStateCommand, *editingHost); 2296 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2297 "HTMLEditor::FormatBlockContainerAsSubAction() failed"); 2298 return EditorBase::ToGenericNSResult(rv); 2299 } 2300 2301 // static 2302 bool HTMLEditor::IsFormatElement(FormatBlockMode aFormatBlockMode, 2303 const nsIContent& aContent) { 2304 // FYI: Optimize for HTML command because it may run too many times. 2305 return MOZ_LIKELY(aFormatBlockMode == FormatBlockMode::HTMLFormatBlockCommand) 2306 ? HTMLEditUtils::IsFormatElementForFormatBlockCommand(aContent) 2307 : (HTMLEditUtils::IsFormatElementForParagraphStateCommand( 2308 aContent) && 2309 // XXX The XUL paragraph state command treats <dl>, <dd> and 2310 // <dt> elements but all handlers do not treat them as a format 2311 // node. Therefore, we keep the traditional behavior here. 2312 !aContent.IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dl, 2313 nsGkAtoms::dt)); 2314 } 2315 2316 NS_IMETHODIMP HTMLEditor::GetParagraphState(bool* aMixed, 2317 nsAString& aFirstParagraphState) { 2318 if (NS_WARN_IF(!aMixed)) { 2319 return NS_ERROR_INVALID_ARG; 2320 } 2321 if (!mInitSucceeded) { 2322 return NS_ERROR_NOT_INITIALIZED; 2323 } 2324 2325 ErrorResult error; 2326 ParagraphStateAtSelection paragraphState( 2327 *this, FormatBlockMode::XULParagraphStateCommand, error); 2328 if (error.Failed()) { 2329 NS_WARNING("ParagraphStateAtSelection failed"); 2330 return error.StealNSResult(); 2331 } 2332 2333 *aMixed = paragraphState.IsMixed(); 2334 if (NS_WARN_IF(!paragraphState.GetFirstParagraphStateAtSelection())) { 2335 // XXX Odd result, but keep this behavior for now... 2336 aFirstParagraphState.AssignASCII("x"); 2337 } else { 2338 paragraphState.GetFirstParagraphStateAtSelection()->ToString( 2339 aFirstParagraphState); 2340 } 2341 return NS_OK; 2342 } 2343 2344 nsresult HTMLEditor::GetBackgroundColorState(bool* aMixed, 2345 nsAString& aOutColor) { 2346 if (NS_WARN_IF(!aMixed)) { 2347 return NS_ERROR_INVALID_ARG; 2348 } 2349 2350 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 2351 if (NS_WARN_IF(!editActionData.CanHandle())) { 2352 return NS_ERROR_NOT_INITIALIZED; 2353 } 2354 2355 if (IsCSSEnabled()) { 2356 // if we are in CSS mode, we have to check if the containing block defines 2357 // a background color 2358 nsresult rv = GetCSSBackgroundColorState( 2359 aMixed, aOutColor, 2360 {RetrievingBackgroundColorOption::OnlyBlockBackgroundColor, 2361 RetrievingBackgroundColorOption:: 2362 DefaultColorIfNoSpecificBackgroundColor}); 2363 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2364 "HTMLEditor::GetCSSBackgroundColorState() failed"); 2365 return EditorBase::ToGenericNSResult(rv); 2366 } 2367 // in HTML mode, we look only at page's background 2368 nsresult rv = GetHTMLBackgroundColorState(aMixed, aOutColor); 2369 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2370 "HTMLEditor::GetCSSBackgroundColorState() failed"); 2371 return EditorBase::ToGenericNSResult(rv); 2372 } 2373 2374 NS_IMETHODIMP HTMLEditor::GetHighlightColorState(bool* aMixed, 2375 nsAString& aOutColor) { 2376 if (NS_WARN_IF(!aMixed)) { 2377 return NS_ERROR_INVALID_ARG; 2378 } 2379 2380 *aMixed = false; 2381 aOutColor.AssignLiteral("transparent"); 2382 if (!IsCSSEnabled() && IsMailEditor()) { 2383 return NS_OK; 2384 } 2385 2386 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 2387 if (NS_WARN_IF(!editActionData.CanHandle())) { 2388 return NS_ERROR_NOT_INITIALIZED; 2389 } 2390 2391 // in CSS mode, text background can be added by the Text Highlight button 2392 // we need to query the background of the selection without looking for 2393 // the block container of the ranges in the selection 2394 RetrievingBackgroundColorOptions options; 2395 if (IsMailEditor()) { 2396 options += RetrievingBackgroundColorOption::StopAtInclusiveAncestorBlock; 2397 } 2398 nsresult rv = GetCSSBackgroundColorState(aMixed, aOutColor, options); 2399 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2400 "HTMLEditor::GetCSSBackgroundColorState() failed"); 2401 return rv; 2402 } 2403 2404 nsresult HTMLEditor::GetCSSBackgroundColorState( 2405 bool* aMixed, nsAString& aOutColor, 2406 RetrievingBackgroundColorOptions aOptions) { 2407 MOZ_ASSERT(IsEditActionDataAvailable()); 2408 2409 if (NS_WARN_IF(!aMixed)) { 2410 return NS_ERROR_INVALID_ARG; 2411 } 2412 2413 *aMixed = false; 2414 // the default background color is transparent 2415 aOutColor.AssignLiteral("transparent"); 2416 2417 RefPtr<const nsRange> firstRange = SelectionRef().GetRangeAt(0); 2418 if (NS_WARN_IF(!firstRange)) { 2419 return NS_ERROR_FAILURE; 2420 } 2421 2422 nsCOMPtr<nsINode> startContainer = firstRange->GetStartContainer(); 2423 if (NS_WARN_IF(!startContainer) || NS_WARN_IF(!startContainer->IsContent())) { 2424 return NS_ERROR_FAILURE; 2425 } 2426 2427 // is the selection collapsed? 2428 nsIContent* contentToExamine; 2429 if (SelectionRef().IsCollapsed() || startContainer->IsText()) { 2430 if (NS_WARN_IF(!startContainer->IsContent())) { 2431 return NS_ERROR_FAILURE; 2432 } 2433 // we want to look at the startContainer and ancestors 2434 contentToExamine = startContainer->AsContent(); 2435 } else { 2436 // otherwise we want to look at the first editable node after 2437 // {startContainer,offset} and its ancestors for divs with alignment on them 2438 contentToExamine = firstRange->GetChildAtStartOffset(); 2439 // GetNextNode(startContainer, offset, true, address_of(contentToExamine)); 2440 } 2441 2442 if (NS_WARN_IF(!contentToExamine)) { 2443 return NS_ERROR_FAILURE; 2444 } 2445 2446 if (aOptions.contains( 2447 RetrievingBackgroundColorOption::OnlyBlockBackgroundColor)) { 2448 // we are querying the block background (and not the text background), let's 2449 // climb to the block container. Note that background color of ancestor 2450 // of editing host may be what the caller wants to know. Therefore, we 2451 // should ignore the editing host boundaries. 2452 Element* const closestBlockElement = 2453 HTMLEditUtils::GetInclusiveAncestorElement( 2454 *contentToExamine, HTMLEditUtils::ClosestBlockElement, 2455 BlockInlineCheck::UseComputedDisplayOutsideStyle); 2456 if (NS_WARN_IF(!closestBlockElement)) { 2457 return NS_OK; 2458 } 2459 2460 for (RefPtr<Element> blockElement = closestBlockElement; blockElement;) { 2461 RefPtr<Element> nextBlockElement = HTMLEditUtils::GetAncestorElement( 2462 *blockElement, HTMLEditUtils::ClosestBlockElement, 2463 BlockInlineCheck::UseComputedDisplayOutsideStyle); 2464 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty( 2465 *blockElement, *nsGkAtoms::background_color, aOutColor); 2466 if (NS_WARN_IF(Destroyed())) { 2467 return NS_ERROR_EDITOR_DESTROYED; 2468 } 2469 if (MaybeNodeRemovalsObservedByDevTools() && 2470 NS_WARN_IF(nextBlockElement != 2471 HTMLEditUtils::GetAncestorElement( 2472 *blockElement, HTMLEditUtils::ClosestBlockElement, 2473 BlockInlineCheck::UseComputedDisplayOutsideStyle))) { 2474 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 2475 } 2476 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 2477 "CSSEditUtils::GetComputedProperty(nsGkAtoms::" 2478 "background_color) failed, but ignored"); 2479 // look at parent if the queried color is transparent and if the node to 2480 // examine is not the root of the document 2481 if (!HTMLEditUtils::IsTransparentCSSColor(aOutColor)) { 2482 return NS_OK; 2483 } 2484 if (aOptions.contains( 2485 RetrievingBackgroundColorOption::StopAtInclusiveAncestorBlock)) { 2486 aOutColor.AssignLiteral("transparent"); 2487 return NS_OK; 2488 } 2489 blockElement = std::move(nextBlockElement); 2490 } 2491 2492 if (aOptions.contains(RetrievingBackgroundColorOption:: 2493 DefaultColorIfNoSpecificBackgroundColor) && 2494 HTMLEditUtils::IsTransparentCSSColor(aOutColor)) { 2495 CSSEditUtils::GetDefaultBackgroundColor(aOutColor); 2496 } 2497 return NS_OK; 2498 } 2499 2500 // no, we are querying the text background for the Text Highlight button 2501 if (contentToExamine->IsText()) { 2502 // if the node of interest is a text node, let's climb a level 2503 contentToExamine = contentToExamine->GetParent(); 2504 } 2505 // Return default value due to no parent node 2506 if (!contentToExamine) { 2507 return NS_OK; 2508 } 2509 2510 for (RefPtr<Element> element : 2511 contentToExamine->InclusiveAncestorsOfType<Element>()) { 2512 // is the node to examine a block ? 2513 if (aOptions.contains( 2514 RetrievingBackgroundColorOption::StopAtInclusiveAncestorBlock) && 2515 HTMLEditUtils::IsBlockElement( 2516 *element, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 2517 // yes it is a block; in that case, the text background color is 2518 // transparent 2519 aOutColor.AssignLiteral("transparent"); 2520 break; 2521 } 2522 2523 // no, it's not; let's retrieve the computed style of background-color 2524 // for the node to examine 2525 nsCOMPtr<nsINode> parentNode = element->GetParentNode(); 2526 DebugOnly<nsresult> rvIgnored = CSSEditUtils::GetComputedProperty( 2527 *element, *nsGkAtoms::background_color, aOutColor); 2528 if (NS_WARN_IF(Destroyed())) { 2529 return NS_ERROR_EDITOR_DESTROYED; 2530 } 2531 if (NS_WARN_IF(parentNode != element->GetParentNode())) { 2532 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 2533 } 2534 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 2535 "CSSEditUtils::GetComputedProperty(nsGkAtoms::" 2536 "background_color) failed, but ignored"); 2537 if (!HTMLEditUtils::IsTransparentCSSColor(aOutColor)) { 2538 HTMLEditUtils::GetNormalizedCSSColorValue( 2539 aOutColor, HTMLEditUtils::ZeroAlphaColor::RGBAValue, aOutColor); 2540 return NS_OK; 2541 } 2542 } 2543 if (aOptions.contains(RetrievingBackgroundColorOption:: 2544 DefaultColorIfNoSpecificBackgroundColor) && 2545 HTMLEditUtils::IsTransparentCSSColor(aOutColor)) { 2546 CSSEditUtils::GetDefaultBackgroundColor(aOutColor); 2547 } 2548 return NS_OK; 2549 } 2550 2551 nsresult HTMLEditor::GetHTMLBackgroundColorState(bool* aMixed, 2552 nsAString& aOutColor) { 2553 MOZ_ASSERT(IsEditActionDataAvailable()); 2554 2555 // TODO: We don't handle "mixed" correctly! 2556 if (NS_WARN_IF(!aMixed)) { 2557 return NS_ERROR_INVALID_ARG; 2558 } 2559 2560 *aMixed = false; 2561 aOutColor.Truncate(); 2562 2563 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError = 2564 GetSelectedOrParentTableElement(); 2565 if (cellOrRowOrTableElementOrError.isErr()) { 2566 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() returned error"); 2567 return cellOrRowOrTableElementOrError.unwrapErr(); 2568 } 2569 2570 for (RefPtr<Element> element = cellOrRowOrTableElementOrError.unwrap(); 2571 element; element = element->GetParentElement()) { 2572 // We are in a cell or selected table 2573 element->GetAttr(nsGkAtoms::bgcolor, aOutColor); 2574 2575 // Done if we have a color explicitly set 2576 if (!aOutColor.IsEmpty()) { 2577 return NS_OK; 2578 } 2579 2580 // Once we hit the body, we're done 2581 if (element->IsHTMLElement(nsGkAtoms::body)) { 2582 return NS_OK; 2583 } 2584 2585 // No color is set, but we need to report visible color inherited 2586 // from nested cells/tables, so search up parent chain so that 2587 // let's keep checking the ancestors. 2588 } 2589 2590 // If no table or cell found, get page body 2591 Element* rootElement = GetRoot(); 2592 if (NS_WARN_IF(!rootElement)) { 2593 return NS_ERROR_FAILURE; 2594 } 2595 2596 rootElement->GetAttr(nsGkAtoms::bgcolor, aOutColor); 2597 return NS_OK; 2598 } 2599 2600 NS_IMETHODIMP HTMLEditor::GetListState(bool* aMixed, bool* aOL, bool* aUL, 2601 bool* aDL) { 2602 if (NS_WARN_IF(!aMixed) || NS_WARN_IF(!aOL) || NS_WARN_IF(!aUL) || 2603 NS_WARN_IF(!aDL)) { 2604 return NS_ERROR_INVALID_ARG; 2605 } 2606 if (!mInitSucceeded) { 2607 return NS_ERROR_NOT_INITIALIZED; 2608 } 2609 2610 ErrorResult error; 2611 ListElementSelectionState state(*this, error); 2612 if (error.Failed()) { 2613 NS_WARNING("ListElementSelectionState failed"); 2614 return error.StealNSResult(); 2615 } 2616 2617 *aMixed = state.IsNotOneTypeListElementSelected(); 2618 *aOL = state.IsOLElementSelected(); 2619 *aUL = state.IsULElementSelected(); 2620 *aDL = state.IsDLElementSelected(); 2621 return NS_OK; 2622 } 2623 2624 NS_IMETHODIMP HTMLEditor::GetListItemState(bool* aMixed, bool* aLI, bool* aDT, 2625 bool* aDD) { 2626 if (NS_WARN_IF(!aMixed) || NS_WARN_IF(!aLI) || NS_WARN_IF(!aDT) || 2627 NS_WARN_IF(!aDD)) { 2628 return NS_ERROR_INVALID_ARG; 2629 } 2630 if (!mInitSucceeded) { 2631 return NS_ERROR_NOT_INITIALIZED; 2632 } 2633 2634 ErrorResult error; 2635 ListItemElementSelectionState state(*this, error); 2636 if (error.Failed()) { 2637 NS_WARNING("ListItemElementSelectionState failed"); 2638 return error.StealNSResult(); 2639 } 2640 2641 // XXX Why do we ignore `<li>` element selected state? 2642 *aMixed = state.IsNotOneTypeDefinitionListItemElementSelected(); 2643 *aLI = state.IsLIElementSelected(); 2644 *aDT = state.IsDTElementSelected(); 2645 *aDD = state.IsDDElementSelected(); 2646 return NS_OK; 2647 } 2648 2649 NS_IMETHODIMP HTMLEditor::GetAlignment(bool* aMixed, 2650 nsIHTMLEditor::EAlignment* aAlign) { 2651 if (NS_WARN_IF(!aMixed) || NS_WARN_IF(!aAlign)) { 2652 return NS_ERROR_INVALID_ARG; 2653 } 2654 if (!mInitSucceeded) { 2655 return NS_ERROR_NOT_INITIALIZED; 2656 } 2657 2658 ErrorResult error; 2659 AlignStateAtSelection state(*this, error); 2660 if (error.Failed()) { 2661 NS_WARNING("AlignStateAtSelection failed"); 2662 return error.StealNSResult(); 2663 } 2664 2665 *aMixed = false; 2666 *aAlign = state.AlignmentAtSelectionStart(); 2667 return NS_OK; 2668 } 2669 2670 NS_IMETHODIMP HTMLEditor::MakeOrChangeList(const nsAString& aListType, 2671 bool aEntireList, 2672 const nsAString& aBulletType) { 2673 RefPtr<nsAtom> listTagName = NS_Atomize(aListType); 2674 if (NS_WARN_IF(!listTagName) || NS_WARN_IF(!listTagName->IsStatic())) { 2675 return NS_ERROR_INVALID_ARG; 2676 } 2677 // MOZ_KnownLive(listTagName->AsStatic()) because nsStaticAtom instances live 2678 // while the process is running. 2679 nsresult rv = MakeOrChangeListAsAction( 2680 MOZ_KnownLive(*listTagName->AsStatic()), aBulletType, 2681 aEntireList ? SelectAllOfCurrentList::Yes : SelectAllOfCurrentList::No); 2682 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2683 "HTMLEditor::MakeOrChangeListAsAction() failed"); 2684 return rv; 2685 } 2686 2687 nsresult HTMLEditor::MakeOrChangeListAsAction( 2688 const nsStaticAtom& aListElementTagName, const nsAString& aBulletType, 2689 SelectAllOfCurrentList aSelectAllOfCurrentList, nsIPrincipal* aPrincipal) { 2690 if (NS_WARN_IF(!mInitSucceeded)) { 2691 return NS_ERROR_NOT_INITIALIZED; 2692 } 2693 2694 AutoEditActionDataSetter editActionData( 2695 *this, HTMLEditUtils::GetEditActionForInsert(aListElementTagName), 2696 aPrincipal); 2697 if (NS_WARN_IF(!editActionData.CanHandle())) { 2698 return NS_ERROR_NOT_INITIALIZED; 2699 } 2700 2701 const RefPtr<Element> editingHost = 2702 ComputeEditingHost(LimitInBodyElement::No); 2703 if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) { 2704 return NS_SUCCESS_DOM_NO_OPERATION; 2705 } 2706 2707 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 2708 if (NS_FAILED(rv)) { 2709 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2710 "MaybeDispatchBeforeInputEvent(), failed"); 2711 return EditorBase::ToGenericNSResult(rv); 2712 } 2713 2714 Result<EditActionResult, nsresult> result = 2715 MakeOrChangeListAndListItemAsSubAction(aListElementTagName, aBulletType, 2716 aSelectAllOfCurrentList, 2717 *editingHost); 2718 if (MOZ_UNLIKELY(result.isErr())) { 2719 NS_WARNING("HTMLEditor::MakeOrChangeListAndListItemAsSubAction() failed"); 2720 return EditorBase::ToGenericNSResult(result.unwrapErr()); 2721 } 2722 return NS_OK; 2723 } 2724 2725 NS_IMETHODIMP HTMLEditor::RemoveList(const nsAString& aListType) { 2726 nsresult rv = RemoveListAsAction(aListType); 2727 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2728 "HTMLEditor::RemoveListAsAction() failed"); 2729 return rv; 2730 } 2731 2732 nsresult HTMLEditor::RemoveListAsAction(const nsAString& aListType, 2733 nsIPrincipal* aPrincipal) { 2734 if (NS_WARN_IF(!mInitSucceeded)) { 2735 return NS_ERROR_NOT_INITIALIZED; 2736 } 2737 2738 // Note that we ignore aListType when we actually remove parent list elements. 2739 // However, we need to set InputEvent.inputType to "insertOrderedList" or 2740 // "insertedUnorderedList" when this is called for 2741 // execCommand("insertorderedlist") or execCommand("insertunorderedlist"). 2742 // Otherwise, comm-central UI may call this methods with "dl" or "". 2743 // So, it's okay to use mismatched EditAction here if this is called in 2744 // comm-central. 2745 2746 RefPtr<nsAtom> listAtom = NS_Atomize(aListType); 2747 if (NS_WARN_IF(!listAtom)) { 2748 return NS_ERROR_INVALID_ARG; 2749 } 2750 AutoEditActionDataSetter editActionData( 2751 *this, HTMLEditUtils::GetEditActionForRemoveList(*listAtom), aPrincipal); 2752 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 2753 if (NS_FAILED(rv)) { 2754 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2755 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 2756 return EditorBase::ToGenericNSResult(rv); 2757 } 2758 2759 const RefPtr<Element> editingHost = ComputeEditingHost(); 2760 if (!editingHost) { 2761 return NS_SUCCESS_DOM_NO_OPERATION; 2762 } 2763 2764 rv = RemoveListAtSelectionAsSubAction(*editingHost); 2765 NS_WARNING_ASSERTION(NS_FAILED(rv), 2766 "HTMLEditor::RemoveListAtSelectionAsSubAction() failed"); 2767 return rv; 2768 } 2769 2770 nsresult HTMLEditor::FormatBlockContainerAsSubAction( 2771 const nsStaticAtom& aTagName, FormatBlockMode aFormatBlockMode, 2772 const Element& aEditingHost) { 2773 MOZ_ASSERT(IsEditActionDataAvailable()); 2774 2775 if (NS_WARN_IF(!mInitSucceeded)) { 2776 return NS_ERROR_NOT_INITIALIZED; 2777 } 2778 2779 MOZ_ASSERT(&aTagName != nsGkAtoms::dd && &aTagName != nsGkAtoms::dt); 2780 2781 AutoPlaceholderBatch treatAsOneTransaction( 2782 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 2783 IgnoredErrorResult ignoredError; 2784 AutoEditSubActionNotifier startToHandleEditSubAction( 2785 *this, EditSubAction::eCreateOrRemoveBlock, nsIEditor::eNext, 2786 ignoredError); 2787 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 2788 return ignoredError.StealNSResult(); 2789 } 2790 NS_WARNING_ASSERTION( 2791 !ignoredError.Failed(), 2792 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 2793 2794 { 2795 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 2796 if (MOZ_UNLIKELY(result.isErr())) { 2797 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 2798 return result.unwrapErr(); 2799 } 2800 if (result.inspect().Canceled()) { 2801 return NS_OK; 2802 } 2803 } 2804 2805 if (IsSelectionRangeContainerNotContent()) { 2806 return NS_SUCCESS_DOM_NO_OPERATION; 2807 } 2808 2809 nsresult rv = EnsureNoPaddingBRElementForEmptyEditor(); 2810 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2811 return NS_ERROR_EDITOR_DESTROYED; 2812 } 2813 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2814 "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() " 2815 "failed, but ignored"); 2816 2817 if (NS_SUCCEEDED(rv) && SelectionRef().IsCollapsed()) { 2818 nsresult rv = EnsureCaretNotAfterInvisibleBRElement(aEditingHost); 2819 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2820 return NS_ERROR_EDITOR_DESTROYED; 2821 } 2822 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 2823 "HTMLEditor::EnsureCaretNotAfterInvisibleBRElement() " 2824 "failed, but ignored"); 2825 if (NS_SUCCEEDED(rv)) { 2826 nsresult rv = PrepareInlineStylesForCaret(); 2827 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2828 return NS_ERROR_EDITOR_DESTROYED; 2829 } 2830 NS_WARNING_ASSERTION( 2831 NS_SUCCEEDED(rv), 2832 "HTMLEditor::PrepareInlineStylesForCaret() failed, but ignored"); 2833 } 2834 } 2835 2836 // FormatBlockContainerWithTransaction() creates AutoSelectionRestorer. 2837 // Therefore, even if it returns NS_OK, editor might have been destroyed 2838 // at restoring Selection. 2839 AutoClonedSelectionRangeArray selectionRanges(SelectionRef()); 2840 Result<RefPtr<Element>, nsresult> suggestBlockElementToPutCaretOrError = 2841 FormatBlockContainerWithTransaction(selectionRanges, aTagName, 2842 aFormatBlockMode, aEditingHost); 2843 if (suggestBlockElementToPutCaretOrError.isErr()) { 2844 NS_WARNING("HTMLEditor::FormatBlockContainerWithTransaction() failed"); 2845 return suggestBlockElementToPutCaretOrError.unwrapErr(); 2846 } 2847 2848 if (selectionRanges.HasSavedRanges()) { 2849 selectionRanges.RestoreFromSavedRanges(); 2850 } 2851 2852 if (selectionRanges.IsCollapsed()) { 2853 // FIXME: If we get rid of the legacy mutation events, we should be able to 2854 // just insert a line break without empty check. 2855 Result<CreateLineBreakResult, nsresult> 2856 insertPaddingBRElementResultOrError = 2857 InsertPaddingBRElementIfInEmptyBlock( 2858 selectionRanges.GetFirstRangeStartPoint<EditorDOMPoint>(), 2859 eNoStrip); 2860 if (MOZ_UNLIKELY(insertPaddingBRElementResultOrError.isErr())) { 2861 NS_WARNING( 2862 "HTMLEditor::InsertPaddingBRElementIfInEmptyBlock(eNoStrip) failed"); 2863 return insertPaddingBRElementResultOrError.unwrapErr(); 2864 } 2865 EditorDOMPoint pointToPutCaret; 2866 insertPaddingBRElementResultOrError.unwrap().MoveCaretPointTo( 2867 pointToPutCaret, *this, 2868 {SuggestCaret::OnlyIfHasSuggestion, 2869 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 2870 if (pointToPutCaret.IsSet()) { 2871 nsresult rv = selectionRanges.Collapse(pointToPutCaret); 2872 if (NS_FAILED(rv)) { 2873 NS_WARNING("AutoClonedRangeArray::Collapse() failed"); 2874 return rv; 2875 } 2876 } 2877 } 2878 2879 if (!suggestBlockElementToPutCaretOrError.inspect() || 2880 !selectionRanges.IsCollapsed()) { 2881 nsresult rv = selectionRanges.ApplyTo(SelectionRef()); 2882 if (NS_WARN_IF(Destroyed())) { 2883 return NS_ERROR_EDITOR_DESTROYED; 2884 } 2885 NS_WARNING_ASSERTION( 2886 NS_SUCCEEDED(rv), 2887 "AutoClonedSelectionRangeArray::ApplyTo() failed, but ignored"); 2888 return rv; 2889 } 2890 2891 const auto firstSelectionStartPoint = 2892 selectionRanges.GetFirstRangeStartPoint<EditorRawDOMPoint>(); 2893 if (NS_WARN_IF(!firstSelectionStartPoint.IsSetAndValidInComposedDoc())) { 2894 return NS_ERROR_FAILURE; 2895 } 2896 Result<EditorRawDOMPoint, nsresult> pointInBlockElementOrError = 2897 HTMLEditUtils::ComputePointToPutCaretInElementIfOutside< 2898 EditorRawDOMPoint>(*suggestBlockElementToPutCaretOrError.inspect(), 2899 firstSelectionStartPoint); 2900 NS_WARNING_ASSERTION( 2901 pointInBlockElementOrError.isOk(), 2902 "HTMLEditUtils::ComputePointToPutCaretInElementIfOutside() failed, but " 2903 "ignored"); 2904 // Note that if the point is unset, it means that firstSelectionStartPoint is 2905 // in the block element. 2906 if (MOZ_LIKELY(pointInBlockElementOrError.isOk()) && 2907 pointInBlockElementOrError.inspect().IsSet()) { 2908 nsresult rv = 2909 selectionRanges.Collapse(pointInBlockElementOrError.inspect()); 2910 if (NS_FAILED(rv)) { 2911 NS_WARNING("AutoClonedRangeArray::Collapse() failed"); 2912 return rv; 2913 } 2914 } 2915 2916 rv = selectionRanges.ApplyTo(SelectionRef()); 2917 if (NS_WARN_IF(Destroyed())) { 2918 return NS_ERROR_EDITOR_DESTROYED; 2919 } 2920 NS_WARNING_ASSERTION( 2921 NS_SUCCEEDED(rv), 2922 "AutoClonedSelectionRangeArray::ApplyTo() failed, but ignored"); 2923 return rv; 2924 } 2925 2926 nsresult HTMLEditor::IndentAsAction(nsIPrincipal* aPrincipal) { 2927 if (NS_WARN_IF(!mInitSucceeded)) { 2928 return NS_ERROR_NOT_INITIALIZED; 2929 } 2930 2931 AutoEditActionDataSetter editActionData(*this, EditAction::eIndent, 2932 aPrincipal); 2933 if (NS_WARN_IF(!editActionData.CanHandle())) { 2934 return NS_ERROR_NOT_INITIALIZED; 2935 } 2936 2937 const RefPtr<Element> editingHost = 2938 ComputeEditingHost(LimitInBodyElement::No); 2939 if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) { 2940 return NS_SUCCESS_DOM_NO_OPERATION; 2941 } 2942 2943 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 2944 if (NS_FAILED(rv)) { 2945 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2946 "MaybeDispatchBeforeInputEvent(), failed"); 2947 return EditorBase::ToGenericNSResult(rv); 2948 } 2949 2950 Result<EditActionResult, nsresult> result = IndentAsSubAction(*editingHost); 2951 if (MOZ_UNLIKELY(result.isErr())) { 2952 NS_WARNING("HTMLEditor::IndentAsSubAction() failed"); 2953 return EditorBase::ToGenericNSResult(result.unwrapErr()); 2954 } 2955 return NS_OK; 2956 } 2957 2958 nsresult HTMLEditor::OutdentAsAction(nsIPrincipal* aPrincipal) { 2959 if (NS_WARN_IF(!mInitSucceeded)) { 2960 return NS_ERROR_NOT_INITIALIZED; 2961 } 2962 2963 AutoEditActionDataSetter editActionData(*this, EditAction::eOutdent, 2964 aPrincipal); 2965 if (NS_WARN_IF(!editActionData.CanHandle())) { 2966 return NS_ERROR_NOT_INITIALIZED; 2967 } 2968 2969 const RefPtr<Element> editingHost = 2970 ComputeEditingHost(LimitInBodyElement::No); 2971 if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) { 2972 return NS_SUCCESS_DOM_NO_OPERATION; 2973 } 2974 2975 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 2976 if (NS_FAILED(rv)) { 2977 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 2978 "MaybeDispatchBeforeInputEvent(), failed"); 2979 return EditorBase::ToGenericNSResult(rv); 2980 } 2981 2982 Result<EditActionResult, nsresult> result = OutdentAsSubAction(*editingHost); 2983 if (MOZ_UNLIKELY(result.isErr())) { 2984 NS_WARNING("HTMLEditor::OutdentAsSubAction() failed"); 2985 return EditorBase::ToGenericNSResult(result.unwrapErr()); 2986 } 2987 return NS_OK; 2988 } 2989 2990 // TODO: IMPLEMENT ALIGNMENT! 2991 2992 nsresult HTMLEditor::AlignAsAction(const nsAString& aAlignType, 2993 nsIPrincipal* aPrincipal) { 2994 AutoEditActionDataSetter editActionData( 2995 *this, HTMLEditUtils::GetEditActionForAlignment(aAlignType), aPrincipal); 2996 if (NS_WARN_IF(!editActionData.CanHandle())) { 2997 return NS_ERROR_NOT_INITIALIZED; 2998 } 2999 3000 const RefPtr<Element> editingHost = 3001 ComputeEditingHost(LimitInBodyElement::No); 3002 if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) { 3003 return NS_SUCCESS_DOM_NO_OPERATION; 3004 } 3005 3006 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 3007 if (NS_FAILED(rv)) { 3008 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3009 "MaybeDispatchBeforeInputEvent(), failed"); 3010 return EditorBase::ToGenericNSResult(rv); 3011 } 3012 3013 Result<EditActionResult, nsresult> result = 3014 AlignAsSubAction(aAlignType, *editingHost); 3015 if (MOZ_UNLIKELY(result.isErr())) { 3016 NS_WARNING("HTMLEditor::AlignAsSubAction() failed"); 3017 return EditorBase::ToGenericNSResult(result.unwrapErr()); 3018 } 3019 return NS_OK; 3020 } 3021 3022 Element* HTMLEditor::GetInclusiveAncestorByTagName(const nsStaticAtom& aTagName, 3023 nsIContent& aContent) const { 3024 MOZ_ASSERT(&aTagName != nsGkAtoms::_empty); 3025 3026 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 3027 if (NS_WARN_IF(!editActionData.CanHandle())) { 3028 return nullptr; 3029 } 3030 3031 return GetInclusiveAncestorByTagNameInternal(aTagName, aContent); 3032 } 3033 3034 Element* HTMLEditor::GetInclusiveAncestorByTagNameAtSelection( 3035 const nsStaticAtom& aTagName) const { 3036 MOZ_ASSERT(IsEditActionDataAvailable()); 3037 MOZ_ASSERT(&aTagName != nsGkAtoms::_empty); 3038 3039 // If no node supplied, get it from anchor node of current selection 3040 const EditorRawDOMPoint atAnchor(SelectionRef().AnchorRef()); 3041 if (NS_WARN_IF(!atAnchor.IsInContentNode())) { 3042 return nullptr; 3043 } 3044 3045 // Try to get the actual selected node 3046 nsIContent* content = nullptr; 3047 if (atAnchor.GetContainer()->HasChildNodes() && 3048 atAnchor.ContainerAs<nsIContent>()) { 3049 content = atAnchor.GetChild(); 3050 } 3051 // Anchor node is probably a text node - just use that 3052 if (!content) { 3053 content = atAnchor.ContainerAs<nsIContent>(); 3054 if (NS_WARN_IF(!content)) { 3055 return nullptr; 3056 } 3057 } 3058 return GetInclusiveAncestorByTagNameInternal(aTagName, *content); 3059 } 3060 3061 Element* HTMLEditor::GetInclusiveAncestorByTagNameInternal( 3062 const nsStaticAtom& aTagName, const nsIContent& aContent) const { 3063 MOZ_ASSERT(&aTagName != nsGkAtoms::_empty); 3064 3065 Element* currentElement = aContent.GetAsElementOrParentElement(); 3066 if (NS_WARN_IF(!currentElement)) { 3067 MOZ_ASSERT(!aContent.GetParentNode()); 3068 return nullptr; 3069 } 3070 3071 bool lookForLink = IsLinkTag(aTagName); 3072 bool lookForNamedAnchor = IsNamedAnchorTag(aTagName); 3073 for (Element* const element : 3074 currentElement->InclusiveAncestorsOfType<Element>()) { 3075 // Stop searching if parent is a body element. Note: Originally used 3076 // IsRoot() to/ stop at table cells, but that's too messy when you are 3077 // trying to find the parent table. 3078 if (element->IsHTMLElement(nsGkAtoms::body)) { 3079 return nullptr; 3080 } 3081 if (lookForLink) { 3082 // Test if we have a link (an anchor with href set) 3083 if (HTMLEditUtils::IsHyperlinkElement(*element)) { 3084 return element; 3085 } 3086 } else if (lookForNamedAnchor) { 3087 // Test if we have a named anchor (an anchor with name set) 3088 if (HTMLEditUtils::IsNamedAnchorElement(*element)) { 3089 return element; 3090 } 3091 } else if (&aTagName == nsGkAtoms::list) { 3092 // Match "ol", "ul", or "dl" for lists 3093 if (HTMLEditUtils::IsListElement(*element)) { 3094 return element; 3095 } 3096 } else if (&aTagName == nsGkAtoms::td) { 3097 // Table cells are another special case: match either "td" or "th" 3098 if (HTMLEditUtils::IsTableCellElement(*element)) { 3099 return element; 3100 } 3101 } else if (&aTagName == element->NodeInfo()->NameAtom()) { 3102 return element; 3103 } 3104 } 3105 return nullptr; 3106 } 3107 3108 NS_IMETHODIMP HTMLEditor::GetElementOrParentByTagName(const nsAString& aTagName, 3109 nsINode* aNode, 3110 Element** aReturn) { 3111 if (NS_WARN_IF(aTagName.IsEmpty()) || NS_WARN_IF(!aReturn)) { 3112 return NS_ERROR_INVALID_ARG; 3113 } 3114 3115 nsStaticAtom* tagName = EditorUtils::GetTagNameAtom(aTagName); 3116 if (NS_WARN_IF(!tagName)) { 3117 // We don't need to support custom elements since this is an internal API. 3118 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND; 3119 } 3120 if (NS_WARN_IF(tagName == nsGkAtoms::_empty)) { 3121 return NS_ERROR_INVALID_ARG; 3122 } 3123 3124 if (!aNode) { 3125 AutoEditActionDataSetter dummyEditAction(*this, EditAction::eNotEditing); 3126 if (NS_WARN_IF(!dummyEditAction.CanHandle())) { 3127 return NS_ERROR_NOT_AVAILABLE; 3128 } 3129 RefPtr<Element> parentElement = 3130 GetInclusiveAncestorByTagNameAtSelection(*tagName); 3131 if (!parentElement) { 3132 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND; 3133 } 3134 parentElement.forget(aReturn); 3135 return NS_OK; 3136 } 3137 3138 if (!aNode->IsContent() || !aNode->GetAsElementOrParentElement()) { 3139 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND; 3140 } 3141 3142 RefPtr<Element> parentElement = 3143 GetInclusiveAncestorByTagName(*tagName, *aNode->AsContent()); 3144 if (!parentElement) { 3145 return NS_SUCCESS_EDITOR_ELEMENT_NOT_FOUND; 3146 } 3147 parentElement.forget(aReturn); 3148 return NS_OK; 3149 } 3150 3151 NS_IMETHODIMP HTMLEditor::GetSelectedElement(const nsAString& aTagName, 3152 nsISupports** aReturn) { 3153 if (NS_WARN_IF(!aReturn)) { 3154 return NS_ERROR_INVALID_ARG; 3155 } 3156 *aReturn = nullptr; 3157 3158 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 3159 if (NS_WARN_IF(!editActionData.CanHandle())) { 3160 return NS_ERROR_NOT_INITIALIZED; 3161 } 3162 3163 ErrorResult error; 3164 nsStaticAtom* tagName = EditorUtils::GetTagNameAtom(aTagName); 3165 if (!aTagName.IsEmpty() && !tagName) { 3166 // We don't need to support custom elements becaus of internal API. 3167 return NS_OK; 3168 } 3169 RefPtr<nsINode> selectedNode = GetSelectedElement(tagName, error); 3170 NS_WARNING_ASSERTION(!error.Failed(), 3171 "HTMLEditor::GetSelectedElement() failed"); 3172 selectedNode.forget(aReturn); 3173 return error.StealNSResult(); 3174 } 3175 3176 already_AddRefed<Element> HTMLEditor::GetSelectedElement(const nsAtom* aTagName, 3177 ErrorResult& aRv) { 3178 MOZ_ASSERT(IsEditActionDataAvailable()); 3179 3180 MOZ_ASSERT(!aRv.Failed()); 3181 3182 // If there is no Selection or two or more selection ranges, that means that 3183 // not only one element is selected so that return nullptr. 3184 if (SelectionRef().RangeCount() != 1) { 3185 return nullptr; 3186 } 3187 3188 bool isLinkTag = aTagName && IsLinkTag(*aTagName); 3189 bool isNamedAnchorTag = aTagName && IsNamedAnchorTag(*aTagName); 3190 3191 RefPtr<nsRange> firstRange = SelectionRef().GetRangeAt(0); 3192 MOZ_ASSERT(firstRange); 3193 3194 const RangeBoundary& startRef = firstRange->StartRef(); 3195 if (NS_WARN_IF(!startRef.IsSet())) { 3196 aRv.Throw(NS_ERROR_FAILURE); 3197 return nullptr; 3198 } 3199 const RangeBoundary& endRef = firstRange->EndRef(); 3200 if (NS_WARN_IF(!endRef.IsSet())) { 3201 aRv.Throw(NS_ERROR_FAILURE); 3202 return nullptr; 3203 } 3204 3205 // Optimization for a single selected element 3206 if (startRef.GetContainer() == endRef.GetContainer()) { 3207 nsIContent* const startContent = startRef.GetChildAtOffset(); 3208 nsIContent* const endContent = endRef.GetChildAtOffset(); 3209 if (startContent && endContent && 3210 startContent->GetNextSibling() == endContent) { 3211 if (!aTagName) { 3212 if (!startContent->IsElement()) { 3213 // This means only a text node or something is selected. We should 3214 // return nullptr in this case since no other elements are selected. 3215 return nullptr; 3216 } 3217 return do_AddRef(startContent->AsElement()); 3218 } 3219 // Test for appropriate node type requested 3220 if (aTagName == startContent->NodeInfo()->NameAtom() || 3221 (isLinkTag && HTMLEditUtils::IsHyperlinkElement(*startContent)) || 3222 (isNamedAnchorTag && 3223 HTMLEditUtils::IsNamedAnchorElement(*startContent))) { 3224 MOZ_ASSERT(startContent->IsElement()); 3225 return do_AddRef(startContent->AsElement()); 3226 } 3227 } 3228 } 3229 3230 if (isLinkTag && startRef.GetContainer()->IsContent() && 3231 endRef.GetContainer()->IsContent()) { 3232 // Link node must be the same for both ends of selection. 3233 Element* parentLinkOfStart = GetInclusiveAncestorByTagNameInternal( 3234 *nsGkAtoms::href, *startRef.GetContainer()->AsContent()); 3235 if (parentLinkOfStart) { 3236 if (SelectionRef().IsCollapsed()) { 3237 // We have just a caret in the link. 3238 return do_AddRef(parentLinkOfStart); 3239 } 3240 // Link node must be the same for both ends of selection. 3241 Element* parentLinkOfEnd = GetInclusiveAncestorByTagNameInternal( 3242 *nsGkAtoms::href, *endRef.GetContainer()->AsContent()); 3243 if (parentLinkOfStart == parentLinkOfEnd) { 3244 return do_AddRef(parentLinkOfStart); 3245 } 3246 } 3247 } 3248 3249 if (SelectionRef().IsCollapsed()) { 3250 return nullptr; 3251 } 3252 3253 PostContentIterator postOrderIter; 3254 nsresult rv = postOrderIter.Init(firstRange); 3255 if (NS_FAILED(rv)) { 3256 aRv.Throw(rv); 3257 return nullptr; 3258 } 3259 3260 RefPtr<Element> lastElementInRange; 3261 for (nsINode* lastNodeInRange = nullptr; !postOrderIter.IsDone(); 3262 postOrderIter.Next()) { 3263 if (lastElementInRange) { 3264 // When any node follows an element node, not only one element is 3265 // selected so that return nullptr. 3266 return nullptr; 3267 } 3268 3269 // This loop ignors any non-element nodes before first element node. 3270 // Its purpose must be that this method treats this case as selecting 3271 // the <b> element: 3272 // - <p>abc <b>d[ef</b>}</p> 3273 // because children of an element node is listed up before the element. 3274 // However, this case must not be expected by the initial developer: 3275 // - <p>a[bc <b>def</b>}</p> 3276 // When we meet non-parent and non-next-sibling node of previous node, 3277 // it means that the range across element boundary (open tag in HTML 3278 // source). So, in this case, we should not say only the following 3279 // element is selected. 3280 nsINode* currentNode = postOrderIter.GetCurrentNode(); 3281 MOZ_ASSERT(currentNode); 3282 if (lastNodeInRange && lastNodeInRange->GetParentNode() != currentNode && 3283 lastNodeInRange->GetNextSibling() != currentNode) { 3284 return nullptr; 3285 } 3286 3287 lastNodeInRange = currentNode; 3288 3289 lastElementInRange = Element::FromNodeOrNull(lastNodeInRange); 3290 if (!lastElementInRange) { 3291 continue; 3292 } 3293 3294 // And also, if it's followed by a <br> element, we shouldn't treat the 3295 // the element is selected like this case: 3296 // - <p><b>[def</b>}<br></p> 3297 // Note that we don't need special handling for <a href> because double 3298 // clicking it selects the element and we use the first path to handle it. 3299 // Additionally, we have this case too: 3300 // - <p><b>[def</b><b>}<br></b></p> 3301 // In these cases, the <br> element is not listed up by PostContentIterator. 3302 // So, we should return nullptr if next sibling is a `<br>` element or 3303 // next sibling starts with `<br>` element. 3304 if (nsIContent* nextSibling = lastElementInRange->GetNextSibling()) { 3305 if (nextSibling->IsHTMLElement(nsGkAtoms::br)) { 3306 return nullptr; 3307 } 3308 nsIContent* firstEditableLeaf = HTMLEditUtils::GetFirstLeafContent( 3309 *nextSibling, {LeafNodeType::OnlyLeafNode}); 3310 if (firstEditableLeaf && 3311 firstEditableLeaf->IsHTMLElement(nsGkAtoms::br)) { 3312 return nullptr; 3313 } 3314 } 3315 3316 if (!aTagName) { 3317 continue; 3318 } 3319 3320 if (isLinkTag && HTMLEditUtils::IsHyperlinkElement(*lastElementInRange)) { 3321 continue; 3322 } 3323 3324 if (isNamedAnchorTag && 3325 HTMLEditUtils::IsNamedAnchorElement(*lastElementInRange)) { 3326 continue; 3327 } 3328 3329 if (aTagName == lastElementInRange->NodeInfo()->NameAtom()) { 3330 continue; 3331 } 3332 3333 // First element in the range does not match what the caller is looking 3334 // for. 3335 return nullptr; 3336 } 3337 return lastElementInRange.forget(); 3338 } 3339 3340 Result<CreateElementResult, nsresult> HTMLEditor::CreateAndInsertElement( 3341 WithTransaction aWithTransaction, const nsAtom& aTagName, 3342 const EditorDOMPoint& aPointToInsert, 3343 const InitializeInsertingElement& aInitializer) { 3344 MOZ_ASSERT(IsEditActionDataAvailable()); 3345 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 3346 3347 // XXX We need offset at new node for RangeUpdaterRef(). Therefore, we need 3348 // to compute the offset now but this is expensive. So, if it's possible, 3349 // we need to redesign RangeUpdaterRef() as avoiding using indices. 3350 (void)aPointToInsert.Offset(); 3351 3352 IgnoredErrorResult ignoredError; 3353 AutoEditSubActionNotifier startToHandleEditSubAction( 3354 *this, EditSubAction::eCreateNode, nsIEditor::eNext, ignoredError); 3355 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3356 return Err(NS_ERROR_EDITOR_DESTROYED); 3357 } 3358 NS_WARNING_ASSERTION( 3359 !ignoredError.Failed(), 3360 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3361 3362 // TODO: This method should have a callback function which is called 3363 // immediately after creating an element but before it's inserted into 3364 // the DOM tree. Then, caller can init the new element's attributes 3365 // and children **without** transactions (it'll reduce the number of 3366 // legacy mutation events). Finally, we can get rid of 3367 // CreatElementTransaction since we can use InsertNodeTransaction 3368 // instead. 3369 3370 auto createNewElementResult = 3371 [&]() MOZ_CAN_RUN_SCRIPT -> Result<CreateElementResult, nsresult> { 3372 RefPtr<Element> newElement = CreateHTMLContent(&aTagName); 3373 if (MOZ_UNLIKELY(!newElement)) { 3374 NS_WARNING("EditorBase::CreateHTMLContent() failed"); 3375 return Err(NS_ERROR_FAILURE); 3376 } 3377 nsresult rv = MarkElementDirty(*newElement); 3378 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 3379 NS_WARNING("EditorBase::MarkElementDirty() caused destroying the editor"); 3380 return Err(NS_ERROR_EDITOR_DESTROYED); 3381 } 3382 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3383 "EditorBase::MarkElementDirty() failed, but ignored"); 3384 rv = aInitializer(*this, *newElement, aPointToInsert); 3385 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "aInitializer failed"); 3386 if (NS_WARN_IF(Destroyed())) { 3387 return Err(NS_ERROR_EDITOR_DESTROYED); 3388 } 3389 RefPtr<InsertNodeTransaction> transaction = 3390 InsertNodeTransaction::Create(*this, *newElement, aPointToInsert); 3391 rv = aWithTransaction == WithTransaction::Yes 3392 ? DoTransactionInternal(transaction) 3393 : transaction->DoTransaction(); 3394 // FYI: Transaction::DoTransaction never returns NS_ERROR_EDITOR_*. 3395 if (MOZ_UNLIKELY(Destroyed())) { 3396 NS_WARNING( 3397 "InsertNodeTransaction::DoTransaction() caused destroying the " 3398 "editor"); 3399 return Err(NS_ERROR_EDITOR_DESTROYED); 3400 } 3401 if (NS_FAILED(rv)) { 3402 NS_WARNING("InsertNodeTransaction::DoTransaction() failed"); 3403 return Err(rv); 3404 } 3405 // Override the success code if new element was moved by the web apps. 3406 if (newElement && 3407 newElement->GetParentNode() != aPointToInsert.GetContainer()) { 3408 NS_WARNING("The new element was not inserted into the expected node"); 3409 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3410 } 3411 return CreateElementResult( 3412 std::move(newElement), 3413 transaction->SuggestPointToPutCaret<EditorDOMPoint>()); 3414 }(); 3415 3416 if (MOZ_UNLIKELY(createNewElementResult.isErr())) { 3417 NS_WARNING("EditorBase::DoTransactionInternal() failed"); 3418 // XXX Why do we do this even when DoTransaction() returned error? 3419 DebugOnly<nsresult> rvIgnored = 3420 RangeUpdaterRef().SelAdjCreateNode(aPointToInsert); 3421 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 3422 "RangeUpdater::SelAdjCreateNode() failed"); 3423 return createNewElementResult; 3424 } 3425 3426 // If we succeeded to create and insert new element, we need to adjust 3427 // ranges in RangeUpdaterRef(). It currently requires offset of the new 3428 // node. So, let's call it with original offset. Note that if 3429 // aPointToInsert stores child node, it may not be at the offset since new 3430 // element must be inserted before the old child. Although, mutation 3431 // observer can do anything, but currently, we don't check it. 3432 DebugOnly<nsresult> rvIgnored = 3433 RangeUpdaterRef().SelAdjCreateNode(EditorRawDOMPoint( 3434 aPointToInsert.GetContainer(), aPointToInsert.Offset())); 3435 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 3436 "RangeUpdater::SelAdjCreateNode() failed, but ignored"); 3437 if (MOZ_LIKELY(createNewElementResult.inspect().GetNewNode())) { 3438 TopLevelEditSubActionDataRef().DidCreateElement( 3439 *this, *createNewElementResult.inspect().GetNewNode()); 3440 } 3441 3442 return createNewElementResult; 3443 } 3444 3445 nsresult HTMLEditor::CopyAttributes(WithTransaction aWithTransaction, 3446 Element& aDestElement, Element& aSrcElement, 3447 const AttributeFilter& aFilterFunc) { 3448 if (!aSrcElement.GetAttrCount()) { 3449 return NS_OK; 3450 } 3451 struct MOZ_STACK_CLASS AttrCache { 3452 int32_t mNamespaceID; 3453 const OwningNonNull<nsAtom> mName; 3454 nsString mValue; 3455 }; 3456 AutoTArray<AttrCache, 16> srcAttrs; 3457 srcAttrs.SetCapacity(aSrcElement.GetAttrCount()); 3458 for (const uint32_t i : IntegerRange(aSrcElement.GetAttrCount())) { 3459 const BorrowedAttrInfo attrInfo = aSrcElement.GetAttrInfoAt(i); 3460 if (const nsAttrName* attrName = attrInfo.mName) { 3461 MOZ_ASSERT(attrName->LocalName()); 3462 MOZ_ASSERT(attrInfo.mValue); 3463 nsString attrValue; 3464 attrInfo.mValue->ToString(attrValue); 3465 srcAttrs.AppendElement(AttrCache{attrInfo.mName->NamespaceID(), 3466 *attrName->LocalName(), attrValue}); 3467 } 3468 } 3469 if (aWithTransaction == WithTransaction::No) { 3470 for (auto& attr : srcAttrs) { 3471 if (!aFilterFunc(*this, aSrcElement, aDestElement, attr.mNamespaceID, 3472 attr.mName, attr.mValue)) { 3473 continue; 3474 } 3475 DebugOnly<nsresult> rvIgnored = 3476 AutoElementAttrAPIWrapper(*this, aDestElement) 3477 .SetAttr(MOZ_KnownLive(attr.mName), attr.mValue, false); 3478 NS_WARNING_ASSERTION( 3479 NS_SUCCEEDED(rvIgnored) && rvIgnored != NS_ERROR_EDITOR_DESTROYED, 3480 "AutoElementAttrAPIWrapper::SetAttr() failed, but ignored"); 3481 } 3482 if (NS_WARN_IF(Destroyed())) { 3483 return NS_ERROR_EDITOR_DESTROYED; 3484 } 3485 return NS_OK; 3486 } 3487 MOZ_ASSERT_UNREACHABLE("Not implemented yet, but you try to use this"); 3488 return NS_ERROR_NOT_IMPLEMENTED; 3489 } 3490 3491 already_AddRefed<Element> HTMLEditor::CreateElementWithDefaults( 3492 const nsAtom& aTagName) { 3493 // NOTE: Despite of public method, this can be called for internal use. 3494 3495 // Although this creates an element, but won't change the DOM tree nor 3496 // transaction. So, EditAction::eNotEditing is proper value here. If 3497 // this is called for internal when there is already AutoEditActionDataSetter 3498 // instance, this would be initialized with its EditAction value. 3499 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 3500 if (NS_WARN_IF(!editActionData.CanHandle())) { 3501 return nullptr; 3502 } 3503 3504 const nsAtom* realTagName = IsLinkTag(aTagName) || IsNamedAnchorTag(aTagName) 3505 ? nsGkAtoms::a 3506 : &aTagName; 3507 3508 // We don't use editor's CreateElement because we don't want to go through 3509 // the transaction system 3510 3511 // New call to use instead to get proper HTML element, bug 39919 3512 RefPtr<Element> newElement = CreateHTMLContent(realTagName); 3513 if (!newElement) { 3514 return nullptr; 3515 } 3516 3517 // Mark the new element dirty, so it will be formatted 3518 // XXX Don't we need to check the error result of setting _moz_dirty attr? 3519 IgnoredErrorResult ignoredError; 3520 newElement->SetAttribute(u"_moz_dirty"_ns, u""_ns, ignoredError); 3521 NS_WARNING_ASSERTION(!ignoredError.Failed(), 3522 "Element::SetAttribute(_moz_dirty) failed, but ignored"); 3523 ignoredError.SuppressException(); 3524 3525 // Set default values for new elements 3526 if (realTagName == nsGkAtoms::table) { 3527 newElement->SetAttr(nsGkAtoms::cellpadding, u"2"_ns, ignoredError); 3528 if (ignoredError.Failed()) { 3529 NS_WARNING("Element::SetAttr(nsGkAtoms::cellpadding, 2) failed"); 3530 return nullptr; 3531 } 3532 ignoredError.SuppressException(); 3533 3534 newElement->SetAttr(nsGkAtoms::cellspacing, u"2"_ns, ignoredError); 3535 if (ignoredError.Failed()) { 3536 NS_WARNING("Element::SetAttr(nsGkAtoms::cellspacing, 2) failed"); 3537 return nullptr; 3538 } 3539 ignoredError.SuppressException(); 3540 3541 newElement->SetAttr(nsGkAtoms::border, u"1"_ns, ignoredError); 3542 if (ignoredError.Failed()) { 3543 NS_WARNING("Element::SetAttr(nsGkAtoms::border, 1) failed"); 3544 return nullptr; 3545 } 3546 } else if (realTagName == nsGkAtoms::td) { 3547 nsresult rv = SetAttributeOrEquivalent(newElement, nsGkAtoms::valign, 3548 u"top"_ns, true); 3549 if (NS_FAILED(rv)) { 3550 NS_WARNING( 3551 "HTMLEditor::SetAttributeOrEquivalent(nsGkAtoms::valign, top) " 3552 "failed"); 3553 return nullptr; 3554 } 3555 } 3556 // ADD OTHER TAGS HERE 3557 3558 return newElement.forget(); 3559 } 3560 3561 NS_IMETHODIMP HTMLEditor::CreateElementWithDefaults(const nsAString& aTagName, 3562 Element** aReturn) { 3563 if (NS_WARN_IF(aTagName.IsEmpty()) || NS_WARN_IF(!aReturn)) { 3564 return NS_ERROR_INVALID_ARG; 3565 } 3566 3567 *aReturn = nullptr; 3568 3569 nsStaticAtom* tagName = EditorUtils::GetTagNameAtom(aTagName); 3570 if (NS_WARN_IF(!tagName)) { 3571 return NS_ERROR_INVALID_ARG; 3572 } 3573 RefPtr<Element> newElement = 3574 CreateElementWithDefaults(MOZ_KnownLive(*tagName)); 3575 if (!newElement) { 3576 NS_WARNING("HTMLEditor::CreateElementWithDefaults() failed"); 3577 return NS_ERROR_FAILURE; 3578 } 3579 newElement.forget(aReturn); 3580 return NS_OK; 3581 } 3582 3583 NS_IMETHODIMP HTMLEditor::InsertLinkAroundSelection(Element* aAnchorElement) { 3584 nsresult rv = InsertLinkAroundSelectionAsAction(aAnchorElement); 3585 NS_WARNING_ASSERTION( 3586 NS_SUCCEEDED(rv), 3587 "HTMLEditor::InsertLinkAroundSelectionAsAction() failed"); 3588 return rv; 3589 } 3590 3591 nsresult HTMLEditor::InsertLinkAroundSelectionAsAction( 3592 Element* aAnchorElement, nsIPrincipal* aPrincipal) { 3593 if (NS_WARN_IF(!aAnchorElement)) { 3594 return NS_ERROR_INVALID_ARG; 3595 } 3596 3597 AutoEditActionDataSetter editActionData(*this, EditAction::eInsertLinkElement, 3598 aPrincipal); 3599 if (NS_WARN_IF(!editActionData.CanHandle())) { 3600 return NS_ERROR_NOT_INITIALIZED; 3601 } 3602 3603 RefPtr<Element> const editingHost = 3604 ComputeEditingHost(LimitInBodyElement::No); 3605 if (NS_WARN_IF(!editingHost)) { 3606 return NS_ERROR_FAILURE; 3607 } 3608 3609 if (IsPlaintextMailComposer() || 3610 editingHost->IsContentEditablePlainTextOnly()) { 3611 return NS_SUCCESS_DOM_NO_OPERATION; 3612 } 3613 3614 if (SelectionRef().IsCollapsed()) { 3615 NS_WARNING("Selection was collapsed"); 3616 return NS_OK; 3617 } 3618 3619 // Be sure we were given an anchor element 3620 RefPtr<HTMLAnchorElement> anchor = 3621 HTMLAnchorElement::FromNodeOrNull(aAnchorElement); 3622 if (!anchor) { 3623 return NS_OK; 3624 } 3625 3626 nsAutoString rawHref; 3627 anchor->GetAttr(nsGkAtoms::href, rawHref); 3628 editActionData.SetData(rawHref); 3629 3630 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 3631 if (NS_FAILED(rv)) { 3632 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3633 "MaybeDispatchBeforeInputEvent(), failed"); 3634 return EditorBase::ToGenericNSResult(rv); 3635 } 3636 3637 // XXX Is this ok? Does this just want to check that we're a link? If so 3638 // there are faster ways to do this. 3639 { 3640 nsAutoCString href; 3641 anchor->GetHref(href); 3642 if (href.IsEmpty()) { 3643 return NS_OK; 3644 } 3645 } 3646 3647 AutoPlaceholderBatch treatAsOneTransaction( 3648 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3649 3650 // Set all attributes found on the supplied anchor element 3651 // TODO: We should stop using this loop for adding attributes to newly created 3652 // `<a href="...">` elements. Then, we can avoid to increate the ref- 3653 // counter of attribute names since we can use nsStaticAtom if we don't 3654 // need to support unknown attributes. 3655 AutoTArray<EditorInlineStyleAndValue, 32> stylesToSet; 3656 if (const uint32_t attrCount = anchor->GetAttrCount()) { 3657 stylesToSet.SetCapacity(attrCount); 3658 for (const uint32_t i : IntegerRange(attrCount)) { 3659 const BorrowedAttrInfo attrInfo = anchor->GetAttrInfoAt(i); 3660 if (const nsAttrName* attrName = attrInfo.mName) { 3661 // We don't need to handle _moz_dirty attribute. If it's required, the 3662 // handler should add it to the new element. 3663 if (attrName->IsAtom() && attrName->Equals(nsGkAtoms::mozdirty)) { 3664 continue; 3665 } 3666 RefPtr<nsAtom> attributeName = attrName->LocalName(); 3667 MOZ_ASSERT(attrInfo.mValue); 3668 nsString attrValue; 3669 attrInfo.mValue->ToString(attrValue); 3670 stylesToSet.AppendElement(EditorInlineStyleAndValue( 3671 *nsGkAtoms::a, std::move(attributeName), std::move(attrValue))); 3672 } 3673 } 3674 } 3675 rv = SetInlinePropertiesAsSubAction(stylesToSet, *editingHost); 3676 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3677 "HTMLEditor::SetInlinePropertiesAsSubAction() failed"); 3678 return rv; 3679 } 3680 3681 nsresult HTMLEditor::SetHTMLBackgroundColorWithTransaction( 3682 const nsAString& aColor) { 3683 MOZ_ASSERT(IsEditActionDataAvailable()); 3684 3685 // Find a selected or enclosing table element to set background on 3686 bool isCellSelected = false; 3687 Result<RefPtr<Element>, nsresult> cellOrRowOrTableElementOrError = 3688 GetSelectedOrParentTableElement(&isCellSelected); 3689 if (cellOrRowOrTableElementOrError.isErr()) { 3690 NS_WARNING("HTMLEditor::GetSelectedOrParentTableElement() failed"); 3691 return cellOrRowOrTableElementOrError.unwrapErr(); 3692 } 3693 3694 bool setColor = !aColor.IsEmpty(); 3695 RefPtr<Element> rootElementOfBackgroundColor = 3696 cellOrRowOrTableElementOrError.unwrap(); 3697 if (rootElementOfBackgroundColor) { 3698 // Needs to set or remove background color of each selected cell elements. 3699 // Therefore, just the cell contains selection range, we don't need to 3700 // do this. Note that users can select each cell, but with Selection API, 3701 // web apps can select <tr> and <td> at same time. With <table>, looks 3702 // odd, though. 3703 if (isCellSelected || rootElementOfBackgroundColor->IsAnyOfHTMLElements( 3704 nsGkAtoms::table, nsGkAtoms::tr)) { 3705 SelectedTableCellScanner scanner(SelectionRef()); 3706 if (scanner.IsInTableCellSelectionMode()) { 3707 if (setColor) { 3708 for (const OwningNonNull<Element>& cellElement : 3709 scanner.ElementsRef()) { 3710 // `MOZ_KnownLive(cellElement)` is safe because of `scanner` 3711 // is stack only class and keeps grabbing it until it's destroyed. 3712 nsresult rv = SetAttributeWithTransaction( 3713 MOZ_KnownLive(cellElement), *nsGkAtoms::bgcolor, aColor); 3714 if (NS_WARN_IF(Destroyed())) { 3715 return NS_ERROR_EDITOR_DESTROYED; 3716 } 3717 if (NS_FAILED(rv)) { 3718 NS_WARNING( 3719 "EditorBase::::SetAttributeWithTransaction(nsGkAtoms::" 3720 "bgcolor) failed"); 3721 return rv; 3722 } 3723 } 3724 return NS_OK; 3725 } 3726 for (const OwningNonNull<Element>& cellElement : 3727 scanner.ElementsRef()) { 3728 // `MOZ_KnownLive(cellElement)` is safe because of `scanner` 3729 // is stack only class and keeps grabbing it until it's destroyed. 3730 nsresult rv = RemoveAttributeWithTransaction( 3731 MOZ_KnownLive(cellElement), *nsGkAtoms::bgcolor); 3732 if (NS_FAILED(rv)) { 3733 NS_WARNING( 3734 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::bgcolor)" 3735 " failed"); 3736 return rv; 3737 } 3738 } 3739 return NS_OK; 3740 } 3741 } 3742 // If we failed to find a cell, fall through to use originally-found element 3743 } else { 3744 // No table element -- set the background color on the body tag 3745 rootElementOfBackgroundColor = GetRoot(); 3746 if (NS_WARN_IF(!rootElementOfBackgroundColor)) { 3747 return NS_ERROR_FAILURE; 3748 } 3749 } 3750 // Use the editor method that goes through the transaction system 3751 if (setColor) { 3752 nsresult rv = SetAttributeWithTransaction(*rootElementOfBackgroundColor, 3753 *nsGkAtoms::bgcolor, aColor); 3754 NS_WARNING_ASSERTION( 3755 NS_SUCCEEDED(rv), 3756 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::bgcolor) failed"); 3757 return rv; 3758 } 3759 nsresult rv = RemoveAttributeWithTransaction(*rootElementOfBackgroundColor, 3760 *nsGkAtoms::bgcolor); 3761 NS_WARNING_ASSERTION( 3762 NS_SUCCEEDED(rv), 3763 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::bgcolor) failed"); 3764 return rv; 3765 } 3766 3767 Result<CaretPoint, nsresult> 3768 HTMLEditor::DeleteEmptyInclusiveAncestorInlineElements( 3769 nsIContent& aContent, const Element& aEditingHost) { 3770 MOZ_ASSERT(IsEditActionDataAvailable()); 3771 MOZ_ASSERT(HTMLEditUtils::IsRemovableFromParentNode(aContent)); 3772 3773 constexpr static HTMLEditUtils::EmptyCheckOptions kOptionsToCheckInline = 3774 HTMLEditUtils::EmptyCheckOptions{ 3775 EmptyCheckOption::TreatBlockAsVisible, 3776 EmptyCheckOption::TreatListItemAsVisible, 3777 EmptyCheckOption::TreatSingleBRElementAsVisible, 3778 EmptyCheckOption::TreatTableCellAsVisible}; 3779 3780 if (&aContent == &aEditingHost || 3781 HTMLEditUtils::IsBlockElement( 3782 aContent, BlockInlineCheck::UseComputedDisplayOutsideStyle) || 3783 !HTMLEditUtils::IsRemovableFromParentNode(aContent) || 3784 !aContent.GetParent() || 3785 !HTMLEditUtils::IsEmptyNode(aContent, kOptionsToCheckInline)) { 3786 return CaretPoint(EditorDOMPoint()); 3787 } 3788 3789 OwningNonNull<nsIContent> content = aContent; 3790 for (nsIContent* parentContent : aContent.AncestorsOfType<nsIContent>()) { 3791 if (HTMLEditUtils::IsBlockElement( 3792 *parentContent, BlockInlineCheck::UseComputedDisplayStyle) || 3793 !HTMLEditUtils::IsRemovableFromParentNode(*parentContent) || 3794 parentContent == &aEditingHost) { 3795 break; 3796 } 3797 bool parentIsEmpty = true; 3798 if (parentContent->GetChildCount() > 1) { 3799 for (nsIContent* sibling = parentContent->GetFirstChild(); sibling; 3800 sibling = sibling->GetNextSibling()) { 3801 if (sibling == content) { 3802 continue; 3803 } 3804 if (!HTMLEditUtils::IsEmptyNode(*sibling, kOptionsToCheckInline)) { 3805 parentIsEmpty = false; 3806 break; 3807 } 3808 } 3809 } 3810 if (!parentIsEmpty) { 3811 break; 3812 } 3813 content = *parentContent; 3814 } 3815 3816 const nsCOMPtr<nsIContent> nextSibling = content->GetNextSibling(); 3817 const nsCOMPtr<nsINode> parentNode = content->GetParentNode(); 3818 MOZ_ASSERT(parentNode); 3819 nsresult rv = DeleteNodeWithTransaction(content); 3820 if (NS_FAILED(rv)) { 3821 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 3822 return Err(rv); 3823 } 3824 if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode) || 3825 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*parentNode))) { 3826 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 3827 } 3828 // Note that even if nextSibling is not editable, we can put caret before it 3829 // unless parentNode is not editable. 3830 return CaretPoint(nextSibling ? EditorDOMPoint(nextSibling) 3831 : EditorDOMPoint::AtEndOf(*parentNode)); 3832 } 3833 3834 nsresult HTMLEditor::DeleteAllChildrenWithTransaction(Element& aElement) { 3835 MOZ_ASSERT(IsEditActionDataAvailable()); 3836 3837 // Prevent rules testing until we're done 3838 IgnoredErrorResult ignoredError; 3839 AutoEditSubActionNotifier startToHandleEditSubAction( 3840 *this, EditSubAction::eDeleteNode, nsIEditor::eNext, ignoredError); 3841 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3842 return ignoredError.StealNSResult(); 3843 } 3844 NS_WARNING_ASSERTION( 3845 !ignoredError.Failed(), 3846 "OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3847 3848 while (nsCOMPtr<nsIContent> child = aElement.GetLastChild()) { 3849 nsresult rv = DeleteNodeWithTransaction(*child); 3850 if (NS_FAILED(rv)) { 3851 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 3852 return rv; 3853 } 3854 } 3855 return NS_OK; 3856 } 3857 3858 NS_IMETHODIMP HTMLEditor::DeleteNode(nsINode* aNode, bool aPreserveSelection, 3859 uint8_t aOptionalArgCount) { 3860 if (NS_WARN_IF(!aNode) || NS_WARN_IF(!aNode->IsContent())) { 3861 return NS_ERROR_INVALID_ARG; 3862 } 3863 3864 AutoEditActionDataSetter editActionData(*this, EditAction::eRemoveNode); 3865 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 3866 if (NS_FAILED(rv)) { 3867 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3868 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 3869 return EditorBase::ToGenericNSResult(rv); 3870 } 3871 3872 // Make dispatch `input` event after stopping preserving selection. 3873 AutoPlaceholderBatch treatAsOneTransaction( 3874 *this, 3875 ScrollSelectionIntoView::No, // not a user interaction 3876 __FUNCTION__); 3877 3878 Maybe<AutoTransactionsConserveSelection> preserveSelection; 3879 if (aOptionalArgCount && aPreserveSelection) { 3880 preserveSelection.emplace(*this); 3881 } 3882 3883 rv = DeleteNodeWithTransaction(MOZ_KnownLive(*aNode->AsContent())); 3884 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3885 "EditorBase::DeleteNodeWithTransaction() failed"); 3886 return rv; 3887 } 3888 3889 Result<CaretPoint, nsresult> HTMLEditor::DeleteTextWithTransaction( 3890 Text& aTextNode, uint32_t aOffset, uint32_t aLength) { 3891 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aTextNode))) { 3892 return Err(NS_ERROR_FAILURE); 3893 } 3894 3895 Result<CaretPoint, nsresult> caretPointOrError = 3896 EditorBase::DeleteTextWithTransaction(aTextNode, aOffset, aLength); 3897 NS_WARNING_ASSERTION(caretPointOrError.isOk(), 3898 "EditorBase::DeleteTextWithTransaction() failed"); 3899 return caretPointOrError; 3900 } 3901 3902 Result<InsertTextResult, nsresult> HTMLEditor::ReplaceTextWithTransaction( 3903 dom::Text& aTextNode, const ReplaceWhiteSpacesData& aData) { 3904 Result<InsertTextResult, nsresult> insertTextResultOrError = 3905 ReplaceTextWithTransaction(aTextNode, aData.mReplaceStartOffset, 3906 aData.ReplaceLength(), 3907 aData.mNormalizedString); 3908 if (MOZ_UNLIKELY(insertTextResultOrError.isErr()) || 3909 aData.mNewOffsetAfterReplace > aTextNode.TextDataLength()) { 3910 return insertTextResultOrError; 3911 } 3912 InsertTextResult insertTextResult = insertTextResultOrError.unwrap(); 3913 insertTextResult.IgnoreCaretPointSuggestion(); 3914 EditorDOMPoint pointToPutCaret(&aTextNode, aData.mNewOffsetAfterReplace); 3915 return InsertTextResult(std::move(insertTextResult), 3916 std::move(pointToPutCaret)); 3917 } 3918 3919 Result<InsertTextResult, nsresult> HTMLEditor::ReplaceTextWithTransaction( 3920 Text& aTextNode, uint32_t aOffset, uint32_t aLength, 3921 const nsAString& aStringToInsert) { 3922 MOZ_ASSERT(IsEditActionDataAvailable()); 3923 MOZ_ASSERT(aLength > 0 || !aStringToInsert.IsEmpty()); 3924 3925 if (aStringToInsert.IsEmpty()) { 3926 Result<CaretPoint, nsresult> caretPointOrError = 3927 DeleteTextWithTransaction(aTextNode, aOffset, aLength); 3928 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 3929 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed"); 3930 return caretPointOrError.propagateErr(); 3931 } 3932 return InsertTextResult(EditorDOMPoint(&aTextNode, aOffset), 3933 caretPointOrError.unwrap()); 3934 } 3935 3936 if (!aLength) { 3937 Result<InsertTextResult, nsresult> insertTextResult = 3938 InsertTextWithTransaction(aStringToInsert, 3939 EditorDOMPoint(&aTextNode, aOffset), 3940 InsertTextTo::ExistingTextNodeIfAvailable); 3941 NS_WARNING_ASSERTION(insertTextResult.isOk(), 3942 "HTMLEditor::InsertTextWithTransaction() failed"); 3943 return insertTextResult; 3944 } 3945 3946 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aTextNode))) { 3947 return Err(NS_ERROR_FAILURE); 3948 } 3949 3950 // This should emulates inserting text for better undo/redo behavior. 3951 IgnoredErrorResult ignoredError; 3952 AutoEditSubActionNotifier startToHandleEditSubAction( 3953 *this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError); 3954 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3955 return Err(NS_ERROR_EDITOR_DESTROYED); 3956 } 3957 NS_WARNING_ASSERTION( 3958 !ignoredError.Failed(), 3959 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3960 3961 // FYI: Create the insertion point before changing the DOM tree because 3962 // the point may become invalid offset after that. 3963 EditorDOMPointInText pointToInsert(&aTextNode, aOffset); 3964 3965 RefPtr<ReplaceTextTransaction> transaction = ReplaceTextTransaction::Create( 3966 *this, aStringToInsert, aTextNode, aOffset, aLength); 3967 MOZ_ASSERT(transaction); 3968 3969 if (aLength && !mActionListeners.IsEmpty()) { 3970 for (auto& listener : mActionListeners.Clone()) { 3971 DebugOnly<nsresult> rvIgnored = 3972 listener->WillDeleteText(&aTextNode, aOffset, aLength); 3973 NS_WARNING_ASSERTION( 3974 NS_SUCCEEDED(rvIgnored), 3975 "nsIEditActionListener::WillDeleteText() failed, but ignored"); 3976 } 3977 } 3978 3979 nsresult rv = DoTransactionInternal(transaction); 3980 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3981 "EditorBase::DoTransactionInternal() failed"); 3982 3983 // Don't check whether we've been destroyed here because we need to notify 3984 // listeners and observers below even if we've already destroyed. 3985 3986 EditorDOMPoint endOfInsertedText(&aTextNode, 3987 aOffset + aStringToInsert.Length()); 3988 3989 if (pointToInsert.IsSet()) { 3990 auto [begin, end] = ComputeInsertedRange(pointToInsert, aStringToInsert); 3991 if (begin.IsSet() && end.IsSet()) { 3992 TopLevelEditSubActionDataRef().DidDeleteText( 3993 *this, begin.RefOrTo<EditorRawDOMPoint>()); 3994 TopLevelEditSubActionDataRef().DidInsertText( 3995 *this, begin.RefOrTo<EditorRawDOMPoint>(), 3996 end.RefOrTo<EditorRawDOMPoint>()); 3997 } 3998 3999 // XXX Should we update endOfInsertedText here? 4000 } 4001 4002 if (!mActionListeners.IsEmpty()) { 4003 for (auto& listener : mActionListeners.Clone()) { 4004 DebugOnly<nsresult> rvIgnored = 4005 listener->DidInsertText(&aTextNode, aOffset, aStringToInsert, rv); 4006 NS_WARNING_ASSERTION( 4007 NS_SUCCEEDED(rvIgnored), 4008 "nsIEditActionListener::DidInsertText() failed, but ignored"); 4009 } 4010 } 4011 4012 if (NS_WARN_IF(Destroyed())) { 4013 return Err(NS_ERROR_EDITOR_DESTROYED); 4014 } 4015 4016 return InsertTextResult( 4017 std::move(endOfInsertedText), 4018 transaction->SuggestPointToPutCaret<EditorDOMPoint>()); 4019 } 4020 4021 Result<InsertTextResult, nsresult> 4022 HTMLEditor::InsertOrReplaceTextWithTransaction( 4023 const EditorDOMPoint& aPointToInsert, 4024 const NormalizedStringToInsertText& aData) { 4025 MOZ_ASSERT(aPointToInsert.IsInContentNodeAndValid()); 4026 MOZ_ASSERT_IF(aData.ReplaceLength(), aPointToInsert.IsInTextNode()); 4027 4028 Result<InsertTextResult, nsresult> insertTextResultOrError = 4029 !aData.ReplaceLength() 4030 ? InsertTextWithTransaction(aData.mNormalizedString, aPointToInsert, 4031 InsertTextTo::SpecifiedPoint) 4032 : ReplaceTextWithTransaction( 4033 MOZ_KnownLive(*aPointToInsert.ContainerAs<Text>()), 4034 aData.mReplaceStartOffset, aData.ReplaceLength(), 4035 aData.mNormalizedString); 4036 if (MOZ_UNLIKELY(insertTextResultOrError.isErr())) { 4037 NS_WARNING(!aData.ReplaceLength() 4038 ? "HTMLEditor::InsertTextWithTransaction() failed" 4039 : "HTMLEditor::ReplaceTextWithTransaction() failed"); 4040 return insertTextResultOrError; 4041 } 4042 InsertTextResult insertTextResult = insertTextResultOrError.unwrap(); 4043 if (!aData.ReplaceLength()) { 4044 auto pointToPutCaret = [&]() -> EditorDOMPoint { 4045 return insertTextResult.HasCaretPointSuggestion() 4046 ? insertTextResult.UnwrapCaretPoint() 4047 : insertTextResult.EndOfInsertedTextRef(); 4048 }(); 4049 return InsertTextResult(std::move(insertTextResult), 4050 std::move(pointToPutCaret)); 4051 } 4052 insertTextResult.IgnoreCaretPointSuggestion(); 4053 Text* const insertedTextNode = 4054 insertTextResult.EndOfInsertedTextRef().GetContainerAs<Text>(); 4055 if (NS_WARN_IF(!insertedTextNode) || 4056 NS_WARN_IF(!insertedTextNode->IsInComposedDoc()) || 4057 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*insertedTextNode))) { 4058 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4059 } 4060 const uint32_t expectedEndOffset = aData.EndOffsetOfInsertedText(); 4061 if (NS_WARN_IF(expectedEndOffset > insertedTextNode->TextDataLength())) { 4062 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4063 } 4064 // We need to return end point of the insertion string instead of end of 4065 // replaced following white-spaces. 4066 EditorDOMPoint endOfNewString(insertedTextNode, expectedEndOffset); 4067 EditorDOMPoint pointToPutCaret = endOfNewString; 4068 return InsertTextResult(std::move(endOfNewString), 4069 CaretPoint(std::move(pointToPutCaret))); 4070 } 4071 4072 Result<InsertTextResult, nsresult> HTMLEditor::InsertTextWithTransaction( 4073 const nsAString& aStringToInsert, const EditorDOMPoint& aPointToInsert, 4074 InsertTextTo aInsertTextTo) { 4075 if (NS_WARN_IF(!aPointToInsert.IsSet())) { 4076 return Err(NS_ERROR_INVALID_ARG); 4077 } 4078 4079 // Do nothing if the node is read-only 4080 if (NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode( 4081 *aPointToInsert.GetContainer()))) { 4082 return Err(NS_ERROR_FAILURE); 4083 } 4084 4085 return EditorBase::InsertTextWithTransaction(aStringToInsert, aPointToInsert, 4086 aInsertTextTo); 4087 } 4088 4089 Result<EditorDOMPoint, nsresult> HTMLEditor::PrepareToInsertLineBreak( 4090 LineBreakType aLineBreakType, const EditorDOMPoint& aPointToInsert) { 4091 MOZ_ASSERT(IsEditActionDataAvailable()); 4092 4093 if (NS_WARN_IF(!aPointToInsert.IsInContentNode())) { 4094 return Err(NS_ERROR_FAILURE); 4095 } 4096 4097 const auto CanInsertLineBreak = [aLineBreakType](const nsIContent& aContent) { 4098 if (aLineBreakType == LineBreakType::BRElement) { 4099 return HTMLEditUtils::CanNodeContain(aContent, *nsGkAtoms::br); 4100 } 4101 MOZ_ASSERT(aLineBreakType == LineBreakType::Linefeed); 4102 return HTMLEditUtils::CanNodeContain(aContent, *nsGkAtoms::textTagName) && 4103 EditorUtils::IsNewLinePreformatted(aContent); 4104 }; 4105 4106 // If we're being initialized, we cannot normalize white-spaces because the 4107 // normalizer may remove invisible `Text`, but it's not allowed during the 4108 // initialization. 4109 // FIXME: Anyway, we should not do this at initialization. This is required 4110 // only for making users can put caret into empty table cells and list items. 4111 const bool canNormalizeWhiteSpaces = mInitSucceeded; 4112 4113 if (!aPointToInsert.IsInTextNode()) { 4114 if (NS_WARN_IF( 4115 !CanInsertLineBreak(*aPointToInsert.ContainerAs<nsIContent>()))) { 4116 return Err(NS_ERROR_FAILURE); 4117 } 4118 if (!canNormalizeWhiteSpaces) { 4119 return aPointToInsert; 4120 } 4121 Result<EditorDOMPoint, nsresult> pointToInsertOrError = 4122 WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( 4123 *this, aPointToInsert, 4124 {WhiteSpaceVisibilityKeeper::NormalizeOption:: 4125 StopIfPrecedingWhiteSpacesEndsWithNBP}); 4126 if (NS_WARN_IF(pointToInsertOrError.isErr())) { 4127 return pointToInsertOrError.propagateErr(); 4128 } 4129 return pointToInsertOrError.unwrap(); 4130 } 4131 4132 // If the text node is not in an element node, we cannot insert a line break 4133 // around the text node. 4134 const Element* const containerOrNewLineBreak = 4135 aPointToInsert.GetContainerParentAs<Element>(); 4136 if (NS_WARN_IF(!containerOrNewLineBreak) || 4137 NS_WARN_IF(!CanInsertLineBreak(*containerOrNewLineBreak))) { 4138 return Err(NS_ERROR_FAILURE); 4139 } 4140 4141 Result<EditorDOMPoint, nsresult> pointToInsertOrError = 4142 canNormalizeWhiteSpaces 4143 ? WhiteSpaceVisibilityKeeper::NormalizeWhiteSpacesToSplitAt( 4144 *this, aPointToInsert, 4145 {WhiteSpaceVisibilityKeeper::NormalizeOption:: 4146 StopIfPrecedingWhiteSpacesEndsWithNBP}) 4147 : aPointToInsert; 4148 if (NS_WARN_IF(pointToInsertOrError.isErr())) { 4149 return pointToInsertOrError.propagateErr(); 4150 } 4151 const EditorDOMPoint pointToInsert = pointToInsertOrError.unwrap(); 4152 if (!pointToInsert.IsInTextNode()) { 4153 return pointToInsert.ParentPoint(); 4154 } 4155 4156 if (pointToInsert.IsStartOfContainer()) { 4157 // Insert before the text node. 4158 return pointToInsert.ParentPoint(); 4159 } 4160 4161 if (pointToInsert.IsEndOfContainer()) { 4162 // Insert after the text node. 4163 return EditorDOMPoint::After(*pointToInsert.ContainerAs<Text>()); 4164 } 4165 4166 MOZ_DIAGNOSTIC_ASSERT(pointToInsert.IsSetAndValid()); 4167 4168 // Unfortunately, we need to split the text node at the offset. 4169 Result<SplitNodeResult, nsresult> splitTextNodeResult = 4170 SplitNodeWithTransaction(pointToInsert); 4171 if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) { 4172 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 4173 return splitTextNodeResult.propagateErr(); 4174 } 4175 4176 // TODO: Stop updating `Selection`. 4177 nsresult rv = splitTextNodeResult.inspect().SuggestCaretPointTo( 4178 *this, {SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 4179 if (NS_FAILED(rv)) { 4180 NS_WARNING("SplitNodeResult::SuggestCaretPointTo() failed"); 4181 return Err(rv); 4182 } 4183 4184 // Insert new line break before the right node. 4185 auto atNextContent = 4186 splitTextNodeResult.inspect().AtNextContent<EditorDOMPoint>(); 4187 if (MOZ_UNLIKELY(!atNextContent.IsInContentNode())) { 4188 NS_WARNING("The next node seems not in the DOM tree"); 4189 return Err(NS_ERROR_FAILURE); 4190 } 4191 return atNextContent; 4192 } 4193 4194 Maybe<HTMLEditor::LineBreakType> HTMLEditor::GetPreferredLineBreakType( 4195 const nsINode& aNode, const Element& aEditingHost) const { 4196 const Element* const container = aNode.GetAsElementOrParentElement(); 4197 if (MOZ_UNLIKELY(!container)) { 4198 return Nothing(); 4199 } 4200 // For backward compatibility, we should not insert a linefeed if 4201 // paragraph separator is set to "br" which is Gecko-specific mode. 4202 if (GetDefaultParagraphSeparator() == ParagraphSeparator::br) { 4203 return Some(LineBreakType::BRElement); 4204 } 4205 // And also if we're the mail composer, the content needs to be serialized. 4206 // Therefore, we should always use <br> for the serializer. 4207 if (IsMailEditor() || IsPlaintextMailComposer()) { 4208 return Some(LineBreakType::BRElement); 4209 } 4210 if (HTMLEditUtils::ShouldInsertLinefeedCharacter(EditorDOMPoint(container, 0), 4211 aEditingHost) && 4212 HTMLEditUtils::CanNodeContain(*container, *nsGkAtoms::textTagName)) { 4213 return Some(LineBreakType::Linefeed); 4214 } 4215 if (MOZ_UNLIKELY( 4216 !HTMLEditUtils::CanNodeContain(*container, *nsGkAtoms::br))) { 4217 return Nothing(); 4218 } 4219 return Some(LineBreakType::BRElement); 4220 } 4221 4222 Result<CreateLineBreakResult, nsresult> HTMLEditor::InsertLineBreak( 4223 WithTransaction aWithTransaction, LineBreakType aLineBreakType, 4224 const EditorDOMPoint& aPointToInsert, EDirection aSelect /* = eNone */) { 4225 MOZ_ASSERT(IsEditActionDataAvailable()); 4226 4227 Result<EditorDOMPoint, nsresult> pointToInsertOrError = 4228 PrepareToInsertLineBreak(aLineBreakType, aPointToInsert); 4229 if (MOZ_UNLIKELY(pointToInsertOrError.isErr())) { 4230 NS_WARNING( 4231 nsPrintfCString("HTMLEditor::PrepareToInsertLineBreak(%s) failed", 4232 ToString(aWithTransaction).c_str()) 4233 .get()); 4234 return pointToInsertOrError.propagateErr(); 4235 } 4236 EditorDOMPoint pointToInsert = pointToInsertOrError.unwrap(); 4237 MOZ_ASSERT(pointToInsert.IsInContentNode()); 4238 MOZ_ASSERT(pointToInsert.IsSetAndValid()); 4239 4240 auto lineBreakOrError = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT 4241 -> Result<EditorLineBreak, nsresult> { 4242 if (aLineBreakType == LineBreakType::BRElement) { 4243 Result<CreateElementResult, nsresult> insertBRElementResultOrError = 4244 InsertBRElement(aWithTransaction, BRElementType::Normal, 4245 pointToInsert); 4246 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 4247 NS_WARNING( 4248 nsPrintfCString( 4249 "EditorBase::InsertBRElement(%s, BRElementType::Normal) failed", 4250 ToString(aWithTransaction).c_str()) 4251 .get()); 4252 return insertBRElementResultOrError.propagateErr(); 4253 } 4254 CreateElementResult insertBRElementResult = 4255 insertBRElementResultOrError.unwrap(); 4256 MOZ_ASSERT(insertBRElementResult.Handled()); 4257 insertBRElementResult.IgnoreCaretPointSuggestion(); 4258 return EditorLineBreak(insertBRElementResult.UnwrapNewNode()); 4259 } 4260 MOZ_ASSERT(aLineBreakType == LineBreakType::Linefeed); 4261 RefPtr<Text> newTextNode = CreateTextNode(u"\n"_ns); 4262 if (NS_WARN_IF(!newTextNode)) { 4263 return Err(NS_ERROR_FAILURE); 4264 } 4265 if (aWithTransaction == WithTransaction::Yes) { 4266 Result<CreateTextResult, nsresult> insertTextNodeResult = 4267 InsertNodeWithTransaction<Text>(*newTextNode, pointToInsert); 4268 if (MOZ_UNLIKELY(insertTextNodeResult.isErr())) { 4269 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); 4270 return insertTextNodeResult.propagateErr(); 4271 } 4272 insertTextNodeResult.unwrap().IgnoreCaretPointSuggestion(); 4273 } else { 4274 (void)pointToInsert.Offset(); 4275 RefPtr<InsertNodeTransaction> transaction = 4276 InsertNodeTransaction::Create(*this, *newTextNode, pointToInsert); 4277 nsresult rv = transaction->DoTransaction(); 4278 if (NS_WARN_IF(Destroyed())) { 4279 return Err(NS_ERROR_EDITOR_DESTROYED); 4280 } 4281 if (NS_FAILED(rv)) { 4282 NS_WARNING("InsertNodeTransaction::DoTransaction() failed"); 4283 return Err(rv); 4284 } 4285 if (NS_WARN_IF(newTextNode->GetParentNode() != 4286 pointToInsert.GetContainer())) { 4287 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4288 } 4289 RangeUpdaterRef().SelAdjInsertNode(EditorRawDOMPoint( 4290 pointToInsert.GetContainer(), pointToInsert.Offset())); 4291 } 4292 if (NS_WARN_IF(!newTextNode->TextDataLength() || 4293 newTextNode->DataBuffer().CharAt(0) != '\n')) { 4294 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4295 } 4296 return EditorLineBreak(std::move(newTextNode), 0u); 4297 }(); 4298 if (MOZ_UNLIKELY(lineBreakOrError.isErr())) { 4299 return lineBreakOrError.propagateErr(); 4300 } 4301 EditorLineBreak lineBreak = lineBreakOrError.unwrap(); 4302 auto pointToPutCaret = [&]() -> EditorDOMPoint { 4303 switch (aSelect) { 4304 case eNext: { 4305 return lineBreak.After<EditorDOMPoint>(); 4306 } 4307 case ePrevious: { 4308 return lineBreak.Before<EditorDOMPoint>(); 4309 } 4310 default: 4311 NS_WARNING( 4312 "aSelect has invalid value, the caller need to set selection " 4313 "by itself"); 4314 [[fallthrough]]; 4315 case eNone: 4316 return lineBreak.To<EditorDOMPoint>(); 4317 } 4318 }(); 4319 return CreateLineBreakResult(std::move(lineBreak), 4320 std::move(pointToPutCaret)); 4321 } 4322 4323 nsresult HTMLEditor::EnsureNoFollowingUnnecessaryLineBreak( 4324 const EditorDOMPoint& aNextOrAfterModifiedPoint) { 4325 MOZ_ASSERT(aNextOrAfterModifiedPoint.IsInContentNode()); 4326 4327 // If the point is in a mailcite in plaintext mail composer (it is a <span> 4328 // styled as block), we should not treat its padding <br> as unnecessary 4329 // because it's required by the serializer to give next content of the 4330 // mailcite has its own line. 4331 if (IsPlaintextMailComposer()) { 4332 const Element* const blockElement = 4333 HTMLEditUtils::GetInclusiveAncestorElement( 4334 *aNextOrAfterModifiedPoint.ContainerAs<nsIContent>(), 4335 HTMLEditUtils::ClosestEditableBlockElement, 4336 BlockInlineCheck::UseComputedDisplayStyle); 4337 if (blockElement && HTMLEditUtils::IsMailCiteElement(*blockElement) && 4338 HTMLEditUtils::IsInlineContent(*blockElement, 4339 BlockInlineCheck::UseHTMLDefaultStyle)) { 4340 return NS_OK; 4341 } 4342 } 4343 4344 const bool isWhiteSpacePreformatted = EditorUtils::IsWhiteSpacePreformatted( 4345 *aNextOrAfterModifiedPoint.ContainerAs<nsIContent>()); 4346 const DebugOnly<bool> isNewLinePreformatted = 4347 EditorUtils::IsNewLinePreformatted( 4348 *aNextOrAfterModifiedPoint.ContainerAs<nsIContent>()); 4349 4350 const Maybe<EditorLineBreak> unnecessaryLineBreak = 4351 HTMLEditUtils::GetFollowingUnnecessaryLineBreak<EditorLineBreak>( 4352 aNextOrAfterModifiedPoint); 4353 if (MOZ_LIKELY(unnecessaryLineBreak.isNothing() || 4354 !unnecessaryLineBreak->IsDeletableFromComposedDoc())) { 4355 return NS_OK; 4356 } 4357 if (unnecessaryLineBreak->IsHTMLBRElement()) { 4358 // If the found unnecessary <br> is a preceding one of a mailcite which is a 4359 // <span> styled as block, we need to preserve the <br> element for the 4360 // serializer to cause a line break before the mailcite. 4361 if (IsPlaintextMailComposer()) { 4362 const WSScanResult nextThing = 4363 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 4364 {}, unnecessaryLineBreak->After<EditorRawDOMPoint>()); 4365 if (nextThing.ReachedOtherBlockElement() && 4366 HTMLEditUtils::IsMailCiteElement(*nextThing.ElementPtr()) && 4367 HTMLEditUtils::IsInlineContent( 4368 *nextThing.ElementPtr(), BlockInlineCheck::UseHTMLDefaultStyle)) { 4369 return NS_OK; 4370 } 4371 } 4372 // If the invisible break is a placeholder of ancestor inline elements, we 4373 // should not delete it to allow users to insert text with the format 4374 // specified by them. 4375 if (HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 4376 unnecessaryLineBreak->BRElementRef(), 4377 BlockInlineCheck::UseComputedDisplayStyle)) { 4378 return NS_OK; 4379 } 4380 nsresult rv = DeleteNodeWithTransaction( 4381 MOZ_KnownLive(unnecessaryLineBreak->BRElementRef())); 4382 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4383 "EditorBase::DeleteNodeWithTransaction() failed " 4384 "to delete unnecessary <br>"); 4385 return rv; 4386 } 4387 MOZ_ASSERT(isNewLinePreformatted); 4388 const auto IsVisibleChar = [&](char16_t aChar) { 4389 switch (aChar) { 4390 case HTMLEditUtils::kNewLine: 4391 return true; 4392 case HTMLEditUtils::kSpace: 4393 case HTMLEditUtils::kTab: 4394 case HTMLEditUtils::kCarriageReturn: 4395 return isWhiteSpacePreformatted; 4396 default: 4397 return true; 4398 } 4399 }; 4400 const CharacterDataBuffer& characterDataBuffer = 4401 unnecessaryLineBreak->TextRef().DataBuffer(); 4402 const uint32_t length = characterDataBuffer.GetLength(); 4403 const DebugOnly<const char16_t> lastChar = 4404 characterDataBuffer.CharAt(length - 1); 4405 MOZ_ASSERT(lastChar == HTMLEditUtils::kNewLine); 4406 const bool textNodeHasVisibleChar = [&]() { 4407 if (length == 1u) { 4408 return false; 4409 } 4410 for (const uint32_t offset : Reversed(IntegerRange(length - 1))) { 4411 if (IsVisibleChar(characterDataBuffer.CharAt(offset))) { 4412 return true; 4413 } 4414 } 4415 return false; 4416 }(); 4417 if (!textNodeHasVisibleChar) { 4418 // If the invisible break is a placeholder of ancestor inline elements, we 4419 // should not delete it to allow users to insert text with the format 4420 // specified by them. 4421 if (HTMLEditUtils::GetMostDistantAncestorEditableEmptyInlineElement( 4422 unnecessaryLineBreak->TextRef(), 4423 BlockInlineCheck::UseComputedDisplayStyle)) { 4424 return NS_OK; 4425 } 4426 nsresult rv = DeleteNodeWithTransaction( 4427 MOZ_KnownLive(unnecessaryLineBreak->TextRef())); 4428 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4429 "EditorBase::DeleteNodeWithTransaction() failed " 4430 "to delete unnecessary Text node"); 4431 return rv; 4432 } 4433 Result<CaretPoint, nsresult> result = 4434 DeleteTextWithTransaction(MOZ_KnownLive(unnecessaryLineBreak->TextRef()), 4435 unnecessaryLineBreak->Offset(), 1); 4436 if (MOZ_UNLIKELY(result.isErr())) { 4437 NS_WARNING("HTMLEditor::DeleteTextWithTransaction() failed"); 4438 return result.unwrapErr(); 4439 } 4440 result.unwrap().IgnoreCaretPointSuggestion(); 4441 return NS_OK; 4442 } 4443 4444 Result<CreateElementResult, nsresult> 4445 HTMLEditor::InsertContainerWithTransaction( 4446 nsIContent& aContentToBeWrapped, const nsAtom& aWrapperTagName, 4447 const InitializeInsertingElement& aInitializer) { 4448 EditorDOMPoint pointToInsertNewContainer(&aContentToBeWrapped); 4449 if (NS_WARN_IF(!pointToInsertNewContainer.IsSet())) { 4450 return Err(NS_ERROR_FAILURE); 4451 } 4452 // aContentToBeWrapped will be moved to the new container before inserting the 4453 // new container. So, when we insert the container, the insertion point is 4454 // before the next sibling of aContentToBeWrapped. 4455 // XXX If pointerToInsertNewContainer stores offset here, the offset and 4456 // referring child node become mismatched. Although, currently this 4457 // is not a problem since InsertNodeTransaction refers only child node. 4458 MOZ_ALWAYS_TRUE(pointToInsertNewContainer.AdvanceOffset()); 4459 4460 // Create new container. 4461 RefPtr<Element> newContainer = CreateHTMLContent(&aWrapperTagName); 4462 if (NS_WARN_IF(!newContainer)) { 4463 return Err(NS_ERROR_FAILURE); 4464 } 4465 4466 if (&aInitializer != &HTMLEditor::DoNothingForNewElement) { 4467 nsresult rv = aInitializer(*this, *newContainer, 4468 EditorDOMPoint(&aContentToBeWrapped)); 4469 if (NS_WARN_IF(Destroyed())) { 4470 return Err(NS_ERROR_EDITOR_DESTROYED); 4471 } 4472 if (NS_FAILED(rv)) { 4473 NS_WARNING("aInitializer() failed"); 4474 return Err(rv); 4475 } 4476 } 4477 4478 // Notify our internal selection state listener 4479 AutoInsertContainerSelNotify selNotify(RangeUpdaterRef()); 4480 4481 // Put aNode in the new container, first. 4482 // XXX Perhaps, we should not remove the container if it's not editable. 4483 nsresult rv = DeleteNodeWithTransaction(aContentToBeWrapped); 4484 if (NS_FAILED(rv)) { 4485 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 4486 return Err(rv); 4487 } 4488 4489 { 4490 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary 4491 // in normal cases. However, it may be required for nested edit 4492 // actions which may be caused by legacy mutation event listeners or 4493 // chrome script. 4494 AutoTransactionsConserveSelection conserveSelection(*this); 4495 Result<CreateContentResult, nsresult> insertContentNodeResult = 4496 InsertNodeWithTransaction(aContentToBeWrapped, 4497 EditorDOMPoint(newContainer, 0u)); 4498 if (MOZ_UNLIKELY(insertContentNodeResult.isErr())) { 4499 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); 4500 return insertContentNodeResult.propagateErr(); 4501 } 4502 insertContentNodeResult.inspect().IgnoreCaretPointSuggestion(); 4503 } 4504 4505 // Put the new container where aNode was. 4506 Result<CreateElementResult, nsresult> insertNewContainerElementResult = 4507 InsertNodeWithTransaction<Element>(*newContainer, 4508 pointToInsertNewContainer); 4509 NS_WARNING_ASSERTION(insertNewContainerElementResult.isOk(), 4510 "EditorBase::InsertNodeWithTransaction() failed"); 4511 return insertNewContainerElementResult; 4512 } 4513 4514 Result<CreateElementResult, nsresult> 4515 HTMLEditor::ReplaceContainerWithTransactionInternal( 4516 Element& aOldContainer, const nsAtom& aTagName, const nsAtom& aAttribute, 4517 const nsAString& aAttributeValue, bool aCloneAllAttributes) { 4518 MOZ_ASSERT(IsEditActionDataAvailable()); 4519 4520 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(aOldContainer)) || 4521 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aOldContainer))) { 4522 return Err(NS_ERROR_FAILURE); 4523 } 4524 4525 // If we're replacing <dd> or <dt> with different type of element, we need to 4526 // split the parent <dl>. 4527 OwningNonNull<Element> containerElementToDelete = aOldContainer; 4528 if (aOldContainer.IsAnyOfHTMLElements(nsGkAtoms::dd, nsGkAtoms::dt) && 4529 &aTagName != nsGkAtoms::dt && &aTagName != nsGkAtoms::dd && 4530 // aOldContainer always has a parent node because of removable. 4531 aOldContainer.GetParentNode()->IsHTMLElement(nsGkAtoms::dl)) { 4532 OwningNonNull<Element> const dlElement = *aOldContainer.GetParentElement(); 4533 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(dlElement)) || 4534 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(dlElement))) { 4535 return Err(NS_ERROR_FAILURE); 4536 } 4537 Result<SplitRangeOffFromNodeResult, nsresult> splitDLElementResult = 4538 SplitRangeOffFromElement(dlElement, aOldContainer, aOldContainer); 4539 if (MOZ_UNLIKELY(splitDLElementResult.isErr())) { 4540 NS_WARNING("HTMLEditor::SplitRangeOffFromElement() failed"); 4541 return splitDLElementResult.propagateErr(); 4542 } 4543 splitDLElementResult.inspect().IgnoreCaretPointSuggestion(); 4544 RefPtr<Element> middleDLElement = aOldContainer.GetParentElement(); 4545 if (NS_WARN_IF(!middleDLElement) || 4546 NS_WARN_IF(!middleDLElement->IsHTMLElement(nsGkAtoms::dl)) || 4547 NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(*middleDLElement))) { 4548 NS_WARNING("The parent <dl> was lost at splitting it"); 4549 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4550 } 4551 containerElementToDelete = std::move(middleDLElement); 4552 } 4553 4554 const RefPtr<Element> newContainer = CreateHTMLContent(&aTagName); 4555 if (NS_WARN_IF(!newContainer)) { 4556 return Err(NS_ERROR_FAILURE); 4557 } 4558 4559 // Set or clone attribute if needed. 4560 // FIXME: What should we do attributes of <dl> elements if we removed it 4561 // above? 4562 if (aCloneAllAttributes) { 4563 MOZ_ASSERT(&aAttribute == nsGkAtoms::_empty); 4564 CloneAttributesWithTransaction(*newContainer, aOldContainer); 4565 } else if (&aAttribute != nsGkAtoms::_empty) { 4566 nsresult rv = newContainer->SetAttr(kNameSpaceID_None, 4567 const_cast<nsAtom*>(&aAttribute), 4568 aAttributeValue, true); 4569 if (NS_FAILED(rv)) { 4570 NS_WARNING("Element::SetAttr() failed"); 4571 return Err(NS_ERROR_FAILURE); 4572 } 4573 } 4574 4575 const OwningNonNull<nsINode> parentNode = 4576 *containerElementToDelete->GetParentNode(); 4577 const nsCOMPtr<nsINode> referenceNode = 4578 containerElementToDelete->GetNextSibling(); 4579 AutoReplaceContainerSelNotify selStateNotify(RangeUpdaterRef(), aOldContainer, 4580 *newContainer); 4581 if (aOldContainer.HasChildren()) { 4582 // TODO: Remove AutoTransactionsConserveSelection here. It's not necessary 4583 // in normal cases. However, it may be required for nested edit 4584 // actions which may be caused by legacy mutation event listeners or 4585 // chrome script. 4586 // Move non-editable children too because its container, aElement, is 4587 // editable so that all children must be removable node. 4588 const OwningNonNull<nsIContent> firstChild = *aOldContainer.GetFirstChild(); 4589 const OwningNonNull<nsIContent> lastChild = *aOldContainer.GetLastChild(); 4590 Result<MoveNodeResult, nsresult> moveChildrenResultOrError = 4591 MoveSiblingsWithTransaction(firstChild, lastChild, 4592 EditorDOMPoint(newContainer, 0)); 4593 if (MOZ_UNLIKELY(moveChildrenResultOrError.isErr())) { 4594 NS_WARNING("HTMLEditor::MoveSiblingsWithTransaction() failed"); 4595 return moveChildrenResultOrError.propagateErr(); 4596 } 4597 // We'll suggest new caret point which is suggested by new container 4598 // element insertion result. Therefore, we need to do nothing here. 4599 moveChildrenResultOrError.inspect().IgnoreCaretPointSuggestion(); 4600 } 4601 4602 // Delete containerElementToDelete from the DOM tree to make it not referred 4603 // by InsertNodeTransaction. 4604 nsresult rv = DeleteNodeWithTransaction(containerElementToDelete); 4605 if (NS_FAILED(rv)) { 4606 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 4607 return Err(rv); 4608 } 4609 4610 if (referenceNode && (!referenceNode->GetParentNode() || 4611 parentNode != referenceNode->GetParentNode())) { 4612 NS_WARNING( 4613 "The reference node for insertion has been moved to different parent, " 4614 "so we got lost the insertion point"); 4615 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 4616 } 4617 4618 // Finally, insert the new node to where probably aOldContainer was. 4619 Result<CreateElementResult, nsresult> insertNewContainerElementResult = 4620 InsertNodeWithTransaction<Element>( 4621 *newContainer, referenceNode ? EditorDOMPoint(referenceNode) 4622 : EditorDOMPoint::AtEndOf(*parentNode)); 4623 NS_WARNING_ASSERTION(insertNewContainerElementResult.isOk(), 4624 "EditorBase::InsertNodeWithTransaction() failed"); 4625 MOZ_ASSERT_IF( 4626 insertNewContainerElementResult.isOk(), 4627 insertNewContainerElementResult.inspect().GetNewNode() == newContainer); 4628 return insertNewContainerElementResult; 4629 } 4630 4631 Result<EditorDOMPoint, nsresult> HTMLEditor::RemoveContainerWithTransaction( 4632 Element& aElement) { 4633 MOZ_ASSERT(IsEditActionDataAvailable()); 4634 4635 if (NS_WARN_IF(!HTMLEditUtils::IsRemovableNode(aElement)) || 4636 NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(aElement))) { 4637 return Err(NS_ERROR_FAILURE); 4638 } 4639 4640 // Notify our internal selection state listener. 4641 AutoRemoveContainerSelNotify selNotify(RangeUpdaterRef(), 4642 EditorRawDOMPoint(&aElement)); 4643 const nsCOMPtr<nsINode> parentNode = aElement.GetParentNode(); 4644 const nsCOMPtr<nsIContent> nextSibling = aElement.GetNextSibling(); 4645 EditorDOMPoint pointToPutCaret; 4646 // FIXME: If we'd improve the range updater to be able to track moving nodes 4647 // after removing the container first, we should do that because 4648 // IMEContentObserver can run faster. 4649 if (aElement.HasChildren()) { 4650 const OwningNonNull<nsIContent> firstChild = *aElement.GetFirstChild(); 4651 const OwningNonNull<nsIContent> lastChild = *aElement.GetLastChild(); 4652 Result<MoveNodeResult, nsresult> moveChildrenResultOrError = 4653 MoveSiblingsWithTransaction(firstChild, lastChild, 4654 nextSibling 4655 ? EditorDOMPoint(nextSibling) 4656 : EditorDOMPoint::AtEndOf(*parentNode)); 4657 if (MOZ_UNLIKELY(moveChildrenResultOrError.isErr())) { 4658 NS_WARNING("HTMLEditor::MoveSiblingsWithTransaction() failed"); 4659 return moveChildrenResultOrError.propagateErr(); 4660 } 4661 pointToPutCaret = moveChildrenResultOrError.unwrap().UnwrapCaretPoint(); 4662 } 4663 { 4664 AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret); 4665 nsresult rv = DeleteNodeWithTransaction(aElement); 4666 if (NS_FAILED(rv)) { 4667 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 4668 return Err(rv); 4669 } 4670 if (NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parentNode)) { 4671 return Err(NS_ERROR_EDITOR_DESTROYED); 4672 } 4673 } 4674 if (pointToPutCaret.IsSetAndValidInComposedDoc()) { 4675 return pointToPutCaret; 4676 } 4677 return nextSibling && nextSibling->GetParentNode() == parentNode 4678 ? EditorDOMPoint(nextSibling) 4679 : EditorDOMPoint::AtEndOf(*parentNode); 4680 } 4681 4682 nsresult HTMLEditor::SelectEntireDocument() { 4683 MOZ_ASSERT(IsEditActionDataAvailable()); 4684 4685 if (!mInitSucceeded) { 4686 return NS_ERROR_NOT_INITIALIZED; 4687 } 4688 4689 // XXX It's odd to select all of the document body if an contenteditable 4690 // element has focus. 4691 RefPtr<Element> bodyOrDocumentElement = GetRoot(); 4692 if (NS_WARN_IF(!bodyOrDocumentElement)) { 4693 return NS_ERROR_NOT_INITIALIZED; 4694 } 4695 4696 // If we're empty, don't select all children because that would select the 4697 // padding <br> element for empty editor. 4698 if (IsEmpty()) { 4699 nsresult rv = CollapseSelectionToStartOf(*bodyOrDocumentElement); 4700 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4701 "EditorBase::CollapseSelectionToStartOf() failed"); 4702 return rv; 4703 } 4704 4705 // Otherwise, select all children. 4706 ErrorResult error; 4707 SelectionRef().SelectAllChildren(*bodyOrDocumentElement, error); 4708 if (NS_WARN_IF(Destroyed())) { 4709 error.SuppressException(); 4710 return NS_ERROR_EDITOR_DESTROYED; 4711 } 4712 NS_WARNING_ASSERTION(!error.Failed(), 4713 "Selection::SelectAllChildren() failed"); 4714 return error.StealNSResult(); 4715 } 4716 4717 nsresult HTMLEditor::SelectAllInternal() { 4718 MOZ_ASSERT(IsEditActionDataAvailable()); 4719 4720 CommitComposition(); 4721 const RefPtr<Document> doc = GetDocument(); 4722 if (NS_WARN_IF(!doc)) { 4723 // The root caller should not use HTMLEditor in this case (mDocument won't 4724 // be cleared except by the cycle collector). 4725 return NS_ERROR_NOT_AVAILABLE; 4726 } 4727 4728 auto GetBodyElementIfElementIsParentOfHTMLBody = 4729 [](const Element& aElement) -> Element* { 4730 if (!aElement.OwnerDoc()->IsHTMLDocument()) { 4731 return const_cast<Element*>(&aElement); 4732 } 4733 HTMLBodyElement* bodyElement = aElement.OwnerDoc()->GetBodyElement(); 4734 return bodyElement && nsContentUtils::ContentIsFlattenedTreeDescendantOf( 4735 bodyElement, &aElement) 4736 ? bodyElement 4737 : const_cast<Element*>(&aElement); 4738 }; 4739 4740 const nsCOMPtr<nsIContent> selectionRootContent = 4741 [&]() MOZ_CAN_RUN_SCRIPT -> nsIContent* { 4742 const RefPtr<Element> elementForComputingSelectionRoot = [&]() -> Element* { 4743 // If there is at least one selection range, we should compute the 4744 // selection root from the anchor node. 4745 if (SelectionRef().RangeCount()) { 4746 if (nsIContent* content = 4747 nsIContent::FromNodeOrNull(SelectionRef().GetAnchorNode())) { 4748 if (content->IsElement()) { 4749 return content->AsElement(); 4750 } 4751 if (Element* parentElement = 4752 content->GetParentElementCrossingShadowRoot()) { 4753 return parentElement; 4754 } 4755 } 4756 } 4757 // If no element contains a selection range, we should select all children 4758 // of the focused element at least. 4759 if (Element* focusedElement = GetFocusedElement()) { 4760 return focusedElement; 4761 } 4762 // Okay, there is no selection range and no focused element, let's select 4763 // all of the body or the document element. 4764 if (Element* const bodyElement = GetBodyElement()) { 4765 return bodyElement; 4766 } 4767 return doc->GetDocumentElement(); 4768 }(); 4769 if (MOZ_UNLIKELY(!elementForComputingSelectionRoot)) { 4770 return nullptr; 4771 } 4772 4773 // Then, compute the selection root content to select all including 4774 // elementToBeSelected. 4775 RefPtr<PresShell> presShell = GetPresShell(); 4776 nsIContent* computedSelectionRootContent = 4777 elementForComputingSelectionRoot->GetSelectionRootContent( 4778 presShell, nsINode::IgnoreOwnIndependentSelection::Yes, 4779 nsINode::AllowCrossShadowBoundary::No); 4780 if (NS_WARN_IF(!computedSelectionRootContent)) { 4781 return nullptr; 4782 } 4783 if (MOZ_UNLIKELY(!computedSelectionRootContent->IsElement())) { 4784 return computedSelectionRootContent; 4785 } 4786 return GetBodyElementIfElementIsParentOfHTMLBody( 4787 *computedSelectionRootContent->AsElement()); 4788 }(); 4789 if (MOZ_UNLIKELY(!selectionRootContent)) { 4790 // If there is no element in the document, the document node can have 4791 // selection range whose container is the document node. However, Chrome 4792 // does not handle "Select All" command in this case (if there is no 4793 // selection range, no new range is created and if there is a collapsed 4794 // range, the range won't be extended to select all children of the 4795 // Document). Let's follow it. 4796 return NS_OK; 4797 } 4798 4799 Maybe<Selection::AutoUserInitiated> userSelection; 4800 // XXX Do we need to mark it as "user initiated" for 4801 // `Document.execCommand("selectAll")`? 4802 if (!selectionRootContent->IsEditable()) { 4803 userSelection.emplace(SelectionRef()); 4804 } 4805 ErrorResult error; 4806 SelectionRef().SelectAllChildren(*selectionRootContent, error); 4807 NS_WARNING_ASSERTION(!error.Failed(), 4808 "Selection::SelectAllChildren() failed"); 4809 return error.StealNSResult(); 4810 } 4811 4812 bool HTMLEditor::SetCaretInTableCell(Element* aElement) { 4813 MOZ_ASSERT(IsEditActionDataAvailable()); 4814 4815 if (!aElement || !aElement->IsHTMLElement() || 4816 !HTMLEditUtils::IsAnyTableElementExceptColumnElement(*aElement)) { 4817 return false; 4818 } 4819 const RefPtr<Element> editingHost = ComputeEditingHost(); 4820 if (!editingHost || !aElement->IsInclusiveDescendantOf(editingHost)) { 4821 return false; 4822 } 4823 4824 nsCOMPtr<nsIContent> deepestFirstChild = aElement; 4825 while (deepestFirstChild->HasChildren()) { 4826 deepestFirstChild = deepestFirstChild->GetFirstChild(); 4827 } 4828 4829 // Set selection at beginning of the found node 4830 nsresult rv = CollapseSelectionToStartOf(*deepestFirstChild); 4831 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4832 "EditorBase::CollapseSelectionToStartOf() failed"); 4833 return NS_SUCCEEDED(rv); 4834 } 4835 4836 /** 4837 * This method scans the selection for adjacent text nodes 4838 * and collapses them into a single text node. 4839 * "adjacent" means literally adjacent siblings of the same parent. 4840 * Uses HTMLEditor::JoinNodesWithTransaction() so action is undoable. 4841 * Should be called within the context of a batch transaction. 4842 */ 4843 nsresult HTMLEditor::CollapseAdjacentTextNodes(nsRange& aRange) { 4844 AutoTransactionsConserveSelection dontChangeMySelection(*this); 4845 4846 // we can't actually do anything during iteration, so store the text nodes in 4847 // an array first. 4848 DOMSubtreeIterator subtreeIter; 4849 if (NS_FAILED(subtreeIter.Init(aRange))) { 4850 NS_WARNING("DOMSubtreeIterator::Init() failed"); 4851 return NS_ERROR_FAILURE; 4852 } 4853 AutoTArray<OwningNonNull<Text>, 8> textNodes; 4854 subtreeIter.AppendNodesToArray( 4855 +[](nsINode& aNode, void*) -> bool { 4856 return EditorUtils::IsEditableContent(*aNode.AsText(), 4857 EditorType::HTML); 4858 }, 4859 textNodes); 4860 4861 if (textNodes.Length() < 2) { 4862 return NS_OK; 4863 } 4864 4865 OwningNonNull<Text> leftTextNode = textNodes[0]; 4866 for (size_t rightTextNodeIndex = 1; rightTextNodeIndex < textNodes.Length(); 4867 rightTextNodeIndex++) { 4868 OwningNonNull<Text>& rightTextNode = textNodes[rightTextNodeIndex]; 4869 // If the leftTextNode has only preformatted line break, keep it as-is. 4870 if (HTMLEditUtils::TextHasOnlyOnePreformattedLinefeed(leftTextNode)) { 4871 leftTextNode = rightTextNode; 4872 continue; 4873 } 4874 // If the rightTextNode has only preformatted line break, keep it as-is, and 4875 // advance the loop next to the rightTextNode. 4876 if (HTMLEditUtils::TextHasOnlyOnePreformattedLinefeed(rightTextNode)) { 4877 if (++rightTextNodeIndex == textNodes.Length()) { 4878 break; 4879 } 4880 leftTextNode = textNodes[rightTextNodeIndex]; 4881 continue; 4882 } 4883 // If the text nodes are not direct siblings, we shouldn't join them, and 4884 // we don't need to handle the left one anymore. 4885 if (leftTextNode->GetNextSibling() != rightTextNode) { 4886 leftTextNode = rightTextNode; 4887 continue; 4888 } 4889 Result<JoinNodesResult, nsresult> joinNodesResultOrError = 4890 JoinTextNodesWithNormalizeWhiteSpaces(MOZ_KnownLive(leftTextNode), 4891 MOZ_KnownLive(rightTextNode)); 4892 if (MOZ_UNLIKELY(joinNodesResultOrError.isErr())) { 4893 NS_WARNING("HTMLEditor::JoinTextNodesWithNormalizeWhiteSpaces() failed"); 4894 return joinNodesResultOrError.unwrapErr(); 4895 } 4896 } 4897 4898 return NS_OK; 4899 } 4900 4901 nsresult HTMLEditor::SetSelectionAtDocumentStart() { 4902 MOZ_ASSERT(IsEditActionDataAvailable()); 4903 4904 RefPtr<Element> rootElement = GetRoot(); 4905 if (NS_WARN_IF(!rootElement)) { 4906 return NS_ERROR_FAILURE; 4907 } 4908 4909 nsresult rv = CollapseSelectionToStartOf(*rootElement); 4910 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4911 "EditorBase::CollapseSelectionToStartOf() failed"); 4912 return rv; 4913 } 4914 4915 /** 4916 * Remove aNode, re-parenting any children into the parent of aNode. In 4917 * addition, insert any br's needed to preserve identity of removed block. 4918 */ 4919 Result<EditorDOMPoint, nsresult> 4920 HTMLEditor::RemoveBlockContainerWithTransaction(Element& aElement) { 4921 MOZ_ASSERT(IsEditActionDataAvailable()); 4922 4923 // Two possibilities: the container could be empty of editable content. If 4924 // that is the case, we need to compare what is before and after aNode to 4925 // determine if we need a br. 4926 // 4927 // Or it could be not empty, in which case we have to compare previous 4928 // sibling and first child to determine if we need a leading br, and compare 4929 // following sibling and last child to determine if we need a trailing br. 4930 4931 const RefPtr<Element> parentElement = aElement.GetParentElement(); 4932 if (NS_WARN_IF((!parentElement))) { 4933 return Err(NS_ERROR_FAILURE); 4934 } 4935 EditorDOMPoint pointToPutCaret; 4936 if (HTMLEditUtils::CanNodeContain(*parentElement, *nsGkAtoms::br)) { 4937 if (nsCOMPtr<nsIContent> child = HTMLEditUtils::GetFirstChild( 4938 aElement, {WalkTreeOption::IgnoreNonEditableNode})) { 4939 // The case of aNode not being empty. We need a br at start unless: 4940 // 1) previous sibling of aNode is a block, OR 4941 // 2) previous sibling of aNode is a br, OR 4942 // 3) first child of aNode is a block OR 4943 // 4) either is null 4944 4945 if (nsIContent* previousSibling = HTMLEditUtils::GetPreviousSibling( 4946 aElement, {WalkTreeOption::IgnoreNonEditableNode})) { 4947 if (!HTMLEditUtils::IsBlockElement( 4948 *previousSibling, 4949 BlockInlineCheck::UseComputedDisplayOutsideStyle) && 4950 !previousSibling->IsHTMLElement(nsGkAtoms::br) && 4951 !HTMLEditUtils::IsBlockElement( 4952 *child, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 4953 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 4954 InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, 4955 EditorDOMPoint(&aElement)); 4956 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 4957 NS_WARNING( 4958 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 4959 "LineBreakType::BRElement) failed"); 4960 return insertBRElementResultOrError.propagateErr(); 4961 } 4962 CreateLineBreakResult insertBRElementResult = 4963 insertBRElementResultOrError.unwrap(); 4964 MOZ_ASSERT(insertBRElementResult.Handled()); 4965 insertBRElementResult.IgnoreCaretPointSuggestion(); 4966 pointToPutCaret = EditorDOMPoint(&aElement, 0); 4967 } 4968 } 4969 4970 // We need a br at end unless: 4971 // 1) following sibling of aNode is a block, OR 4972 // 2) last child of aNode is a block, OR 4973 // 3) last child of aNode is a br OR 4974 // 4) either is null 4975 4976 if (nsIContent* nextSibling = HTMLEditUtils::GetNextSibling( 4977 aElement, {WalkTreeOption::IgnoreNonEditableNode})) { 4978 if (nextSibling && 4979 !HTMLEditUtils::IsBlockElement( 4980 *nextSibling, BlockInlineCheck::UseComputedDisplayStyle)) { 4981 if (nsIContent* lastChild = HTMLEditUtils::GetLastChild( 4982 aElement, {WalkTreeOption::IgnoreNonEditableNode}, 4983 BlockInlineCheck::Unused)) { 4984 if (!HTMLEditUtils::IsBlockElement( 4985 *lastChild, BlockInlineCheck::UseComputedDisplayStyle) && 4986 !lastChild->IsHTMLElement(nsGkAtoms::br)) { 4987 Result<CreateLineBreakResult, nsresult> 4988 insertBRElementResultOrError = InsertLineBreak( 4989 WithTransaction::Yes, LineBreakType::BRElement, 4990 EditorDOMPoint::After(aElement)); 4991 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 4992 NS_WARNING( 4993 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 4994 "LineBreakType::BRElement) failed"); 4995 return insertBRElementResultOrError.propagateErr(); 4996 } 4997 CreateLineBreakResult insertBRElementResult = 4998 insertBRElementResultOrError.unwrap(); 4999 MOZ_ASSERT(insertBRElementResult.Handled()); 5000 insertBRElementResult.IgnoreCaretPointSuggestion(); 5001 pointToPutCaret = EditorDOMPoint::AtEndOf(aElement); 5002 } 5003 } 5004 } 5005 } 5006 } else if (nsIContent* previousSibling = HTMLEditUtils::GetPreviousSibling( 5007 aElement, {WalkTreeOption::IgnoreNonEditableNode})) { 5008 // The case of aNode being empty. We need a br at start unless: 5009 // 1) previous sibling of aNode is a block, OR 5010 // 2) previous sibling of aNode is a br, OR 5011 // 3) following sibling of aNode is a block, OR 5012 // 4) following sibling of aNode is a br OR 5013 // 5) either is null 5014 if (!HTMLEditUtils::IsBlockElement( 5015 *previousSibling, BlockInlineCheck::UseComputedDisplayStyle) && 5016 !previousSibling->IsHTMLElement(nsGkAtoms::br)) { 5017 if (nsIContent* nextSibling = HTMLEditUtils::GetNextSibling( 5018 aElement, {WalkTreeOption::IgnoreNonEditableNode})) { 5019 if (!HTMLEditUtils::IsBlockElement( 5020 *nextSibling, BlockInlineCheck::UseComputedDisplayStyle) && 5021 !nextSibling->IsHTMLElement(nsGkAtoms::br)) { 5022 Result<CreateLineBreakResult, nsresult> 5023 insertBRElementResultOrError = InsertLineBreak( 5024 WithTransaction::Yes, LineBreakType::BRElement, 5025 EditorDOMPoint(&aElement)); 5026 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 5027 NS_WARNING( 5028 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 5029 "LineBreakType::BRElement) failed"); 5030 return insertBRElementResultOrError.propagateErr(); 5031 } 5032 CreateLineBreakResult insertBRElementResult = 5033 insertBRElementResultOrError.unwrap(); 5034 MOZ_ASSERT(insertBRElementResult.Handled()); 5035 insertBRElementResult.IgnoreCaretPointSuggestion(); 5036 pointToPutCaret = EditorDOMPoint(&aElement, 0); 5037 } 5038 } 5039 } 5040 } 5041 } 5042 5043 // Now remove container 5044 AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret); 5045 Result<EditorDOMPoint, nsresult> unwrapBlockElementResult = 5046 RemoveContainerWithTransaction(aElement); 5047 if (MOZ_UNLIKELY(unwrapBlockElementResult.isErr())) { 5048 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed"); 5049 return unwrapBlockElementResult; 5050 } 5051 trackPointToPutCaret.FlushAndStopTracking(); 5052 if (AllowsTransactionsToChangeSelection() && 5053 unwrapBlockElementResult.inspect().IsSet()) { 5054 pointToPutCaret = unwrapBlockElementResult.unwrap(); 5055 } 5056 return pointToPutCaret; // May be unset 5057 } 5058 5059 Result<SplitNodeResult, nsresult> HTMLEditor::SplitNodeWithTransaction( 5060 const EditorDOMPoint& aStartOfRightNode) { 5061 MOZ_ASSERT(IsEditActionDataAvailable()); 5062 5063 if (NS_WARN_IF(!aStartOfRightNode.IsInContentNode())) { 5064 return Err(NS_ERROR_INVALID_ARG); 5065 } 5066 MOZ_ASSERT(aStartOfRightNode.IsSetAndValid()); 5067 5068 if (NS_WARN_IF(!HTMLEditUtils::IsSplittableNode( 5069 *aStartOfRightNode.ContainerAs<nsIContent>()))) { 5070 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 5071 } 5072 5073 IgnoredErrorResult ignoredError; 5074 AutoEditSubActionNotifier startToHandleEditSubAction( 5075 *this, EditSubAction::eSplitNode, nsIEditor::eNext, ignoredError); 5076 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 5077 return Err(NS_ERROR_EDITOR_DESTROYED); 5078 } 5079 NS_WARNING_ASSERTION( 5080 !ignoredError.Failed(), 5081 "OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 5082 5083 RefPtr<SplitNodeTransaction> transaction = 5084 SplitNodeTransaction::Create(*this, aStartOfRightNode); 5085 nsresult rv = DoTransactionInternal(transaction); 5086 if (NS_WARN_IF(Destroyed())) { 5087 NS_WARNING( 5088 "EditorBase::DoTransactionInternal() caused destroying the editor"); 5089 return Err(NS_ERROR_EDITOR_DESTROYED); 5090 } 5091 if (NS_FAILED(rv)) { 5092 NS_WARNING("EditorBase::DoTransactionInternal() failed"); 5093 return Err(rv); 5094 } 5095 5096 nsIContent* newContent = transaction->GetNewContent(); 5097 nsIContent* splitContent = transaction->GetSplitContent(); 5098 if (NS_WARN_IF(!newContent) || NS_WARN_IF(!splitContent)) { 5099 return Err(NS_ERROR_FAILURE); 5100 } 5101 TopLevelEditSubActionDataRef().DidSplitContent(*this, *splitContent, 5102 *newContent); 5103 if (NS_WARN_IF(!newContent->IsInComposedDoc()) || 5104 NS_WARN_IF(!splitContent->IsInComposedDoc())) { 5105 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 5106 } 5107 5108 return SplitNodeResult(*newContent, *splitContent); 5109 } 5110 5111 Result<SplitNodeResult, nsresult> HTMLEditor::SplitNodeDeepWithTransaction( 5112 nsIContent& aMostAncestorToSplit, 5113 const EditorDOMPoint& aDeepestStartOfRightNode, 5114 SplitAtEdges aSplitAtEdges) { 5115 MOZ_ASSERT(aDeepestStartOfRightNode.IsSetAndValidInComposedDoc()); 5116 MOZ_ASSERT( 5117 aDeepestStartOfRightNode.GetContainer() == &aMostAncestorToSplit || 5118 EditorUtils::IsDescendantOf(*aDeepestStartOfRightNode.GetContainer(), 5119 aMostAncestorToSplit)); 5120 5121 if (NS_WARN_IF(!aDeepestStartOfRightNode.IsInComposedDoc())) { 5122 return Err(NS_ERROR_INVALID_ARG); 5123 } 5124 5125 nsCOMPtr<nsIContent> newLeftNodeOfMostAncestor; 5126 EditorDOMPoint atStartOfRightNode(aDeepestStartOfRightNode); 5127 // lastResult is as explained by its name, the last result which may not be 5128 // split a node actually. 5129 SplitNodeResult lastResult = SplitNodeResult::NotHandled(atStartOfRightNode); 5130 MOZ_ASSERT(lastResult.AtSplitPoint<EditorRawDOMPoint>() 5131 .IsSetAndValidInComposedDoc()); 5132 5133 while (true) { 5134 // Need to insert rules code call here to do things like not split a list 5135 // if you are after the last <li> or before the first, etc. For now we 5136 // just have some smarts about unnecessarily splitting text nodes, which 5137 // should be universal enough to put straight in this EditorBase routine. 5138 auto* splittingContent = atStartOfRightNode.GetContainerAs<nsIContent>(); 5139 if (NS_WARN_IF(!splittingContent)) { 5140 lastResult.IgnoreCaretPointSuggestion(); 5141 return Err(NS_ERROR_FAILURE); 5142 } 5143 // If we meet an orphan node before meeting aMostAncestorToSplit, we need 5144 // to stop splitting. This is a bug of the caller. 5145 if (NS_WARN_IF(splittingContent != &aMostAncestorToSplit && 5146 !atStartOfRightNode.GetContainerParentAs<nsIContent>())) { 5147 lastResult.IgnoreCaretPointSuggestion(); 5148 return Err(NS_ERROR_FAILURE); 5149 } 5150 // If the container is not splitable node such as comment node, atomic 5151 // element, etc, we should keep it as-is, and try to split its parents. 5152 if (!HTMLEditUtils::IsSplittableNode(*splittingContent)) { 5153 if (splittingContent == &aMostAncestorToSplit) { 5154 return lastResult; 5155 } 5156 atStartOfRightNode.Set(splittingContent); 5157 continue; 5158 } 5159 5160 // If the split point is middle of the node or the node is not a text node 5161 // and we're allowed to create empty element node, split it. 5162 if ((aSplitAtEdges == SplitAtEdges::eAllowToCreateEmptyContainer && 5163 !atStartOfRightNode.IsInTextNode()) || 5164 (!atStartOfRightNode.IsStartOfContainer() && 5165 !atStartOfRightNode.IsEndOfContainer())) { 5166 Result<SplitNodeResult, nsresult> splitNodeResult = 5167 SplitNodeWithTransaction(atStartOfRightNode); 5168 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 5169 lastResult.IgnoreCaretPointSuggestion(); 5170 return splitNodeResult; 5171 } 5172 lastResult = SplitNodeResult::MergeWithDeeperSplitNodeResult( 5173 splitNodeResult.unwrap(), lastResult); 5174 if (NS_WARN_IF(!lastResult.AtSplitPoint<EditorRawDOMPoint>() 5175 .IsInComposedDoc())) { 5176 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 5177 } 5178 MOZ_ASSERT(lastResult.HasCaretPointSuggestion()); 5179 MOZ_ASSERT(lastResult.GetOriginalContent() == splittingContent); 5180 if (splittingContent == &aMostAncestorToSplit) { 5181 // Actually, we split aMostAncestorToSplit. 5182 return lastResult; 5183 } 5184 5185 // Then, try to split its parent before current node. 5186 atStartOfRightNode = lastResult.AtNextContent<EditorDOMPoint>(); 5187 } 5188 // If the split point is end of the node and it is a text node or we're not 5189 // allowed to create empty container node, try to split its parent after it. 5190 else if (!atStartOfRightNode.IsStartOfContainer()) { 5191 lastResult = SplitNodeResult::HandledButDidNotSplitDueToEndOfContainer( 5192 *splittingContent, &lastResult); 5193 MOZ_ASSERT(lastResult.AtSplitPoint<EditorRawDOMPoint>() 5194 .IsSetAndValidInComposedDoc()); 5195 if (splittingContent == &aMostAncestorToSplit) { 5196 return lastResult; 5197 } 5198 5199 // Try to split its parent after current node. 5200 atStartOfRightNode.SetAfter(splittingContent); 5201 } 5202 // If the split point is start of the node and it is a text node or we're 5203 // not allowed to create empty container node, try to split its parent. 5204 else { 5205 if (splittingContent == &aMostAncestorToSplit) { 5206 return SplitNodeResult::HandledButDidNotSplitDueToStartOfContainer( 5207 *splittingContent, &lastResult); 5208 } 5209 5210 // Try to split its parent before current node. 5211 // XXX This is logically wrong. If we've already split something but 5212 // this is the last splitable content node in the limiter, this 5213 // method will return "not handled". 5214 lastResult = SplitNodeResult::NotHandled(atStartOfRightNode, &lastResult); 5215 MOZ_ASSERT(lastResult.AtSplitPoint<EditorRawDOMPoint>() 5216 .IsSetAndValidInComposedDoc()); 5217 atStartOfRightNode.Set(splittingContent); 5218 MOZ_ASSERT(atStartOfRightNode.IsSetAndValidInComposedDoc()); 5219 } 5220 } 5221 5222 // Not reached because while (true) loop never breaks. 5223 } 5224 5225 Result<SplitNodeResult, nsresult> HTMLEditor::DoSplitNode( 5226 const EditorDOMPoint& aStartOfRightNode, nsIContent& aNewNode) { 5227 // Ensure computing the offset if it's initialized with a child content node. 5228 (void)aStartOfRightNode.Offset(); 5229 5230 // XXX Perhaps, aStartOfRightNode may be invalid if this is a redo 5231 // operation after modifying DOM node with JS. 5232 if (NS_WARN_IF(!aStartOfRightNode.IsInContentNode())) { 5233 return Err(NS_ERROR_INVALID_ARG); 5234 } 5235 MOZ_DIAGNOSTIC_ASSERT(aStartOfRightNode.IsSetAndValid()); 5236 5237 // Remember all selection points. 5238 AutoTArray<SavedRange, 10> savedRanges; 5239 for (SelectionType selectionType : kPresentSelectionTypes) { 5240 SavedRange savingRange; 5241 savingRange.mSelection = GetSelection(selectionType); 5242 if (NS_WARN_IF(!savingRange.mSelection && 5243 selectionType == SelectionType::eNormal)) { 5244 return Err(NS_ERROR_FAILURE); 5245 } 5246 if (!savingRange.mSelection) { 5247 // For non-normal selections, skip over the non-existing ones. 5248 continue; 5249 } 5250 5251 for (uint32_t j : IntegerRange(savingRange.mSelection->RangeCount())) { 5252 const nsRange* r = savingRange.mSelection->GetRangeAt(j); 5253 MOZ_ASSERT(r); 5254 MOZ_ASSERT(r->IsPositioned()); 5255 // XXX Looks like that SavedRange should have mStart and mEnd which 5256 // are RangeBoundary. Then, we can avoid to compute offset here. 5257 savingRange.mStartContainer = r->GetStartContainer(); 5258 savingRange.mStartOffset = r->StartOffset(); 5259 savingRange.mEndContainer = r->GetEndContainer(); 5260 savingRange.mEndOffset = r->EndOffset(); 5261 5262 savedRanges.AppendElement(savingRange); 5263 } 5264 } 5265 5266 const nsCOMPtr<nsINode> containerParentNode = 5267 aStartOfRightNode.GetContainerParent(); 5268 if (NS_WARN_IF(!containerParentNode)) { 5269 return Err(NS_ERROR_FAILURE); 5270 } 5271 5272 // For the performance of IMEContentObserver, we should move all data into 5273 // aNewNode first because IMEContentObserver needs to compute moved content 5274 // length only once when aNewNode is connected. 5275 5276 // If we are splitting a text node, we need to move its some data to the 5277 // new text node. 5278 MOZ_DIAGNOSTIC_ASSERT_IF(aStartOfRightNode.IsInTextNode(), aNewNode.IsText()); 5279 MOZ_DIAGNOSTIC_ASSERT_IF(!aStartOfRightNode.IsInTextNode(), 5280 !aNewNode.IsText()); 5281 const nsCOMPtr<nsIContent> firstChildOfRightNode = 5282 aStartOfRightNode.GetChild(); 5283 nsresult rv = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT { 5284 if (aStartOfRightNode.IsEndOfContainer()) { 5285 return NS_OK; // No content which should be moved into aNewNode. 5286 } 5287 if (aStartOfRightNode.IsInTextNode()) { 5288 Text* originalTextNode = aStartOfRightNode.ContainerAs<Text>(); 5289 Text* newTextNode = aNewNode.AsText(); 5290 nsAutoString movingText; 5291 const uint32_t cutStartOffset = aStartOfRightNode.Offset(); 5292 const uint32_t cutLength = 5293 originalTextNode->Length() - aStartOfRightNode.Offset(); 5294 IgnoredErrorResult error; 5295 originalTextNode->SubstringData(cutStartOffset, cutLength, movingText, 5296 error); 5297 NS_WARNING_ASSERTION(!error.Failed(), 5298 "Text::SubstringData() failed, but ignored"); 5299 error.SuppressException(); 5300 5301 // XXX This call may destroy us. 5302 DoDeleteText(MOZ_KnownLive(*originalTextNode), cutStartOffset, cutLength, 5303 error); 5304 NS_WARNING_ASSERTION(!error.Failed(), 5305 "EditorBase::DoDeleteText() failed, but ignored"); 5306 error.SuppressException(); 5307 5308 // XXX This call may destroy us. 5309 DoSetText(MOZ_KnownLive(*newTextNode), movingText, error); 5310 NS_WARNING_ASSERTION(!error.Failed(), 5311 "EditorBase::DoSetText() failed, but ignored"); 5312 return NS_OK; 5313 } 5314 5315 // If the right node is new one and splitting at start of the container, 5316 // we need to move all children to the new right node. 5317 if (!firstChildOfRightNode->GetPreviousSibling()) { 5318 // XXX Why do we ignore an error while moving nodes from the right 5319 // node to the left node? 5320 nsresult rv = MoveAllChildren( 5321 // MOZ_KnownLive because aStartOfRightNode grabs the container. 5322 MOZ_KnownLive(*aStartOfRightNode.GetContainer()), 5323 EditorRawDOMPoint(&aNewNode, 0u)); 5324 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5325 "HTMLEditor::MoveAllChildren() failed"); 5326 return rv; 5327 } 5328 5329 // If the right node is new one and splitting at middle of the node, we need 5330 // to move inclusive next siblings of the split point to the new right node. 5331 // XXX Why do we ignore an error while moving nodes from the right node 5332 // to the left node? 5333 nsresult rv = MoveInclusiveNextSiblings(*firstChildOfRightNode, 5334 EditorRawDOMPoint(&aNewNode, 0u)); 5335 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 5336 "HTMLEditor::MoveInclusiveNextSiblings() failed"); 5337 return rv; 5338 }(); 5339 5340 // To avoid a dataloss bug, we should try to insert aNewNode even if we've 5341 // already been destroyed. 5342 if (NS_WARN_IF(!aStartOfRightNode.GetContainerParent())) { 5343 return NS_WARN_IF(Destroyed()) ? Err(NS_ERROR_EDITOR_DESTROYED) 5344 : Err(NS_ERROR_FAILURE); 5345 } 5346 5347 // Finally, we should insert aNewNode which already has proper data or 5348 // children. 5349 { 5350 const nsCOMPtr<nsIContent> nextSibling = 5351 aStartOfRightNode.GetContainer()->GetNextSibling(); 5352 AutoNodeAPIWrapper nodeWrapper(*this, *containerParentNode); 5353 nsresult rv = nodeWrapper.InsertBefore(aNewNode, nextSibling); 5354 if (NS_FAILED(rv)) { 5355 NS_WARNING("AutoNodeAPIWrapper::InsertBefore() failed"); 5356 return Err(rv); 5357 } 5358 NS_WARNING_ASSERTION( 5359 nodeWrapper.IsExpectedResult(), 5360 "Inserting new node caused other mutations, but ignored"); 5361 } 5362 if (NS_FAILED(rv)) { 5363 NS_WARNING("Moving children from left node to right node failed"); 5364 return Err(rv); 5365 } 5366 5367 // Handle selection 5368 // TODO: Stop doing this, this shouldn't be necessary to update selection. 5369 if (RefPtr<PresShell> presShell = GetPresShell()) { 5370 presShell->FlushPendingNotifications(FlushType::Frames); 5371 } 5372 NS_WARNING_ASSERTION(!Destroyed(), 5373 "The editor is destroyed during splitting a node"); 5374 5375 const bool allowedTransactionsToChangeSelection = 5376 AllowsTransactionsToChangeSelection(); 5377 5378 IgnoredErrorResult error; 5379 RefPtr<Selection> previousSelection; 5380 for (SavedRange& savedRange : savedRanges) { 5381 // If we have not seen the selection yet, clear all of its ranges. 5382 if (savedRange.mSelection != previousSelection) { 5383 MOZ_KnownLive(savedRange.mSelection)->RemoveAllRanges(error); 5384 if (MOZ_UNLIKELY(error.Failed())) { 5385 NS_WARNING("Selection::RemoveAllRanges() failed"); 5386 return Err(error.StealNSResult()); 5387 } 5388 previousSelection = savedRange.mSelection; 5389 } 5390 5391 // XXX Looks like that we don't need to modify normal selection here 5392 // because selection will be modified by the caller if 5393 // AllowsTransactionsToChangeSelection() will return true. 5394 if (allowedTransactionsToChangeSelection && 5395 savedRange.mSelection->Type() == SelectionType::eNormal) { 5396 // If the editor should adjust the selection, don't bother restoring 5397 // the ranges for the normal selection here. 5398 continue; 5399 } 5400 5401 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer, 5402 uint32_t& aOffset) { 5403 if (aContainer != aStartOfRightNode.GetContainer()) { 5404 return; 5405 } 5406 5407 // If the container is the left node and offset is after the split 5408 // point, the content was moved from the right node to aNewNode. 5409 // So, we need to change the container to aNewNode and decrease the 5410 // offset. 5411 if (aOffset >= aStartOfRightNode.Offset()) { 5412 aContainer = &aNewNode; 5413 aOffset -= aStartOfRightNode.Offset(); 5414 } 5415 }; 5416 AdjustDOMPoint(savedRange.mStartContainer, savedRange.mStartOffset); 5417 AdjustDOMPoint(savedRange.mEndContainer, savedRange.mEndOffset); 5418 5419 RefPtr<nsRange> newRange = 5420 nsRange::Create(savedRange.mStartContainer, savedRange.mStartOffset, 5421 savedRange.mEndContainer, savedRange.mEndOffset, error); 5422 if (MOZ_UNLIKELY(error.Failed())) { 5423 NS_WARNING("nsRange::Create() failed"); 5424 return Err(error.StealNSResult()); 5425 } 5426 // The `MOZ_KnownLive` annotation is only necessary because of a bug 5427 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253) in the 5428 // static analyzer. 5429 MOZ_KnownLive(savedRange.mSelection) 5430 ->AddRangeAndSelectFramesAndNotifyListeners(*newRange, error); 5431 if (MOZ_UNLIKELY(error.Failed())) { 5432 NS_WARNING( 5433 "Selection::AddRangeAndSelectFramesAndNotifyListeners() failed"); 5434 return Err(error.StealNSResult()); 5435 } 5436 } 5437 5438 // We don't need to set selection here because the caller should do that 5439 // in any case. 5440 5441 // If splitting the node causes running mutation event listener and we've 5442 // got unexpected result, we should return error because callers will 5443 // continue to do their work without complicated DOM tree result. 5444 // NOTE: Perhaps, we shouldn't do this immediately after each DOM tree change 5445 // because stopping handling it causes some data loss. E.g., user 5446 // may loose the text which is moved to the new text node. 5447 // XXX We cannot check all descendants in the right node and the new left 5448 // node for performance reason. I think that if caller needs to access 5449 // some of the descendants, they should check by themselves. 5450 if (NS_WARN_IF(containerParentNode != 5451 aStartOfRightNode.GetContainer()->GetParentNode()) || 5452 NS_WARN_IF(containerParentNode != aNewNode.GetParentNode()) || 5453 NS_WARN_IF(aNewNode.GetPreviousSibling() != 5454 aStartOfRightNode.GetContainer())) { 5455 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 5456 } 5457 5458 DebugOnly<nsresult> rvIgnored = RangeUpdaterRef().SelAdjSplitNode( 5459 *aStartOfRightNode.ContainerAs<nsIContent>(), aStartOfRightNode.Offset(), 5460 aNewNode); 5461 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 5462 "RangeUpdater::SelAdjSplitNode() failed, but ignored"); 5463 5464 return SplitNodeResult(aNewNode, 5465 *aStartOfRightNode.ContainerAs<nsIContent>()); 5466 } 5467 5468 Result<JoinNodesResult, nsresult> HTMLEditor::JoinNodesWithTransaction( 5469 nsIContent& aLeftContent, nsIContent& aRightContent) { 5470 MOZ_ASSERT(IsEditActionDataAvailable()); 5471 MOZ_ASSERT(&aLeftContent != &aRightContent); 5472 MOZ_ASSERT(aLeftContent.GetParentNode()); 5473 MOZ_ASSERT(aRightContent.GetParentNode()); 5474 MOZ_ASSERT(aLeftContent.GetParentNode() == aRightContent.GetParentNode()); 5475 5476 IgnoredErrorResult ignoredError; 5477 AutoEditSubActionNotifier startToHandleEditSubAction( 5478 *this, EditSubAction::eJoinNodes, nsIEditor::ePrevious, ignoredError); 5479 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 5480 return Err(ignoredError.StealNSResult()); 5481 } 5482 NS_WARNING_ASSERTION( 5483 !ignoredError.Failed(), 5484 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 5485 5486 if (NS_WARN_IF(!aRightContent.GetParentNode())) { 5487 return Err(NS_ERROR_FAILURE); 5488 } 5489 5490 RefPtr<JoinNodesTransaction> transaction = 5491 JoinNodesTransaction::MaybeCreate(*this, aLeftContent, aRightContent); 5492 if (MOZ_UNLIKELY(!transaction)) { 5493 NS_WARNING("JoinNodesTransaction::MaybeCreate() failed"); 5494 return Err(NS_ERROR_FAILURE); 5495 } 5496 5497 const nsresult rv = DoTransactionInternal(transaction); 5498 // FYI: Now, DidJoinNodesTransaction() must have been run if succeeded. 5499 if (NS_WARN_IF(Destroyed())) { 5500 return Err(NS_ERROR_EDITOR_DESTROYED); 5501 } 5502 5503 // This shouldn't occur unless the cycle collector runs by chrome script 5504 // forcibly. 5505 if (NS_WARN_IF(!transaction->GetRemovedContent()) || 5506 NS_WARN_IF(!transaction->GetExistingContent())) { 5507 return Err(NS_ERROR_UNEXPECTED); 5508 } 5509 5510 // If joined node is moved to different place, offset may not have any 5511 // meaning. In this case, the web app modified the DOM tree takes on the 5512 // responsibility for the remaning things. 5513 if (NS_WARN_IF(transaction->GetExistingContent()->GetParent() != 5514 transaction->GetParentNode())) { 5515 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 5516 } 5517 5518 if (NS_FAILED(rv)) { 5519 NS_WARNING("EditorBase::DoTransactionInternal() failed"); 5520 return Err(rv); 5521 } 5522 5523 return JoinNodesResult(transaction->CreateJoinedPoint<EditorDOMPoint>(), 5524 *transaction->GetRemovedContent()); 5525 } 5526 5527 void HTMLEditor::DidJoinNodesTransaction( 5528 const JoinNodesTransaction& aTransaction, nsresult aDoJoinNodesResult) { 5529 // This shouldn't occur unless the cycle collector runs by chrome script 5530 // forcibly. 5531 if (MOZ_UNLIKELY(NS_WARN_IF(!aTransaction.GetRemovedContent()) || 5532 NS_WARN_IF(!aTransaction.GetExistingContent()))) { 5533 return; 5534 } 5535 5536 // If joined node is moved to different place, offset may not have any 5537 // meaning. In this case, the web app modified the DOM tree takes on the 5538 // responsibility for the remaning things. 5539 if (MOZ_UNLIKELY(aTransaction.GetExistingContent()->GetParentNode() != 5540 aTransaction.GetParentNode())) { 5541 return; 5542 } 5543 5544 // Be aware, the joined point should be created for each call because 5545 // they may refer the child node, but some of them may change the DOM tree 5546 // after that, thus we need to avoid invalid point (Although it shouldn't 5547 // occur). 5548 TopLevelEditSubActionDataRef().DidJoinContents( 5549 *this, aTransaction.CreateJoinedPoint<EditorRawDOMPoint>()); 5550 5551 if (NS_SUCCEEDED(aDoJoinNodesResult)) { 5552 if (RefPtr<TextServicesDocument> textServicesDocument = 5553 mTextServicesDocument) { 5554 textServicesDocument->DidJoinContents( 5555 aTransaction.CreateJoinedPoint<EditorRawDOMPoint>(), 5556 *aTransaction.GetRemovedContent()); 5557 } 5558 } 5559 5560 if (!mActionListeners.IsEmpty()) { 5561 for (auto& listener : mActionListeners.Clone()) { 5562 DebugOnly<nsresult> rvIgnored = listener->DidJoinContents( 5563 aTransaction.CreateJoinedPoint<EditorRawDOMPoint>(), 5564 aTransaction.GetRemovedContent()); 5565 NS_WARNING_ASSERTION( 5566 NS_SUCCEEDED(rvIgnored), 5567 "nsIEditActionListener::DidJoinContents() failed, but ignored"); 5568 } 5569 } 5570 } 5571 5572 nsresult HTMLEditor::DoJoinNodes(nsIContent& aContentToKeep, 5573 nsIContent& aContentToRemove) { 5574 MOZ_ASSERT(IsEditActionDataAvailable()); 5575 5576 const uint32_t keepingContentLength = aContentToKeep.Length(); 5577 const EditorDOMPoint oldPointAtRightContent(&aContentToRemove); 5578 if (MOZ_LIKELY(oldPointAtRightContent.IsSet())) { 5579 (void)oldPointAtRightContent.Offset(); // Fix the offset 5580 } 5581 5582 // Remember all selection points. 5583 // XXX Do we need to restore all types of selections by ourselves? Normal 5584 // selection should be modified later as result of handling edit action. 5585 // IME selections shouldn't be there when nodes are joined. Spellcheck 5586 // selections should be recreated with newer text. URL selections 5587 // shouldn't be there because of used only by the URL bar. 5588 AutoTArray<SavedRange, 10> savedRanges; 5589 { 5590 EditorRawDOMPoint atRemovingNode(&aContentToRemove); 5591 EditorRawDOMPoint atNodeToKeep(&aContentToKeep); 5592 for (SelectionType selectionType : kPresentSelectionTypes) { 5593 SavedRange savingRange; 5594 savingRange.mSelection = GetSelection(selectionType); 5595 if (selectionType == SelectionType::eNormal) { 5596 if (NS_WARN_IF(!savingRange.mSelection)) { 5597 return NS_ERROR_FAILURE; 5598 } 5599 } else if (!savingRange.mSelection) { 5600 // For non-normal selections, skip over the non-existing ones. 5601 continue; 5602 } 5603 5604 const uint32_t rangeCount = savingRange.mSelection->RangeCount(); 5605 for (const uint32_t j : IntegerRange(rangeCount)) { 5606 MOZ_ASSERT(savingRange.mSelection->RangeCount() == rangeCount); 5607 const RefPtr<nsRange> r = savingRange.mSelection->GetRangeAt(j); 5608 MOZ_ASSERT(r); 5609 MOZ_ASSERT(r->IsPositioned()); 5610 savingRange.mStartContainer = r->GetStartContainer(); 5611 savingRange.mStartOffset = r->StartOffset(); 5612 savingRange.mEndContainer = r->GetEndContainer(); 5613 savingRange.mEndOffset = r->EndOffset(); 5614 5615 // If selection endpoint is between the nodes, remember it as being 5616 // in the one that is going away instead. This simplifies later 5617 // selection adjustment logic at end of this method. 5618 if (savingRange.mStartContainer) { 5619 MOZ_ASSERT(savingRange.mEndContainer); 5620 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer, 5621 uint32_t& aOffset) { 5622 // If range boundary points aContentToRemove and aContentToKeep is 5623 // its left node, remember it as being at end of aContentToKeep. 5624 // Then, it will point start of the first content of moved content 5625 // from aContentToRemove. 5626 if (aContainer == atRemovingNode.GetContainer() && 5627 atNodeToKeep.Offset() < aOffset && 5628 aOffset <= atRemovingNode.Offset()) { 5629 aContainer = &aContentToKeep; 5630 aOffset = keepingContentLength; 5631 } 5632 }; 5633 AdjustDOMPoint(savingRange.mStartContainer, savingRange.mStartOffset); 5634 AdjustDOMPoint(savingRange.mEndContainer, savingRange.mEndOffset); 5635 } 5636 5637 savedRanges.AppendElement(savingRange); 5638 } 5639 } 5640 } 5641 5642 // OK, ready to do join now. 5643 nsresult rv = [&]() MOZ_NEVER_INLINE_DEBUG MOZ_CAN_RUN_SCRIPT { 5644 // If it's a text node, just shuffle around some text. 5645 if (aContentToKeep.IsText() && aContentToRemove.IsText()) { 5646 nsAutoString rightText; 5647 aContentToRemove.AsText()->GetData(rightText); 5648 // Delete the node first to minimize the text change range from 5649 // IMEContentObserver of view. 5650 { 5651 AutoNodeAPIWrapper nodeWrapper(*this, aContentToRemove); 5652 if (NS_FAILED(nodeWrapper.Remove())) { 5653 NS_WARNING("AutoNodeAPIWrapper::Remove() failed, but ignored"); 5654 } else { 5655 NS_WARNING_ASSERTION( 5656 nodeWrapper.IsExpectedResult(), 5657 "Deleting node caused other mutations, but ignored"); 5658 } 5659 } 5660 // Even if we've already destroyed, let's update aContentToKeep for 5661 // avoiding a dataloss bug. 5662 IgnoredErrorResult ignoredError; 5663 DoInsertText(MOZ_KnownLive(*aContentToKeep.AsText()), 5664 aContentToKeep.AsText()->TextDataLength(), rightText, 5665 ignoredError); 5666 if (NS_WARN_IF(Destroyed())) { 5667 return NS_ERROR_EDITOR_DESTROYED; 5668 } 5669 NS_WARNING_ASSERTION(!ignoredError.Failed(), 5670 "EditorBase::DoSetText() failed, but ignored"); 5671 return NS_OK; 5672 } 5673 // Otherwise it's an interior node, so shuffle around the children. 5674 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfChildContents; 5675 HTMLEditUtils::CollectAllChildren(aContentToRemove, arrayOfChildContents); 5676 // Delete the node first to minimize the text change range from 5677 // IMEContentObserver of view. 5678 { 5679 AutoNodeAPIWrapper nodeWrapper(*this, aContentToRemove); 5680 if (NS_FAILED(nodeWrapper.Remove())) { 5681 NS_WARNING("AutoNodeAPIWrapper::Remove() failed, but ignored"); 5682 } else { 5683 NS_WARNING_ASSERTION( 5684 nodeWrapper.IsExpectedResult(), 5685 "Deleting node caused other mutations, but ignored"); 5686 } 5687 } 5688 // Even if we've already destroyed, let's update aContentToKeep for avoiding 5689 // a dataloss bug. 5690 nsresult rv = NS_OK; 5691 for (const OwningNonNull<nsIContent>& child : arrayOfChildContents) { 5692 AutoNodeAPIWrapper nodeWrapper(*this, aContentToKeep); 5693 nsresult rvInner = nodeWrapper.AppendChild(MOZ_KnownLive(child)); 5694 if (NS_FAILED(rvInner)) { 5695 NS_WARNING("AutoNodeAPIWrapper::AppendChild() failed"); 5696 rv = rvInner; 5697 } else { 5698 NS_WARNING_ASSERTION( 5699 nodeWrapper.IsExpectedResult(), 5700 "Appending child caused other mutations, but ignored"); 5701 } 5702 } 5703 if (NS_WARN_IF(Destroyed())) { 5704 return NS_ERROR_EDITOR_DESTROYED; 5705 } 5706 return rv; 5707 }(); 5708 5709 if (MOZ_LIKELY(oldPointAtRightContent.IsSet())) { 5710 DebugOnly<nsresult> rvIgnored = RangeUpdaterRef().SelAdjJoinNodes( 5711 EditorRawDOMPoint(&aContentToKeep, std::min(keepingContentLength, 5712 aContentToKeep.Length())), 5713 aContentToRemove, oldPointAtRightContent); 5714 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 5715 "RangeUpdater::SelAdjJoinNodes() failed, but ignored"); 5716 } 5717 if (MOZ_UNLIKELY(NS_FAILED(rv))) { 5718 return rv; 5719 } 5720 5721 const bool allowedTransactionsToChangeSelection = 5722 AllowsTransactionsToChangeSelection(); 5723 5724 // And adjust the selection if needed. 5725 RefPtr<Selection> previousSelection; 5726 for (SavedRange& savedRange : savedRanges) { 5727 // If we have not seen the selection yet, clear all of its ranges. 5728 if (savedRange.mSelection != previousSelection) { 5729 IgnoredErrorResult error; 5730 MOZ_KnownLive(savedRange.mSelection)->RemoveAllRanges(error); 5731 if (NS_WARN_IF(Destroyed())) { 5732 return NS_ERROR_EDITOR_DESTROYED; 5733 } 5734 if (error.Failed()) { 5735 NS_WARNING("Selection::RemoveAllRanges() failed"); 5736 return error.StealNSResult(); 5737 } 5738 previousSelection = savedRange.mSelection; 5739 } 5740 5741 if (allowedTransactionsToChangeSelection && 5742 savedRange.mSelection->Type() == SelectionType::eNormal) { 5743 // If the editor should adjust the selection, don't bother restoring 5744 // the ranges for the normal selection here. 5745 continue; 5746 } 5747 5748 auto AdjustDOMPoint = [&](nsCOMPtr<nsINode>& aContainer, 5749 uint32_t& aOffset) { 5750 // Now, all content of aContentToRemove are moved to end of 5751 // aContentToKeep. Therefore, if a range boundary was in 5752 // aContentToRemove, we need to change the container to aContentToKeep and 5753 // adjust the offset to after the original content of aContentToKeep. 5754 if (aContainer == &aContentToRemove) { 5755 aContainer = &aContentToKeep; 5756 aOffset += keepingContentLength; 5757 } 5758 }; 5759 AdjustDOMPoint(savedRange.mStartContainer, savedRange.mStartOffset); 5760 AdjustDOMPoint(savedRange.mEndContainer, savedRange.mEndOffset); 5761 5762 const RefPtr<nsRange> newRange = nsRange::Create( 5763 savedRange.mStartContainer, savedRange.mStartOffset, 5764 savedRange.mEndContainer, savedRange.mEndOffset, IgnoreErrors()); 5765 if (!newRange) { 5766 NS_WARNING("nsRange::Create() failed"); 5767 return NS_ERROR_FAILURE; 5768 } 5769 5770 IgnoredErrorResult error; 5771 // The `MOZ_KnownLive` annotation is only necessary because of a bug 5772 // (https://bugzilla.mozilla.org/show_bug.cgi?id=1622253) in the 5773 // static analyzer. 5774 MOZ_KnownLive(savedRange.mSelection) 5775 ->AddRangeAndSelectFramesAndNotifyListeners(*newRange, error); 5776 if (NS_WARN_IF(Destroyed())) { 5777 return NS_ERROR_EDITOR_DESTROYED; 5778 } 5779 if (NS_WARN_IF(error.Failed())) { 5780 return error.StealNSResult(); 5781 } 5782 } 5783 5784 if (allowedTransactionsToChangeSelection) { 5785 // Editor wants us to set selection at join point. 5786 DebugOnly<nsresult> rvIgnored = CollapseSelectionToStartOf(aContentToKeep); 5787 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 5788 NS_WARNING( 5789 "EditorBase::CollapseSelectionTo() caused destroying the editor"); 5790 return NS_ERROR_EDITOR_DESTROYED; 5791 } 5792 NS_WARNING_ASSERTION( 5793 NS_SUCCEEDED(rv), 5794 "EditorBases::CollapseSelectionTos() failed, but ignored"); 5795 } 5796 5797 return NS_OK; 5798 } 5799 5800 Result<MoveNodeResult, nsresult> HTMLEditor::MoveNodeWithTransaction( 5801 nsIContent& aContentToMove, const EditorDOMPoint& aPointToInsert) { 5802 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 5803 5804 EditorDOMPoint oldPoint(&aContentToMove); 5805 if (NS_WARN_IF(!oldPoint.IsSet())) { 5806 return Err(NS_ERROR_FAILURE); 5807 } 5808 5809 // Don't do anything if it's already in right place. 5810 if (aPointToInsert == oldPoint) { 5811 return MoveNodeResult::IgnoredResult(aPointToInsert.NextPoint()); 5812 } 5813 5814 RefPtr<MoveNodeTransaction> moveNodeTransaction = 5815 MoveNodeTransaction::MaybeCreate(*this, aContentToMove, aPointToInsert); 5816 if (MOZ_UNLIKELY(!moveNodeTransaction)) { 5817 NS_WARNING("MoveNodeTransaction::MaybeCreate() failed"); 5818 return Err(NS_ERROR_FAILURE); 5819 } 5820 5821 IgnoredErrorResult ignoredError; 5822 AutoEditSubActionNotifier startToHandleEditSubAction( 5823 *this, EditSubAction::eMoveNode, nsIEditor::eNext, ignoredError); 5824 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 5825 return Err(ignoredError.StealNSResult()); 5826 } 5827 NS_WARNING_ASSERTION( 5828 !ignoredError.Failed(), 5829 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 5830 5831 TopLevelEditSubActionDataRef().WillDeleteContent(*this, aContentToMove); 5832 5833 nsresult rv = DoTransactionInternal(moveNodeTransaction); 5834 if (NS_SUCCEEDED(rv)) { 5835 if (mTextServicesDocument) { 5836 const OwningNonNull<TextServicesDocument> textServicesDocument = 5837 *mTextServicesDocument; 5838 textServicesDocument->DidDeleteContent(aContentToMove); 5839 } 5840 } 5841 5842 if (!mActionListeners.IsEmpty()) { 5843 for (auto& listener : mActionListeners.Clone()) { 5844 DebugOnly<nsresult> rvIgnored = 5845 listener->DidDeleteNode(&aContentToMove, rv); 5846 NS_WARNING_ASSERTION( 5847 NS_SUCCEEDED(rvIgnored), 5848 "nsIEditActionListener::DidDeleteNode() failed, but ignored"); 5849 } 5850 } 5851 5852 if (MOZ_UNLIKELY(Destroyed())) { 5853 NS_WARNING( 5854 "MoveNodeTransaction::DoTransaction() caused destroying the editor"); 5855 return Err(NS_ERROR_EDITOR_DESTROYED); 5856 } 5857 5858 if (NS_FAILED(rv)) { 5859 NS_WARNING("MoveNodeTransaction::DoTransaction() failed"); 5860 return Err(rv); 5861 } 5862 5863 TopLevelEditSubActionDataRef().DidInsertContent(*this, aContentToMove); 5864 5865 return MoveNodeResult::HandledResult( 5866 moveNodeTransaction->SuggestNextInsertionPoint().To<EditorDOMPoint>(), 5867 moveNodeTransaction->SuggestPointToPutCaret().To<EditorDOMPoint>()); 5868 } 5869 5870 Result<MoveNodeResult, nsresult> HTMLEditor::MoveSiblingsWithTransaction( 5871 nsIContent& aFirstContentToMove, nsIContent& aLastContentToMove, 5872 const EditorDOMPoint& aPointToInsert) { 5873 MOZ_ASSERT(aPointToInsert.IsSetAndValid()); 5874 MOZ_ASSERT(aFirstContentToMove.GetParentNode() == 5875 aLastContentToMove.GetParentNode()); 5876 if (&aFirstContentToMove == &aLastContentToMove) { 5877 Result<MoveNodeResult, nsresult> moveNodeResultOrError = 5878 MoveNodeWithTransaction(aFirstContentToMove, aPointToInsert); 5879 NS_WARNING_ASSERTION(moveNodeResultOrError.isOk(), 5880 "HTMLEditor::MoveNodeWithTransaction() failed"); 5881 return moveNodeResultOrError; 5882 } 5883 5884 MOZ_ASSERT(*aFirstContentToMove.ComputeIndexInParentNode() < 5885 *aLastContentToMove.ComputeIndexInParentNode()); 5886 5887 // Don't do anything if it's already in right place. 5888 { 5889 const EditorDOMPoint atFirstContent(&aFirstContentToMove); 5890 if (NS_WARN_IF(!atFirstContent.IsSet())) { 5891 return Err(NS_ERROR_FAILURE); 5892 } 5893 const EditorDOMPoint atLastContent(&aLastContentToMove); 5894 if (NS_WARN_IF(!atLastContent.IsSet())) { 5895 return Err(NS_ERROR_FAILURE); 5896 } 5897 if (aPointToInsert.GetContainer() == atFirstContent.GetContainer() && 5898 atFirstContent.EqualsOrIsBefore(aPointToInsert) && 5899 aPointToInsert.EqualsOrIsBefore( 5900 atLastContent.NextPoint<EditorRawDOMPoint>())) { 5901 return MoveNodeResult::IgnoredResult(atLastContent.NextPoint()); 5902 } 5903 } 5904 5905 const RefPtr<MoveSiblingsTransaction> moveSiblingsTransaction = 5906 MoveSiblingsTransaction::MaybeCreate(*this, aFirstContentToMove, 5907 aLastContentToMove, aPointToInsert); 5908 if (MOZ_UNLIKELY(!moveSiblingsTransaction)) { 5909 NS_WARNING("MoveNodeTransaction::MaybeCreate() failed"); 5910 return Err(NS_ERROR_FAILURE); 5911 } 5912 5913 IgnoredErrorResult ignoredError; 5914 AutoEditSubActionNotifier startToHandleEditSubAction( 5915 *this, EditSubAction::eMoveNode, nsIEditor::eNext, ignoredError); 5916 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 5917 return Err(ignoredError.StealNSResult()); 5918 } 5919 NS_WARNING_ASSERTION( 5920 !ignoredError.Failed(), 5921 "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 5922 5923 // WillDeleteContent will extend the changed range to contain all points where 5924 // the moved nodes were. Therefore, we need to call it only for the first and 5925 // and the last one. 5926 TopLevelEditSubActionDataRef().WillDeleteContent(*this, aFirstContentToMove); 5927 TopLevelEditSubActionDataRef().WillDeleteContent(*this, aLastContentToMove); 5928 5929 nsresult rv = DoTransactionInternal(moveSiblingsTransaction); 5930 Maybe<CopyableAutoTArray<OwningNonNull<nsIContent>, 64>> movedSiblings; 5931 if (NS_SUCCEEDED(rv)) { 5932 if (mTextServicesDocument) { 5933 movedSiblings.emplace(moveSiblingsTransaction->TargetSiblings()); 5934 const OwningNonNull<TextServicesDocument> textServicesDocument = 5935 *mTextServicesDocument; 5936 for (const OwningNonNull<nsIContent>& movedContent : 5937 movedSiblings.ref()) { 5938 textServicesDocument->DidDeleteContent(MOZ_KnownLive(*movedContent)); 5939 } 5940 } 5941 } 5942 5943 if (!mActionListeners.IsEmpty()) { 5944 if (!movedSiblings) { 5945 movedSiblings.emplace(moveSiblingsTransaction->TargetSiblings()); 5946 } 5947 for (auto& listener : mActionListeners.Clone()) { 5948 for (const OwningNonNull<nsIContent>& movedContent : 5949 movedSiblings.ref()) { 5950 DebugOnly<nsresult> rvIgnored = 5951 listener->DidDeleteNode(MOZ_KnownLive(movedContent), rv); 5952 NS_WARNING_ASSERTION( 5953 NS_SUCCEEDED(rvIgnored), 5954 "nsIEditActionListener::DidDeleteNode() failed, but ignored"); 5955 } 5956 } 5957 } 5958 5959 if (MOZ_UNLIKELY(Destroyed())) { 5960 NS_WARNING( 5961 "MoveNodeTransaction::DoTransaction() caused destroying the editor"); 5962 return Err(NS_ERROR_EDITOR_DESTROYED); 5963 } 5964 5965 if (NS_FAILED(rv)) { 5966 NS_WARNING("MoveNodeTransaction::DoTransaction() failed"); 5967 return Err(rv); 5968 } 5969 5970 nsIContent* const firstMovedContentInExpectedContainer = 5971 moveSiblingsTransaction->GetFirstMovedContent(); 5972 nsIContent* const lastMovedContentInExpectedContainer = 5973 moveSiblingsTransaction->GetLastMovedContent(); 5974 if (!firstMovedContentInExpectedContainer) { 5975 return MoveNodeResult::IgnoredResult(aPointToInsert); 5976 } 5977 MOZ_ASSERT(lastMovedContentInExpectedContainer); 5978 5979 // DidInsertContent will extend the changed range to contain all moved 5980 // contents. Therefore, we need to call it only for the first and and the 5981 // last one. 5982 TopLevelEditSubActionDataRef().DidInsertContent( 5983 *this, *firstMovedContentInExpectedContainer); 5984 if (firstMovedContentInExpectedContainer == 5985 lastMovedContentInExpectedContainer) { 5986 // Only one node was moved. 5987 return MoveNodeResult::HandledResult( 5988 moveSiblingsTransaction->SuggestNextInsertionPoint() 5989 .To<EditorDOMPoint>(), 5990 moveSiblingsTransaction->SuggestPointToPutCaret().To<EditorDOMPoint>()); 5991 } 5992 TopLevelEditSubActionDataRef().DidInsertContent( 5993 *this, *lastMovedContentInExpectedContainer); 5994 return MoveNodeResult::HandledResult( 5995 *firstMovedContentInExpectedContainer, 5996 moveSiblingsTransaction->SuggestNextInsertionPoint().To<EditorDOMPoint>(), 5997 moveSiblingsTransaction->SuggestPointToPutCaret().To<EditorDOMPoint>()); 5998 } 5999 6000 Result<RefPtr<Element>, nsresult> HTMLEditor::DeleteSelectionAndCreateElement( 6001 nsAtom& aTag, const InitializeInsertingElement& aInitializer) { 6002 MOZ_ASSERT(IsEditActionDataAvailable()); 6003 6004 nsresult rv = DeleteSelectionAndPrepareToCreateNode(); 6005 if (NS_FAILED(rv)) { 6006 NS_WARNING("HTMLEditor::DeleteSelectionAndPrepareToCreateNode() failed"); 6007 return Err(rv); 6008 } 6009 6010 EditorDOMPoint pointToInsert(SelectionRef().AnchorRef()); 6011 if (!pointToInsert.IsSet()) { 6012 return Err(NS_ERROR_FAILURE); 6013 } 6014 Result<CreateElementResult, nsresult> createNewElementResult = 6015 CreateAndInsertElement(WithTransaction::Yes, aTag, pointToInsert, 6016 aInitializer); 6017 if (MOZ_UNLIKELY(createNewElementResult.isErr())) { 6018 NS_WARNING( 6019 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed"); 6020 return createNewElementResult.propagateErr(); 6021 } 6022 MOZ_ASSERT(createNewElementResult.inspect().GetNewNode()); 6023 6024 // We want the selection to be just after the new node 6025 createNewElementResult.inspect().IgnoreCaretPointSuggestion(); 6026 rv = CollapseSelectionTo( 6027 EditorRawDOMPoint::After(*createNewElementResult.inspect().GetNewNode())); 6028 if (NS_FAILED(rv)) { 6029 NS_WARNING("EditorBase::CollapseSelectionTo() failed"); 6030 return Err(rv); 6031 } 6032 return createNewElementResult.unwrap().UnwrapNewNode(); 6033 } 6034 6035 nsresult HTMLEditor::DeleteSelectionAndPrepareToCreateNode() { 6036 MOZ_ASSERT(IsEditActionDataAvailable()); 6037 6038 if (NS_WARN_IF(!SelectionRef().GetAnchorFocusRange())) { 6039 return NS_OK; 6040 } 6041 6042 if (!SelectionRef().GetAnchorFocusRange()->Collapsed()) { 6043 nsresult rv = 6044 DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eStrip); 6045 if (NS_FAILED(rv)) { 6046 NS_WARNING("EditorBase::DeleteSelectionAsSubAction() failed"); 6047 return rv; 6048 } 6049 MOZ_ASSERT(SelectionRef().GetAnchorFocusRange() && 6050 SelectionRef().GetAnchorFocusRange()->Collapsed(), 6051 "Selection not collapsed after delete"); 6052 } 6053 6054 // If the selection is a chardata node, split it if necessary and compute 6055 // where to put the new node 6056 EditorDOMPoint atAnchor(SelectionRef().AnchorRef()); 6057 if (NS_WARN_IF(!atAnchor.IsSet()) || !atAnchor.IsInDataNode()) { 6058 return NS_OK; 6059 } 6060 6061 if (NS_WARN_IF(!atAnchor.GetContainerParent())) { 6062 return NS_ERROR_FAILURE; 6063 } 6064 6065 if (atAnchor.IsStartOfContainer()) { 6066 const EditorRawDOMPoint atAnchorContainer(atAnchor.GetContainer()); 6067 if (NS_WARN_IF(!atAnchorContainer.IsSetAndValid())) { 6068 return NS_ERROR_FAILURE; 6069 } 6070 nsresult rv = CollapseSelectionTo(atAnchorContainer); 6071 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6072 "EditorBase::CollapseSelectionTo() failed"); 6073 return rv; 6074 } 6075 6076 if (atAnchor.IsEndOfContainer()) { 6077 EditorRawDOMPoint afterAnchorContainer(atAnchor.GetContainer()); 6078 if (NS_WARN_IF(!afterAnchorContainer.AdvanceOffset())) { 6079 return NS_ERROR_FAILURE; 6080 } 6081 nsresult rv = CollapseSelectionTo(afterAnchorContainer); 6082 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6083 "EditorBase::CollapseSelectionTo() failed"); 6084 return rv; 6085 } 6086 6087 Result<SplitNodeResult, nsresult> splitAtAnchorResult = 6088 SplitNodeWithTransaction(atAnchor); 6089 if (MOZ_UNLIKELY(splitAtAnchorResult.isErr())) { 6090 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 6091 return splitAtAnchorResult.unwrapErr(); 6092 } 6093 6094 splitAtAnchorResult.inspect().IgnoreCaretPointSuggestion(); 6095 const auto atRightContent = 6096 splitAtAnchorResult.inspect().AtNextContent<EditorRawDOMPoint>(); 6097 if (NS_WARN_IF(!atRightContent.IsSet())) { 6098 return NS_ERROR_FAILURE; 6099 } 6100 MOZ_ASSERT(atRightContent.IsSetAndValid()); 6101 nsresult rv = CollapseSelectionTo(atRightContent); 6102 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6103 "EditorBase::CollapseSelectionTo() failed"); 6104 return rv; 6105 } 6106 6107 bool HTMLEditor::IsEmpty() const { 6108 if (mPaddingBRElementForEmptyEditor) { 6109 return true; 6110 } 6111 6112 const Element* activeElement = 6113 GetDocument() ? GetDocument()->GetActiveElement() : nullptr; 6114 const Element* editingHostOrBodyOrRootElement = 6115 activeElement && activeElement->IsEditable() 6116 ? ComputeEditingHost(*activeElement, LimitInBodyElement::No) 6117 : ComputeEditingHost(LimitInBodyElement::No); 6118 if (MOZ_UNLIKELY(!editingHostOrBodyOrRootElement)) { 6119 // If there is no active element nor no selection range in the document, 6120 // let's check entire the document as what we do traditionally. 6121 editingHostOrBodyOrRootElement = GetRoot(); 6122 if (!editingHostOrBodyOrRootElement) { 6123 return true; 6124 } 6125 } 6126 6127 for (nsIContent* childContent = 6128 editingHostOrBodyOrRootElement->GetFirstChild(); 6129 childContent; childContent = childContent->GetNextSibling()) { 6130 if (!childContent->IsText() || childContent->Length()) { 6131 return false; 6132 } 6133 } 6134 return true; 6135 } 6136 6137 // add to aElement the CSS inline styles corresponding to the HTML attribute 6138 // aAttribute with its value aValue 6139 nsresult HTMLEditor::SetAttributeOrEquivalent(Element* aElement, 6140 nsAtom* aAttribute, 6141 const nsAString& aValue, 6142 bool aSuppressTransaction) { 6143 MOZ_ASSERT(aElement); 6144 MOZ_ASSERT(aAttribute); 6145 6146 nsAutoScriptBlocker scriptBlocker; 6147 nsStyledElement* styledElement = nsStyledElement::FromNodeOrNull(aElement); 6148 if (!IsCSSEnabled()) { 6149 // we are not in an HTML+CSS editor; let's set the attribute the HTML way 6150 if (EditorElementStyle::IsHTMLStyle(aAttribute)) { 6151 const EditorElementStyle elementStyle = 6152 EditorElementStyle::Create(*aAttribute); 6153 if (styledElement && elementStyle.IsCSSRemovable(*styledElement)) { 6154 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must 6155 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method. 6156 nsresult rv = CSSEditUtils::RemoveCSSEquivalentToStyle( 6157 aSuppressTransaction ? WithTransaction::No : WithTransaction::Yes, 6158 *this, MOZ_KnownLive(*styledElement), elementStyle, nullptr); 6159 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 6160 return NS_ERROR_EDITOR_DESTROYED; 6161 } 6162 NS_WARNING_ASSERTION( 6163 NS_SUCCEEDED(rv), 6164 "CSSEditUtils::RemoveCSSEquivalentToStyle() failed, but ignored"); 6165 } 6166 } 6167 if (aSuppressTransaction) { 6168 AutoElementAttrAPIWrapper elementWrapper(*this, *aElement); 6169 nsresult rv = elementWrapper.SetAttr(aAttribute, aValue, true); 6170 if (NS_FAILED(rv)) { 6171 NS_WARNING("AutoElementAttrAPIWrapper::SetAttr() failed"); 6172 return rv; 6173 } 6174 NS_WARNING_ASSERTION( 6175 elementWrapper.IsExpectedResult(aValue), 6176 "Setting attribute caused other mutations, but ignored"); 6177 return NS_OK; 6178 } 6179 nsresult rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue); 6180 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6181 "EditorBase::SetAttributeWithTransaction() failed"); 6182 return rv; 6183 } 6184 6185 if (EditorElementStyle::IsHTMLStyle(aAttribute)) { 6186 const EditorElementStyle elementStyle = 6187 EditorElementStyle::Create(*aAttribute); 6188 if (styledElement && elementStyle.IsCSSSettable(*styledElement)) { 6189 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must 6190 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method. 6191 Result<size_t, nsresult> count = CSSEditUtils::SetCSSEquivalentToStyle( 6192 aSuppressTransaction ? WithTransaction::No : WithTransaction::Yes, 6193 *this, MOZ_KnownLive(*styledElement), elementStyle, &aValue); 6194 if (MOZ_UNLIKELY(count.isErr())) { 6195 if (NS_WARN_IF(count.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 6196 return NS_ERROR_EDITOR_DESTROYED; 6197 } 6198 NS_WARNING( 6199 "CSSEditUtils::SetCSSEquivalentToStyle() failed, but ignored"); 6200 } 6201 if (count.inspect()) { 6202 // we found an equivalence ; let's remove the HTML attribute itself if 6203 // it is set 6204 nsAutoString existingValue; 6205 if (!aElement->GetAttr(aAttribute, existingValue)) { 6206 return NS_OK; 6207 } 6208 6209 if (aSuppressTransaction) { 6210 AutoElementAttrAPIWrapper elementWrapper(*this, *aElement); 6211 nsresult rv = elementWrapper.UnsetAttr(aAttribute, true); 6212 if (NS_FAILED(rv)) { 6213 NS_WARNING("AutoElementAttrAPIWrapper::UnsetAttr() failed"); 6214 return rv; 6215 } 6216 NS_WARNING_ASSERTION( 6217 elementWrapper.IsExpectedResult(EmptyString()), 6218 "Removing attribute caused other mutations, but ignored"); 6219 return NS_OK; 6220 } 6221 nsresult rv = RemoveAttributeWithTransaction(*aElement, *aAttribute); 6222 NS_WARNING_ASSERTION( 6223 NS_SUCCEEDED(rv), 6224 "EditorBase::RemoveAttributeWithTransaction() failed"); 6225 return rv; 6226 } 6227 } 6228 } 6229 6230 // count is an integer that represents the number of CSS declarations 6231 // applied to the element. If it is zero, we found no equivalence in this 6232 // implementation for the attribute 6233 if (aAttribute == nsGkAtoms::style) { 6234 // if it is the style attribute, just add the new value to the existing 6235 // style attribute's value 6236 nsString existingValue; // Use nsString to avoid copying the string 6237 // buffer at setting the attribute below. 6238 aElement->GetAttr(nsGkAtoms::style, existingValue); 6239 if (!existingValue.IsEmpty()) { 6240 existingValue.Append(HTMLEditUtils::kSpace); 6241 } 6242 existingValue.Append(aValue); 6243 if (aSuppressTransaction) { 6244 AutoElementAttrAPIWrapper elementWrapper(*this, *aElement); 6245 nsresult rv = 6246 elementWrapper.SetAttr(nsGkAtoms::style, existingValue, true); 6247 if (NS_FAILED(rv)) { 6248 NS_WARNING("AutoElementAttrAPIWrapper::SetAttr() failed"); 6249 return rv; 6250 } 6251 NS_WARNING_ASSERTION( 6252 elementWrapper.IsExpectedResult(existingValue), 6253 "Setting style attribute caused other mutations, but ignored"); 6254 return NS_OK; 6255 } 6256 nsresult rv = SetAttributeWithTransaction(*aElement, *nsGkAtoms::style, 6257 existingValue); 6258 NS_WARNING_ASSERTION( 6259 NS_SUCCEEDED(rv), 6260 "EditorBase::SetAttributeWithTransaction(nsGkAtoms::style) failed"); 6261 return rv; 6262 } 6263 6264 // we have no CSS equivalence for this attribute and it is not the style 6265 // attribute; let's set it the good'n'old HTML way 6266 if (aSuppressTransaction) { 6267 AutoElementAttrAPIWrapper elementWrapper(*this, *aElement); 6268 nsresult rv = elementWrapper.SetAttr(aAttribute, aValue, true); 6269 if (NS_FAILED(rv)) { 6270 NS_WARNING("AutoElementAttrAPIWrapper::SetAttr() failed"); 6271 return rv; 6272 } 6273 NS_WARNING_ASSERTION( 6274 elementWrapper.IsExpectedResult(aValue), 6275 "Setting attribute caused other mutations, but ignored"); 6276 return NS_OK; 6277 } 6278 nsresult rv = SetAttributeWithTransaction(*aElement, *aAttribute, aValue); 6279 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6280 "EditorBase::SetAttributeWithTransaction() failed"); 6281 return rv; 6282 } 6283 6284 nsresult HTMLEditor::RemoveAttributeOrEquivalent(Element* aElement, 6285 nsAtom* aAttribute, 6286 bool aSuppressTransaction) { 6287 MOZ_ASSERT(aElement); 6288 MOZ_ASSERT(aAttribute); 6289 6290 if (IsCSSEnabled() && EditorElementStyle::IsHTMLStyle(aAttribute)) { 6291 const EditorElementStyle elementStyle = 6292 EditorElementStyle::Create(*aAttribute); 6293 if (elementStyle.IsCSSRemovable(*aElement)) { 6294 // XXX It might be keep handling attribute even if aElement is not 6295 // an nsStyledElement instance. 6296 nsStyledElement* styledElement = 6297 nsStyledElement::FromNodeOrNull(aElement); 6298 if (NS_WARN_IF(!styledElement)) { 6299 return NS_ERROR_INVALID_ARG; 6300 } 6301 // MOZ_KnownLive(*styledElement): It's aElement and its lifetime must 6302 // be guaranteed by the caller because of MOZ_CAN_RUN_SCRIPT method. 6303 nsresult rv = CSSEditUtils::RemoveCSSEquivalentToStyle( 6304 aSuppressTransaction ? WithTransaction::No : WithTransaction::Yes, 6305 *this, MOZ_KnownLive(*styledElement), elementStyle, nullptr); 6306 if (NS_FAILED(rv)) { 6307 NS_WARNING("CSSEditUtils::RemoveCSSEquivalentToStyle() failed"); 6308 return rv; 6309 } 6310 } 6311 } 6312 6313 if (!aElement->HasAttr(aAttribute)) { 6314 return NS_OK; 6315 } 6316 6317 if (aSuppressTransaction) { 6318 AutoElementAttrAPIWrapper elementWrapper(*this, *aElement); 6319 nsresult rv = elementWrapper.UnsetAttr(aAttribute, true); 6320 if (NS_FAILED(rv)) { 6321 NS_WARNING("AutoElementAttrAPIWrapper::UnsetAttr() failed"); 6322 return rv; 6323 } 6324 NS_WARNING_ASSERTION( 6325 elementWrapper.IsExpectedResult(EmptyString()), 6326 "Removing attribute caused other mutations, but ignored"); 6327 return NS_OK; 6328 } 6329 nsresult rv = RemoveAttributeWithTransaction(*aElement, *aAttribute); 6330 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6331 "EditorBase::RemoveAttributeWithTransaction() failed"); 6332 return rv; 6333 } 6334 6335 NS_IMETHODIMP HTMLEditor::SetIsCSSEnabled(bool aIsCSSPrefChecked) { 6336 AutoEditActionDataSetter editActionData(*this, 6337 EditAction::eEnableOrDisableCSS); 6338 if (NS_WARN_IF(!editActionData.CanHandle())) { 6339 return NS_ERROR_NOT_INITIALIZED; 6340 } 6341 6342 mIsCSSPrefChecked = aIsCSSPrefChecked; 6343 return NS_OK; 6344 } 6345 6346 // Set the block background color 6347 nsresult HTMLEditor::SetBlockBackgroundColorWithCSSAsSubAction( 6348 const nsAString& aColor) { 6349 MOZ_ASSERT(IsEditActionDataAvailable()); 6350 6351 // background-color change and committing composition should be undone 6352 // together 6353 AutoPlaceholderBatch treatAsOneTransaction( 6354 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 6355 6356 CommitComposition(); 6357 6358 // XXX Shouldn't we do this before calling `CommitComposition()`? 6359 if (IsPlaintextMailComposer()) { 6360 return NS_OK; 6361 } 6362 6363 { 6364 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 6365 if (MOZ_UNLIKELY(result.isErr())) { 6366 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 6367 return result.unwrapErr(); 6368 } 6369 if (result.inspect().Canceled()) { 6370 return NS_OK; 6371 } 6372 } 6373 6374 IgnoredErrorResult ignoredError; 6375 AutoEditSubActionNotifier startToHandleEditSubAction( 6376 *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError); 6377 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 6378 return ignoredError.StealNSResult(); 6379 } 6380 NS_WARNING_ASSERTION(!ignoredError.Failed(), 6381 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() " 6382 "failed, but ignored"); 6383 6384 // TODO: We don't need AutoTransactionsConserveSelection here in the normal 6385 // cases, but removing this may cause the behavior with the legacy 6386 // mutation event listeners. We should try to delete this in a bug. 6387 AutoTransactionsConserveSelection dontChangeMySelection(*this); 6388 6389 AutoClonedSelectionRangeArray selectionRanges(SelectionRef()); 6390 MOZ_ALWAYS_TRUE(selectionRanges.SaveAndTrackRanges(*this)); 6391 for (const OwningNonNull<nsRange>& domRange : selectionRanges.Ranges()) { 6392 EditorDOMRange range(domRange); 6393 if (NS_WARN_IF(!range.IsPositioned())) { 6394 continue; 6395 } 6396 6397 if (range.InSameContainer()) { 6398 // If the range is in a text node, set background color of its parent 6399 // block. 6400 if (range.StartRef().IsInTextNode()) { 6401 const RefPtr<nsStyledElement> editableBlockStyledElement = 6402 nsStyledElement::FromNodeOrNull(HTMLEditUtils::GetAncestorElement( 6403 *range.StartRef().ContainerAs<Text>(), 6404 HTMLEditUtils::ClosestEditableBlockElement, 6405 BlockInlineCheck::UseComputedDisplayOutsideStyle)); 6406 if (!editableBlockStyledElement || 6407 !EditorElementStyle::BGColor().IsCSSSettable( 6408 *editableBlockStyledElement)) { 6409 continue; 6410 } 6411 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle( 6412 WithTransaction::Yes, *this, *editableBlockStyledElement, 6413 EditorElementStyle::BGColor(), &aColor); 6414 if (MOZ_UNLIKELY(result.isErr())) { 6415 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 6416 return NS_ERROR_EDITOR_DESTROYED; 6417 } 6418 NS_WARNING( 6419 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::" 6420 "BGColor()) failed, but ignored"); 6421 } 6422 continue; 6423 } 6424 6425 // If `Selection` is collapsed in a `<body>` element, set background 6426 // color of the `<body>` element. 6427 if (range.Collapsed() && 6428 range.StartRef().IsContainerHTMLElement(nsGkAtoms::body)) { 6429 const RefPtr<nsStyledElement> styledElement = 6430 range.StartRef().GetContainerAs<nsStyledElement>(); 6431 if (!styledElement || 6432 !EditorElementStyle::BGColor().IsCSSSettable(*styledElement)) { 6433 continue; 6434 } 6435 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle( 6436 WithTransaction::Yes, *this, *styledElement, 6437 EditorElementStyle::BGColor(), &aColor); 6438 if (MOZ_UNLIKELY(result.isErr())) { 6439 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 6440 return NS_ERROR_EDITOR_DESTROYED; 6441 } 6442 NS_WARNING( 6443 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::" 6444 "BGColor()) failed, but ignored"); 6445 } 6446 continue; 6447 } 6448 6449 // If one node is selected, set background color of it if it's a 6450 // block, or of its parent block otherwise. 6451 if ((range.StartRef().IsStartOfContainer() && 6452 range.EndRef().IsStartOfContainer()) || 6453 range.StartRef().Offset() + 1 == range.EndRef().Offset()) { 6454 if (NS_WARN_IF(range.StartRef().IsInDataNode())) { 6455 continue; 6456 } 6457 const RefPtr<nsStyledElement> editableBlockStyledElement = 6458 nsStyledElement::FromNodeOrNull( 6459 HTMLEditUtils::GetInclusiveAncestorElement( 6460 *range.StartRef().GetChild(), 6461 HTMLEditUtils::ClosestEditableBlockElement, 6462 BlockInlineCheck::UseComputedDisplayOutsideStyle)); 6463 if (!editableBlockStyledElement || 6464 !EditorElementStyle::BGColor().IsCSSSettable( 6465 *editableBlockStyledElement)) { 6466 continue; 6467 } 6468 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle( 6469 WithTransaction::Yes, *this, *editableBlockStyledElement, 6470 EditorElementStyle::BGColor(), &aColor); 6471 if (MOZ_UNLIKELY(result.isErr())) { 6472 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 6473 return NS_ERROR_EDITOR_DESTROYED; 6474 } 6475 NS_WARNING( 6476 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::" 6477 "BGColor()) failed, but ignored"); 6478 } 6479 continue; 6480 } 6481 } // if (range.InSameContainer()) 6482 6483 // Collect editable nodes which are entirely contained in the range. 6484 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContents; 6485 { 6486 ContentSubtreeIterator subtreeIter; 6487 // If there is no node which is entirely in the range, 6488 // `ContentSubtreeIterator::Init()` fails, but this is possible case, 6489 // don't warn it. 6490 nsresult rv = subtreeIter.Init(range.StartRef().ToRawRangeBoundary(), 6491 range.EndRef().ToRawRangeBoundary()); 6492 NS_WARNING_ASSERTION( 6493 NS_SUCCEEDED(rv), 6494 "ContentSubtreeIterator::Init() failed, but ignored"); 6495 if (NS_SUCCEEDED(rv)) { 6496 for (; !subtreeIter.IsDone(); subtreeIter.Next()) { 6497 nsINode* node = subtreeIter.GetCurrentNode(); 6498 if (NS_WARN_IF(!node)) { 6499 return NS_ERROR_FAILURE; 6500 } 6501 if (node->IsContent() && EditorUtils::IsEditableContent( 6502 *node->AsContent(), EditorType::HTML)) { 6503 arrayOfContents.AppendElement(*node->AsContent()); 6504 } 6505 } 6506 } 6507 } 6508 6509 // This caches block parent if we set its background color. 6510 RefPtr<Element> handledBlockParent; 6511 6512 // If start node is a text node, set background color of its parent 6513 // block. 6514 if (range.StartRef().IsInTextNode() && 6515 EditorUtils::IsEditableContent(*range.StartRef().ContainerAs<Text>(), 6516 EditorType::HTML)) { 6517 Element* const editableBlockElement = HTMLEditUtils::GetAncestorElement( 6518 *range.StartRef().ContainerAs<Text>(), 6519 HTMLEditUtils::ClosestEditableBlockElement, 6520 BlockInlineCheck::UseComputedDisplayOutsideStyle); 6521 if (editableBlockElement && handledBlockParent != editableBlockElement) { 6522 handledBlockParent = editableBlockElement; 6523 nsStyledElement* const blockStyledElement = 6524 nsStyledElement::FromNode(handledBlockParent); 6525 if (blockStyledElement && 6526 EditorElementStyle::BGColor().IsCSSSettable(*blockStyledElement)) { 6527 // MOZ_KnownLive(*blockStyledElement): It's handledBlockParent 6528 // whose type is RefPtr. 6529 Result<size_t, nsresult> result = 6530 CSSEditUtils::SetCSSEquivalentToStyle( 6531 WithTransaction::Yes, *this, 6532 MOZ_KnownLive(*blockStyledElement), 6533 EditorElementStyle::BGColor(), &aColor); 6534 if (MOZ_UNLIKELY(result.isErr())) { 6535 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 6536 return NS_ERROR_EDITOR_DESTROYED; 6537 } 6538 NS_WARNING( 6539 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::" 6540 "BGColor()) failed, but ignored"); 6541 } 6542 } 6543 } 6544 } 6545 6546 // Then, set background color of each block or block parent of all nodes 6547 // in the range entirely. 6548 for (OwningNonNull<nsIContent>& content : arrayOfContents) { 6549 Element* const editableBlockElement = 6550 HTMLEditUtils::GetInclusiveAncestorElement( 6551 content, HTMLEditUtils::ClosestEditableBlockElement, 6552 BlockInlineCheck::UseComputedDisplayOutsideStyle); 6553 if (editableBlockElement && handledBlockParent != editableBlockElement) { 6554 handledBlockParent = editableBlockElement; 6555 nsStyledElement* const blockStyledElement = 6556 nsStyledElement::FromNode(handledBlockParent); 6557 if (blockStyledElement && 6558 EditorElementStyle::BGColor().IsCSSSettable(*blockStyledElement)) { 6559 // MOZ_KnownLive(*blockStyledElement): It's handledBlockParent whose 6560 // type is RefPtr. 6561 Result<size_t, nsresult> result = 6562 CSSEditUtils::SetCSSEquivalentToStyle( 6563 WithTransaction::Yes, *this, 6564 MOZ_KnownLive(*blockStyledElement), 6565 EditorElementStyle::BGColor(), &aColor); 6566 if (MOZ_UNLIKELY(result.isErr())) { 6567 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 6568 return NS_ERROR_EDITOR_DESTROYED; 6569 } 6570 NS_WARNING( 6571 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::" 6572 "BGColor()) failed, but ignored"); 6573 } 6574 } 6575 } 6576 } 6577 6578 // Finally, if end node is a text node, set background color of its 6579 // parent block. 6580 if (range.EndRef().IsInTextNode() && 6581 EditorUtils::IsEditableContent(*range.EndRef().ContainerAs<Text>(), 6582 EditorType::HTML)) { 6583 Element* const editableBlockElement = HTMLEditUtils::GetAncestorElement( 6584 *range.EndRef().ContainerAs<Text>(), 6585 HTMLEditUtils::ClosestEditableBlockElement, 6586 BlockInlineCheck::UseComputedDisplayOutsideStyle); 6587 if (editableBlockElement && handledBlockParent != editableBlockElement) { 6588 const RefPtr<nsStyledElement> blockStyledElement = 6589 nsStyledElement::FromNode(editableBlockElement); 6590 if (blockStyledElement && 6591 EditorElementStyle::BGColor().IsCSSSettable(*blockStyledElement)) { 6592 Result<size_t, nsresult> result = 6593 CSSEditUtils::SetCSSEquivalentToStyle( 6594 WithTransaction::Yes, *this, *blockStyledElement, 6595 EditorElementStyle::BGColor(), &aColor); 6596 if (MOZ_UNLIKELY(result.isErr())) { 6597 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 6598 return NS_ERROR_EDITOR_DESTROYED; 6599 } 6600 NS_WARNING( 6601 "CSSEditUtils::SetCSSEquivalentToStyle(EditorElementStyle::" 6602 "BGColor()) failed, but ignored"); 6603 } 6604 } 6605 } 6606 } 6607 } // for-loop of selectionRanges 6608 6609 MOZ_ASSERT(selectionRanges.HasSavedRanges()); 6610 selectionRanges.RestoreFromSavedRanges(); 6611 nsresult rv = selectionRanges.ApplyTo(SelectionRef()); 6612 if (NS_WARN_IF(Destroyed())) { 6613 return NS_ERROR_EDITOR_DESTROYED; 6614 } 6615 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6616 "AutoClonedSelectionRangeArray::ApplyTo() failed"); 6617 return rv; 6618 } 6619 6620 NS_IMETHODIMP HTMLEditor::SetBackgroundColor(const nsAString& aColor) { 6621 nsresult rv = SetBackgroundColorAsAction(aColor); 6622 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 6623 "HTMLEditor::SetBackgroundColorAsAction() failed"); 6624 return rv; 6625 } 6626 6627 nsresult HTMLEditor::SetBackgroundColorAsAction(const nsAString& aColor, 6628 nsIPrincipal* aPrincipal) { 6629 AutoEditActionDataSetter editActionData( 6630 *this, EditAction::eSetBackgroundColor, aPrincipal); 6631 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 6632 if (NS_FAILED(rv)) { 6633 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 6634 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 6635 return EditorBase::ToGenericNSResult(rv); 6636 } 6637 6638 if (IsCSSEnabled()) { 6639 // if we are in CSS mode, we have to apply the background color to the 6640 // containing block (or the body if we have no block-level element in 6641 // the document) 6642 nsresult rv = SetBlockBackgroundColorWithCSSAsSubAction(aColor); 6643 NS_WARNING_ASSERTION( 6644 NS_SUCCEEDED(rv), 6645 "HTMLEditor::SetBlockBackgroundColorWithCSSAsSubAction() failed"); 6646 return EditorBase::ToGenericNSResult(rv); 6647 } 6648 6649 // but in HTML mode, we can only set the document's background color 6650 rv = SetHTMLBackgroundColorWithTransaction(aColor); 6651 NS_WARNING_ASSERTION( 6652 NS_SUCCEEDED(rv), 6653 "HTMLEditor::SetHTMLBackgroundColorWithTransaction() failed"); 6654 return EditorBase::ToGenericNSResult(rv); 6655 } 6656 6657 Result<EditorDOMPoint, nsresult> 6658 HTMLEditor::CopyLastEditableChildStylesWithTransaction( 6659 Element& aPreviousBlock, Element& aNewBlock, const Element& aEditingHost) { 6660 MOZ_ASSERT(IsEditActionDataAvailable()); 6661 6662 // First, clear out aNewBlock. Contract is that we want only the styles 6663 // from aPreviousBlock. 6664 AutoTArray<OwningNonNull<nsIContent>, 32> newBlockChildren; 6665 HTMLEditUtils::CollectAllChildren(aNewBlock, newBlockChildren); 6666 for (const OwningNonNull<nsIContent>& child : newBlockChildren) { 6667 // MOZ_KNownLive(child) because of bug 1622253 6668 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive(child)); 6669 if (NS_FAILED(rv)) { 6670 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 6671 return Err(rv); 6672 } 6673 } 6674 if (MOZ_UNLIKELY(aNewBlock.GetFirstChild())) { 6675 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 6676 } 6677 6678 // XXX aNewBlock may be moved or removed. Even in such case, we should 6679 // keep cloning the styles? 6680 6681 // Look for the deepest last editable leaf node in aPreviousBlock. 6682 // Then, if found one is a <br> element, look for non-<br> element. 6683 nsIContent* deepestEditableContent = nullptr; 6684 for (nsCOMPtr<nsIContent> child = &aPreviousBlock; child; 6685 child = HTMLEditUtils::GetLastChild( 6686 *child, {WalkTreeOption::IgnoreNonEditableNode})) { 6687 deepestEditableContent = child; 6688 } 6689 while (deepestEditableContent && 6690 deepestEditableContent->IsHTMLElement(nsGkAtoms::br)) { 6691 deepestEditableContent = HTMLEditUtils::GetPreviousContent( 6692 *deepestEditableContent, {WalkTreeOption::IgnoreNonEditableNode}, 6693 BlockInlineCheck::UseComputedDisplayOutsideStyle, &aEditingHost); 6694 } 6695 if (!deepestEditableContent) { 6696 return EditorDOMPoint(&aNewBlock, 0u); 6697 } 6698 6699 Element* deepestVisibleEditableElement = 6700 deepestEditableContent->GetAsElementOrParentElement(); 6701 if (!deepestVisibleEditableElement) { 6702 return EditorDOMPoint(&aNewBlock, 0u); 6703 } 6704 6705 // Clone inline elements to keep current style in the new block. 6706 // XXX Looks like that this is really slow if lastEditableDescendant is 6707 // far from aPreviousBlock. Probably, we should clone inline containers 6708 // from ancestor to descendants without transactions, then, insert it 6709 // after that with transaction. 6710 RefPtr<Element> lastClonedElement, firstClonedElement; 6711 for (RefPtr<Element> elementInPreviousBlock = deepestVisibleEditableElement; 6712 elementInPreviousBlock && elementInPreviousBlock != &aPreviousBlock; 6713 elementInPreviousBlock = elementInPreviousBlock->GetParentElement()) { 6714 if (!HTMLEditUtils::IsInlineStyleElement(*elementInPreviousBlock) && 6715 !elementInPreviousBlock->IsHTMLElement(nsGkAtoms::span)) { 6716 continue; 6717 } 6718 OwningNonNull<nsAtom> tagName = 6719 *elementInPreviousBlock->NodeInfo()->NameAtom(); 6720 // At first time, just create the most descendant inline container 6721 // element. 6722 if (!firstClonedElement) { 6723 Result<CreateElementResult, nsresult> createNewElementResult = 6724 CreateAndInsertElement( 6725 WithTransaction::Yes, tagName, EditorDOMPoint(&aNewBlock, 0u), 6726 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868 6727 [&elementInPreviousBlock]( 6728 HTMLEditor& aHTMLEditor, Element& aNewElement, 6729 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY { 6730 // Clone all attributes. Note that despite the method name, 6731 // CloneAttributesWithTransaction does not create 6732 // transactions in this case because aNewElement has not 6733 // been connected yet. 6734 // XXX Looks like that this clones id attribute too. 6735 aHTMLEditor.CloneAttributesWithTransaction( 6736 aNewElement, *elementInPreviousBlock); 6737 return NS_OK; 6738 }); 6739 if (MOZ_UNLIKELY(createNewElementResult.isErr())) { 6740 NS_WARNING( 6741 "HTMLEditor::CreateAndInsertElement(WithTransaction::Yes) failed"); 6742 return createNewElementResult.propagateErr(); 6743 } 6744 CreateElementResult unwrappedCreateNewElementResult = 6745 createNewElementResult.unwrap(); 6746 // We'll return with a point suggesting new caret position and the 6747 // following path does not require an update of selection here. 6748 // Therefore, we don't need to update selection here. 6749 unwrappedCreateNewElementResult.IgnoreCaretPointSuggestion(); 6750 firstClonedElement = lastClonedElement = 6751 unwrappedCreateNewElementResult.UnwrapNewNode(); 6752 continue; 6753 } 6754 // Otherwise, inserts new parent inline container to the previous inserted 6755 // inline container. 6756 Result<CreateElementResult, nsresult> wrapClonedElementResult = 6757 InsertContainerWithTransaction(*lastClonedElement, tagName); 6758 if (MOZ_UNLIKELY(wrapClonedElementResult.isErr())) { 6759 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed"); 6760 return wrapClonedElementResult.propagateErr(); 6761 } 6762 CreateElementResult unwrappedWrapClonedElementResult = 6763 wrapClonedElementResult.unwrap(); 6764 // We'll return with a point suggesting new caret so that we don't need to 6765 // update selection here. 6766 unwrappedWrapClonedElementResult.IgnoreCaretPointSuggestion(); 6767 MOZ_ASSERT(unwrappedWrapClonedElementResult.GetNewNode()); 6768 lastClonedElement = unwrappedWrapClonedElementResult.UnwrapNewNode(); 6769 CloneAttributesWithTransaction(*lastClonedElement, *elementInPreviousBlock); 6770 if (NS_WARN_IF(Destroyed())) { 6771 return Err(NS_ERROR_EDITOR_DESTROYED); 6772 } 6773 } 6774 6775 if (!firstClonedElement) { 6776 // XXX Even if no inline elements are cloned, shouldn't we create new 6777 // <br> element for aNewBlock? 6778 return EditorDOMPoint(&aNewBlock, 0u); 6779 } 6780 6781 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 6782 InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, 6783 EditorDOMPoint(firstClonedElement, 0u)); 6784 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 6785 NS_WARNING( 6786 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 6787 "LineBreakType::BRElement) failed"); 6788 return insertBRElementResultOrError.propagateErr(); 6789 } 6790 CreateLineBreakResult insertBRElementResult = 6791 insertBRElementResultOrError.unwrap(); 6792 MOZ_ASSERT(insertBRElementResult.Handled()); 6793 insertBRElementResult.IgnoreCaretPointSuggestion(); 6794 return insertBRElementResult.AtLineBreak<EditorDOMPoint>(); 6795 } 6796 6797 nsresult HTMLEditor::GetElementOrigin(Element& aElement, int32_t& aX, 6798 int32_t& aY) { 6799 aX = 0; 6800 aY = 0; 6801 6802 if (NS_WARN_IF(!IsInitialized())) { 6803 return NS_ERROR_NOT_INITIALIZED; 6804 } 6805 PresShell* presShell = GetPresShell(); 6806 if (NS_WARN_IF(!presShell)) { 6807 return NS_ERROR_NOT_INITIALIZED; 6808 } 6809 6810 nsIFrame* frame = aElement.GetPrimaryFrame(); 6811 if (NS_WARN_IF(!frame)) { 6812 return NS_OK; 6813 } 6814 6815 nsIFrame* absoluteContainerBlockFrame = 6816 presShell->GetAbsoluteContainingBlock(frame); 6817 if (NS_WARN_IF(!absoluteContainerBlockFrame)) { 6818 return NS_OK; 6819 } 6820 nsPoint off = frame->GetOffsetTo(absoluteContainerBlockFrame); 6821 aX = nsPresContext::AppUnitsToIntCSSPixels(off.x); 6822 aY = nsPresContext::AppUnitsToIntCSSPixels(off.y); 6823 6824 return NS_OK; 6825 } 6826 6827 Element* HTMLEditor::GetSelectionContainerElement() const { 6828 MOZ_ASSERT(IsEditActionDataAvailable()); 6829 6830 nsINode* focusNode = nullptr; 6831 if (SelectionRef().IsCollapsed()) { 6832 focusNode = SelectionRef().GetFocusNode(); 6833 if (NS_WARN_IF(!focusNode)) { 6834 return nullptr; 6835 } 6836 } else { 6837 const uint32_t rangeCount = SelectionRef().RangeCount(); 6838 MOZ_ASSERT(rangeCount, "If 0, Selection::IsCollapsed() should return true"); 6839 6840 if (rangeCount == 1) { 6841 const nsRange* range = SelectionRef().GetRangeAt(0); 6842 6843 const RangeBoundary& startRef = range->StartRef(); 6844 const RangeBoundary& endRef = range->EndRef(); 6845 6846 // This method called GetSelectedElement() to retrieve proper container 6847 // when only one node is selected. However, it simply returns start 6848 // node of Selection with additional cost. So, we do not need to call 6849 // it anymore. 6850 if (startRef.GetContainer()->IsElement() && 6851 startRef.GetContainer() == endRef.GetContainer() && 6852 startRef.GetChildAtOffset() && 6853 startRef.GetChildAtOffset()->GetNextSibling() == 6854 endRef.GetChildAtOffset()) { 6855 focusNode = startRef.GetChildAtOffset(); 6856 MOZ_ASSERT(focusNode, "Start container must not be nullptr"); 6857 } else { 6858 focusNode = range->GetClosestCommonInclusiveAncestor(); 6859 if (!focusNode) { 6860 NS_WARNING( 6861 "AbstractRange::GetClosestCommonInclusiveAncestor() returned " 6862 "nullptr"); 6863 return nullptr; 6864 } 6865 } 6866 } else { 6867 for (const uint32_t i : IntegerRange(rangeCount)) { 6868 MOZ_ASSERT(SelectionRef().RangeCount() == rangeCount); 6869 const nsRange* range = SelectionRef().GetRangeAt(i); 6870 MOZ_ASSERT(range); 6871 nsINode* startContainer = range->GetStartContainer(); 6872 if (!focusNode) { 6873 focusNode = startContainer; 6874 } else if (focusNode != startContainer) { 6875 // XXX Looks odd to use parent of startContainer because previous 6876 // range may not be in the parent node of current 6877 // startContainer. 6878 focusNode = startContainer->GetParentNode(); 6879 // XXX Looks odd to break the for-loop here because we refer only 6880 // first range and another range which starts from different 6881 // container, and the latter range is preferred. Why? 6882 break; 6883 } 6884 } 6885 if (!focusNode) { 6886 NS_WARNING("Focused node of selection was not found"); 6887 return nullptr; 6888 } 6889 } 6890 } 6891 6892 if (focusNode->IsText()) { 6893 focusNode = focusNode->GetParentNode(); 6894 if (NS_WARN_IF(!focusNode)) { 6895 return nullptr; 6896 } 6897 } 6898 6899 if (NS_WARN_IF(!focusNode->IsElement())) { 6900 return nullptr; 6901 } 6902 return focusNode->AsElement(); 6903 } 6904 6905 NS_IMETHODIMP HTMLEditor::IsAnonymousElement(Element* aElement, bool* aReturn) { 6906 if (NS_WARN_IF(!aElement)) { 6907 return NS_ERROR_INVALID_ARG; 6908 } 6909 *aReturn = aElement->IsRootOfNativeAnonymousSubtree(); 6910 return NS_OK; 6911 } 6912 6913 nsresult HTMLEditor::SetReturnInParagraphCreatesNewParagraph( 6914 bool aCreatesNewParagraph) { 6915 mCRInParagraphCreatesParagraph = aCreatesNewParagraph; 6916 return NS_OK; 6917 } 6918 6919 bool HTMLEditor::GetReturnInParagraphCreatesNewParagraph() const { 6920 return mCRInParagraphCreatesParagraph; 6921 } 6922 6923 nsresult HTMLEditor::GetReturnInParagraphCreatesNewParagraph( 6924 bool* aCreatesNewParagraph) { 6925 *aCreatesNewParagraph = mCRInParagraphCreatesParagraph; 6926 return NS_OK; 6927 } 6928 6929 NS_IMETHODIMP HTMLEditor::GetWrapWidth(int32_t* aWrapColumn) { 6930 if (NS_WARN_IF(!aWrapColumn)) { 6931 return NS_ERROR_INVALID_ARG; 6932 } 6933 *aWrapColumn = WrapWidth(); 6934 return NS_OK; 6935 } 6936 6937 // 6938 // See if the style value includes this attribute, and if it does, 6939 // cut out everything from the attribute to the next semicolon. 6940 // 6941 static void CutStyle(const char* stylename, nsString& styleValue) { 6942 // Find the current wrapping type: 6943 int32_t styleStart = styleValue.LowerCaseFindASCII(stylename); 6944 if (styleStart >= 0) { 6945 int32_t styleEnd = styleValue.Find(u";", styleStart); 6946 if (styleEnd > styleStart) { 6947 styleValue.Cut(styleStart, styleEnd - styleStart + 1); 6948 } else { 6949 styleValue.Cut(styleStart, styleValue.Length() - styleStart); 6950 } 6951 } 6952 } 6953 6954 NS_IMETHODIMP HTMLEditor::SetWrapWidth(int32_t aWrapColumn) { 6955 AutoEditActionDataSetter editActionData(*this, EditAction::eSetWrapWidth); 6956 if (NS_WARN_IF(!editActionData.CanHandle())) { 6957 return NS_ERROR_NOT_INITIALIZED; 6958 } 6959 6960 mWrapColumn = aWrapColumn; 6961 6962 // Make sure we're a plaintext editor, otherwise we shouldn't 6963 // do the rest of this. 6964 if (!IsPlaintextMailComposer()) { 6965 return NS_OK; 6966 } 6967 6968 // Ought to set a style sheet here... 6969 const RefPtr<Element> rootElement = GetRoot(); 6970 if (NS_WARN_IF(!rootElement)) { 6971 return NS_ERROR_NOT_INITIALIZED; 6972 } 6973 6974 // Get the current style for this root element: 6975 nsAutoString styleValue; 6976 rootElement->GetAttr(nsGkAtoms::style, styleValue); 6977 6978 // We'll replace styles for these values: 6979 CutStyle("white-space", styleValue); 6980 CutStyle("width", styleValue); 6981 CutStyle("font-family", styleValue); 6982 6983 // If we have other style left, trim off any existing semicolons 6984 // or white-space, then add a known semicolon-space: 6985 if (!styleValue.IsEmpty()) { 6986 styleValue.Trim("; \t", false, true); 6987 styleValue.AppendLiteral("; "); 6988 } 6989 6990 // Make sure we have fixed-width font. This should be done for us, 6991 // but it isn't, see bug 22502, so we have to add "font: -moz-fixed;". 6992 // Only do this if we're wrapping. 6993 if (IsWrapHackEnabled() && aWrapColumn >= 0) { 6994 styleValue.AppendLiteral("font-family: -moz-fixed; "); 6995 } 6996 6997 // and now we're ready to set the new white-space/wrapping style. 6998 if (aWrapColumn > 0) { 6999 // Wrap to a fixed column. 7000 styleValue.AppendLiteral("white-space: pre-wrap; width: "); 7001 styleValue.AppendInt(aWrapColumn); 7002 styleValue.AppendLiteral("ch;"); 7003 } else if (!aWrapColumn) { 7004 styleValue.AppendLiteral("white-space: pre-wrap;"); 7005 } else { 7006 styleValue.AppendLiteral("white-space: pre;"); 7007 } 7008 7009 AutoElementAttrAPIWrapper elementWrapper(*this, *rootElement); 7010 nsresult rv = elementWrapper.SetAttr(nsGkAtoms::style, styleValue, true); 7011 if (NS_FAILED(rv)) { 7012 NS_WARNING("AutoElementAttrAPIWrapper::SetAttr() failed"); 7013 return rv; 7014 } 7015 NS_WARNING_ASSERTION( 7016 elementWrapper.IsExpectedResult(styleValue), 7017 "Setting style attribute caused other mutations, but ignored"); 7018 return NS_OK; 7019 } 7020 7021 Element* HTMLEditor::GetFocusedElement() const { 7022 Element* const focusedElement = nsFocusManager::GetFocusedElementStatic(); 7023 7024 Document* document = GetDocument(); 7025 if (NS_WARN_IF(!document)) { 7026 return nullptr; 7027 } 7028 const bool inDesignMode = focusedElement ? focusedElement->IsInDesignMode() 7029 : document->IsInDesignMode(); 7030 if (!focusedElement) { 7031 // in designMode, nobody gets focus in most cases. 7032 if (inDesignMode && OurWindowHasFocus()) { 7033 return document->GetRootElement(); 7034 } 7035 return nullptr; 7036 } 7037 7038 if (inDesignMode) { 7039 return OurWindowHasFocus() && 7040 focusedElement->IsInclusiveDescendantOf(document) 7041 ? focusedElement 7042 : nullptr; 7043 } 7044 7045 // We're HTML editor for contenteditable 7046 7047 // If the focused content isn't editable, or it has independent selection, 7048 // we don't have focus. 7049 if (!focusedElement->HasFlag(NODE_IS_EDITABLE) || 7050 focusedElement->HasIndependentSelection()) { 7051 return nullptr; 7052 } 7053 // If our window is focused, we're focused. 7054 return OurWindowHasFocus() ? focusedElement : nullptr; 7055 } 7056 7057 bool HTMLEditor::IsActiveInDOMWindow() const { 7058 nsFocusManager* focusManager = nsFocusManager::GetFocusManager(); 7059 if (NS_WARN_IF(!focusManager)) { 7060 return false; 7061 } 7062 7063 Document* document = GetDocument(); 7064 if (NS_WARN_IF(!document)) { 7065 return false; 7066 } 7067 7068 // If we're in designMode, we're always active in the DOM window. 7069 if (IsInDesignMode()) { 7070 return true; 7071 } 7072 7073 nsPIDOMWindowOuter* ourWindow = document->GetWindow(); 7074 nsCOMPtr<nsPIDOMWindowOuter> win; 7075 nsIContent* content = nsFocusManager::GetFocusedDescendant( 7076 ourWindow, nsFocusManager::eOnlyCurrentWindow, getter_AddRefs(win)); 7077 if (!content) { 7078 return false; 7079 } 7080 7081 if (content->IsInDesignMode()) { 7082 return true; 7083 } 7084 7085 // We're HTML editor for contenteditable 7086 7087 // If the active content isn't editable, or it has independent selection, 7088 // we're not active). 7089 if (!content->HasFlag(NODE_IS_EDITABLE) || 7090 content->HasIndependentSelection()) { 7091 return false; 7092 } 7093 return true; 7094 } 7095 7096 Element* HTMLEditor::ComputeEditingHostInternal( 7097 const nsIContent* aContent, LimitInBodyElement aLimitInBodyElement) const { 7098 Document* document = GetDocument(); 7099 if (NS_WARN_IF(!document)) { 7100 return nullptr; 7101 } 7102 7103 auto MaybeLimitInBodyElement = 7104 [&](const Element* aCandidateEditingHost) -> Element* { 7105 if (!aCandidateEditingHost) { 7106 return nullptr; 7107 } 7108 if (aLimitInBodyElement != LimitInBodyElement::Yes) { 7109 return const_cast<Element*>(aCandidateEditingHost); 7110 } 7111 // By default, we should limit editing host to the <body> element for 7112 // avoiding deleting or creating unexpected elements outside the <body>. 7113 // However, this is incompatible with Chrome so that we should stop 7114 // doing this with adding safety checks more. 7115 if (document->GetBodyElement() && 7116 nsContentUtils::ContentIsFlattenedTreeDescendantOf( 7117 aCandidateEditingHost, document->GetBodyElement())) { 7118 return const_cast<Element*>(aCandidateEditingHost); 7119 } 7120 // XXX If aContent is an editing host and has no parent node, we reach here, 7121 // but returning the <body> which is not connected to aContent is odd. 7122 return document->GetBodyElement(); 7123 }; 7124 7125 // We're HTML editor for contenteditable 7126 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 7127 if (NS_WARN_IF(!editActionData.CanHandle())) { 7128 return nullptr; 7129 } 7130 7131 const nsIContent* const content = [&]() -> const nsIContent* { 7132 if (aContent) { 7133 return aContent; 7134 } 7135 // If there are selection ranges, let's look for their common ancestor's 7136 // editing host because selection ranges may be visible for users. 7137 nsIContent* selectionCommonAncestor = nullptr; 7138 for (uint32_t i : IntegerRange(SelectionRef().RangeCount())) { 7139 nsRange* range = SelectionRef().GetRangeAt(i); 7140 MOZ_ASSERT(range); 7141 nsIContent* commonAncestor = 7142 nsIContent::FromNodeOrNull(range->GetCommonAncestorContainer( 7143 IgnoreErrors(), AllowRangeCrossShadowBoundary::Yes)); 7144 if (MOZ_UNLIKELY(!commonAncestor)) { 7145 continue; 7146 } 7147 if (!selectionCommonAncestor) { 7148 selectionCommonAncestor = commonAncestor; 7149 } else { 7150 selectionCommonAncestor = nsIContent::FromNodeOrNull( 7151 nsContentUtils::GetCommonFlattenedTreeAncestorForSelection( 7152 commonAncestor, selectionCommonAncestor)); 7153 } 7154 } 7155 if (selectionCommonAncestor) { 7156 return selectionCommonAncestor; 7157 } 7158 // Otherwise, let's use the focused element in the window. 7159 nsPIDOMWindowInner* const innerWindow = document->GetInnerWindow(); 7160 if (MOZ_UNLIKELY(!innerWindow)) { 7161 return nullptr; 7162 } 7163 if (Element* focusedElementInWindow = innerWindow->GetFocusedElement()) { 7164 if (focusedElementInWindow->ChromeOnlyAccess()) { 7165 focusedElementInWindow = Element::FromNodeOrNull( 7166 // XXX Should we use 7167 // nsIContent::FindFirstNonChromeOnlyAccessContent() instead of 7168 // nsINode::GetClosestNativeAnonymousSubtreeRootParentOrHost()? 7169 focusedElementInWindow 7170 ->GetClosestNativeAnonymousSubtreeRootParentOrHost()); 7171 } 7172 if (focusedElementInWindow) { 7173 return focusedElementInWindow->IsEditable() ? focusedElementInWindow 7174 : nullptr; 7175 } 7176 } 7177 // If there is no focused element and the document is in the design mode, 7178 // let's return the <body>. 7179 if (document->IsInDesignMode()) { 7180 return document->GetBodyElement(); 7181 } 7182 // Otherwise, we cannot find the editing host... 7183 return nullptr; 7184 }(); 7185 if ((content && content->IsInDesignMode()) || 7186 (!content && document->IsInDesignMode())) { 7187 // FIXME: There may be no <body>. In such case and aLimitInBodyElement is 7188 // "No", we should use root element instead. 7189 return document->GetBodyElement(); 7190 } 7191 7192 if (NS_WARN_IF(!content)) { 7193 return nullptr; 7194 } 7195 7196 // If the active content isn't editable, we're not active. 7197 if (!content->HasFlag(NODE_IS_EDITABLE)) { 7198 return nullptr; 7199 } 7200 7201 // Although the content shouldn't be in a native anonymous subtree, but 7202 // perhaps due to a bug of Selection or Range API, it may occur. HTMLEditor 7203 // shouldn't touch native anonymous subtree so that return nullptr in such 7204 // case. 7205 if (MOZ_UNLIKELY(content->IsInNativeAnonymousSubtree())) { 7206 return nullptr; 7207 } 7208 7209 // Note that `Selection` can be in <input> or <textarea>. In the case, we 7210 // need to look for an ancestor which does not have editable parent. 7211 return MaybeLimitInBodyElement( 7212 const_cast<nsIContent*>(content)->GetEditingHost()); 7213 } 7214 7215 void HTMLEditor::NotifyEditingHostMaybeChanged() { 7216 // Note that even if the document is in design mode, a contenteditable element 7217 // in a shadow tree is focusable. Therefore, we may need to update editing 7218 // host even when the document is in design mode. 7219 if (MOZ_UNLIKELY(NS_WARN_IF(!GetDocument()))) { 7220 return; 7221 } 7222 7223 // We're HTML editor for contenteditable 7224 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 7225 if (NS_WARN_IF(!editActionData.CanHandle())) { 7226 return; 7227 } 7228 7229 // Get selection ancestor limit which may be old editing host. 7230 nsIContent* ancestorLimiter = SelectionRef().GetAncestorLimiter(); 7231 if (!ancestorLimiter) { 7232 // If we've not initialized selection ancestor limit, we should wait focus 7233 // event to set proper limiter. 7234 return; 7235 } 7236 7237 // Compute current editing host. 7238 Element* const editingHost = ComputeEditingHost(); 7239 if (NS_WARN_IF(!editingHost)) { 7240 return; 7241 } 7242 7243 // Update selection ancestor limit if current editing host includes the 7244 // previous editing host. 7245 // Additionally, the editing host may be an element in shadow DOM and the 7246 // shadow host is in designMode. In this case, we need to set the editing 7247 // host as the new selection limiter. 7248 if (ancestorLimiter->IsInclusiveDescendantOf(editingHost) || 7249 (ancestorLimiter->IsInDesignMode() != editingHost->IsInDesignMode())) { 7250 // Note that don't call HTMLEditor::InitializeSelectionAncestorLimit() 7251 // here because it may collapse selection to the first editable node. 7252 EditorBase::InitializeSelectionAncestorLimit(*editingHost); 7253 } 7254 } 7255 7256 EventTarget* HTMLEditor::GetDOMEventTarget() const { 7257 // Don't use getDocument here, because we have no way of knowing 7258 // whether Init() was ever called. So we need to get the document 7259 // ourselves, if it exists. 7260 Document* doc = GetDocument(); 7261 MOZ_ASSERT(doc, "The HTMLEditor has not been initialized yet"); 7262 if (!doc) { 7263 return nullptr; 7264 } 7265 7266 // Register the EditorEventListener to the parent of window. 7267 // 7268 // The advantage of this approach is HTMLEditor can still 7269 // receive events when shadow dom is involved. 7270 if (nsPIDOMWindowOuter* win = doc->GetWindow()) { 7271 return win->GetParentTarget(); 7272 } 7273 return nullptr; 7274 } 7275 7276 Element* HTMLEditor::GetBodyElement() const { 7277 Document* document = GetDocument(); 7278 MOZ_ASSERT(document, "The HTMLEditor hasn't been initialized yet"); 7279 if (NS_WARN_IF(!document)) { 7280 return nullptr; 7281 } 7282 return document->GetBody(); 7283 } 7284 7285 nsINode* HTMLEditor::GetFocusedNode() const { 7286 Element* focusedElement = GetFocusedElement(); 7287 if (!focusedElement) { 7288 return nullptr; 7289 } 7290 7291 // focusedElement might be non-null even 7292 // nsFocusManager::GetFocusedElementStatic() returns null. That's the 7293 // designMode case, and in that case our GetFocusedElement() returns the root 7294 // element, but we want to return the document. 7295 7296 if ((focusedElement = nsFocusManager::GetFocusedElementStatic())) { 7297 return focusedElement; 7298 } 7299 7300 return GetDocument(); 7301 } 7302 7303 bool HTMLEditor::OurWindowHasFocus() const { 7304 nsFocusManager* focusManager = nsFocusManager::GetFocusManager(); 7305 if (NS_WARN_IF(!focusManager)) { 7306 return false; 7307 } 7308 nsPIDOMWindowOuter* focusedWindow = focusManager->GetFocusedWindow(); 7309 if (!focusedWindow) { 7310 return false; 7311 } 7312 Document* document = GetDocument(); 7313 if (NS_WARN_IF(!document)) { 7314 return false; 7315 } 7316 nsPIDOMWindowOuter* ourWindow = document->GetWindow(); 7317 return ourWindow == focusedWindow; 7318 } 7319 7320 bool HTMLEditor::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) const { 7321 if (!EditorBase::IsAcceptableInputEvent(aGUIEvent)) { 7322 return false; 7323 } 7324 7325 // While there is composition, all composition events in its top level 7326 // window are always fired on the composing editor. Therefore, if this 7327 // editor has composition, the composition events should be handled in this 7328 // editor. 7329 if (mComposition && aGUIEvent->AsCompositionEvent()) { 7330 return true; 7331 } 7332 7333 nsCOMPtr<nsINode> eventTargetNode = 7334 nsINode::FromEventTargetOrNull(aGUIEvent->GetOriginalDOMEventTarget()); 7335 if (NS_WARN_IF(!eventTargetNode)) { 7336 return false; 7337 } 7338 7339 if (eventTargetNode->IsContent()) { 7340 eventTargetNode = 7341 eventTargetNode->AsContent()->FindFirstNonChromeOnlyAccessContent(); 7342 if (NS_WARN_IF(!eventTargetNode)) { 7343 return false; 7344 } 7345 } 7346 7347 RefPtr<Document> document = GetDocument(); 7348 if (NS_WARN_IF(!document)) { 7349 return false; 7350 } 7351 7352 if (eventTargetNode->IsInDesignMode()) { 7353 // If this editor is in designMode and the event target is the document, 7354 // the event is for this editor. 7355 if (eventTargetNode->IsDocument()) { 7356 return eventTargetNode == document; 7357 } 7358 // Otherwise, check whether the event target is in this document or not. 7359 if (NS_WARN_IF(!eventTargetNode->IsContent())) { 7360 return false; 7361 } 7362 if (document == eventTargetNode->GetUncomposedDoc()) { 7363 return true; 7364 } 7365 } 7366 7367 // Space event for <button> and <summary> with contenteditable 7368 // should be handle by the themselves. 7369 if (aGUIEvent->mMessage == eKeyPress && 7370 aGUIEvent->AsKeyboardEvent()->ShouldWorkAsSpaceKey()) { 7371 nsGenericHTMLElement* element = 7372 HTMLButtonElement::FromNode(eventTargetNode); 7373 if (!element) { 7374 element = HTMLSummaryElement::FromNode(eventTargetNode); 7375 } 7376 7377 if (element && element->IsContentEditable()) { 7378 return false; 7379 } 7380 } 7381 // This HTML editor is for contenteditable. We need to check the validity 7382 // of the target. 7383 if (NS_WARN_IF(!eventTargetNode->IsContent())) { 7384 return false; 7385 } 7386 7387 // If the event is a mouse event, we need to check if the target content is 7388 // the focused editing host or its descendant. 7389 if (aGUIEvent->AsMouseEventBase()) { 7390 nsIContent* editingHost = ComputeEditingHost(); 7391 // If there is no active editing host, we cannot handle the mouse event 7392 // correctly. 7393 if (!editingHost) { 7394 return false; 7395 } 7396 // If clicked on non-editable root element but the body element is the 7397 // active editing host, we should assume that the click event is 7398 // targetted. 7399 if (eventTargetNode == document->GetRootElement() && 7400 !eventTargetNode->HasFlag(NODE_IS_EDITABLE) && 7401 editingHost == document->GetBodyElement()) { 7402 eventTargetNode = editingHost; 7403 } 7404 // If the target element is neither the active editing host nor a 7405 // descendant of it, we may not be able to handle the event. 7406 if (!eventTargetNode->IsInclusiveDescendantOf(editingHost)) { 7407 return false; 7408 } 7409 // If the clicked element has an independent selection, we shouldn't 7410 // handle this click event. 7411 if (eventTargetNode->AsContent()->HasIndependentSelection()) { 7412 return false; 7413 } 7414 // If the target content is editable, we should handle this event. 7415 return eventTargetNode->HasFlag(NODE_IS_EDITABLE); 7416 } 7417 7418 // If the target of the other events which target focused element isn't 7419 // editable or has an independent selection, this editor shouldn't handle 7420 // the event. 7421 if (!eventTargetNode->HasFlag(NODE_IS_EDITABLE) || 7422 eventTargetNode->AsContent()->HasIndependentSelection()) { 7423 return false; 7424 } 7425 7426 // Finally, check whether we're actually focused or not. When we're not 7427 // focused, we should ignore the dispatched event by script (or something) 7428 // because content editable element needs selection in itself for editing. 7429 // However, when we're not focused, it's not guaranteed. 7430 return IsActiveInDOMWindow(); 7431 } 7432 7433 Result<widget::IMEState, nsresult> HTMLEditor::GetPreferredIMEState() const { 7434 // HTML editor don't prefer the CSS ime-mode because IE didn't do so too. 7435 return IMEState{IsReadonly() ? IMEEnabled::Disabled : IMEEnabled::Enabled, 7436 IMEState::DONT_CHANGE_OPEN_STATE}; 7437 } 7438 7439 already_AddRefed<Element> HTMLEditor::GetInputEventTargetElement() const { 7440 // If there is no selection ranges, we'll do nothing. Therefore, 7441 // `beforeinput` event should not be fired. 7442 // FIXME: If there is no selection but we've already modified the DOM, 7443 // we should fire `input` event on the editing host. However, we cannot 7444 // know which one was the editing host when we touched the DOM. 7445 if (MOZ_UNLIKELY(!SelectionRef().RangeCount())) { 7446 return nullptr; 7447 } 7448 7449 RefPtr<Element> target = ComputeEditingHost(LimitInBodyElement::No); 7450 if (target) { 7451 return target.forget(); 7452 } 7453 7454 // When there is no active editing host due to focus node is a 7455 // non-editable node, we should look for its editable parent to 7456 // dispatch `beforeinput` event. 7457 nsIContent* focusContent = 7458 nsIContent::FromNodeOrNull(SelectionRef().GetFocusNode()); 7459 if (!focusContent || focusContent->IsEditable()) { 7460 return nullptr; 7461 } 7462 for (Element* element : focusContent->AncestorsOfType<Element>()) { 7463 if (element->IsEditable()) { 7464 target = element->GetEditingHost(); 7465 return target.forget(); 7466 } 7467 } 7468 return nullptr; 7469 } 7470 7471 } // namespace mozilla