HTMLStyleEditor.cpp (195203B)
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 "ErrorList.h" 7 #include "HTMLEditor.h" 8 #include "HTMLEditorInlines.h" 9 #include "HTMLEditorNestedClasses.h" 10 11 #include "AutoClonedRangeArray.h" 12 #include "CSSEditUtils.h" 13 #include "EditAction.h" 14 #include "EditorLineBreak.h" 15 #include "EditorUtils.h" 16 #include "HTMLEditHelpers.h" 17 #include "HTMLEditUtils.h" 18 #include "PendingStyles.h" 19 #include "SelectionState.h" 20 #include "WSRunScanner.h" 21 22 #include "mozilla/Assertions.h" 23 #include "mozilla/ContentIterator.h" 24 #include "mozilla/EditorForwards.h" 25 #include "mozilla/mozalloc.h" 26 #include "mozilla/SelectionState.h" 27 #include "mozilla/dom/AncestorIterator.h" 28 #include "mozilla/dom/Element.h" 29 #include "mozilla/dom/ElementInlines.h" 30 #include "mozilla/dom/HTMLBRElement.h" 31 #include "mozilla/dom/NameSpaceConstants.h" 32 #include "mozilla/dom/Selection.h" 33 #include "mozilla/dom/Text.h" 34 35 #include "nsAString.h" 36 #include "nsAtom.h" 37 #include "nsAttrName.h" 38 #include "nsAttrValue.h" 39 #include "nsCaseTreatment.h" 40 #include "nsColor.h" 41 #include "nsComponentManagerUtils.h" 42 #include "nsDebug.h" 43 #include "nsError.h" 44 #include "nsGkAtoms.h" 45 #include "nsIContent.h" 46 #include "nsINode.h" 47 #include "nsIPrincipal.h" 48 #include "nsISupportsImpl.h" 49 #include "nsLiteralString.h" 50 #include "nsNameSpaceManager.h" 51 #include "nsRange.h" 52 #include "nsReadableUtils.h" 53 #include "nsString.h" 54 #include "nsStringFwd.h" 55 #include "nsStyledElement.h" 56 #include "nsTArray.h" 57 #include "nsTextNode.h" 58 #include "nsUnicharUtils.h" 59 60 namespace mozilla { 61 62 using namespace dom; 63 64 using EditablePointOption = HTMLEditUtils::EditablePointOption; 65 using EditablePointOptions = HTMLEditUtils::EditablePointOptions; 66 using EmptyCheckOption = HTMLEditUtils::EmptyCheckOption; 67 using LeafNodeType = HTMLEditUtils::LeafNodeType; 68 using LeafNodeTypes = HTMLEditUtils::LeafNodeTypes; 69 using WalkTreeOption = HTMLEditUtils::WalkTreeOption; 70 71 template nsresult HTMLEditor::SetInlinePropertiesAsSubAction( 72 const AutoTArray<EditorInlineStyleAndValue, 1>& aStylesToSet, 73 const Element& aEditingHost); 74 template nsresult HTMLEditor::SetInlinePropertiesAsSubAction( 75 const AutoTArray<EditorInlineStyleAndValue, 32>& aStylesToSet, 76 const Element& aEditingHost); 77 78 template nsresult HTMLEditor::SetInlinePropertiesAroundRanges( 79 AutoClonedRangeArray& aRanges, 80 const AutoTArray<EditorInlineStyleAndValue, 1>& aStylesToSet); 81 template nsresult HTMLEditor::SetInlinePropertiesAroundRanges( 82 AutoClonedRangeArray& aRanges, 83 const AutoTArray<EditorInlineStyleAndValue, 32>& aStylesToSet); 84 85 nsresult HTMLEditor::SetInlinePropertyAsAction(nsStaticAtom& aProperty, 86 nsStaticAtom* aAttribute, 87 const nsAString& aValue, 88 nsIPrincipal* aPrincipal) { 89 AutoEditActionDataSetter editActionData( 90 *this, 91 HTMLEditUtils::GetEditActionForFormatText(aProperty, aAttribute, true), 92 aPrincipal); 93 if (NS_WARN_IF(!editActionData.CanHandle())) { 94 return NS_ERROR_NOT_INITIALIZED; 95 } 96 97 const RefPtr<Element> editingHost = 98 ComputeEditingHost(LimitInBodyElement::No); 99 if (NS_WARN_IF(!editingHost)) { 100 return NS_ERROR_FAILURE; 101 } 102 if (IsPlaintextMailComposer() || 103 editingHost->IsContentEditablePlainTextOnly()) { 104 return NS_SUCCESS_DOM_NO_OPERATION; 105 } 106 107 switch (editActionData.GetEditAction()) { 108 case EditAction::eSetFontFamilyProperty: 109 MOZ_ASSERT(!aValue.IsVoid()); 110 // XXX Should we trim unnecessary white-spaces? 111 editActionData.SetData(aValue); 112 break; 113 case EditAction::eSetColorProperty: 114 case EditAction::eSetBackgroundColorPropertyInline: 115 editActionData.SetColorData(aValue); 116 break; 117 default: 118 break; 119 } 120 121 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 122 if (NS_FAILED(rv)) { 123 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 124 "MaybeDispatchBeforeInputEvent(), failed"); 125 return EditorBase::ToGenericNSResult(rv); 126 } 127 128 // XXX Due to bug 1659276 and bug 1659924, we should not scroll selection 129 // into view after setting the new style. 130 AutoPlaceholderBatch treatAsOneTransaction(*this, ScrollSelectionIntoView::No, 131 __FUNCTION__); 132 133 nsStaticAtom* property = &aProperty; 134 nsStaticAtom* attribute = aAttribute; 135 nsString value(aValue); 136 if (attribute == nsGkAtoms::color || attribute == nsGkAtoms::bgcolor) { 137 if (!IsCSSEnabled()) { 138 // We allow CSS style color value even in the HTML mode. In the cases, 139 // we will apply the style with CSS. For considering it in the value 140 // as-is if it's a known CSS keyboard, `rgb()` or `rgba()` style. 141 // NOTE: It may be later that we set the color into the DOM tree and at 142 // that time, IsCSSEnabled() may return true. E.g., setting color value 143 // to collapsed selection, then, change the CSS enabled, finally, user 144 // types text there. 145 if (!HTMLEditUtils::MaybeCSSSpecificColorValue(value)) { 146 HTMLEditUtils::GetNormalizedHTMLColorValue(value, value); 147 } 148 } else { 149 HTMLEditUtils::GetNormalizedCSSColorValue( 150 value, HTMLEditUtils::ZeroAlphaColor::RGBAValue, value); 151 } 152 } 153 154 AutoTArray<EditorInlineStyle, 1> stylesToRemove; 155 if (&aProperty == nsGkAtoms::sup) { 156 // Superscript and Subscript styles are mutually exclusive. 157 stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::sub)); 158 } else if (&aProperty == nsGkAtoms::sub) { 159 // Superscript and Subscript styles are mutually exclusive. 160 stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::sup)); 161 } 162 // Handling `<tt>` element code was implemented for composer (bug 115922). 163 // This shouldn't work with `Document.execCommand()`. Currently, aPrincipal 164 // is set only when the root caller is Document::ExecCommand() so that 165 // we should handle `<tt>` element only when aPrincipal is nullptr that 166 // must be only when XUL command is executed on composer. 167 else if (!aPrincipal) { 168 if (&aProperty == nsGkAtoms::tt) { 169 stylesToRemove.AppendElement( 170 EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face)); 171 } else if (&aProperty == nsGkAtoms::font && aAttribute == nsGkAtoms::face) { 172 if (!value.LowerCaseEqualsASCII("tt")) { 173 stylesToRemove.AppendElement(EditorInlineStyle(*nsGkAtoms::tt)); 174 } else { 175 stylesToRemove.AppendElement( 176 EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face)); 177 // Override property, attribute and value if the new font face value is 178 // "tt". 179 property = nsGkAtoms::tt; 180 attribute = nullptr; 181 value.Truncate(); 182 } 183 } 184 } 185 186 if (!stylesToRemove.IsEmpty()) { 187 nsresult rv = 188 RemoveInlinePropertiesAsSubAction(stylesToRemove, *editingHost); 189 if (NS_FAILED(rv)) { 190 NS_WARNING("HTMLEditor::RemoveInlinePropertiesAsSubAction() failed"); 191 return rv; 192 } 193 } 194 195 AutoTArray<EditorInlineStyleAndValue, 1> styleToSet; 196 styleToSet.AppendElement( 197 attribute 198 ? EditorInlineStyleAndValue(*property, *attribute, std::move(value)) 199 : EditorInlineStyleAndValue(*property)); 200 rv = SetInlinePropertiesAsSubAction(styleToSet, *editingHost); 201 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 202 "HTMLEditor::SetInlinePropertiesAsSubAction() failed"); 203 return EditorBase::ToGenericNSResult(rv); 204 } 205 206 NS_IMETHODIMP HTMLEditor::SetInlineProperty(const nsAString& aProperty, 207 const nsAString& aAttribute, 208 const nsAString& aValue) { 209 nsStaticAtom* property = NS_GetStaticAtom(aProperty); 210 if (NS_WARN_IF(!property)) { 211 return NS_ERROR_INVALID_ARG; 212 } 213 nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute); 214 AutoEditActionDataSetter editActionData( 215 *this, 216 HTMLEditUtils::GetEditActionForFormatText(*property, attribute, true)); 217 if (NS_WARN_IF(!editActionData.CanHandle())) { 218 return NS_ERROR_NOT_INITIALIZED; 219 } 220 221 const RefPtr<Element> editingHost = 222 ComputeEditingHost(LimitInBodyElement::No); 223 if (NS_WARN_IF(!editingHost)) { 224 return NS_ERROR_FAILURE; 225 } 226 if (IsPlaintextMailComposer() || 227 editingHost->IsContentEditablePlainTextOnly()) { 228 return NS_SUCCESS_DOM_NO_OPERATION; 229 } 230 231 switch (editActionData.GetEditAction()) { 232 case EditAction::eSetFontFamilyProperty: 233 MOZ_ASSERT(!aValue.IsVoid()); 234 // XXX Should we trim unnecessary white-spaces? 235 editActionData.SetData(aValue); 236 break; 237 case EditAction::eSetColorProperty: 238 case EditAction::eSetBackgroundColorPropertyInline: 239 editActionData.SetColorData(aValue); 240 break; 241 default: 242 break; 243 } 244 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 245 if (NS_FAILED(rv)) { 246 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 247 "MaybeDispatchBeforeInputEvent(), failed"); 248 return EditorBase::ToGenericNSResult(rv); 249 } 250 251 AutoTArray<EditorInlineStyleAndValue, 1> styleToSet; 252 styleToSet.AppendElement( 253 attribute ? EditorInlineStyleAndValue(*property, *attribute, aValue) 254 : EditorInlineStyleAndValue(*property)); 255 rv = SetInlinePropertiesAsSubAction(styleToSet, *editingHost); 256 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 257 "HTMLEditor::SetInlinePropertiesAsSubAction() failed"); 258 return EditorBase::ToGenericNSResult(rv); 259 } 260 261 template <size_t N> 262 nsresult HTMLEditor::SetInlinePropertiesAsSubAction( 263 const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet, 264 const Element& aEditingHost) { 265 MOZ_ASSERT(IsEditActionDataAvailable()); 266 MOZ_ASSERT(!aStylesToSet.IsEmpty()); 267 268 DebugOnly<nsresult> rvIgnored = CommitComposition(); 269 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 270 "EditorBase::CommitComposition() failed, but ignored"); 271 if (MOZ_UNLIKELY(&aEditingHost != 272 ComputeEditingHost(LimitInBodyElement::No))) { 273 NS_WARNING("Editing host has been changed during committing composition"); 274 return NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE; 275 } 276 277 if (SelectionRef().IsCollapsed()) { 278 // Manipulating text attributes on a collapsed selection only sets state 279 // for the next text insertion 280 mPendingStylesToApplyToNewContent->PreserveStyles(aStylesToSet); 281 return NS_OK; 282 } 283 284 { 285 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 286 if (MOZ_UNLIKELY(result.isErr())) { 287 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 288 return result.unwrapErr(); 289 } 290 if (result.inspect().Canceled()) { 291 return NS_OK; 292 } 293 } 294 295 AutoPlaceholderBatch treatAsOneTransaction( 296 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 297 IgnoredErrorResult ignoredError; 298 AutoEditSubActionNotifier startToHandleEditSubAction( 299 *this, EditSubAction::eInsertElement, nsIEditor::eNext, ignoredError); 300 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 301 return ignoredError.StealNSResult(); 302 } 303 NS_WARNING_ASSERTION( 304 !ignoredError.Failed(), 305 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 306 307 // TODO: We don't need AutoTransactionsConserveSelection here in the normal 308 // cases, but removing this may cause the behavior with the legacy 309 // mutation event listeners. We should try to delete this in a bug. 310 AutoTransactionsConserveSelection dontChangeMySelection(*this); 311 312 AutoClonedSelectionRangeArray selectionRanges(SelectionRef()); 313 nsresult rv = SetInlinePropertiesAroundRanges(selectionRanges, aStylesToSet); 314 if (NS_FAILED(rv)) { 315 NS_WARNING("HTMLEditor::SetInlinePropertiesAroundRanges() failed"); 316 return rv; 317 } 318 MOZ_ASSERT(!selectionRanges.HasSavedRanges()); 319 rv = selectionRanges.ApplyTo(SelectionRef()); 320 if (NS_WARN_IF(Destroyed())) { 321 return NS_ERROR_EDITOR_DESTROYED; 322 } 323 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 324 "AutoClonedSelectionRangeArray::ApplyTo() failed"); 325 return rv; 326 } 327 328 template <size_t N> 329 nsresult HTMLEditor::SetInlinePropertiesAroundRanges( 330 AutoClonedRangeArray& aRanges, 331 const AutoTArray<EditorInlineStyleAndValue, N>& aStylesToSet) { 332 MOZ_ASSERT(!aRanges.HasSavedRanges()); 333 for (const EditorInlineStyleAndValue& styleToSet : aStylesToSet) { 334 AutoInlineStyleSetter inlineStyleSetter(styleToSet); 335 for (OwningNonNull<nsRange>& domRange : aRanges.Ranges()) { 336 inlineStyleSetter.Reset(); 337 auto rangeOrError = 338 [&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMRange, nsresult> { 339 EditorDOMRange range(domRange); 340 // If we're setting <font>, we want to remove ancestors which set 341 // `font-size` or <font size="..."> recursively. Therefore, for 342 // extending the ranges to contain all ancestors in the range, we need 343 // to split ancestors first. 344 // XXX: Blink and WebKit inserts <font> elements to inner most 345 // elements, however, we cannot do it under current design because 346 // once we contain ancestors which have `font-size` or are 347 // <font size="...">, we lost the original ranges which we wanted to 348 // apply the style. For fixing this, we need to manage both ranges, but 349 // it's too expensive especially we allow to run script when we touch 350 // the DOM tree. Additionally, font-size value affects the height 351 // of the element, but does not affect the height of ancestor inline 352 // elements. Therefore, following the behavior may cause similar issue 353 // as bug 1808906. So at least for now, we should not do this big work. 354 if (styleToSet.IsStyleOfFontElement()) { 355 Result<SplitRangeOffResult, nsresult> splitAncestorsResult = 356 SplitAncestorStyledInlineElementsAtRangeEdges( 357 range, styleToSet, SplitAtEdges::eDoNotCreateEmptyContainer); 358 if (MOZ_UNLIKELY(splitAncestorsResult.isErr())) { 359 NS_WARNING( 360 "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() " 361 "failed"); 362 return splitAncestorsResult.propagateErr(); 363 } 364 SplitRangeOffResult unwrappedResult = splitAncestorsResult.unwrap(); 365 unwrappedResult.IgnoreCaretPointSuggestion(); 366 range = unwrappedResult.RangeRef(); 367 if (NS_WARN_IF(!range.IsPositionedAndValid())) { 368 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 369 } 370 } 371 Result<EditorRawDOMRange, nsresult> rangeOrError = 372 inlineStyleSetter.ExtendOrShrinkRangeToApplyTheStyle(*this, range); 373 if (MOZ_UNLIKELY(rangeOrError.isErr())) { 374 NS_WARNING( 375 "HTMLEditor::ExtendOrShrinkRangeToApplyTheStyle() failed, but " 376 "ignored"); 377 return EditorDOMRange(); 378 } 379 return EditorDOMRange(rangeOrError.unwrap()); 380 }(); 381 if (MOZ_UNLIKELY(rangeOrError.isErr())) { 382 return rangeOrError.unwrapErr(); 383 } 384 385 const EditorDOMRange range = rangeOrError.unwrap(); 386 if (!range.IsPositioned()) { 387 continue; 388 } 389 390 // If the range is collapsed, we should insert new element there. 391 if (range.Collapsed()) { 392 Result<RefPtr<Text>, nsresult> emptyTextNodeOrError = 393 AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle( 394 *this, range.StartRef()); 395 if (MOZ_UNLIKELY(emptyTextNodeOrError.isErr())) { 396 NS_WARNING( 397 "AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle() " 398 "failed"); 399 return emptyTextNodeOrError.unwrapErr(); 400 } 401 if (MOZ_UNLIKELY(!emptyTextNodeOrError.inspect())) { 402 continue; // Couldn't insert text node there 403 } 404 RefPtr<Text> emptyTextNode = emptyTextNodeOrError.unwrap(); 405 Result<CaretPoint, nsresult> caretPointOrError = 406 inlineStyleSetter 407 .ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle( 408 *this, *emptyTextNode); 409 if (MOZ_UNLIKELY(caretPointOrError.isErr())) { 410 NS_WARNING( 411 "AutoInlineStyleSetter::" 412 "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed"); 413 return caretPointOrError.unwrapErr(); 414 } 415 DebugOnly<nsresult> rvIgnored = domRange->CollapseTo(emptyTextNode, 0); 416 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 417 "nsRange::CollapseTo() failed, but ignored"); 418 continue; 419 } 420 421 // Use const_cast hack here for preventing the others to update the range. 422 AutoTrackDOMRange trackRange(RangeUpdaterRef(), 423 const_cast<EditorDOMRange*>(&range)); 424 auto UpdateSelectionRange = [&]() MOZ_CAN_RUN_SCRIPT { 425 // If inlineStyleSetter creates elements or setting styles, we should 426 // select between start of first element and end of last element. 427 if (inlineStyleSetter.FirstHandledPointRef().IsInContentNode()) { 428 MOZ_ASSERT(inlineStyleSetter.LastHandledPointRef().IsInContentNode()); 429 const auto startPoint = 430 !inlineStyleSetter.FirstHandledPointRef().IsStartOfContainer() 431 ? inlineStyleSetter.FirstHandledPointRef() 432 .To<EditorRawDOMPoint>() 433 : HTMLEditUtils::GetDeepestEditableStartPointOf< 434 EditorRawDOMPoint>( 435 *inlineStyleSetter.FirstHandledPointRef() 436 .ContainerAs<nsIContent>(), 437 {EditablePointOption::RecognizeInvisibleWhiteSpaces, 438 EditablePointOption::StopAtComment}); 439 const auto endPoint = 440 !inlineStyleSetter.LastHandledPointRef().IsEndOfContainer() 441 ? inlineStyleSetter.LastHandledPointRef() 442 .To<EditorRawDOMPoint>() 443 : HTMLEditUtils::GetDeepestEditableEndPointOf< 444 EditorRawDOMPoint>( 445 *inlineStyleSetter.LastHandledPointRef() 446 .ContainerAs<nsIContent>(), 447 {EditablePointOption::RecognizeInvisibleWhiteSpaces, 448 EditablePointOption::StopAtComment}); 449 nsresult rv = domRange->SetStartAndEnd( 450 startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary()); 451 if (NS_SUCCEEDED(rv)) { 452 trackRange.StopTracking(); 453 return; 454 } 455 } 456 // Otherwise, use the range computed with the tracking original range. 457 trackRange.FlushAndStopTracking(); 458 domRange->SetStartAndEnd(range.StartRef().ToRawRangeBoundary(), 459 range.EndRef().ToRawRangeBoundary()); 460 }; 461 462 // If range is in a text node, apply new style simply. 463 if (range.InSameContainer() && range.StartRef().IsInTextNode()) { 464 // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`. 465 // MOZ_KnownLive(styleToSet.*) due to bug 1622253. 466 Result<SplitRangeOffFromNodeResult, nsresult> 467 wrapTextInStyledElementResult = 468 inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode( 469 *this, MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()), 470 range.StartRef().Offset(), range.EndRef().Offset()); 471 if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) { 472 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed"); 473 return wrapTextInStyledElementResult.unwrapErr(); 474 } 475 // The caller should handle the ranges as Selection if necessary, and we 476 // don't want to update aRanges with this result. 477 wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion(); 478 UpdateSelectionRange(); 479 continue; 480 } 481 482 // Collect editable nodes which are entirely contained in the range. 483 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContentsAroundRange; 484 { 485 ContentSubtreeIterator subtreeIter; 486 // If there is no node which is entirely in the range, 487 // `ContentSubtreeIterator::Init()` fails, but this is possible case, 488 // don't warn it. 489 if (NS_SUCCEEDED( 490 subtreeIter.Init(range.StartRef().ToRawRangeBoundary(), 491 range.EndRef().ToRawRangeBoundary()))) { 492 for (; !subtreeIter.IsDone(); subtreeIter.Next()) { 493 nsINode* node = subtreeIter.GetCurrentNode(); 494 if (NS_WARN_IF(!node)) { 495 return NS_ERROR_FAILURE; 496 } 497 if (MOZ_UNLIKELY(!node->IsContent())) { 498 continue; 499 } 500 // We don't need to wrap non-editable node in new inline element 501 // nor shouldn't modify `style` attribute of non-editable element. 502 if (!EditorUtils::IsEditableContent(*node->AsContent(), 503 EditorType::HTML)) { 504 continue; 505 } 506 // We shouldn't wrap invisible text node in new inline element. 507 if (node->IsText() && 508 !HTMLEditUtils::IsVisibleTextNode(*node->AsText())) { 509 continue; 510 } 511 arrayOfContentsAroundRange.AppendElement(*node->AsContent()); 512 } 513 } 514 } 515 516 // If start node is a text node, apply new style to a part of it. 517 if (range.StartRef().IsInTextNode() && 518 EditorUtils::IsEditableContent(*range.StartRef().ContainerAs<Text>(), 519 EditorType::HTML)) { 520 // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`. 521 // MOZ_KnownLive(styleToSet.*) due to bug 1622253. 522 Result<SplitRangeOffFromNodeResult, nsresult> 523 wrapTextInStyledElementResult = 524 inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode( 525 *this, MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()), 526 range.StartRef().Offset(), 527 range.StartRef().ContainerAs<Text>()->TextDataLength()); 528 if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) { 529 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed"); 530 return wrapTextInStyledElementResult.unwrapErr(); 531 } 532 // The caller should handle the ranges as Selection if necessary, and we 533 // don't want to update aRanges with this result. 534 wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion(); 535 } 536 537 // Then, apply new style to all nodes in the range entirely. 538 for (auto& content : arrayOfContentsAroundRange) { 539 // MOZ_KnownLive due to bug 1622253. 540 Result<CaretPoint, nsresult> pointToPutCaretOrError = 541 inlineStyleSetter 542 .ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle( 543 *this, MOZ_KnownLive(*content)); 544 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 545 NS_WARNING( 546 "AutoInlineStyleSetter::" 547 "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed"); 548 return pointToPutCaretOrError.unwrapErr(); 549 } 550 // The caller should handle the ranges as Selection if necessary, and we 551 // don't want to update aRanges with this result. 552 pointToPutCaretOrError.inspect().IgnoreCaretPointSuggestion(); 553 } 554 555 // Finally, if end node is a text node, apply new style to a part of it. 556 if (range.EndRef().IsInTextNode() && 557 EditorUtils::IsEditableContent(*range.EndRef().ContainerAs<Text>(), 558 EditorType::HTML)) { 559 // MOZ_KnownLive(...ContainerAs<Text>()) because of grabbed by `range`. 560 // MOZ_KnownLive(styleToSet.mAttribute) due to bug 1622253. 561 Result<SplitRangeOffFromNodeResult, nsresult> 562 wrapTextInStyledElementResult = 563 inlineStyleSetter.SplitTextNodeAndApplyStyleToMiddleNode( 564 *this, MOZ_KnownLive(*range.EndRef().ContainerAs<Text>()), 565 0, range.EndRef().Offset()); 566 if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) { 567 NS_WARNING("HTMLEditor::SetInlinePropertyOnTextNode() failed"); 568 return wrapTextInStyledElementResult.unwrapErr(); 569 } 570 // The caller should handle the ranges as Selection if necessary, and we 571 // don't want to update aRanges with this result. 572 wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion(); 573 } 574 UpdateSelectionRange(); 575 } 576 } 577 return NS_OK; 578 } 579 580 // static 581 Result<RefPtr<Text>, nsresult> 582 HTMLEditor::AutoInlineStyleSetter::GetEmptyTextNodeToApplyNewStyle( 583 HTMLEditor& aHTMLEditor, const EditorDOMPoint& aCandidatePointToInsert) { 584 auto pointToInsertNewText = 585 HTMLEditUtils::GetBetterCaretPositionToInsertText<EditorDOMPoint>( 586 aCandidatePointToInsert); 587 if (MOZ_UNLIKELY(!pointToInsertNewText.IsSet())) { 588 return RefPtr<Text>(); // cannot insert text there 589 } 590 auto pointToInsertNewStyleOrError = 591 [&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMPoint, nsresult> { 592 if (!pointToInsertNewText.IsInTextNode()) { 593 return pointToInsertNewText; 594 } 595 if (!pointToInsertNewText.ContainerAs<Text>()->TextDataLength()) { 596 return pointToInsertNewText; // Use it 597 } 598 if (pointToInsertNewText.IsStartOfContainer()) { 599 return pointToInsertNewText.ParentPoint(); 600 } 601 if (pointToInsertNewText.IsEndOfContainer()) { 602 return EditorDOMPoint::After(*pointToInsertNewText.ContainerAs<Text>()); 603 } 604 Result<SplitNodeResult, nsresult> splitTextNodeResult = 605 aHTMLEditor.SplitNodeWithTransaction(pointToInsertNewText); 606 if (MOZ_UNLIKELY(splitTextNodeResult.isErr())) { 607 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 608 return splitTextNodeResult.propagateErr(); 609 } 610 SplitNodeResult unwrappedSplitTextNodeResult = splitTextNodeResult.unwrap(); 611 unwrappedSplitTextNodeResult.IgnoreCaretPointSuggestion(); 612 return unwrappedSplitTextNodeResult.AtSplitPoint<EditorDOMPoint>(); 613 }(); 614 if (MOZ_UNLIKELY(pointToInsertNewStyleOrError.isErr())) { 615 return pointToInsertNewStyleOrError.propagateErr(); 616 } 617 618 // If we already have empty text node which is available for placeholder in 619 // new styled element, let's use it. 620 if (pointToInsertNewStyleOrError.inspect().IsInTextNode()) { 621 return RefPtr<Text>( 622 pointToInsertNewStyleOrError.inspect().ContainerAs<Text>()); 623 } 624 625 // Otherwise, we need an empty text node to create new inline style. 626 RefPtr<Text> newEmptyTextNode = aHTMLEditor.CreateTextNode(u""_ns); 627 if (MOZ_UNLIKELY(!newEmptyTextNode)) { 628 NS_WARNING("EditorBase::CreateTextNode() failed"); 629 return Err(NS_ERROR_FAILURE); 630 } 631 Result<CreateTextResult, nsresult> insertNewTextNodeResult = 632 aHTMLEditor.InsertNodeWithTransaction<Text>( 633 *newEmptyTextNode, pointToInsertNewStyleOrError.inspect()); 634 if (MOZ_UNLIKELY(insertNewTextNodeResult.isErr())) { 635 NS_WARNING("EditorBase::InsertNodeWithTransaction() failed"); 636 return insertNewTextNodeResult.propagateErr(); 637 } 638 insertNewTextNodeResult.inspect().IgnoreCaretPointSuggestion(); 639 return newEmptyTextNode; 640 } 641 642 Result<bool, nsresult> 643 HTMLEditor::AutoInlineStyleSetter::ElementIsGoodContainerForTheStyle( 644 HTMLEditor& aHTMLEditor, Element& aElement) const { 645 // If the editor is in the CSS mode and the style can be specified with CSS, 646 // we should not use existing HTML element as a new container. 647 const bool isCSSEditable = IsCSSSettable(aElement); 648 if (!aHTMLEditor.IsCSSEnabled() || !isCSSEditable) { 649 // First check for <b>, <i>, etc. 650 if (aElement.IsHTMLElement(&HTMLPropertyRef()) && 651 !HTMLEditUtils::ElementHasAttribute(aElement) && !mAttribute) { 652 return true; 653 } 654 655 // Now look for things like <font> 656 if (mAttribute) { 657 nsString attrValue; 658 if (aElement.IsHTMLElement(&HTMLPropertyRef()) && 659 !HTMLEditUtils::ElementHasAttributeExcept(aElement, *mAttribute) && 660 aElement.GetAttr(mAttribute, attrValue)) { 661 if (attrValue.Equals(mAttributeValue, 662 nsCaseInsensitiveStringComparator)) { 663 return true; 664 } 665 if (mAttribute == nsGkAtoms::color || 666 mAttribute == nsGkAtoms::bgcolor) { 667 if (aHTMLEditor.IsCSSEnabled()) { 668 if (HTMLEditUtils::IsSameCSSColorValue(mAttributeValue, 669 attrValue)) { 670 return true; 671 } 672 } else if (HTMLEditUtils::IsSameHTMLColorValue( 673 mAttributeValue, attrValue, 674 HTMLEditUtils::TransparentKeyword::Allowed)) { 675 return true; 676 } 677 } 678 } 679 } 680 681 if (!isCSSEditable) { 682 return false; 683 } 684 } 685 686 // No luck so far. Now we check for a <span> with a single style="" 687 // attribute that sets only the style we're looking for, if this type of 688 // style supports it 689 if (!aElement.IsHTMLElement(nsGkAtoms::span) || 690 !aElement.HasAttr(nsGkAtoms::style) || 691 HTMLEditUtils::ElementHasAttributeExcept(aElement, *nsGkAtoms::style)) { 692 return false; 693 } 694 695 nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement); 696 if (MOZ_UNLIKELY(!styledElement)) { 697 return false; 698 } 699 700 // Some CSS styles are not so simple. For instance, underline is 701 // "text-decoration: underline", which decomposes into four different text-* 702 // properties. So for now, we just create a span, add the desired style, and 703 // see if it matches. 704 RefPtr<Element> newSpanElement = 705 aHTMLEditor.CreateHTMLContent(nsGkAtoms::span); 706 if (MOZ_UNLIKELY(!newSpanElement)) { 707 NS_WARNING("EditorBase::CreateHTMLContent(nsGkAtoms::span) failed"); 708 return false; 709 } 710 nsStyledElement* styledNewSpanElement = 711 nsStyledElement::FromNode(newSpanElement); 712 if (MOZ_UNLIKELY(!styledNewSpanElement)) { 713 return false; 714 } 715 // MOZ_KnownLive(*styledNewSpanElement): It's newSpanElement whose type is 716 // RefPtr. 717 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle( 718 WithTransaction::No, aHTMLEditor, MOZ_KnownLive(*styledNewSpanElement), 719 *this, &mAttributeValue); 720 if (MOZ_UNLIKELY(result.isErr())) { 721 // The call shouldn't return destroyed error because it must be 722 // impossible to run script with modifying the new orphan node. 723 MOZ_ASSERT_UNREACHABLE("How did you destroy this editor?"); 724 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 725 return Err(NS_ERROR_EDITOR_DESTROYED); 726 } 727 return false; 728 } 729 return CSSEditUtils::DoStyledElementsHaveSameStyle(*styledNewSpanElement, 730 *styledElement); 731 } 732 733 bool HTMLEditor::AutoInlineStyleSetter::ElementIsGoodContainerToSetStyle( 734 nsStyledElement& aStyledElement) const { 735 if (!HTMLEditUtils::IsContainerNode(aStyledElement) || 736 !EditorUtils::IsEditableContent(aStyledElement, EditorType::HTML)) { 737 return false; 738 } 739 740 // If it has `style` attribute, let's use it. 741 if (aStyledElement.HasAttr(nsGkAtoms::style)) { 742 return true; 743 } 744 745 // If it has `class` or `id` attribute, the element may have specific rule. 746 // For applying the new style, we may need to set `style` attribute to it 747 // to override the specified rule. 748 if (aStyledElement.HasAttr(nsGkAtoms::id) || 749 aStyledElement.HasAttr(nsGkAtoms::_class)) { 750 return true; 751 } 752 753 // If we're setting text-decoration and the element represents a value of 754 // text-decoration, <ins> or <del>, let's use it. 755 if (IsStyleOfTextDecoration(IgnoreSElement::No) && 756 aStyledElement.IsAnyOfHTMLElements(nsGkAtoms::u, nsGkAtoms::s, 757 nsGkAtoms::strike, nsGkAtoms::ins, 758 nsGkAtoms::del)) { 759 return true; 760 } 761 762 // If we're setting font-size, color or background-color, we should use <font> 763 // for compatibility with the other browsers. 764 if (&HTMLPropertyRef() == nsGkAtoms::font && 765 aStyledElement.IsHTMLElement(nsGkAtoms::font)) { 766 return true; 767 } 768 769 // If the element has one or more <br> (even if it's invisible), we don't 770 // want to use the <span> for compatibility with the other browsers. 771 if (aStyledElement.QuerySelector("br"_ns, IgnoreErrors())) { 772 return false; 773 } 774 775 // NOTE: The following code does not match with the other browsers not 776 // completely. Blink considers this with relation with the range. 777 // However, we cannot do it now. We should fix here after or at 778 // fixing bug 1792386. 779 780 // If it's only visible element child of parent block, let's use it. 781 // E.g., we don't want to create new <span> when 782 // `<p>{ <span>abc</span> }</p>`. 783 if (aStyledElement.GetParentElement() && 784 HTMLEditUtils::IsBlockElement( 785 *aStyledElement.GetParentElement(), 786 BlockInlineCheck::UseComputedDisplayStyle)) { 787 for (nsIContent* previousSibling = aStyledElement.GetPreviousSibling(); 788 previousSibling; 789 previousSibling = previousSibling->GetPreviousSibling()) { 790 if (previousSibling->IsElement()) { 791 return false; // Assume any elements visible. 792 } 793 if (Text* text = Text::FromNode(previousSibling)) { 794 if (HTMLEditUtils::IsVisibleTextNode(*text)) { 795 return false; 796 } 797 continue; 798 } 799 } 800 for (nsIContent* nextSibling = aStyledElement.GetNextSibling(); nextSibling; 801 nextSibling = nextSibling->GetNextSibling()) { 802 if (nextSibling->IsElement()) { 803 if (!HTMLEditUtils::IsInvisibleBRElement(*nextSibling)) { 804 return false; 805 } 806 continue; // The invisible <br> element may be followed by a child 807 // block, let's continue to check it. 808 } 809 if (Text* text = Text::FromNode(nextSibling)) { 810 if (HTMLEditUtils::IsVisibleTextNode(*text)) { 811 return false; 812 } 813 continue; 814 } 815 } 816 return true; 817 } 818 819 // Otherwise, wrap it into new <span> for making 820 // `<span>[abc</span> <span>def]</span>` become 821 // `<span style="..."><span>abc</span> <span>def</span></span>` rather 822 // than `<span style="...">abc <span>def</span></span>`. 823 return false; 824 } 825 826 Result<SplitRangeOffFromNodeResult, nsresult> 827 HTMLEditor::AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode( 828 HTMLEditor& aHTMLEditor, Text& aText, uint32_t aStartOffset, 829 uint32_t aEndOffset) { 830 const RefPtr<Element> element = aText.GetParentElement(); 831 if (!element || !HTMLEditUtils::CanNodeContain(*element, HTMLPropertyRef())) { 832 OnHandled(EditorDOMPoint(&aText, aStartOffset), 833 EditorDOMPoint(&aText, aEndOffset)); 834 return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr); 835 } 836 837 // Don't need to do anything if no characters actually selected 838 if (aStartOffset == aEndOffset) { 839 OnHandled(EditorDOMPoint(&aText, aStartOffset), 840 EditorDOMPoint(&aText, aEndOffset)); 841 return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr); 842 } 843 844 // Don't need to do anything if property already set on node 845 if (IsCSSSettable(*element)) { 846 // The HTML styles defined by this have a CSS equivalence for node; 847 // let's check if it carries those CSS styles 848 nsAutoString value(mAttributeValue); 849 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError = 850 CSSEditUtils::IsComputedCSSEquivalentTo(aHTMLEditor, *element, *this, 851 value); 852 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) { 853 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed"); 854 return isComputedCSSEquivalentToStyleOrError.propagateErr(); 855 } 856 if (isComputedCSSEquivalentToStyleOrError.unwrap()) { 857 OnHandled(EditorDOMPoint(&aText, aStartOffset), 858 EditorDOMPoint(&aText, aEndOffset)); 859 return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr); 860 } 861 } else if (HTMLEditUtils::IsInlineStyleSetByElement(aText, *this, 862 &mAttributeValue)) { 863 OnHandled(EditorDOMPoint(&aText, aStartOffset), 864 EditorDOMPoint(&aText, aEndOffset)); 865 return SplitRangeOffFromNodeResult(nullptr, &aText, nullptr); 866 } 867 868 // Make the range an independent node. 869 auto splitAtEndResult = 870 [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> { 871 EditorDOMPoint atEnd(&aText, aEndOffset); 872 if (atEnd.IsEndOfContainer()) { 873 return SplitNodeResult::NotHandled(atEnd); 874 } 875 // We need to split off back of text node 876 Result<SplitNodeResult, nsresult> splitNodeResult = 877 aHTMLEditor.SplitNodeWithTransaction(atEnd); 878 if (splitNodeResult.isErr()) { 879 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 880 return splitNodeResult; 881 } 882 if (MOZ_UNLIKELY(!splitNodeResult.inspect().HasCaretPointSuggestion())) { 883 NS_WARNING( 884 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret " 885 "point"); 886 return Err(NS_ERROR_FAILURE); 887 } 888 return splitNodeResult; 889 }(); 890 if (MOZ_UNLIKELY(splitAtEndResult.isErr())) { 891 return splitAtEndResult.propagateErr(); 892 } 893 SplitNodeResult unwrappedSplitAtEndResult = splitAtEndResult.unwrap(); 894 EditorDOMPoint pointToPutCaret = unwrappedSplitAtEndResult.UnwrapCaretPoint(); 895 auto splitAtStartResult = 896 [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> { 897 EditorDOMPoint atStart(unwrappedSplitAtEndResult.DidSplit() 898 ? unwrappedSplitAtEndResult.GetPreviousContent() 899 : &aText, 900 aStartOffset); 901 if (atStart.IsStartOfContainer()) { 902 return SplitNodeResult::NotHandled(atStart); 903 } 904 // We need to split off front of text node 905 Result<SplitNodeResult, nsresult> splitNodeResult = 906 aHTMLEditor.SplitNodeWithTransaction(atStart); 907 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 908 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 909 return splitNodeResult; 910 } 911 if (MOZ_UNLIKELY(!splitNodeResult.inspect().HasCaretPointSuggestion())) { 912 NS_WARNING( 913 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret " 914 "point"); 915 return Err(NS_ERROR_FAILURE); 916 } 917 return splitNodeResult; 918 }(); 919 if (MOZ_UNLIKELY(splitAtStartResult.isErr())) { 920 return splitAtStartResult.propagateErr(); 921 } 922 SplitNodeResult unwrappedSplitAtStartResult = splitAtStartResult.unwrap(); 923 if (unwrappedSplitAtStartResult.HasCaretPointSuggestion()) { 924 pointToPutCaret = unwrappedSplitAtStartResult.UnwrapCaretPoint(); 925 } 926 927 MOZ_ASSERT_IF(unwrappedSplitAtStartResult.DidSplit(), 928 unwrappedSplitAtStartResult.GetPreviousContent()->IsText()); 929 MOZ_ASSERT_IF(unwrappedSplitAtStartResult.DidSplit(), 930 unwrappedSplitAtStartResult.GetNextContent()->IsText()); 931 MOZ_ASSERT_IF(unwrappedSplitAtEndResult.DidSplit(), 932 unwrappedSplitAtEndResult.GetPreviousContent()->IsText()); 933 MOZ_ASSERT_IF(unwrappedSplitAtEndResult.DidSplit(), 934 unwrappedSplitAtEndResult.GetNextContent()->IsText()); 935 // Note that those text nodes are grabbed by unwrappedSplitAtStartResult, 936 // unwrappedSplitAtEndResult or the callers. Therefore, we don't need to make 937 // them strong pointer. 938 Text* const leftTextNode = 939 unwrappedSplitAtStartResult.DidSplit() 940 ? unwrappedSplitAtStartResult.GetPreviousContentAs<Text>() 941 : nullptr; 942 Text* const middleTextNode = 943 unwrappedSplitAtStartResult.DidSplit() 944 ? unwrappedSplitAtStartResult.GetNextContentAs<Text>() 945 : (unwrappedSplitAtEndResult.DidSplit() 946 ? unwrappedSplitAtEndResult.GetPreviousContentAs<Text>() 947 : &aText); 948 Text* const rightTextNode = 949 unwrappedSplitAtEndResult.DidSplit() 950 ? unwrappedSplitAtEndResult.GetNextContentAs<Text>() 951 : nullptr; 952 if (mAttribute) { 953 // Look for siblings that are correct type of node 954 nsIContent* sibling = HTMLEditUtils::GetPreviousSibling( 955 *middleTextNode, {WalkTreeOption::IgnoreNonEditableNode}); 956 if (sibling && sibling->IsElement()) { 957 OwningNonNull<Element> element(*sibling->AsElement()); 958 Result<bool, nsresult> result = 959 ElementIsGoodContainerForTheStyle(aHTMLEditor, element); 960 if (MOZ_UNLIKELY(result.isErr())) { 961 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed"); 962 return result.propagateErr(); 963 } 964 if (result.inspect()) { 965 // Previous sib is already right kind of inline node; slide this over 966 Result<MoveNodeResult, nsresult> moveTextNodeResult = 967 aHTMLEditor.MoveNodeToEndWithTransaction( 968 MOZ_KnownLive(*middleTextNode), element); 969 if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) { 970 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 971 return moveTextNodeResult.propagateErr(); 972 } 973 MoveNodeResult unwrappedMoveTextNodeResult = 974 moveTextNodeResult.unwrap(); 975 unwrappedMoveTextNodeResult.MoveCaretPointTo( 976 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 977 OnHandled(*middleTextNode); 978 return SplitRangeOffFromNodeResult(leftTextNode, middleTextNode, 979 rightTextNode, 980 std::move(pointToPutCaret)); 981 } 982 } 983 sibling = HTMLEditUtils::GetNextSibling( 984 *middleTextNode, {WalkTreeOption::IgnoreNonEditableNode}); 985 if (sibling && sibling->IsElement()) { 986 OwningNonNull<Element> element(*sibling->AsElement()); 987 Result<bool, nsresult> result = 988 ElementIsGoodContainerForTheStyle(aHTMLEditor, element); 989 if (MOZ_UNLIKELY(result.isErr())) { 990 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed"); 991 return result.propagateErr(); 992 } 993 if (result.inspect()) { 994 // Following sib is already right kind of inline node; slide this over 995 Result<MoveNodeResult, nsresult> moveTextNodeResult = 996 aHTMLEditor.MoveNodeWithTransaction(MOZ_KnownLive(*middleTextNode), 997 EditorDOMPoint(sibling, 0u)); 998 if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) { 999 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 1000 return moveTextNodeResult.propagateErr(); 1001 } 1002 MoveNodeResult unwrappedMoveTextNodeResult = 1003 moveTextNodeResult.unwrap(); 1004 unwrappedMoveTextNodeResult.MoveCaretPointTo( 1005 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 1006 OnHandled(*middleTextNode); 1007 return SplitRangeOffFromNodeResult(leftTextNode, middleTextNode, 1008 rightTextNode, 1009 std::move(pointToPutCaret)); 1010 } 1011 } 1012 } 1013 1014 // Wrap the node inside inline node. 1015 Result<CaretPoint, nsresult> setStyleResult = 1016 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle( 1017 aHTMLEditor, MOZ_KnownLive(*middleTextNode)); 1018 if (MOZ_UNLIKELY(setStyleResult.isErr())) { 1019 NS_WARNING( 1020 "AutoInlineStyleSetter::" 1021 "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed"); 1022 return setStyleResult.propagateErr(); 1023 } 1024 return SplitRangeOffFromNodeResult( 1025 leftTextNode, middleTextNode, rightTextNode, 1026 setStyleResult.unwrap().UnwrapCaretPoint()); 1027 } 1028 1029 Result<CaretPoint, nsresult> HTMLEditor::AutoInlineStyleSetter::ApplyStyle( 1030 HTMLEditor& aHTMLEditor, nsIContent& aContent) { 1031 // If this is an element that can't be contained in a span, we have to 1032 // recurse to its children. 1033 if (!HTMLEditUtils::CanNodeContain(*nsGkAtoms::span, aContent)) { 1034 if (!aContent.HasChildren()) { 1035 return CaretPoint(EditorDOMPoint()); 1036 } 1037 1038 AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfContents; 1039 HTMLEditUtils::CollectChildren( 1040 aContent, arrayOfContents, 1041 {CollectChildrenOption::IgnoreNonEditableChildren, 1042 CollectChildrenOption::IgnoreInvisibleTextNodes}); 1043 1044 // Then loop through the list, set the property on each node. 1045 EditorDOMPoint pointToPutCaret; 1046 for (const OwningNonNull<nsIContent>& content : arrayOfContents) { 1047 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to 1048 // keep it alive. 1049 Result<CaretPoint, nsresult> setInlinePropertyResult = 1050 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle( 1051 aHTMLEditor, MOZ_KnownLive(content)); 1052 if (MOZ_UNLIKELY(setInlinePropertyResult.isErr())) { 1053 NS_WARNING( 1054 "AutoInlineStyleSetter::" 1055 "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed"); 1056 return setInlinePropertyResult; 1057 } 1058 setInlinePropertyResult.unwrap().MoveCaretPointTo( 1059 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 1060 } 1061 return CaretPoint(std::move(pointToPutCaret)); 1062 } 1063 1064 // First check if there's an adjacent sibling we can put our node into. 1065 nsCOMPtr<nsIContent> previousSibling = HTMLEditUtils::GetPreviousSibling( 1066 aContent, {WalkTreeOption::IgnoreNonEditableNode}); 1067 nsCOMPtr<nsIContent> nextSibling = HTMLEditUtils::GetNextSibling( 1068 aContent, {WalkTreeOption::IgnoreNonEditableNode}); 1069 if (RefPtr<Element> previousElement = 1070 Element::FromNodeOrNull(previousSibling)) { 1071 Result<bool, nsresult> canMoveIntoPreviousSibling = 1072 ElementIsGoodContainerForTheStyle(aHTMLEditor, *previousElement); 1073 if (MOZ_UNLIKELY(canMoveIntoPreviousSibling.isErr())) { 1074 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed"); 1075 return canMoveIntoPreviousSibling.propagateErr(); 1076 } 1077 if (canMoveIntoPreviousSibling.inspect()) { 1078 Result<MoveNodeResult, nsresult> moveNodeResult = 1079 aHTMLEditor.MoveNodeToEndWithTransaction(aContent, *previousSibling); 1080 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 1081 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 1082 return moveNodeResult.propagateErr(); 1083 } 1084 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 1085 RefPtr<Element> nextElement = Element::FromNodeOrNull(nextSibling); 1086 if (!nextElement) { 1087 OnHandled(aContent); 1088 return CaretPoint(unwrappedMoveNodeResult.UnwrapCaretPoint()); 1089 } 1090 Result<bool, nsresult> canMoveIntoNextSibling = 1091 ElementIsGoodContainerForTheStyle(aHTMLEditor, *nextElement); 1092 if (MOZ_UNLIKELY(canMoveIntoNextSibling.isErr())) { 1093 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed"); 1094 unwrappedMoveNodeResult.IgnoreCaretPointSuggestion(); 1095 return canMoveIntoNextSibling.propagateErr(); 1096 } 1097 if (!canMoveIntoNextSibling.inspect()) { 1098 OnHandled(aContent); 1099 return CaretPoint(unwrappedMoveNodeResult.UnwrapCaretPoint()); 1100 } 1101 unwrappedMoveNodeResult.IgnoreCaretPointSuggestion(); 1102 1103 // JoinNodesWithTransaction (DoJoinNodes) tries to collapse selection to 1104 // the joined point and we want to skip updating `Selection` here. 1105 AutoTransactionsConserveSelection dontChangeMySelection(aHTMLEditor); 1106 Result<JoinNodesResult, nsresult> joinNodesResult = 1107 aHTMLEditor.JoinNodesWithTransaction(*previousElement, *nextElement); 1108 if (MOZ_UNLIKELY(joinNodesResult.isErr())) { 1109 NS_WARNING("HTMLEditor::JoinNodesWithTransaction() failed"); 1110 return joinNodesResult.propagateErr(); 1111 } 1112 // So, let's take it. 1113 OnHandled(aContent); 1114 return CaretPoint( 1115 joinNodesResult.inspect().AtJoinedPoint<EditorDOMPoint>()); 1116 } 1117 } 1118 1119 if (RefPtr<Element> nextElement = Element::FromNodeOrNull(nextSibling)) { 1120 Result<bool, nsresult> canMoveIntoNextSibling = 1121 ElementIsGoodContainerForTheStyle(aHTMLEditor, *nextElement); 1122 if (MOZ_UNLIKELY(canMoveIntoNextSibling.isErr())) { 1123 NS_WARNING("HTMLEditor::ElementIsGoodContainerForTheStyle() failed"); 1124 return canMoveIntoNextSibling.propagateErr(); 1125 } 1126 if (canMoveIntoNextSibling.inspect()) { 1127 Result<MoveNodeResult, nsresult> moveNodeResult = 1128 aHTMLEditor.MoveNodeWithTransaction(aContent, 1129 EditorDOMPoint(nextElement, 0u)); 1130 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 1131 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 1132 return moveNodeResult.propagateErr(); 1133 } 1134 OnHandled(aContent); 1135 return CaretPoint(moveNodeResult.unwrap().UnwrapCaretPoint()); 1136 } 1137 } 1138 1139 // Don't need to do anything if property already set on node 1140 if (const RefPtr<Element> element = aContent.GetAsElementOrParentElement()) { 1141 if (IsCSSSettable(*element)) { 1142 nsAutoString value(mAttributeValue); 1143 // MOZ_KnownLive(element) because it's aContent. 1144 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError = 1145 CSSEditUtils::IsComputedCSSEquivalentTo(aHTMLEditor, *element, *this, 1146 value); 1147 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) { 1148 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed"); 1149 return isComputedCSSEquivalentToStyleOrError.propagateErr(); 1150 } 1151 if (isComputedCSSEquivalentToStyleOrError.unwrap()) { 1152 OnHandled(aContent); 1153 return CaretPoint(EditorDOMPoint()); 1154 } 1155 } else if (HTMLEditUtils::IsInlineStyleSetByElement(*element, *this, 1156 &mAttributeValue)) { 1157 OnHandled(aContent); 1158 return CaretPoint(EditorDOMPoint()); 1159 } 1160 } 1161 1162 auto ShouldUseCSS = [&]() { 1163 if (aHTMLEditor.IsCSSEnabled() && aContent.GetAsElementOrParentElement() && 1164 IsCSSSettable(*aContent.GetAsElementOrParentElement())) { 1165 return true; 1166 } 1167 // bgcolor is always done using CSS 1168 if (mAttribute == nsGkAtoms::bgcolor) { 1169 return true; 1170 } 1171 // called for removing parent style, we should use CSS with <span> element. 1172 if (IsStyleToInvert()) { 1173 return true; 1174 } 1175 // If we set color value, the value may be able to specified only with CSS. 1176 // In that case, we need to use CSS even in the HTML mode. 1177 if (mAttribute == nsGkAtoms::color) { 1178 return mAttributeValue.First() != '#' && 1179 !HTMLEditUtils::CanConvertToHTMLColorValue(mAttributeValue); 1180 } 1181 return false; 1182 }; 1183 1184 if (ShouldUseCSS()) { 1185 // We need special handlings for text-decoration. 1186 if (IsStyleOfTextDecoration(IgnoreSElement::No)) { 1187 Result<CaretPoint, nsresult> result = 1188 ApplyCSSTextDecoration(aHTMLEditor, aContent); 1189 NS_WARNING_ASSERTION( 1190 result.isOk(), 1191 "AutoInlineStyleSetter::ApplyCSSTextDecoration() failed"); 1192 return result; 1193 } 1194 EditorDOMPoint pointToPutCaret; 1195 RefPtr<nsStyledElement> styledElement = [&]() -> nsStyledElement* { 1196 auto* const styledElement = nsStyledElement::FromNode(&aContent); 1197 return styledElement && ElementIsGoodContainerToSetStyle(*styledElement) 1198 ? styledElement 1199 : nullptr; 1200 }(); 1201 1202 // If we don't have good element to set the style, let's create new <span>. 1203 if (!styledElement) { 1204 Result<CreateElementResult, nsresult> wrapInSpanElementResult = 1205 aHTMLEditor.InsertContainerWithTransaction(aContent, 1206 *nsGkAtoms::span); 1207 if (MOZ_UNLIKELY(wrapInSpanElementResult.isErr())) { 1208 NS_WARNING( 1209 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) " 1210 "failed"); 1211 return wrapInSpanElementResult.propagateErr(); 1212 } 1213 CreateElementResult unwrappedWrapInSpanElementResult = 1214 wrapInSpanElementResult.unwrap(); 1215 MOZ_ASSERT(unwrappedWrapInSpanElementResult.GetNewNode()); 1216 unwrappedWrapInSpanElementResult.MoveCaretPointTo( 1217 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 1218 styledElement = nsStyledElement::FromNode( 1219 unwrappedWrapInSpanElementResult.GetNewNode()); 1220 MOZ_ASSERT(styledElement); 1221 if (MOZ_UNLIKELY(!styledElement)) { 1222 // Don't return error to avoid creating new path to throwing error. 1223 OnHandled(aContent); 1224 return CaretPoint(pointToPutCaret); 1225 } 1226 } 1227 1228 // Add the CSS styles corresponding to the HTML style request 1229 if (IsCSSSettable(*styledElement)) { 1230 Result<size_t, nsresult> result = CSSEditUtils::SetCSSEquivalentToStyle( 1231 WithTransaction::Yes, aHTMLEditor, *styledElement, *this, 1232 &mAttributeValue); 1233 if (MOZ_UNLIKELY(result.isErr())) { 1234 if (NS_WARN_IF(result.inspectErr() == NS_ERROR_EDITOR_DESTROYED)) { 1235 return Err(NS_ERROR_EDITOR_DESTROYED); 1236 } 1237 NS_WARNING( 1238 "CSSEditUtils::SetCSSEquivalentToStyle() failed, but ignored"); 1239 } 1240 } 1241 OnHandled(aContent); 1242 return CaretPoint(pointToPutCaret); 1243 } 1244 1245 nsAutoString attributeValue(mAttributeValue); 1246 if (mAttribute == nsGkAtoms::color && mAttributeValue.First() != '#') { 1247 // At here, all color values should be able to be parsed as a CSS color 1248 // value. Therefore, we need to convert it to normalized HTML color value. 1249 HTMLEditUtils::ConvertToNormalizedHTMLColorValue(attributeValue, 1250 attributeValue); 1251 } 1252 1253 // is it already the right kind of node, but with wrong attribute? 1254 if (aContent.IsHTMLElement(&HTMLPropertyRef())) { 1255 if (NS_WARN_IF(!mAttribute)) { 1256 return Err(NS_ERROR_INVALID_ARG); 1257 } 1258 // Just set the attribute on it. 1259 nsresult rv = aHTMLEditor.SetAttributeWithTransaction( 1260 MOZ_KnownLive(*aContent.AsElement()), *mAttribute, attributeValue); 1261 if (NS_WARN_IF(aHTMLEditor.Destroyed())) { 1262 return Err(NS_ERROR_EDITOR_DESTROYED); 1263 } 1264 if (NS_FAILED(rv)) { 1265 NS_WARNING("EditorBase::SetAttributeWithTransaction() failed"); 1266 return Err(rv); 1267 } 1268 OnHandled(aContent); 1269 return CaretPoint(EditorDOMPoint()); 1270 } 1271 1272 // ok, chuck it in its very own container 1273 Result<CreateElementResult, nsresult> wrapWithNewElementToFormatResult = 1274 aHTMLEditor.InsertContainerWithTransaction( 1275 aContent, MOZ_KnownLive(HTMLPropertyRef()), 1276 !mAttribute ? HTMLEditor::DoNothingForNewElement 1277 // MOZ_CAN_RUN_SCRIPT_BOUNDARY due to bug 1758868 1278 : [&](HTMLEditor& aHTMLEditor, Element& aNewElement, 1279 const EditorDOMPoint&) MOZ_CAN_RUN_SCRIPT_BOUNDARY { 1280 nsresult rv = 1281 aNewElement.SetAttr(kNameSpaceID_None, mAttribute, 1282 attributeValue, false); 1283 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 1284 "Element::SetAttr() failed"); 1285 return rv; 1286 }); 1287 if (MOZ_UNLIKELY(wrapWithNewElementToFormatResult.isErr())) { 1288 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed"); 1289 return wrapWithNewElementToFormatResult.propagateErr(); 1290 } 1291 OnHandled(aContent); 1292 MOZ_ASSERT(wrapWithNewElementToFormatResult.inspect().GetNewNode()); 1293 return CaretPoint( 1294 wrapWithNewElementToFormatResult.unwrap().UnwrapCaretPoint()); 1295 } 1296 1297 Result<CaretPoint, nsresult> 1298 HTMLEditor::AutoInlineStyleSetter::ApplyCSSTextDecoration( 1299 HTMLEditor& aHTMLEditor, nsIContent& aContent) { 1300 MOZ_ASSERT(IsStyleOfTextDecoration(IgnoreSElement::No)); 1301 1302 EditorDOMPoint pointToPutCaret; 1303 RefPtr<nsStyledElement> styledElement = nsStyledElement::FromNode(aContent); 1304 nsAutoString newTextDecorationValue; 1305 if (&HTMLPropertyRef() == nsGkAtoms::u) { 1306 newTextDecorationValue.AssignLiteral(u"underline"); 1307 } else if (&HTMLPropertyRef() == nsGkAtoms::s || 1308 &HTMLPropertyRef() == nsGkAtoms::strike) { 1309 newTextDecorationValue.AssignLiteral(u"line-through"); 1310 } else { 1311 MOZ_ASSERT_UNREACHABLE( 1312 "Was new value added in " 1313 "IsStyleOfTextDecoration(IgnoreSElement::No))?"); 1314 } 1315 if (styledElement && IsCSSSettable(*styledElement) && 1316 ElementIsGoodContainerToSetStyle(*styledElement)) { 1317 nsAutoString textDecorationValue; 1318 nsresult rv = CSSEditUtils::GetSpecifiedProperty( 1319 *styledElement, *nsGkAtoms::text_decoration, textDecorationValue); 1320 if (NS_FAILED(rv)) { 1321 NS_WARNING( 1322 "CSSEditUtils::GetSpecifiedProperty(nsGkAtoms::text_decoration) " 1323 "failed"); 1324 return Err(rv); 1325 } 1326 // However, if the element is an element to style the text-decoration, 1327 // replace it with new <span>. 1328 if (styledElement && styledElement->IsAnyOfHTMLElements( 1329 nsGkAtoms::u, nsGkAtoms::s, nsGkAtoms::strike)) { 1330 Result<CreateElementResult, nsresult> replaceResult = 1331 aHTMLEditor.ReplaceContainerAndCloneAttributesWithTransaction( 1332 *styledElement, *nsGkAtoms::span); 1333 if (MOZ_UNLIKELY(replaceResult.isErr())) { 1334 NS_WARNING( 1335 "HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction() " 1336 "failed"); 1337 return replaceResult.propagateErr(); 1338 } 1339 CreateElementResult unwrappedReplaceResult = replaceResult.unwrap(); 1340 MOZ_ASSERT(unwrappedReplaceResult.GetNewNode()); 1341 unwrappedReplaceResult.MoveCaretPointTo( 1342 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 1343 // The new <span> needs to specify the original element's text-decoration 1344 // style unless it's specified explicitly. 1345 if (textDecorationValue.IsEmpty()) { 1346 if (!newTextDecorationValue.IsEmpty()) { 1347 newTextDecorationValue.Append(HTMLEditUtils::kSpace); 1348 } 1349 if (styledElement->IsHTMLElement(nsGkAtoms::u)) { 1350 newTextDecorationValue.AppendLiteral(u"underline"); 1351 } else { 1352 newTextDecorationValue.AppendLiteral(u"line-through"); 1353 } 1354 } 1355 styledElement = 1356 nsStyledElement::FromNode(unwrappedReplaceResult.GetNewNode()); 1357 if (NS_WARN_IF(!styledElement)) { 1358 OnHandled(aContent); 1359 return CaretPoint(pointToPutCaret); 1360 } 1361 } 1362 // If the element has default style, we need to keep it after specifying 1363 // text-decoration. 1364 else if (textDecorationValue.IsEmpty() && 1365 styledElement->IsAnyOfHTMLElements(nsGkAtoms::u, nsGkAtoms::ins)) { 1366 if (!newTextDecorationValue.IsEmpty()) { 1367 newTextDecorationValue.Append(HTMLEditUtils::kSpace); 1368 } 1369 newTextDecorationValue.AppendLiteral(u"underline"); 1370 } else if (textDecorationValue.IsEmpty() && 1371 styledElement->IsAnyOfHTMLElements( 1372 nsGkAtoms::s, nsGkAtoms::strike, nsGkAtoms::del)) { 1373 if (!newTextDecorationValue.IsEmpty()) { 1374 newTextDecorationValue.Append(HTMLEditUtils::kSpace); 1375 } 1376 newTextDecorationValue.AppendLiteral(u"line-through"); 1377 } 1378 } 1379 // Otherwise, use new <span> element. 1380 else { 1381 Result<CreateElementResult, nsresult> wrapInSpanElementResult = 1382 aHTMLEditor.InsertContainerWithTransaction(aContent, *nsGkAtoms::span); 1383 if (MOZ_UNLIKELY(wrapInSpanElementResult.isErr())) { 1384 NS_WARNING( 1385 "HTMLEditor::InsertContainerWithTransaction(nsGkAtoms::span) failed"); 1386 return wrapInSpanElementResult.propagateErr(); 1387 } 1388 CreateElementResult unwrappedWrapInSpanElementResult = 1389 wrapInSpanElementResult.unwrap(); 1390 MOZ_ASSERT(unwrappedWrapInSpanElementResult.GetNewNode()); 1391 unwrappedWrapInSpanElementResult.MoveCaretPointTo( 1392 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 1393 styledElement = nsStyledElement::FromNode( 1394 unwrappedWrapInSpanElementResult.GetNewNode()); 1395 if (NS_WARN_IF(!styledElement)) { 1396 OnHandled(aContent); 1397 return CaretPoint(pointToPutCaret); 1398 } 1399 } 1400 1401 nsresult rv = CSSEditUtils::SetCSSPropertyWithTransaction( 1402 aHTMLEditor, *styledElement, *nsGkAtoms::text_decoration, 1403 newTextDecorationValue); 1404 if (NS_FAILED(rv)) { 1405 NS_WARNING("CSSEditUtils::SetCSSPropertyWithTransaction() failed"); 1406 return Err(rv); 1407 } 1408 OnHandled(aContent); 1409 return CaretPoint(pointToPutCaret); 1410 } 1411 1412 Result<CaretPoint, nsresult> HTMLEditor::AutoInlineStyleSetter:: 1413 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(HTMLEditor& aHTMLEditor, 1414 nsIContent& aContent) { 1415 if (NS_WARN_IF(!aContent.GetParentNode())) { 1416 return Err(NS_ERROR_FAILURE); 1417 } 1418 OwningNonNull<nsINode> parent = *aContent.GetParentNode(); 1419 nsCOMPtr<nsIContent> previousSibling = aContent.GetPreviousSibling(), 1420 nextSibling = aContent.GetNextSibling(); 1421 EditorDOMPoint pointToPutCaret; 1422 if (aContent.IsElement()) { 1423 Result<EditorDOMPoint, nsresult> removeStyleResult = 1424 aHTMLEditor.RemoveStyleInside(MOZ_KnownLive(*aContent.AsElement()), 1425 *this, SpecifiedStyle::Preserve); 1426 if (MOZ_UNLIKELY(removeStyleResult.isErr())) { 1427 NS_WARNING("HTMLEditor::RemoveStyleInside() failed"); 1428 return removeStyleResult.propagateErr(); 1429 } 1430 if (removeStyleResult.inspect().IsSet()) { 1431 pointToPutCaret = removeStyleResult.unwrap(); 1432 } 1433 if (nsStaticAtom* similarElementNameAtom = GetSimilarElementNameAtom()) { 1434 Result<EditorDOMPoint, nsresult> removeStyleResult = 1435 aHTMLEditor.RemoveStyleInside( 1436 MOZ_KnownLive(*aContent.AsElement()), 1437 EditorInlineStyle(*similarElementNameAtom), 1438 SpecifiedStyle::Preserve); 1439 if (MOZ_UNLIKELY(removeStyleResult.isErr())) { 1440 NS_WARNING("HTMLEditor::RemoveStyleInside() failed"); 1441 return removeStyleResult.propagateErr(); 1442 } 1443 if (removeStyleResult.inspect().IsSet()) { 1444 pointToPutCaret = removeStyleResult.unwrap(); 1445 } 1446 } 1447 } 1448 1449 if (aContent.GetParentNode()) { 1450 // The node is still where it was 1451 Result<CaretPoint, nsresult> pointToPutCaretOrError = 1452 ApplyStyle(aHTMLEditor, aContent); 1453 NS_WARNING_ASSERTION(pointToPutCaretOrError.isOk(), 1454 "AutoInlineStyleSetter::ApplyStyle() failed"); 1455 return pointToPutCaretOrError; 1456 } 1457 1458 // It's vanished. Use the old siblings for reference to construct a 1459 // list. But first, verify that the previous/next siblings are still 1460 // where we expect them; otherwise we have to give up. 1461 if (NS_WARN_IF(previousSibling && 1462 previousSibling->GetParentNode() != parent) || 1463 NS_WARN_IF(nextSibling && nextSibling->GetParentNode() != parent) || 1464 NS_WARN_IF(!parent->IsInComposedDoc())) { 1465 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 1466 } 1467 AutoTArray<OwningNonNull<nsIContent>, 24> nodesToSet; 1468 for (nsIContent* content = previousSibling ? previousSibling->GetNextSibling() 1469 : parent->GetFirstChild(); 1470 content && content != nextSibling; content = content->GetNextSibling()) { 1471 if (EditorUtils::IsEditableContent(*content, EditorType::HTML)) { 1472 nodesToSet.AppendElement(*content); 1473 } 1474 } 1475 1476 for (OwningNonNull<nsIContent>& content : nodesToSet) { 1477 // MOZ_KnownLive because 'nodesToSet' is guaranteed to 1478 // keep it alive. 1479 Result<CaretPoint, nsresult> pointToPutCaretOrError = 1480 ApplyStyle(aHTMLEditor, MOZ_KnownLive(content)); 1481 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 1482 NS_WARNING("AutoInlineStyleSetter::ApplyStyle() failed"); 1483 return pointToPutCaretOrError; 1484 } 1485 pointToPutCaretOrError.unwrap().MoveCaretPointTo( 1486 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 1487 } 1488 1489 return CaretPoint(pointToPutCaret); 1490 } 1491 1492 bool HTMLEditor::AutoInlineStyleSetter::ContentIsElementSettingTheStyle( 1493 const HTMLEditor& aHTMLEditor, nsIContent& aContent) const { 1494 Element* const element = Element::FromNode(&aContent); 1495 if (!element) { 1496 return false; 1497 } 1498 if (IsRepresentedBy(*element)) { 1499 return true; 1500 } 1501 Result<bool, nsresult> specified = IsSpecifiedBy(aHTMLEditor, *element); 1502 NS_WARNING_ASSERTION(specified.isOk(), 1503 "EditorInlineStyle::IsSpecified() failed, but ignored"); 1504 return specified.unwrapOr(false); 1505 } 1506 1507 // static 1508 nsIContent* HTMLEditor::AutoInlineStyleSetter::GetNextEditableInlineContent( 1509 const nsIContent& aContent, const nsINode* aLimiter) { 1510 auto* const nextContentInRange = [&]() -> nsIContent* { 1511 for (nsIContent* parent : aContent.InclusiveAncestorsOfType<nsIContent>()) { 1512 if (parent == aLimiter || 1513 !EditorUtils::IsEditableContent(*parent, EditorType::HTML) || 1514 (parent->IsElement() && 1515 (HTMLEditUtils::IsBlockElement( 1516 *parent->AsElement(), 1517 BlockInlineCheck::UseComputedDisplayOutsideStyle) || 1518 HTMLEditUtils::IsDisplayInsideFlowRoot(*parent->AsElement())))) { 1519 return nullptr; 1520 } 1521 if (nsIContent* nextSibling = parent->GetNextSibling()) { 1522 return nextSibling; 1523 } 1524 } 1525 return nullptr; 1526 }(); 1527 return nextContentInRange && 1528 EditorUtils::IsEditableContent(*nextContentInRange, 1529 EditorType::HTML) && 1530 !HTMLEditUtils::IsBlockElement( 1531 *nextContentInRange, 1532 BlockInlineCheck::UseComputedDisplayOutsideStyle) 1533 ? nextContentInRange 1534 : nullptr; 1535 } 1536 1537 // static 1538 nsIContent* HTMLEditor::AutoInlineStyleSetter::GetPreviousEditableInlineContent( 1539 const nsIContent& aContent, const nsINode* aLimiter) { 1540 auto* const previousContentInRange = [&]() -> nsIContent* { 1541 for (nsIContent* parent : aContent.InclusiveAncestorsOfType<nsIContent>()) { 1542 if (parent == aLimiter || 1543 !EditorUtils::IsEditableContent(*parent, EditorType::HTML) || 1544 (parent->IsElement() && 1545 (HTMLEditUtils::IsBlockElement( 1546 *parent->AsElement(), 1547 BlockInlineCheck::UseComputedDisplayOutsideStyle) || 1548 HTMLEditUtils::IsDisplayInsideFlowRoot(*parent->AsElement())))) { 1549 return nullptr; 1550 } 1551 if (nsIContent* previousSibling = parent->GetPreviousSibling()) { 1552 return previousSibling; 1553 } 1554 } 1555 return nullptr; 1556 }(); 1557 return previousContentInRange && 1558 EditorUtils::IsEditableContent(*previousContentInRange, 1559 EditorType::HTML) && 1560 !HTMLEditUtils::IsBlockElement( 1561 *previousContentInRange, 1562 BlockInlineCheck::UseComputedDisplayOutsideStyle) 1563 ? previousContentInRange 1564 : nullptr; 1565 } 1566 1567 EditorRawDOMPoint HTMLEditor::AutoInlineStyleSetter::GetShrunkenRangeStart( 1568 const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange, 1569 const nsINode& aCommonAncestorOfRange, 1570 const nsIContent* aFirstEntirelySelectedContentNodeInRange) const { 1571 const EditorDOMPoint& startRef = aRange.StartRef(); 1572 // <a> cannot be nested and it should be represented with one element as far 1573 // as possible. Therefore, we don't need to shrink the range. 1574 if (IsStyleOfAnchorElement()) { 1575 return startRef.To<EditorRawDOMPoint>(); 1576 } 1577 // If the start boundary is at end of a node, we need to shrink the range 1578 // to next content, e.g., `abc[<b>def` should be `abc<b>[def` unless the 1579 // <b> is not entirely selected. 1580 auto* const nextContentOrStartContainer = [&]() -> nsIContent* { 1581 if (!startRef.IsInContentNode()) { 1582 return nullptr; 1583 } 1584 if (!startRef.IsEndOfContainer()) { 1585 return startRef.ContainerAs<nsIContent>(); 1586 } 1587 nsIContent* const nextContent = 1588 AutoInlineStyleSetter::GetNextEditableInlineContent( 1589 *startRef.ContainerAs<nsIContent>(), &aCommonAncestorOfRange); 1590 return nextContent ? nextContent : startRef.ContainerAs<nsIContent>(); 1591 }(); 1592 if (MOZ_UNLIKELY(!nextContentOrStartContainer)) { 1593 return startRef.To<EditorRawDOMPoint>(); 1594 } 1595 EditorRawDOMPoint startPoint = 1596 nextContentOrStartContainer != startRef.ContainerAs<nsIContent>() 1597 ? EditorRawDOMPoint(nextContentOrStartContainer) 1598 : startRef.To<EditorRawDOMPoint>(); 1599 MOZ_ASSERT(startPoint.IsSet()); 1600 // If the start point points a content node, let's try to move it down to 1601 // start of the child recursively. 1602 while (nsIContent* child = startPoint.GetChild()) { 1603 // We shouldn't cross editable and block boundary. 1604 if (!EditorUtils::IsEditableContent(*child, EditorType::HTML) || 1605 HTMLEditUtils::IsBlockElement( 1606 *child, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 1607 break; 1608 } 1609 // If we reach a text node, the minimized range starts from start of it. 1610 if (child->IsText()) { 1611 startPoint.Set(child, 0u); 1612 break; 1613 } 1614 // Don't shrink the range into element which applies the style to children 1615 // because we want to update the element. E.g., if we are setting 1616 // background color, we want to update style attribute of an element which 1617 // specifies background color with `style` attribute. 1618 if (child == aFirstEntirelySelectedContentNodeInRange) { 1619 break; 1620 } 1621 // We should not start from an atomic element such as <br>, <img>, etc. 1622 if (!HTMLEditUtils::IsContainerNode(*child)) { 1623 break; 1624 } 1625 // If the element specifies the style, we should update it. Therefore, we 1626 // need to wrap it in the range. 1627 if (ContentIsElementSettingTheStyle(aHTMLEditor, *child)) { 1628 break; 1629 } 1630 // If the child is an `<a>`, we should not shrink the range into it 1631 // because user may not want to keep editing in the link except when user 1632 // tries to update selection into it obviously. 1633 if (child->IsHTMLElement(nsGkAtoms::a)) { 1634 break; 1635 } 1636 startPoint.Set(child, 0u); 1637 } 1638 return startPoint; 1639 } 1640 1641 EditorRawDOMPoint HTMLEditor::AutoInlineStyleSetter::GetShrunkenRangeEnd( 1642 const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange, 1643 const nsINode& aCommonAncestorOfRange, 1644 const nsIContent* aLastEntirelySelectedContentNodeInRange) const { 1645 const EditorDOMPoint& endRef = aRange.EndRef(); 1646 // <a> cannot be nested and it should be represented with one element as far 1647 // as possible. Therefore, we don't need to shrink the range. 1648 if (IsStyleOfAnchorElement()) { 1649 return endRef.To<EditorRawDOMPoint>(); 1650 } 1651 // If the end boundary is at start of a node, we need to shrink the range 1652 // to previous content, e.g., `abc</b>]def` should be `abc]</b>def` unless 1653 // the <b> is not entirely selected. 1654 auto* const previousContentOrEndContainer = [&]() -> nsIContent* { 1655 if (!endRef.IsInContentNode()) { 1656 return nullptr; 1657 } 1658 if (!endRef.IsStartOfContainer()) { 1659 return endRef.ContainerAs<nsIContent>(); 1660 } 1661 nsIContent* const previousContent = 1662 AutoInlineStyleSetter::GetPreviousEditableInlineContent( 1663 *endRef.ContainerAs<nsIContent>(), &aCommonAncestorOfRange); 1664 return previousContent ? previousContent : endRef.ContainerAs<nsIContent>(); 1665 }(); 1666 if (MOZ_UNLIKELY(!previousContentOrEndContainer)) { 1667 return endRef.To<EditorRawDOMPoint>(); 1668 } 1669 EditorRawDOMPoint endPoint = 1670 previousContentOrEndContainer != endRef.ContainerAs<nsIContent>() 1671 ? EditorRawDOMPoint::After(*previousContentOrEndContainer) 1672 : endRef.To<EditorRawDOMPoint>(); 1673 MOZ_ASSERT(endPoint.IsSet()); 1674 // If the end point points after a content node, let's try to move it down 1675 // to end of the child recursively. 1676 while (nsIContent* child = endPoint.GetPreviousSiblingOfChild()) { 1677 // We shouldn't cross editable and block boundary. 1678 if (!EditorUtils::IsEditableContent(*child, EditorType::HTML) || 1679 HTMLEditUtils::IsBlockElement( 1680 *child, BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 1681 break; 1682 } 1683 // If we reach a text node, the minimized range starts from start of it. 1684 if (child->IsText()) { 1685 endPoint.SetToEndOf(child); 1686 break; 1687 } 1688 // Don't shrink the range into element which applies the style to children 1689 // because we want to update the element. E.g., if we are setting 1690 // background color, we want to update style attribute of an element which 1691 // specifies background color with `style` attribute. 1692 if (child == aLastEntirelySelectedContentNodeInRange) { 1693 break; 1694 } 1695 // We should not end in an atomic element such as <br>, <img>, etc. 1696 if (!HTMLEditUtils::IsContainerNode(*child)) { 1697 break; 1698 } 1699 // If the element specifies the style, we should update it. Therefore, we 1700 // need to wrap it in the range. 1701 if (ContentIsElementSettingTheStyle(aHTMLEditor, *child)) { 1702 break; 1703 } 1704 // If the child is an `<a>`, we should not shrink the range into it 1705 // because user may not want to keep editing in the link except when user 1706 // tries to update selection into it obviously. 1707 if (child->IsHTMLElement(nsGkAtoms::a)) { 1708 break; 1709 } 1710 endPoint.SetToEndOf(child); 1711 } 1712 return endPoint; 1713 } 1714 1715 EditorRawDOMPoint HTMLEditor::AutoInlineStyleSetter:: 1716 GetExtendedRangeStartToWrapAncestorApplyingSameStyle( 1717 const HTMLEditor& aHTMLEditor, 1718 const EditorRawDOMPoint& aStartPoint) const { 1719 MOZ_ASSERT(aStartPoint.IsSetAndValid()); 1720 1721 EditorRawDOMPoint startPoint = aStartPoint; 1722 // FIXME: This should handle ignore invisible white-spaces before the position 1723 // if it's in a text node or invisible white-spaces. 1724 if (!startPoint.IsStartOfContainer() || 1725 startPoint.GetContainer()->GetPreviousSibling()) { 1726 return startPoint; 1727 } 1728 1729 // FYI: Currently, we don't support setting `font-size` even in the CSS mode. 1730 // Therefore, if the style is <font size="...">, we always set a <font>. 1731 const bool isSettingFontElement = 1732 IsStyleOfFontSize() || 1733 (!aHTMLEditor.IsCSSEnabled() && IsStyleOfFontElement()); 1734 Element* mostDistantStartParentHavingStyle = nullptr; 1735 for (Element* parent : 1736 startPoint.GetContainer()->InclusiveAncestorsOfType<Element>()) { 1737 if (!EditorUtils::IsEditableContent(*parent, EditorType::HTML) || 1738 HTMLEditUtils::IsBlockElement( 1739 *parent, BlockInlineCheck::UseComputedDisplayOutsideStyle) || 1740 HTMLEditUtils::IsDisplayInsideFlowRoot(*parent)) { 1741 break; 1742 } 1743 if (ContentIsElementSettingTheStyle(aHTMLEditor, *parent)) { 1744 mostDistantStartParentHavingStyle = parent; 1745 } 1746 // If we're setting <font> element and there is a <font> element which is 1747 // entirely selected, we should use it. 1748 else if (isSettingFontElement && parent->IsHTMLElement(nsGkAtoms::font)) { 1749 mostDistantStartParentHavingStyle = parent; 1750 } 1751 if (parent->GetPreviousSibling()) { 1752 break; // The parent is not first element in its parent, stop climbing. 1753 } 1754 } 1755 if (mostDistantStartParentHavingStyle) { 1756 startPoint.Set(mostDistantStartParentHavingStyle); 1757 } 1758 return startPoint; 1759 } 1760 1761 EditorRawDOMPoint HTMLEditor::AutoInlineStyleSetter:: 1762 GetExtendedRangeEndToWrapAncestorApplyingSameStyle( 1763 const HTMLEditor& aHTMLEditor, 1764 const EditorRawDOMPoint& aEndPoint) const { 1765 MOZ_ASSERT(aEndPoint.IsSetAndValid()); 1766 1767 EditorRawDOMPoint endPoint = aEndPoint; 1768 // FIXME: This should ignore invisible white-spaces after the position if it's 1769 // in a text node, invisible <br> or following invisible text nodes. 1770 if (!endPoint.IsEndOfContainer() || 1771 endPoint.GetContainer()->GetNextSibling()) { 1772 return endPoint; 1773 } 1774 1775 // FYI: Currently, we don't support setting `font-size` even in the CSS mode. 1776 // Therefore, if the style is <font size="...">, we always set a <font>. 1777 const bool isSettingFontElement = 1778 IsStyleOfFontSize() || 1779 (!aHTMLEditor.IsCSSEnabled() && IsStyleOfFontElement()); 1780 Element* mostDistantEndParentHavingStyle = nullptr; 1781 for (Element* parent : 1782 endPoint.GetContainer()->InclusiveAncestorsOfType<Element>()) { 1783 if (!EditorUtils::IsEditableContent(*parent, EditorType::HTML) || 1784 HTMLEditUtils::IsBlockElement( 1785 *parent, BlockInlineCheck::UseComputedDisplayOutsideStyle) || 1786 HTMLEditUtils::IsDisplayInsideFlowRoot(*parent)) { 1787 break; 1788 } 1789 if (ContentIsElementSettingTheStyle(aHTMLEditor, *parent)) { 1790 mostDistantEndParentHavingStyle = parent; 1791 } 1792 // If we're setting <font> element and there is a <font> element which is 1793 // entirely selected, we should use it. 1794 else if (isSettingFontElement && parent->IsHTMLElement(nsGkAtoms::font)) { 1795 mostDistantEndParentHavingStyle = parent; 1796 } 1797 if (parent->GetNextSibling()) { 1798 break; // The parent is not last element in its parent, stop climbing. 1799 } 1800 } 1801 if (mostDistantEndParentHavingStyle) { 1802 endPoint.SetAfter(mostDistantEndParentHavingStyle); 1803 } 1804 return endPoint; 1805 } 1806 1807 EditorRawDOMRange HTMLEditor::AutoInlineStyleSetter:: 1808 GetExtendedRangeToMinimizeTheNumberOfNewElements( 1809 const HTMLEditor& aHTMLEditor, const nsINode& aCommonAncestor, 1810 EditorRawDOMPoint&& aStartPoint, EditorRawDOMPoint&& aEndPoint) const { 1811 MOZ_ASSERT(aStartPoint.IsSet()); 1812 MOZ_ASSERT(aEndPoint.IsSet()); 1813 1814 // For minimizing the number of new elements, we should extend the range as 1815 // far as possible. E.g., `<span>[abc</span> <span>def]</span>` should be 1816 // styled as `<b><span>abc</span> <span>def</span></b>`. 1817 // Similarly, if the range crosses a block boundary, we should do same thing. 1818 // I.e., `<p><span>[abc</span></p><p><span>def]</span></p>` should become 1819 // `<p><b><span>abc</span></b></p><p><b><span>def</span></b></p>`. 1820 if (aStartPoint.GetContainer() != aEndPoint.GetContainer()) { 1821 while (aStartPoint.GetContainer() != &aCommonAncestor && 1822 aStartPoint.IsInContentNode() && aStartPoint.GetContainerParent() && 1823 aStartPoint.IsStartOfContainer()) { 1824 if (!EditorUtils::IsEditableContent( 1825 *aStartPoint.ContainerAs<nsIContent>(), EditorType::HTML) || 1826 (aStartPoint.ContainerAs<nsIContent>()->IsElement() && 1827 (HTMLEditUtils::IsBlockElement( 1828 *aStartPoint.ContainerAs<Element>(), 1829 BlockInlineCheck::UseComputedDisplayOutsideStyle) || 1830 HTMLEditUtils::IsDisplayInsideFlowRoot( 1831 *aStartPoint.ContainerAs<Element>())))) { 1832 break; 1833 } 1834 aStartPoint = aStartPoint.ParentPoint(); 1835 } 1836 while (aEndPoint.GetContainer() != &aCommonAncestor && 1837 aEndPoint.IsInContentNode() && aEndPoint.GetContainerParent() && 1838 aEndPoint.IsEndOfContainer()) { 1839 if (!EditorUtils::IsEditableContent(*aEndPoint.ContainerAs<nsIContent>(), 1840 EditorType::HTML) || 1841 (aEndPoint.ContainerAs<nsIContent>()->IsElement() && 1842 (HTMLEditUtils::IsBlockElement( 1843 *aEndPoint.ContainerAs<Element>(), 1844 BlockInlineCheck::UseComputedDisplayOutsideStyle) || 1845 HTMLEditUtils::IsDisplayInsideFlowRoot( 1846 *aEndPoint.ContainerAs<Element>())))) { 1847 break; 1848 } 1849 aEndPoint.SetAfter(aEndPoint.ContainerAs<nsIContent>()); 1850 } 1851 } 1852 1853 // Additionally, if we'll set a CSS style, we want to wrap elements which 1854 // should have the new style into the range to avoid creating new <span> 1855 // element. 1856 if (!IsRepresentableWithHTML() || 1857 (aHTMLEditor.IsCSSEnabled() && IsCSSSettable(*nsGkAtoms::span))) { 1858 // First, if pointing in a text node, use parent point. 1859 if (aStartPoint.IsInContentNode() && aStartPoint.IsStartOfContainer() && 1860 aStartPoint.GetContainerParentAs<nsIContent>() && 1861 EditorUtils::IsEditableContent( 1862 *aStartPoint.ContainerParentAs<nsIContent>(), EditorType::HTML) && 1863 (!aStartPoint.GetContainerAs<Element>() || 1864 !HTMLEditUtils::IsContainerNode( 1865 *aStartPoint.ContainerAs<nsIContent>())) && 1866 EditorUtils::IsEditableContent(*aStartPoint.ContainerAs<nsIContent>(), 1867 EditorType::HTML)) { 1868 aStartPoint = aStartPoint.ParentPoint(); 1869 MOZ_ASSERT(aStartPoint.IsSet()); 1870 } 1871 if (aEndPoint.IsInContentNode() && aEndPoint.IsEndOfContainer() && 1872 aEndPoint.GetContainerParentAs<nsIContent>() && 1873 EditorUtils::IsEditableContent( 1874 *aEndPoint.ContainerParentAs<nsIContent>(), EditorType::HTML) && 1875 (!aEndPoint.GetContainerAs<Element>() || 1876 !HTMLEditUtils::IsContainerNode( 1877 *aEndPoint.ContainerAs<nsIContent>())) && 1878 EditorUtils::IsEditableContent(*aEndPoint.ContainerAs<nsIContent>(), 1879 EditorType::HTML)) { 1880 aEndPoint.SetAfter(aEndPoint.GetContainer()); 1881 MOZ_ASSERT(aEndPoint.IsSet()); 1882 } 1883 // Then, wrap the container if it's a good element to set a CSS property. 1884 if (aStartPoint.IsInContentNode() && aStartPoint.GetContainerParent() && 1885 // The point must be start of the container 1886 aStartPoint.IsStartOfContainer() && 1887 // only if the pointing first child node cannot have `style` attribute 1888 (!aStartPoint.GetChildAs<nsStyledElement>() || 1889 !ElementIsGoodContainerToSetStyle( 1890 *aStartPoint.ChildAs<nsStyledElement>())) && 1891 // but don't cross block boundary at climbing up the tree 1892 !HTMLEditUtils::IsBlockElement( 1893 *aStartPoint.ContainerAs<nsIContent>(), 1894 BlockInlineCheck::UseComputedDisplayOutsideStyle) && 1895 // and the container is a good editable element to set CSS style 1896 aStartPoint.GetContainerAs<nsStyledElement>() && 1897 ElementIsGoodContainerToSetStyle( 1898 *aStartPoint.ContainerAs<nsStyledElement>())) { 1899 aStartPoint = aStartPoint.ParentPoint(); 1900 MOZ_ASSERT(aStartPoint.IsSet()); 1901 } 1902 if (aEndPoint.IsInContentNode() && aEndPoint.GetContainerParent() && 1903 // The point must be end of the container 1904 aEndPoint.IsEndOfContainer() && 1905 // only if the pointing last child node cannot have `style` attribute 1906 (aEndPoint.IsStartOfContainer() || 1907 !aEndPoint.GetPreviousSiblingOfChildAs<nsStyledElement>() || 1908 !ElementIsGoodContainerToSetStyle( 1909 *aEndPoint.GetPreviousSiblingOfChildAs<nsStyledElement>())) && 1910 // but don't cross block boundary at climbing up the tree 1911 !HTMLEditUtils::IsBlockElement( 1912 *aEndPoint.ContainerAs<nsIContent>(), 1913 BlockInlineCheck::UseComputedDisplayOutsideStyle) && 1914 // and the container is a good editable element to set CSS style 1915 aEndPoint.GetContainerAs<nsStyledElement>() && 1916 ElementIsGoodContainerToSetStyle( 1917 *aEndPoint.ContainerAs<nsStyledElement>())) { 1918 aEndPoint.SetAfter(aEndPoint.GetContainer()); 1919 MOZ_ASSERT(aEndPoint.IsSet()); 1920 } 1921 } 1922 1923 return EditorRawDOMRange(std::move(aStartPoint), std::move(aEndPoint)); 1924 } 1925 1926 Result<EditorRawDOMRange, nsresult> 1927 HTMLEditor::AutoInlineStyleSetter::ExtendOrShrinkRangeToApplyTheStyle( 1928 const HTMLEditor& aHTMLEditor, const EditorDOMRange& aRange) const { 1929 if (NS_WARN_IF(!aRange.IsPositioned())) { 1930 return Err(NS_ERROR_FAILURE); 1931 } 1932 1933 // For avoiding assertion hits in the utility methods, check whether the 1934 // range is in same subtree, first. Even if the range crosses a subtree 1935 // boundary, it's not a bug of this module. 1936 nsINode* commonAncestor = aRange.GetClosestCommonInclusiveAncestor(); 1937 if (NS_WARN_IF(!commonAncestor)) { 1938 return Err(NS_ERROR_FAILURE); 1939 } 1940 1941 // If the range does not select only invisible <br> element, let's extend the 1942 // range to contain the <br> element. 1943 EditorDOMRange range(aRange); 1944 if (range.EndRef().IsInContentNode()) { 1945 const WSScanResult nextContentData = 1946 WSRunScanner::ScanInclusiveNextVisibleNodeOrBlockBoundary( 1947 {WSRunScanner::Option::OnlyEditableNodes}, range.EndRef()); 1948 if (nextContentData.ReachedInvisibleBRElement() && 1949 nextContentData.BRElementPtr()->GetParentElement() && 1950 HTMLEditUtils::IsInlineContent( 1951 *nextContentData.BRElementPtr()->GetParentElement(), 1952 BlockInlineCheck::UseComputedDisplayOutsideStyle)) { 1953 range.SetEnd(EditorDOMPoint::After(*nextContentData.BRElementPtr())); 1954 MOZ_ASSERT(range.EndRef().IsSet()); 1955 commonAncestor = range.GetClosestCommonInclusiveAncestor(); 1956 if (NS_WARN_IF(!commonAncestor)) { 1957 return Err(NS_ERROR_FAILURE); 1958 } 1959 } 1960 } 1961 1962 // If the range is collapsed, we don't want to replace ancestors unless it's 1963 // in an empty element. 1964 if (range.Collapsed() && range.StartRef().GetContainer()->Length()) { 1965 return EditorRawDOMRange(range); 1966 } 1967 1968 // First, shrink the given range to minimize new style applied contents. 1969 // However, we should not shrink the range into entirely selected element. 1970 // E.g., if `abc[<i>def</i>]ghi`, shouldn't shrink it as 1971 // `abc<i>[def]</i>ghi`. 1972 EditorRawDOMPoint startPoint, endPoint; 1973 if (range.Collapsed()) { 1974 startPoint = endPoint = range.StartRef().To<EditorRawDOMPoint>(); 1975 } else { 1976 ContentSubtreeIterator iter; 1977 if (NS_FAILED(iter.Init(range.StartRef().ToRawRangeBoundary(), 1978 range.EndRef().ToRawRangeBoundary()))) { 1979 NS_WARNING("ContentSubtreeIterator::Init() failed"); 1980 return Err(NS_ERROR_FAILURE); 1981 } 1982 nsIContent* const firstContentEntirelyInRange = 1983 nsIContent::FromNodeOrNull(iter.GetCurrentNode()); 1984 nsIContent* const lastContentEntirelyInRange = [&]() { 1985 iter.Last(); 1986 return nsIContent::FromNodeOrNull(iter.GetCurrentNode()); 1987 }(); 1988 1989 // Compute the shrunken range boundaries. 1990 startPoint = GetShrunkenRangeStart(aHTMLEditor, range, *commonAncestor, 1991 firstContentEntirelyInRange); 1992 MOZ_ASSERT(startPoint.IsSet()); 1993 endPoint = GetShrunkenRangeEnd(aHTMLEditor, range, *commonAncestor, 1994 lastContentEntirelyInRange); 1995 MOZ_ASSERT(endPoint.IsSet()); 1996 1997 // If shrunken range is swapped, it could like this case: 1998 // `abc[</span><span>]def`, starts at very end of a node and ends at 1999 // very start of immediately next node. In this case, we should use 2000 // the original range instead. 2001 if (MOZ_UNLIKELY(!startPoint.EqualsOrIsBefore(endPoint))) { 2002 startPoint = range.StartRef().To<EditorRawDOMPoint>(); 2003 endPoint = range.EndRef().To<EditorRawDOMPoint>(); 2004 } 2005 } 2006 2007 // Then, we may need to extend the range to wrap parent inline elements 2008 // which specify same style since we need to remove same style elements to 2009 // apply new value. E.g., abc 2010 // <span style="background-color: red"> 2011 // <span style="background-color: blue">[def]</span> 2012 // </span> 2013 // ghi 2014 // In this case, we need to wrap the other <span> element if setting 2015 // background color. Then, the inner <span> element is removed and the 2016 // other <span> element's style attribute will be updated rather than 2017 // inserting new <span> element. 2018 startPoint = GetExtendedRangeStartToWrapAncestorApplyingSameStyle(aHTMLEditor, 2019 startPoint); 2020 MOZ_ASSERT(startPoint.IsSet()); 2021 endPoint = 2022 GetExtendedRangeEndToWrapAncestorApplyingSameStyle(aHTMLEditor, endPoint); 2023 MOZ_ASSERT(endPoint.IsSet()); 2024 2025 // Finally, we need to extend the range unless the range is in an element to 2026 // reduce the number of creating new elements. E.g., if now selects 2027 // `<span>[abc</span><span>def]</span>`, we should make it 2028 // `<b><span>abc</span><span>def</span></b>` rather than 2029 // `<span><b>abc</b></span><span><b>def</b></span>`. 2030 EditorRawDOMRange finalRange = 2031 GetExtendedRangeToMinimizeTheNumberOfNewElements( 2032 aHTMLEditor, *commonAncestor, std::move(startPoint), 2033 std::move(endPoint)); 2034 #if 0 2035 fprintf(stderr, 2036 "ExtendOrShrinkRangeToApplyTheStyle:\n" 2037 " Result: {(\n %s\n ) - (\n %s\n )},\n" 2038 " Input: {(\n %s\n ) - (\n %s\n )}\n", 2039 ToString(finalRange.StartRef()).c_str(), 2040 ToString(finalRange.EndRef()).c_str(), 2041 ToString(aRange.StartRef()).c_str(), 2042 ToString(aRange.EndRef()).c_str()); 2043 #endif 2044 return finalRange; 2045 } 2046 2047 Result<SplitRangeOffResult, nsresult> 2048 HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges( 2049 const EditorDOMRange& aRange, const EditorInlineStyle& aStyle, 2050 SplitAtEdges aSplitAtEdges) { 2051 MOZ_ASSERT(IsEditActionDataAvailable()); 2052 2053 if (NS_WARN_IF(!aRange.IsPositioned())) { 2054 return Err(NS_ERROR_FAILURE); 2055 } 2056 2057 EditorDOMRange range(aRange); 2058 2059 // split any matching style nodes above the start of range 2060 auto resultAtStart = 2061 [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> { 2062 AutoTrackDOMRange tracker(RangeUpdaterRef(), &range); 2063 Result<SplitNodeResult, nsresult> result = 2064 SplitAncestorStyledInlineElementsAt(range.StartRef(), aStyle, 2065 aSplitAtEdges); 2066 if (MOZ_UNLIKELY(result.isErr())) { 2067 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed"); 2068 return result; 2069 } 2070 tracker.FlushAndStopTracking(); 2071 if (result.inspect().Handled()) { 2072 auto startOfRange = result.inspect().AtSplitPoint<EditorDOMPoint>(); 2073 if (!startOfRange.IsSet()) { 2074 result.inspect().IgnoreCaretPointSuggestion(); 2075 NS_WARNING( 2076 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return " 2077 "split point"); 2078 return Err(NS_ERROR_FAILURE); 2079 } 2080 range.SetStart(std::move(startOfRange)); 2081 } else if (MOZ_UNLIKELY(!range.IsPositioned())) { 2082 NS_WARNING( 2083 "HTMLEditor::SplitAncestorStyledInlineElementsAt() caused unexpected " 2084 "DOM tree"); 2085 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2086 } 2087 return result; 2088 }(); 2089 if (MOZ_UNLIKELY(resultAtStart.isErr())) { 2090 return resultAtStart.propagateErr(); 2091 } 2092 SplitNodeResult unwrappedResultAtStart = resultAtStart.unwrap(); 2093 2094 // second verse, same as the first... 2095 auto resultAtEnd = 2096 [&]() MOZ_CAN_RUN_SCRIPT -> Result<SplitNodeResult, nsresult> { 2097 AutoTrackDOMRange tracker(RangeUpdaterRef(), &range); 2098 Result<SplitNodeResult, nsresult> result = 2099 SplitAncestorStyledInlineElementsAt(range.EndRef(), aStyle, 2100 aSplitAtEdges); 2101 if (MOZ_UNLIKELY(result.isErr())) { 2102 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed"); 2103 return result; 2104 } 2105 tracker.FlushAndStopTracking(); 2106 if (result.inspect().Handled()) { 2107 auto endOfRange = result.inspect().AtSplitPoint<EditorDOMPoint>(); 2108 if (!endOfRange.IsSet()) { 2109 result.inspect().IgnoreCaretPointSuggestion(); 2110 NS_WARNING( 2111 "HTMLEditor::SplitAncestorStyledInlineElementsAt() didn't return " 2112 "split point"); 2113 return Err(NS_ERROR_FAILURE); 2114 } 2115 range.SetEnd(std::move(endOfRange)); 2116 } else if (MOZ_UNLIKELY(!range.IsPositioned())) { 2117 NS_WARNING( 2118 "HTMLEditor::SplitAncestorStyledInlineElementsAt() caused unexpected " 2119 "DOM tree"); 2120 return Err(NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE); 2121 } 2122 return result; 2123 }(); 2124 if (MOZ_UNLIKELY(resultAtEnd.isErr())) { 2125 unwrappedResultAtStart.IgnoreCaretPointSuggestion(); 2126 return resultAtEnd.propagateErr(); 2127 } 2128 2129 return SplitRangeOffResult(std::move(range), 2130 std::move(unwrappedResultAtStart), 2131 resultAtEnd.unwrap()); 2132 } 2133 2134 Result<SplitNodeResult, nsresult> 2135 HTMLEditor::SplitAncestorStyledInlineElementsAt( 2136 const EditorDOMPoint& aPointToSplit, const EditorInlineStyle& aStyle, 2137 SplitAtEdges aSplitAtEdges) { 2138 // If the point is in a non-content node, e.g., in the document node, we 2139 // should split nothing. 2140 if (MOZ_UNLIKELY(!aPointToSplit.IsInContentNode())) { 2141 return SplitNodeResult::NotHandled(aPointToSplit); 2142 } 2143 2144 // We assume that this method is called only when we're removing style(s). 2145 // Even if we're in HTML mode and there is no presentation element in the 2146 // block, we may need to overwrite the block's style with `<span>` element 2147 // and CSS. For example, `<h1>` element has `font-weight: bold;` as its 2148 // default style. If `Document.execCommand("bold")` is called for its 2149 // text, we should make it unbold. Therefore, we shouldn't check 2150 // IsCSSEnabled() in most cases. However, there is an exception. 2151 // FontFaceStateCommand::SetState() calls RemoveInlinePropertyAsAction() 2152 // with nsGkAtoms::tt before calling SetInlinePropertyAsAction() if we 2153 // are handling a XUL command. Only in that case, we need to check 2154 // IsCSSEnabled(). 2155 const bool handleCSS = 2156 aStyle.mHTMLProperty != nsGkAtoms::tt || IsCSSEnabled(); 2157 2158 AutoTArray<OwningNonNull<Element>, 24> arrayOfParents; 2159 for (Element* element : 2160 aPointToSplit.GetContainer()->InclusiveAncestorsOfType<Element>()) { 2161 if (element->IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::head, 2162 nsGkAtoms::html) || 2163 HTMLEditUtils::IsBlockElement( 2164 *element, BlockInlineCheck::UseComputedDisplayOutsideStyle) || 2165 !element->GetParent() || 2166 !EditorUtils::IsEditableContent(*element->GetParent(), 2167 EditorType::HTML) || 2168 NS_WARN_IF(!HTMLEditUtils::IsSplittableNode(*element))) { 2169 break; 2170 } 2171 arrayOfParents.AppendElement(*element); 2172 } 2173 2174 // Split any matching style nodes above the point. 2175 SplitNodeResult result = SplitNodeResult::NotHandled(aPointToSplit); 2176 MOZ_ASSERT(!result.Handled()); 2177 EditorDOMPoint pointToPutCaret; 2178 for (const OwningNonNull<Element>& element : arrayOfParents) { 2179 auto isSetByCSSOrError = [&]() -> Result<bool, nsresult> { 2180 if (!handleCSS) { 2181 return false; 2182 } 2183 // The HTML style defined by aStyle has a CSS equivalence in this 2184 // implementation for the node; let's check if it carries those CSS 2185 // styles 2186 if (aStyle.IsCSSRemovable(*element)) { 2187 nsAutoString firstValue; 2188 Result<bool, nsresult> isSpecifiedByCSSOrError = 2189 CSSEditUtils::IsSpecifiedCSSEquivalentTo(*this, *element, aStyle, 2190 firstValue); 2191 if (MOZ_UNLIKELY(isSpecifiedByCSSOrError.isErr())) { 2192 result.IgnoreCaretPointSuggestion(); 2193 NS_WARNING("CSSEditUtils::IsSpecifiedCSSEquivalentTo() failed"); 2194 return isSpecifiedByCSSOrError; 2195 } 2196 if (isSpecifiedByCSSOrError.unwrap()) { 2197 return true; 2198 } 2199 } 2200 // If this is <sub> or <sup>, we won't use vertical-align CSS property 2201 // because <sub>/<sup> changes font size but neither `vertical-align: 2202 // sub` nor `vertical-align: super` changes it (bug 394304 comment 2). 2203 // Therefore, they are not equivalents. However, they're obviously 2204 // conflict with vertical-align style. Thus, we need to remove ancestor 2205 // elements having vertical-align style. 2206 if (aStyle.IsStyleConflictingWithVerticalAlign()) { 2207 nsAutoString value; 2208 nsresult rv = CSSEditUtils::GetSpecifiedProperty( 2209 *element, *nsGkAtoms::vertical_align, value); 2210 if (NS_FAILED(rv)) { 2211 NS_WARNING("CSSEditUtils::GetSpecifiedProperty() failed"); 2212 result.IgnoreCaretPointSuggestion(); 2213 return Err(rv); 2214 } 2215 if (!value.IsEmpty()) { 2216 return true; 2217 } 2218 } 2219 return false; 2220 }(); 2221 if (MOZ_UNLIKELY(isSetByCSSOrError.isErr())) { 2222 return isSetByCSSOrError.propagateErr(); 2223 } 2224 if (!isSetByCSSOrError.inspect()) { 2225 if (!aStyle.IsStyleToClearAllInlineStyles()) { 2226 // If we're removing a link style and the element is an <a href>, we 2227 // need to split it. 2228 if (aStyle.mHTMLProperty == nsGkAtoms::href && 2229 HTMLEditUtils::IsHyperlinkElement(element)) { 2230 } 2231 // If we're removing HTML style, we should split only the element 2232 // which represents the style. 2233 else if (!element->IsHTMLElement(aStyle.mHTMLProperty) || 2234 (aStyle.mAttribute && !element->HasAttr(aStyle.mAttribute))) { 2235 continue; 2236 } 2237 // If we're setting <font> related styles, it means that we're not 2238 // toggling the style. In this case, we need to remove parent <font> 2239 // elements and/or update parent <font> elements if there are some 2240 // elements which have the attribute. However, we should not touch if 2241 // the value is same as what the caller setting to keep the DOM tree 2242 // as-is as far as possible. 2243 if (aStyle.IsStyleOfFontElement() && aStyle.MaybeHasValue()) { 2244 const nsAttrValue* const attrValue = 2245 element->GetParsedAttr(aStyle.mAttribute); 2246 if (attrValue) { 2247 if (aStyle.mAttribute == nsGkAtoms::size) { 2248 if (attrValue->Type() == nsAttrValue::eInteger && 2249 nsContentUtils::ParseLegacyFontSize( 2250 aStyle.AsInlineStyleAndValue().mAttributeValue) == 2251 attrValue->GetIntegerValue()) { 2252 continue; 2253 } 2254 } else if (aStyle.mAttribute == nsGkAtoms::color) { 2255 nsAttrValue newValue; 2256 nscolor oldColor, newColor; 2257 if (attrValue->Type() == nsAttrValue::eColor && 2258 attrValue->GetColorValue(oldColor) && 2259 newValue.ParseColor( 2260 aStyle.AsInlineStyleAndValue().mAttributeValue) && 2261 newValue.GetColorValue(newColor) && oldColor == newColor) { 2262 continue; 2263 } 2264 } else if (attrValue->Equals( 2265 aStyle.AsInlineStyleAndValue().mAttributeValue, 2266 eIgnoreCase)) { 2267 continue; 2268 } 2269 } 2270 } 2271 } 2272 // If aProperty is nullptr, we need to split any style. 2273 else if (!EditorUtils::IsEditableContent(element, EditorType::HTML) || 2274 !HTMLEditUtils::IsRemovableInlineStyleElement(*element)) { 2275 continue; 2276 } 2277 } 2278 2279 // Found a style node we need to split. 2280 // XXX If first content is a text node and CSS is enabled, we call this 2281 // with text node but in such case, this does nothing, but returns 2282 // as handled with setting only previous or next node. If its parent 2283 // is a block, we do nothing but return as handled. 2284 AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret); 2285 Result<SplitNodeResult, nsresult> splitNodeResult = 2286 SplitNodeDeepWithTransaction(MOZ_KnownLive(element), 2287 result.AtSplitPoint<EditorDOMPoint>(), 2288 aSplitAtEdges); 2289 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 2290 NS_WARNING("HTMLEditor::SplitNodeDeepWithTransaction() failed"); 2291 return splitNodeResult; 2292 } 2293 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap(); 2294 trackPointToPutCaret.FlushAndStopTracking(); 2295 unwrappedSplitNodeResult.MoveCaretPointTo( 2296 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 2297 2298 // If it's not handled, it means that `content` is not a splittable node 2299 // like a void element even if it has some children, and the split point 2300 // is middle of it. 2301 if (!unwrappedSplitNodeResult.Handled()) { 2302 continue; 2303 } 2304 // Respect the last split result which actually did it. 2305 if (!result.DidSplit() || unwrappedSplitNodeResult.DidSplit()) { 2306 result = unwrappedSplitNodeResult.ToHandledResult(); 2307 } 2308 MOZ_ASSERT(result.Handled()); 2309 } 2310 2311 return pointToPutCaret.IsSet() 2312 ? SplitNodeResult(std::move(result), std::move(pointToPutCaret)) 2313 : std::move(result); 2314 } 2315 2316 Result<EditorDOMPoint, nsresult> HTMLEditor::ClearStyleAt( 2317 const EditorDOMPoint& aPoint, const EditorInlineStyle& aStyleToRemove, 2318 SpecifiedStyle aSpecifiedStyle, const Element& aEditingHost) { 2319 MOZ_ASSERT(IsEditActionDataAvailable()); 2320 2321 if (NS_WARN_IF(!aPoint.IsSet())) { 2322 return Err(NS_ERROR_INVALID_ARG); 2323 } 2324 2325 // TODO: We should rewrite this to stop unnecessary element creation and 2326 // deleting it later because it causes the original element may be 2327 // removed from the DOM tree even if same element is still in the 2328 // DOM tree from point of view of users. 2329 2330 // First, split inline elements at the point. 2331 // E.g., if aStyleToRemove.mHTMLProperty is nsGkAtoms::b and 2332 // `<p><b><i>a[]bc</i></b></p>`, we want to make it as 2333 // `<p><b><i>a</i></b><b><i>bc</i></b></p>`. 2334 EditorDOMPoint pointToPutCaret(aPoint); 2335 AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret); 2336 Result<SplitNodeResult, nsresult> splitNodeResult = 2337 SplitAncestorStyledInlineElementsAt( 2338 aPoint, aStyleToRemove, SplitAtEdges::eAllowToCreateEmptyContainer); 2339 if (MOZ_UNLIKELY(splitNodeResult.isErr())) { 2340 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed"); 2341 return splitNodeResult.propagateErr(); 2342 } 2343 trackPointToPutCaret.FlushAndStopTracking(); 2344 SplitNodeResult unwrappedSplitNodeResult = splitNodeResult.unwrap(); 2345 unwrappedSplitNodeResult.MoveCaretPointTo( 2346 pointToPutCaret, *this, 2347 {SuggestCaret::OnlyIfHasSuggestion, 2348 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 2349 2350 // If there is no styled inline elements of aStyleToRemove, we just return the 2351 // given point. 2352 // E.g., `<p><i>a[]bc</i></p>` for nsGkAtoms::b. 2353 if (!unwrappedSplitNodeResult.Handled()) { 2354 return pointToPutCaret; 2355 } 2356 2357 // If it did split nodes, but topmost ancestor inline element is split 2358 // at start of it, we don't need the empty inline element. Let's remove 2359 // it now. Then, we'll get the following DOM tree if there is no "a" in the 2360 // above case: 2361 // <p><b><i>bc</i></b></p> 2362 // ^^ 2363 if (unwrappedSplitNodeResult.GetPreviousContent() && 2364 HTMLEditUtils::IsEmptyNode( 2365 *unwrappedSplitNodeResult.GetPreviousContent(), 2366 {EmptyCheckOption::TreatSingleBRElementAsVisible, 2367 EmptyCheckOption::TreatListItemAsVisible, 2368 EmptyCheckOption::TreatTableCellAsVisible})) { 2369 AutoTrackDOMPoint trackPointToPutCaret(RangeUpdaterRef(), &pointToPutCaret); 2370 // Delete previous node if it's empty. 2371 // MOZ_KnownLive(unwrappedSplitNodeResult.GetPreviousContent()): 2372 // It's grabbed by unwrappedSplitNodeResult. 2373 nsresult rv = DeleteNodeWithTransaction( 2374 MOZ_KnownLive(*unwrappedSplitNodeResult.GetPreviousContent())); 2375 if (NS_FAILED(rv)) { 2376 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 2377 return Err(rv); 2378 } 2379 } 2380 2381 // If we reached block from end of a text node, we can do nothing here. 2382 // E.g., `<p style="font-weight: bold;">a[]bc</p>` for nsGkAtoms::b and 2383 // we're in CSS mode. 2384 // XXX Chrome resets block style and creates `<span>` elements for each 2385 // line in this case. 2386 if (!unwrappedSplitNodeResult.GetNextContent()) { 2387 return pointToPutCaret; 2388 } 2389 2390 // Otherwise, the next node is topmost ancestor inline element which has 2391 // the style. We want to put caret between the split nodes, but we need 2392 // to keep other styles. Therefore, next, we need to split at start of 2393 // the next node. The first example should become 2394 // `<p><b><i>a</i></b><b><i></i></b><b><i>bc</i></b></p>`. 2395 // ^^^^^^^^^^^^^^ 2396 nsIContent* firstLeafChildOfNextNode = HTMLEditUtils::GetFirstLeafContent( 2397 *unwrappedSplitNodeResult.GetNextContent(), {LeafNodeType::OnlyLeafNode}); 2398 EditorDOMPoint atStartOfNextNode( 2399 firstLeafChildOfNextNode ? firstLeafChildOfNextNode 2400 : unwrappedSplitNodeResult.GetNextContent(), 2401 0); 2402 Maybe<EditorLineBreak> lineBreak; 2403 // But don't try to split non-containers like `<br>`, `<hr>` and `<img>` 2404 // element. 2405 if (!atStartOfNextNode.IsInContentNode() || 2406 !HTMLEditUtils::IsContainerNode( 2407 *atStartOfNextNode.ContainerAs<nsIContent>())) { 2408 // If it's a `<br>` element, let's move it into new node later. 2409 auto* const brElement = 2410 HTMLBRElement::FromNode(atStartOfNextNode.GetContainer()); 2411 if (brElement) { 2412 lineBreak.emplace(*brElement); 2413 } 2414 if (!atStartOfNextNode.GetContainerParentAs<nsIContent>()) { 2415 NS_WARNING("atStartOfNextNode was in an orphan node"); 2416 return Err(NS_ERROR_FAILURE); 2417 } 2418 atStartOfNextNode.Set(atStartOfNextNode.GetContainerParent(), 0); 2419 } 2420 AutoTrackDOMPoint trackPointToPutCaret2(RangeUpdaterRef(), &pointToPutCaret); 2421 Result<SplitNodeResult, nsresult> splitResultAtStartOfNextNode = 2422 SplitAncestorStyledInlineElementsAt( 2423 atStartOfNextNode, aStyleToRemove, 2424 SplitAtEdges::eAllowToCreateEmptyContainer); 2425 if (MOZ_UNLIKELY(splitResultAtStartOfNextNode.isErr())) { 2426 NS_WARNING("HTMLEditor::SplitAncestorStyledInlineElementsAt() failed"); 2427 return splitResultAtStartOfNextNode.propagateErr(); 2428 } 2429 trackPointToPutCaret2.FlushAndStopTracking(); 2430 SplitNodeResult unwrappedSplitResultAtStartOfNextNode = 2431 splitResultAtStartOfNextNode.unwrap(); 2432 unwrappedSplitResultAtStartOfNextNode.MoveCaretPointTo( 2433 pointToPutCaret, *this, 2434 {SuggestCaret::OnlyIfHasSuggestion, 2435 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 2436 2437 if (unwrappedSplitResultAtStartOfNextNode.Handled() && 2438 unwrappedSplitResultAtStartOfNextNode.GetNextContent()) { 2439 // If the right inline elements are empty, we should remove them. E.g., 2440 // if the split point is at end of a text node (or end of an inline 2441 // element), e.g., <div><b><i>abc[]</i></b></div>, then now, it's been 2442 // changed to: 2443 // <div><b><i>abc</i></b><b><i>[]</i></b><b><i></i></b></div> 2444 // ^^^^^^^^^^^^^^ 2445 // We will change it to: 2446 // <div><b><i>abc</i></b><b><i>[]</i></b></div> 2447 // ^^ 2448 // And if it has only padding <br> element, we should move it into the 2449 // previous <i> which will have new content. 2450 bool seenBR = false; 2451 if (HTMLEditUtils::IsEmptyNode( 2452 *unwrappedSplitResultAtStartOfNextNode.GetNextContent(), 2453 {EmptyCheckOption::TreatListItemAsVisible, 2454 EmptyCheckOption::TreatTableCellAsVisible}, 2455 &seenBR)) { 2456 // MOZ_KnownLive because of grabbed by 2457 // unwrappedSplitResultAtStartOfNextNode. 2458 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive( 2459 *unwrappedSplitResultAtStartOfNextNode.GetNextContent())); 2460 if (NS_FAILED(rv)) { 2461 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 2462 return Err(rv); 2463 } 2464 } 2465 } 2466 2467 if (!unwrappedSplitResultAtStartOfNextNode.Handled()) { 2468 return std::move(pointToPutCaret); 2469 } 2470 2471 // If there is no content, we should return here. 2472 // XXX Is this possible case without mutation event listener? 2473 if (!unwrappedSplitResultAtStartOfNextNode.GetPreviousContent()) { 2474 // XXX This is really odd, but we return this value... 2475 const auto splitPoint = 2476 unwrappedSplitNodeResult.AtSplitPoint<EditorRawDOMPoint>(); 2477 const auto splitPointAtStartOfNextNode = 2478 unwrappedSplitResultAtStartOfNextNode.AtSplitPoint<EditorRawDOMPoint>(); 2479 return EditorDOMPoint(splitPoint.GetContainer(), 2480 splitPointAtStartOfNextNode.Offset()); 2481 } 2482 2483 // Now, we want to put `<br>` element into the empty split node if 2484 // it was in next node of the first split. 2485 // E.g., `<p><b><i>a</i></b><b><i><br></i></b><b><i>bc</i></b></p>` 2486 nsIContent* firstLeafChildOfPreviousNode = HTMLEditUtils::GetFirstLeafContent( 2487 *unwrappedSplitResultAtStartOfNextNode.GetPreviousContent(), 2488 {LeafNodeType::OnlyLeafNode}); 2489 pointToPutCaret.Set( 2490 firstLeafChildOfPreviousNode 2491 ? firstLeafChildOfPreviousNode 2492 : unwrappedSplitResultAtStartOfNextNode.GetPreviousContent(), 2493 0); 2494 2495 // If the right node starts with a line break, suck it out of right node and 2496 // into the left node left node. This is so we you don't revert back to the 2497 // previous style if you happen to click at the end of a line. 2498 if (lineBreak.isSome()) { 2499 if (lineBreak->IsInComposedDoc()) { 2500 Result<EditorDOMPoint, nsresult> lineBreakPointOrError = 2501 DeleteLineBreakWithTransaction(lineBreak.ref(), nsIEditor::eStrip, 2502 aEditingHost); 2503 if (MOZ_UNLIKELY(lineBreakPointOrError.isErr())) { 2504 NS_WARNING("HTMLEditor::DeleteLineBreakWithTransaction() failed"); 2505 return lineBreakPointOrError.propagateErr(); 2506 } 2507 } 2508 Result<CreateLineBreakResult, nsresult> insertBRElementResultOrError = 2509 InsertLineBreak(WithTransaction::Yes, LineBreakType::BRElement, 2510 pointToPutCaret); 2511 if (MOZ_UNLIKELY(insertBRElementResultOrError.isErr())) { 2512 NS_WARNING( 2513 "HTMLEditor::InsertLineBreak(WithTransaction::Yes, " 2514 "LineBreakType::BRElement) failed"); 2515 return insertBRElementResultOrError.propagateErr(); 2516 } 2517 CreateLineBreakResult insertBRElementResult = 2518 insertBRElementResultOrError.unwrap(); 2519 insertBRElementResult.MoveCaretPointTo( 2520 pointToPutCaret, *this, 2521 {SuggestCaret::OnlyIfHasSuggestion, 2522 SuggestCaret::OnlyIfTransactionsAllowedToDoIt}); 2523 2524 if (unwrappedSplitResultAtStartOfNextNode.GetNextContent() && 2525 unwrappedSplitResultAtStartOfNextNode.GetNextContent() 2526 ->IsInComposedDoc()) { 2527 // If we split inline elements at immediately before <br> element which is 2528 // the last visible content in the right element, we don't need the right 2529 // element anymore. Otherwise, we'll create the following DOM tree: 2530 // - <b>abc</b>{}<br><b></b> 2531 // ^^^^^^^ 2532 // - <b><i>abc</i></b><i><br></i><b></b> 2533 // ^^^^^^^ 2534 if (HTMLEditUtils::IsEmptyNode( 2535 *unwrappedSplitResultAtStartOfNextNode.GetNextContent(), 2536 {EmptyCheckOption::TreatSingleBRElementAsVisible, 2537 EmptyCheckOption::TreatListItemAsVisible, 2538 EmptyCheckOption::TreatTableCellAsVisible})) { 2539 // MOZ_KnownLive because the result is grabbed by 2540 // unwrappedSplitResultAtStartOfNextNode. 2541 nsresult rv = DeleteNodeWithTransaction(MOZ_KnownLive( 2542 *unwrappedSplitResultAtStartOfNextNode.GetNextContent())); 2543 if (NS_FAILED(rv)) { 2544 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 2545 return Err(rv); 2546 } 2547 } 2548 // If the next content has only one <br> element, there may be empty 2549 // inline elements around it. We don't need them anymore because user 2550 // cannot put caret into them. E.g., <b><i>abc[]<br></i><br></b> has 2551 // been changed to <b><i>abc</i></b><i>{}<br></i><b><i></i><br></b> now. 2552 // ^^^^^^^^^^^^^^^^^^ 2553 // We don't need the empty <i>. 2554 else if (HTMLEditUtils::IsEmptyNode( 2555 *unwrappedSplitResultAtStartOfNextNode.GetNextContent(), 2556 {EmptyCheckOption::TreatListItemAsVisible, 2557 EmptyCheckOption::TreatTableCellAsVisible})) { 2558 AutoTArray<OwningNonNull<nsIContent>, 4> emptyInlineContainerElements; 2559 HTMLEditUtils::CollectEmptyInlineContainerDescendants( 2560 *unwrappedSplitResultAtStartOfNextNode.GetNextContentAs<Element>(), 2561 emptyInlineContainerElements, 2562 {EmptyCheckOption::TreatSingleBRElementAsVisible, 2563 EmptyCheckOption::TreatListItemAsVisible, 2564 EmptyCheckOption::TreatTableCellAsVisible}, 2565 BlockInlineCheck::UseComputedDisplayOutsideStyle); 2566 for (const OwningNonNull<nsIContent>& emptyInlineContainerElement : 2567 emptyInlineContainerElements) { 2568 // MOZ_KnownLive(emptyInlineContainerElement) due to bug 1622253. 2569 nsresult rv = DeleteNodeWithTransaction( 2570 MOZ_KnownLive(emptyInlineContainerElement)); 2571 if (NS_FAILED(rv)) { 2572 NS_WARNING("EditorBase::DeleteNodeWithTransaction() failed"); 2573 return Err(rv); 2574 } 2575 } 2576 } 2577 } 2578 2579 // Update the child. 2580 pointToPutCaret.Set(pointToPutCaret.GetContainer(), 0); 2581 } 2582 // Finally, remove the specified style in the previous node at the 2583 // second split and tells good insertion point to the caller. I.e., we 2584 // want to make the first example as: 2585 // `<p><b><i>a</i></b><i>[]</i><b><i>bc</i></b></p>` 2586 // ^^^^^^^^^ 2587 if (auto* const previousElementOfSplitPoint = 2588 unwrappedSplitResultAtStartOfNextNode 2589 .GetPreviousContentAs<Element>()) { 2590 // Track the point at the new hierarchy. This is so we can know where 2591 // to put the selection after we call RemoveStyleInside(). 2592 // RemoveStyleInside() could remove any and all of those nodes, so I 2593 // have to use the range tracking system to find the right spot to put 2594 // selection. 2595 AutoTrackDOMPoint tracker(RangeUpdaterRef(), &pointToPutCaret); 2596 // MOZ_KnownLive(previousElementOfSplitPoint): 2597 // It's grabbed by unwrappedSplitResultAtStartOfNextNode. 2598 Result<EditorDOMPoint, nsresult> removeStyleResult = 2599 RemoveStyleInside(MOZ_KnownLive(*previousElementOfSplitPoint), 2600 aStyleToRemove, aSpecifiedStyle); 2601 if (MOZ_UNLIKELY(removeStyleResult.isErr())) { 2602 NS_WARNING("HTMLEditor::RemoveStyleInside() failed"); 2603 return removeStyleResult; 2604 } 2605 // We've already computed a suggested caret position at start of first leaf 2606 // which is stored in pointToPutCaret, so we don't need to update it here. 2607 } 2608 return pointToPutCaret; 2609 } 2610 2611 Result<EditorDOMPoint, nsresult> HTMLEditor::RemoveStyleInside( 2612 Element& aElement, const EditorInlineStyle& aStyleToRemove, 2613 SpecifiedStyle aSpecifiedStyle) { 2614 // First, handle all descendants. 2615 AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfChildContents; 2616 HTMLEditUtils::CollectAllChildren(aElement, arrayOfChildContents); 2617 EditorDOMPoint pointToPutCaret; 2618 for (const OwningNonNull<nsIContent>& child : arrayOfChildContents) { 2619 if (!child->IsElement()) { 2620 continue; 2621 } 2622 Result<EditorDOMPoint, nsresult> removeStyleResult = RemoveStyleInside( 2623 MOZ_KnownLive(*child->AsElement()), aStyleToRemove, aSpecifiedStyle); 2624 if (MOZ_UNLIKELY(removeStyleResult.isErr())) { 2625 NS_WARNING("HTMLEditor::RemoveStyleInside() failed"); 2626 return removeStyleResult; 2627 } 2628 if (removeStyleResult.inspect().IsSet()) { 2629 pointToPutCaret = removeStyleResult.unwrap(); 2630 } 2631 } 2632 2633 // TODO: It seems that if aElement is not editable, we should insert new 2634 // container to remove the style if possible. 2635 if (!EditorUtils::IsEditableContent(aElement, EditorType::HTML)) { 2636 return pointToPutCaret; 2637 } 2638 2639 // Next, remove CSS style first. Then, `style` attribute will be removed if 2640 // the corresponding CSS property is last one. 2641 auto isStyleSpecifiedOrError = [&]() -> Result<bool, nsresult> { 2642 if (!aStyleToRemove.IsCSSRemovable(aElement)) { 2643 return false; 2644 } 2645 MOZ_ASSERT(!aStyleToRemove.IsStyleToClearAllInlineStyles()); 2646 Result<bool, nsresult> elementHasSpecifiedCSSEquivalentStylesOrError = 2647 CSSEditUtils::HaveSpecifiedCSSEquivalentStyles(*this, aElement, 2648 aStyleToRemove); 2649 NS_WARNING_ASSERTION( 2650 elementHasSpecifiedCSSEquivalentStylesOrError.isOk(), 2651 "CSSEditUtils::HaveSpecifiedCSSEquivalentStyles() failed"); 2652 return elementHasSpecifiedCSSEquivalentStylesOrError; 2653 }(); 2654 if (MOZ_UNLIKELY(isStyleSpecifiedOrError.isErr())) { 2655 return isStyleSpecifiedOrError.propagateErr(); 2656 } 2657 bool styleSpecified = isStyleSpecifiedOrError.unwrap(); 2658 if (nsStyledElement* styledElement = nsStyledElement::FromNode(&aElement)) { 2659 if (styleSpecified) { 2660 // MOZ_KnownLive(*styledElement) because it's an alias of aElement. 2661 nsresult rv = CSSEditUtils::RemoveCSSEquivalentToStyle( 2662 WithTransaction::Yes, *this, MOZ_KnownLive(*styledElement), 2663 aStyleToRemove, nullptr); 2664 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 2665 return Err(NS_ERROR_EDITOR_DESTROYED); 2666 } 2667 NS_WARNING_ASSERTION( 2668 NS_SUCCEEDED(rv), 2669 "CSSEditUtils::RemoveCSSEquivalentToStyle() failed, but ignored"); 2670 } 2671 2672 // If the style is <sub> or <sup>, we won't use vertical-align CSS 2673 // property because <sub>/<sup> changes font size but neither 2674 // `vertical-align: sub` nor `vertical-align: super` changes it 2675 // (bug 394304 comment 2). Therefore, they are not equivalents. However, 2676 // they're obviously conflict with vertical-align style. Thus, we need to 2677 // remove the vertical-align style from elements. 2678 if (aStyleToRemove.IsStyleConflictingWithVerticalAlign()) { 2679 nsAutoString value; 2680 nsresult rv = CSSEditUtils::GetSpecifiedProperty( 2681 aElement, *nsGkAtoms::vertical_align, value); 2682 if (NS_FAILED(rv)) { 2683 NS_WARNING("CSSEditUtils::GetSpecifiedProperty() failed"); 2684 return Err(rv); 2685 } 2686 if (!value.IsEmpty()) { 2687 // MOZ_KnownLive(*styledElement) because it's an alias of aElement. 2688 nsresult rv = CSSEditUtils::RemoveCSSPropertyWithTransaction( 2689 *this, MOZ_KnownLive(*styledElement), *nsGkAtoms::vertical_align, 2690 value); 2691 if (NS_FAILED(rv)) { 2692 NS_WARNING("CSSEditUtils::RemoveCSSPropertyWithTransaction() failed"); 2693 return Err(rv); 2694 } 2695 styleSpecified = true; 2696 } 2697 } 2698 } 2699 2700 // Then, if we could and should remove or replace aElement, let's do it. Or 2701 // just remove attribute. 2702 const bool isStyleRepresentedByElement = 2703 !aStyleToRemove.IsStyleToClearAllInlineStyles() && 2704 aStyleToRemove.IsRepresentedBy(aElement); 2705 2706 auto ShouldUpdateDOMTree = [&]() { 2707 // If we're removing any inline styles and aElement is an inline style 2708 // element, we can remove or replace it. 2709 if (aStyleToRemove.IsStyleToClearAllInlineStyles() && 2710 HTMLEditUtils::IsRemovableInlineStyleElement(aElement)) { 2711 return true; 2712 } 2713 // If we're a specific style and aElement represents it, we can remove or 2714 // replace the element or remove the corresponding attribute. 2715 if (isStyleRepresentedByElement) { 2716 return true; 2717 } 2718 // If we've removed a CSS style from the `style` attribute of aElement, we 2719 // could remove the element. 2720 return aElement.IsHTMLElement(nsGkAtoms::span) && styleSpecified; 2721 }; 2722 if (!ShouldUpdateDOMTree()) { 2723 return pointToPutCaret; 2724 } 2725 2726 const bool elementHasNecessaryAttributes = [&]() { 2727 // If we're not removing nor replacing aElement itself, we don't need to 2728 // take care of its `style` and `class` attributes even if aSpecifiedStyle 2729 // is `Discard` because aSpecifiedStyle is not intended to be used in this 2730 // case. 2731 if (!isStyleRepresentedByElement) { 2732 return HTMLEditUtils::ElementHasAttributeExcept(aElement, 2733 *nsGkAtoms::_empty); 2734 } 2735 // If we're removing links, we don't need to keep <a> even if it has some 2736 // specific attributes because it cannot be nested. However, if and only if 2737 // it has `style` attribute and aSpecifiedStyle is not `Discard`, we need to 2738 // replace it with new <span> to keep the style. 2739 if (aStyleToRemove.IsStyleOfAnchorElement()) { 2740 return aSpecifiedStyle == SpecifiedStyle::Preserve && 2741 (aElement.HasNonEmptyAttr(nsGkAtoms::style) || 2742 aElement.HasNonEmptyAttr(nsGkAtoms::_class)); 2743 } 2744 nsAtom& attrKeepStaying = aStyleToRemove.mAttribute 2745 ? *aStyleToRemove.mAttribute 2746 : *nsGkAtoms::_empty; 2747 return aSpecifiedStyle == SpecifiedStyle::Preserve 2748 // If we're try to remove the element but the caller wants to 2749 // preserve the style, check whether aElement has attributes 2750 // except the removing attribute since `style` and `class` should 2751 // keep existing to preserve the style. 2752 ? HTMLEditUtils::ElementHasAttributeExcept(aElement, 2753 attrKeepStaying) 2754 // If we're try to remove the element and the caller wants to 2755 // discard the style specified to the element, check whether 2756 // aElement has attributes except the removing attribute, `style` 2757 // and `class` since we don't want to keep these attributes. 2758 : HTMLEditUtils::ElementHasAttributeExcept( 2759 aElement, attrKeepStaying, *nsGkAtoms::style, 2760 *nsGkAtoms::_class); 2761 }(); 2762 2763 // If the element is not a <span> and still has some attributes, we should 2764 // replace it with new <span>. 2765 auto ReplaceWithNewSpan = [&]() { 2766 if (aStyleToRemove.IsStyleToClearAllInlineStyles()) { 2767 return false; // Remove it even if it has attributes. 2768 } 2769 if (aElement.IsHTMLElement(nsGkAtoms::span)) { 2770 return false; // Don't replace <span> with new <span>. 2771 } 2772 if (!isStyleRepresentedByElement) { 2773 return false; // Keep non-related element as-is. 2774 } 2775 if (!elementHasNecessaryAttributes) { 2776 return false; // Should remove it instead of replacing it. 2777 } 2778 if (aElement.IsHTMLElement(nsGkAtoms::font)) { 2779 // Replace <font> if it won't have its specific attributes. 2780 return (aStyleToRemove.mHTMLProperty == nsGkAtoms::color || 2781 !aElement.HasAttr(nsGkAtoms::color)) && 2782 (aStyleToRemove.mHTMLProperty == nsGkAtoms::face || 2783 !aElement.HasAttr(nsGkAtoms::face)) && 2784 (aStyleToRemove.mHTMLProperty == nsGkAtoms::size || 2785 !aElement.HasAttr(nsGkAtoms::size)); 2786 } 2787 // The styled element has only global attributes, let's replace it with new 2788 // <span> with cloning the attributes. 2789 return true; 2790 }; 2791 2792 if (ReplaceWithNewSpan()) { 2793 // Before cloning the attribute to new element, let's remove it. 2794 if (aStyleToRemove.mAttribute) { 2795 nsresult rv = 2796 RemoveAttributeWithTransaction(aElement, *aStyleToRemove.mAttribute); 2797 if (NS_FAILED(rv)) { 2798 NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed"); 2799 return Err(rv); 2800 } 2801 } 2802 if (aSpecifiedStyle == SpecifiedStyle::Discard) { 2803 nsresult rv = RemoveAttributeWithTransaction(aElement, *nsGkAtoms::style); 2804 if (NS_FAILED(rv)) { 2805 NS_WARNING( 2806 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::style) " 2807 "failed"); 2808 return Err(rv); 2809 } 2810 rv = RemoveAttributeWithTransaction(aElement, *nsGkAtoms::_class); 2811 if (NS_FAILED(rv)) { 2812 NS_WARNING( 2813 "EditorBase::RemoveAttributeWithTransaction(nsGkAtoms::_class) " 2814 "failed"); 2815 return Err(rv); 2816 } 2817 } 2818 // Move `style` attribute and `class` element to span element before 2819 // removing aElement from the tree. 2820 auto replaceWithSpanResult = 2821 [&]() MOZ_CAN_RUN_SCRIPT -> Result<CreateElementResult, nsresult> { 2822 if (!aStyleToRemove.IsStyleOfAnchorElement()) { 2823 return ReplaceContainerAndCloneAttributesWithTransaction( 2824 aElement, *nsGkAtoms::span); 2825 } 2826 nsString styleValue; // Use nsString to avoid copying the buffer at 2827 // setting the attribute. 2828 aElement.GetAttr(nsGkAtoms::style, styleValue); 2829 return ReplaceContainerWithTransaction(aElement, *nsGkAtoms::span, 2830 *nsGkAtoms::style, styleValue); 2831 }(); 2832 if (MOZ_UNLIKELY(replaceWithSpanResult.isErr())) { 2833 NS_WARNING( 2834 "HTMLEditor::ReplaceContainerWithTransaction(nsGkAtoms::span) " 2835 "failed"); 2836 return replaceWithSpanResult.propagateErr(); 2837 } 2838 CreateElementResult unwrappedReplaceWithSpanResult = 2839 replaceWithSpanResult.unwrap(); 2840 if (AllowsTransactionsToChangeSelection()) { 2841 unwrappedReplaceWithSpanResult.MoveCaretPointTo( 2842 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 2843 } else { 2844 unwrappedReplaceWithSpanResult.IgnoreCaretPointSuggestion(); 2845 } 2846 return pointToPutCaret; 2847 } 2848 2849 auto RemoveElement = [&]() { 2850 if (aStyleToRemove.IsStyleToClearAllInlineStyles()) { 2851 MOZ_ASSERT(HTMLEditUtils::IsRemovableInlineStyleElement(aElement)); 2852 return true; 2853 } 2854 // If the element still has some attributes, we should not remove it to keep 2855 // current presentation and/or semantics. 2856 if (elementHasNecessaryAttributes) { 2857 return false; 2858 } 2859 // If the style is represented by the element, let's remove it. 2860 if (isStyleRepresentedByElement) { 2861 return true; 2862 } 2863 // If we've removed a CSS style and that made the <span> element have no 2864 // attributes, we can delete it. 2865 if (styleSpecified && aElement.IsHTMLElement(nsGkAtoms::span)) { 2866 return true; 2867 } 2868 return false; 2869 }; 2870 2871 if (RemoveElement()) { 2872 Result<EditorDOMPoint, nsresult> unwrapElementResult = 2873 RemoveContainerWithTransaction(aElement); 2874 if (MOZ_UNLIKELY(unwrapElementResult.isErr())) { 2875 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed"); 2876 return unwrapElementResult.propagateErr(); 2877 } 2878 if (AllowsTransactionsToChangeSelection() && 2879 unwrapElementResult.inspect().IsSet()) { 2880 pointToPutCaret = unwrapElementResult.unwrap(); 2881 } 2882 return pointToPutCaret; 2883 } 2884 2885 // If the element needs to keep having some attributes, just remove the 2886 // attribute. Note that we don't need to remove `style` attribute here when 2887 // aSpecifiedStyle is `Discard` because we've already removed unnecessary 2888 // CSS style above. 2889 if (isStyleRepresentedByElement && aStyleToRemove.mAttribute) { 2890 nsresult rv = 2891 RemoveAttributeWithTransaction(aElement, *aStyleToRemove.mAttribute); 2892 if (NS_FAILED(rv)) { 2893 NS_WARNING("EditorBase::RemoveAttributeWithTransaction() failed"); 2894 return Err(rv); 2895 } 2896 } 2897 return pointToPutCaret; 2898 } 2899 2900 EditorRawDOMRange HTMLEditor::GetExtendedRangeWrappingNamedAnchor( 2901 const EditorRawDOMRange& aRange) const { 2902 MOZ_ASSERT(aRange.StartRef().IsSet()); 2903 MOZ_ASSERT(aRange.EndRef().IsSet()); 2904 2905 // FYI: We don't want to stop at ancestor block boundaries to extend the range 2906 // because <a name> can have block elements with low level DOM API. We want 2907 // to remove any <a name> ancestors to remove the style. 2908 2909 EditorRawDOMRange newRange(aRange); 2910 for (Element* element : 2911 aRange.StartRef().GetContainer()->InclusiveAncestorsOfType<Element>()) { 2912 if (!HTMLEditUtils::IsNamedAnchorElement(*element)) { 2913 continue; 2914 } 2915 newRange.SetStart(EditorRawDOMPoint(element)); 2916 } 2917 for (Element* element : 2918 aRange.EndRef().GetContainer()->InclusiveAncestorsOfType<Element>()) { 2919 if (!HTMLEditUtils::IsNamedAnchorElement(*element)) { 2920 continue; 2921 } 2922 newRange.SetEnd(EditorRawDOMPoint::After(*element)); 2923 } 2924 return newRange; 2925 } 2926 2927 EditorRawDOMRange HTMLEditor::GetExtendedRangeWrappingEntirelySelectedElements( 2928 const EditorRawDOMRange& aRange) const { 2929 MOZ_ASSERT(aRange.StartRef().IsSet()); 2930 MOZ_ASSERT(aRange.EndRef().IsSet()); 2931 2932 // FYI: We don't want to stop at ancestor block boundaries to extend the range 2933 // because the style may come from inline parents of block elements which may 2934 // occur in invalid DOM tree. We want to split any (even invalid) ancestors 2935 // at removing the styles. 2936 2937 EditorRawDOMRange newRange(aRange); 2938 while (newRange.StartRef().IsInContentNode() && 2939 newRange.StartRef().IsStartOfContainer()) { 2940 if (!EditorUtils::IsEditableContent( 2941 *newRange.StartRef().ContainerAs<nsIContent>(), EditorType::HTML)) { 2942 break; 2943 } 2944 newRange.SetStart(newRange.StartRef().ParentPoint()); 2945 } 2946 while (newRange.EndRef().IsInContentNode() && 2947 newRange.EndRef().IsEndOfContainer()) { 2948 if (!EditorUtils::IsEditableContent( 2949 *newRange.EndRef().ContainerAs<nsIContent>(), EditorType::HTML)) { 2950 break; 2951 } 2952 newRange.SetEnd( 2953 EditorRawDOMPoint::After(*newRange.EndRef().ContainerAs<nsIContent>())); 2954 } 2955 return newRange; 2956 } 2957 2958 nsresult HTMLEditor::GetInlinePropertyBase(const EditorInlineStyle& aStyle, 2959 const nsAString* aValue, 2960 bool* aFirst, bool* aAny, bool* aAll, 2961 nsAString* outValue) const { 2962 MOZ_ASSERT(!aStyle.IsStyleToClearAllInlineStyles()); 2963 MOZ_ASSERT(IsEditActionDataAvailable()); 2964 2965 *aAny = false; 2966 *aAll = true; 2967 *aFirst = false; 2968 bool first = true; 2969 2970 const bool isCollapsed = SelectionRef().IsCollapsed(); 2971 RefPtr<nsRange> range = SelectionRef().GetRangeAt(0); 2972 // XXX: Should be a while loop, to get each separate range 2973 // XXX: ERROR_HANDLING can currentItem be null? 2974 if (range) { 2975 // For each range, set a flag 2976 bool firstNodeInRange = true; 2977 2978 if (isCollapsed) { 2979 if (NS_WARN_IF(!range->GetStartContainer())) { 2980 return NS_ERROR_FAILURE; 2981 } 2982 nsString tOutString; 2983 const PendingStyleState styleState = [&]() { 2984 if (aStyle.mAttribute) { 2985 auto state = mPendingStylesToApplyToNewContent->GetStyleState( 2986 *aStyle.mHTMLProperty, aStyle.mAttribute, &tOutString); 2987 if (outValue) { 2988 outValue->Assign(tOutString); 2989 } 2990 return state; 2991 } 2992 return mPendingStylesToApplyToNewContent->GetStyleState( 2993 *aStyle.mHTMLProperty); 2994 }(); 2995 if (styleState != PendingStyleState::NotUpdated) { 2996 *aFirst = *aAny = *aAll = 2997 (styleState == PendingStyleState::BeingPreserved); 2998 return NS_OK; 2999 } 3000 3001 nsIContent* const collapsedContent = 3002 nsIContent::FromNode(range->GetStartContainer()); 3003 if (MOZ_LIKELY(collapsedContent && 3004 collapsedContent->GetAsElementOrParentElement()) && 3005 aStyle.IsCSSSettable( 3006 *collapsedContent->GetAsElementOrParentElement())) { 3007 if (aValue) { 3008 tOutString.Assign(*aValue); 3009 } 3010 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError = 3011 CSSEditUtils::IsComputedCSSEquivalentTo( 3012 *this, MOZ_KnownLive(*collapsedContent), aStyle, tOutString); 3013 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) { 3014 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed"); 3015 return isComputedCSSEquivalentToStyleOrError.unwrapErr(); 3016 } 3017 *aFirst = *aAny = *aAll = 3018 isComputedCSSEquivalentToStyleOrError.unwrap(); 3019 if (outValue) { 3020 outValue->Assign(tOutString); 3021 } 3022 return NS_OK; 3023 } 3024 3025 *aFirst = *aAny = *aAll = 3026 collapsedContent && HTMLEditUtils::IsInlineStyleSetByElement( 3027 *collapsedContent, aStyle, aValue, outValue); 3028 return NS_OK; 3029 } 3030 3031 // Non-collapsed selection 3032 3033 nsAutoString firstValue, theValue; 3034 3035 nsCOMPtr<nsINode> endNode = range->GetEndContainer(); 3036 uint32_t endOffset = range->EndOffset(); 3037 3038 PostContentIterator postOrderIter; 3039 DebugOnly<nsresult> rvIgnored = postOrderIter.Init(range); 3040 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 3041 "Failed to initialize post-order content iterator"); 3042 for (; !postOrderIter.IsDone(); postOrderIter.Next()) { 3043 if (postOrderIter.GetCurrentNode()->IsHTMLElement(nsGkAtoms::body)) { 3044 break; 3045 } 3046 RefPtr<Text> textNode = Text::FromNode(postOrderIter.GetCurrentNode()); 3047 if (!textNode) { 3048 continue; 3049 } 3050 3051 // just ignore any non-editable nodes 3052 if (!EditorUtils::IsEditableContent(*textNode, EditorType::HTML) || 3053 !HTMLEditUtils::IsVisibleTextNode(*textNode)) { 3054 continue; 3055 } 3056 3057 if (!isCollapsed && first && firstNodeInRange) { 3058 firstNodeInRange = false; 3059 if (range->StartOffset() == textNode->TextDataLength()) { 3060 continue; 3061 } 3062 } else if (textNode == endNode && !endOffset) { 3063 continue; 3064 } 3065 3066 const RefPtr<Element> element = textNode->GetParentElement(); 3067 3068 bool isSet = false; 3069 if (first) { 3070 if (element) { 3071 if (aStyle.IsCSSSettable(*element)) { 3072 // The HTML styles defined by aHTMLProperty/aAttribute have a CSS 3073 // equivalence in this implementation for node; let's check if it 3074 // carries those CSS styles 3075 if (aValue) { 3076 firstValue.Assign(*aValue); 3077 } 3078 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError = 3079 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element, aStyle, 3080 firstValue); 3081 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) { 3082 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed"); 3083 return isComputedCSSEquivalentToStyleOrError.unwrapErr(); 3084 } 3085 isSet = isComputedCSSEquivalentToStyleOrError.unwrap(); 3086 } else { 3087 isSet = HTMLEditUtils::IsInlineStyleSetByElement( 3088 *element, aStyle, aValue, &firstValue); 3089 } 3090 } 3091 *aFirst = isSet; 3092 first = false; 3093 if (outValue) { 3094 *outValue = firstValue; 3095 } 3096 } else { 3097 if (element) { 3098 if (aStyle.IsCSSSettable(*element)) { 3099 // The HTML styles defined by aHTMLProperty/aAttribute have a CSS 3100 // equivalence in this implementation for node; let's check if it 3101 // carries those CSS styles 3102 if (aValue) { 3103 theValue.Assign(*aValue); 3104 } 3105 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError = 3106 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element, aStyle, 3107 theValue); 3108 if (MOZ_UNLIKELY(isComputedCSSEquivalentToStyleOrError.isErr())) { 3109 NS_WARNING("CSSEditUtils::IsComputedCSSEquivalentTo() failed"); 3110 return isComputedCSSEquivalentToStyleOrError.unwrapErr(); 3111 } 3112 isSet = isComputedCSSEquivalentToStyleOrError.unwrap(); 3113 } else { 3114 isSet = HTMLEditUtils::IsInlineStyleSetByElement(*element, aStyle, 3115 aValue, &theValue); 3116 } 3117 } 3118 3119 if (firstValue != theValue && 3120 // For text-decoration related HTML properties, i.e. <u> and 3121 // <strike>, we have to also check |isSet| because text-decoration 3122 // is a shorthand property, and it may contains other unrelated 3123 // longhand components, e.g. text-decoration-color, so we have to do 3124 // an extra check before setting |*aAll| to false. 3125 // e.g. 3126 // firstValue: "underline rgb(0, 0, 0)" 3127 // theValue: "underline rgb(0, 0, 238)" // <a> uses blue color 3128 // These two values should be the same if we are checking `<u>`. 3129 // That's why we need to check |*aFirst| and |isSet|. 3130 // 3131 // This is a work-around for text-decoration. 3132 // The spec issue: https://github.com/w3c/editing/issues/241. 3133 // Once this spec issue is resolved, we could drop this work-around 3134 // check. 3135 (!aStyle.IsStyleOfTextDecoration( 3136 EditorInlineStyle::IgnoreSElement::Yes) || 3137 *aFirst != isSet)) { 3138 *aAll = false; 3139 } 3140 } 3141 3142 if (isSet) { 3143 *aAny = true; 3144 } else { 3145 *aAll = false; 3146 } 3147 } 3148 } 3149 if (!*aAny) { 3150 // make sure that if none of the selection is set, we don't report all is 3151 // set 3152 *aAll = false; 3153 } 3154 return NS_OK; 3155 } 3156 3157 nsresult HTMLEditor::GetInlineProperty(nsStaticAtom& aHTMLProperty, 3158 nsAtom* aAttribute, 3159 const nsAString& aValue, bool* aFirst, 3160 bool* aAny, bool* aAll) const { 3161 if (NS_WARN_IF(!aFirst) || NS_WARN_IF(!aAny) || NS_WARN_IF(!aAll)) { 3162 return NS_ERROR_INVALID_ARG; 3163 } 3164 3165 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 3166 if (NS_WARN_IF(!editActionData.CanHandle())) { 3167 return NS_ERROR_NOT_INITIALIZED; 3168 } 3169 3170 const nsAString* val = !aValue.IsEmpty() ? &aValue : nullptr; 3171 nsresult rv = 3172 GetInlinePropertyBase(EditorInlineStyle(aHTMLProperty, aAttribute), val, 3173 aFirst, aAny, aAll, nullptr); 3174 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3175 "HTMLEditor::GetInlinePropertyBase() failed"); 3176 return EditorBase::ToGenericNSResult(rv); 3177 } 3178 3179 NS_IMETHODIMP HTMLEditor::GetInlinePropertyWithAttrValue( 3180 const nsAString& aHTMLProperty, const nsAString& aAttribute, 3181 const nsAString& aValue, bool* aFirst, bool* aAny, bool* aAll, 3182 nsAString& outValue) { 3183 nsStaticAtom* property = NS_GetStaticAtom(aHTMLProperty); 3184 if (NS_WARN_IF(!property)) { 3185 return NS_ERROR_INVALID_ARG; 3186 } 3187 nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute); 3188 // MOZ_KnownLive because nsStaticAtom is available until shutting down. 3189 nsresult rv = GetInlinePropertyWithAttrValue(MOZ_KnownLive(*property), 3190 MOZ_KnownLive(attribute), aValue, 3191 aFirst, aAny, aAll, outValue); 3192 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3193 "HTMLEditor::GetInlinePropertyWithAttrValue() failed"); 3194 return rv; 3195 } 3196 3197 nsresult HTMLEditor::GetInlinePropertyWithAttrValue( 3198 nsStaticAtom& aHTMLProperty, nsAtom* aAttribute, const nsAString& aValue, 3199 bool* aFirst, bool* aAny, bool* aAll, nsAString& outValue) { 3200 if (NS_WARN_IF(!aFirst) || NS_WARN_IF(!aAny) || NS_WARN_IF(!aAll)) { 3201 return NS_ERROR_INVALID_ARG; 3202 } 3203 3204 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 3205 if (NS_WARN_IF(!editActionData.CanHandle())) { 3206 return NS_ERROR_NOT_INITIALIZED; 3207 } 3208 3209 const nsAString* val = !aValue.IsEmpty() ? &aValue : nullptr; 3210 nsresult rv = 3211 GetInlinePropertyBase(EditorInlineStyle(aHTMLProperty, aAttribute), val, 3212 aFirst, aAny, aAll, &outValue); 3213 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3214 "HTMLEditor::GetInlinePropertyBase() failed"); 3215 return EditorBase::ToGenericNSResult(rv); 3216 } 3217 3218 nsresult HTMLEditor::RemoveAllInlinePropertiesAsAction( 3219 nsIPrincipal* aPrincipal) { 3220 AutoEditActionDataSetter editActionData( 3221 *this, EditAction::eRemoveAllInlineStyleProperties, aPrincipal); 3222 if (NS_WARN_IF(!editActionData.CanHandle())) { 3223 return NS_ERROR_NOT_INITIALIZED; 3224 } 3225 3226 const RefPtr<Element> editingHost = 3227 ComputeEditingHost(LimitInBodyElement::No); 3228 if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) { 3229 return NS_SUCCESS_DOM_NO_OPERATION; 3230 } 3231 3232 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 3233 if (NS_FAILED(rv)) { 3234 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3235 "MaybeDispatchBeforeInputEvent(), failed"); 3236 return EditorBase::ToGenericNSResult(rv); 3237 } 3238 3239 AutoPlaceholderBatch treatAsOneTransaction( 3240 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3241 IgnoredErrorResult ignoredError; 3242 AutoEditSubActionNotifier startToHandleEditSubAction( 3243 *this, EditSubAction::eRemoveAllTextProperties, nsIEditor::eNext, 3244 ignoredError); 3245 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3246 return EditorBase::ToGenericNSResult(ignoredError.StealNSResult()); 3247 } 3248 NS_WARNING_ASSERTION( 3249 !ignoredError.Failed(), 3250 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3251 3252 AutoTArray<EditorInlineStyle, 1> removeAllInlineStyles; 3253 removeAllInlineStyles.AppendElement(EditorInlineStyle::RemoveAllStyles()); 3254 rv = RemoveInlinePropertiesAsSubAction(removeAllInlineStyles, *editingHost); 3255 NS_WARNING_ASSERTION( 3256 NS_SUCCEEDED(rv), 3257 "HTMLEditor::RemoveInlinePropertiesAsSubAction() failed"); 3258 return EditorBase::ToGenericNSResult(rv); 3259 } 3260 3261 nsresult HTMLEditor::RemoveInlinePropertyAsAction(nsStaticAtom& aHTMLProperty, 3262 nsStaticAtom* aAttribute, 3263 nsIPrincipal* aPrincipal) { 3264 AutoEditActionDataSetter editActionData( 3265 *this, 3266 HTMLEditUtils::GetEditActionForFormatText(aHTMLProperty, aAttribute, 3267 false), 3268 aPrincipal); 3269 if (NS_WARN_IF(!editActionData.CanHandle())) { 3270 return NS_ERROR_NOT_INITIALIZED; 3271 } 3272 3273 const RefPtr<Element> editingHost = 3274 ComputeEditingHost(LimitInBodyElement::No); 3275 if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) { 3276 return NS_SUCCESS_DOM_NO_OPERATION; 3277 } 3278 3279 switch (editActionData.GetEditAction()) { 3280 case EditAction::eRemoveFontFamilyProperty: 3281 MOZ_ASSERT(!u""_ns.IsVoid()); 3282 editActionData.SetData(u""_ns); 3283 break; 3284 case EditAction::eRemoveColorProperty: 3285 case EditAction::eRemoveBackgroundColorPropertyInline: 3286 editActionData.SetColorData(u""_ns); 3287 break; 3288 default: 3289 break; 3290 } 3291 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 3292 if (NS_FAILED(rv)) { 3293 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3294 "MaybeDispatchBeforeInputEvent(), failed"); 3295 return EditorBase::ToGenericNSResult(rv); 3296 } 3297 3298 AutoTArray<EditorInlineStyle, 8> removeInlineStyleAndRelatedElements; 3299 AppendInlineStyleAndRelatedStyle(EditorInlineStyle(aHTMLProperty, aAttribute), 3300 removeInlineStyleAndRelatedElements); 3301 rv = RemoveInlinePropertiesAsSubAction(removeInlineStyleAndRelatedElements, 3302 *editingHost); 3303 NS_WARNING_ASSERTION( 3304 NS_SUCCEEDED(rv), 3305 "HTMLEditor::RemoveInlinePropertiesAsSubAction() failed"); 3306 return EditorBase::ToGenericNSResult(rv); 3307 } 3308 3309 NS_IMETHODIMP HTMLEditor::RemoveInlineProperty(const nsAString& aProperty, 3310 const nsAString& aAttribute) { 3311 nsStaticAtom* property = NS_GetStaticAtom(aProperty); 3312 nsStaticAtom* attribute = EditorUtils::GetAttributeAtom(aAttribute); 3313 3314 AutoEditActionDataSetter editActionData( 3315 *this, 3316 HTMLEditUtils::GetEditActionForFormatText(*property, attribute, false)); 3317 if (NS_WARN_IF(!editActionData.CanHandle())) { 3318 return NS_ERROR_NOT_INITIALIZED; 3319 } 3320 3321 const RefPtr<Element> editingHost = 3322 ComputeEditingHost(LimitInBodyElement::No); 3323 if (!editingHost || editingHost->IsContentEditablePlainTextOnly()) { 3324 return NS_SUCCESS_DOM_NO_OPERATION; 3325 } 3326 3327 switch (editActionData.GetEditAction()) { 3328 case EditAction::eRemoveFontFamilyProperty: 3329 MOZ_ASSERT(!EmptyString().IsVoid()); 3330 editActionData.SetData(EmptyString()); 3331 break; 3332 case EditAction::eRemoveColorProperty: 3333 case EditAction::eRemoveBackgroundColorPropertyInline: 3334 editActionData.SetColorData(EmptyString()); 3335 break; 3336 default: 3337 break; 3338 } 3339 nsresult rv = editActionData.MaybeDispatchBeforeInputEvent(); 3340 if (NS_FAILED(rv)) { 3341 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3342 "MaybeDispatchBeforeInputEvent(), failed"); 3343 return EditorBase::ToGenericNSResult(rv); 3344 } 3345 3346 AutoTArray<EditorInlineStyle, 1> removeOneInlineStyle; 3347 removeOneInlineStyle.AppendElement(EditorInlineStyle(*property, attribute)); 3348 rv = RemoveInlinePropertiesAsSubAction(removeOneInlineStyle, *editingHost); 3349 NS_WARNING_ASSERTION( 3350 NS_SUCCEEDED(rv), 3351 "HTMLEditor::RemoveInlinePropertiesAsSubAction() failed"); 3352 return EditorBase::ToGenericNSResult(rv); 3353 } 3354 3355 void HTMLEditor::AppendInlineStyleAndRelatedStyle( 3356 const EditorInlineStyle& aStyleToRemove, 3357 nsTArray<EditorInlineStyle>& aStylesToRemove) const { 3358 if (nsStaticAtom* similarElementName = 3359 aStyleToRemove.GetSimilarElementNameAtom()) { 3360 EditorInlineStyle anotherStyle(*similarElementName); 3361 if (!aStylesToRemove.Contains(anotherStyle)) { 3362 aStylesToRemove.AppendElement(std::move(anotherStyle)); 3363 } 3364 } else if (aStyleToRemove.mHTMLProperty == nsGkAtoms::font) { 3365 if (aStyleToRemove.mAttribute == nsGkAtoms::size) { 3366 EditorInlineStyle big(*nsGkAtoms::big), small(*nsGkAtoms::small); 3367 if (!aStylesToRemove.Contains(big)) { 3368 aStylesToRemove.AppendElement(std::move(big)); 3369 } 3370 if (!aStylesToRemove.Contains(small)) { 3371 aStylesToRemove.AppendElement(std::move(small)); 3372 } 3373 } 3374 // Handling <tt> element code was implemented for composer (bug 115922). 3375 // This shouldn't work with Document.execCommand() for compatibility with 3376 // the other browsers. Currently, edit action principal is set only when 3377 // the root caller is Document::ExecCommand() so that we should handle <tt> 3378 // element only when the principal is nullptr that must be only when XUL 3379 // command is executed on composer. 3380 else if (aStyleToRemove.mAttribute == nsGkAtoms::face && 3381 !GetEditActionPrincipal()) { 3382 EditorInlineStyle tt(*nsGkAtoms::tt); 3383 if (!aStylesToRemove.Contains(tt)) { 3384 aStylesToRemove.AppendElement(std::move(tt)); 3385 } 3386 } 3387 } 3388 if (!aStylesToRemove.Contains(aStyleToRemove)) { 3389 aStylesToRemove.AppendElement(aStyleToRemove); 3390 } 3391 } 3392 3393 nsresult HTMLEditor::RemoveInlinePropertiesAsSubAction( 3394 const nsTArray<EditorInlineStyle>& aStylesToRemove, 3395 const Element& aEditingHost) { 3396 MOZ_ASSERT(IsEditActionDataAvailable()); 3397 MOZ_ASSERT(!aStylesToRemove.IsEmpty()); 3398 3399 DebugOnly<nsresult> rvIgnored = CommitComposition(); 3400 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 3401 "EditorBase::CommitComposition() failed, but ignored"); 3402 3403 if (SelectionRef().IsCollapsed()) { 3404 // Manipulating text attributes on a collapsed selection only sets state 3405 // for the next text insertion 3406 mPendingStylesToApplyToNewContent->ClearStyles(aStylesToRemove); 3407 return NS_OK; 3408 } 3409 3410 { 3411 Result<EditActionResult, nsresult> result = CanHandleHTMLEditSubAction(); 3412 if (MOZ_UNLIKELY(result.isErr())) { 3413 NS_WARNING("HTMLEditor::CanHandleHTMLEditSubAction() failed"); 3414 return result.unwrapErr(); 3415 } 3416 if (result.inspect().Canceled()) { 3417 return NS_OK; 3418 } 3419 } 3420 3421 AutoPlaceholderBatch treatAsOneTransaction( 3422 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3423 IgnoredErrorResult ignoredError; 3424 AutoEditSubActionNotifier startToHandleEditSubAction( 3425 *this, EditSubAction::eRemoveTextProperty, nsIEditor::eNext, 3426 ignoredError); 3427 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3428 return ignoredError.StealNSResult(); 3429 } 3430 NS_WARNING_ASSERTION( 3431 !ignoredError.Failed(), 3432 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3433 3434 // TODO: We don't need AutoTransactionsConserveSelection here in the normal 3435 // cases, but removing this may cause the behavior with the legacy 3436 // mutation event listeners. We should try to delete this in a bug. 3437 AutoTransactionsConserveSelection dontChangeMySelection(*this); 3438 3439 AutoClonedSelectionRangeArray selectionRanges(SelectionRef()); 3440 for (const EditorInlineStyle& styleToRemove : aStylesToRemove) { 3441 // The ranges may be updated by changing the DOM tree. In strictly 3442 // speaking, we should save and restore the ranges at every range loop, 3443 // but we've never done so and it may be expensive if there are a lot of 3444 // ranges. Therefore, we should do it for every style handling for now. 3445 // TODO: We should collect everything required for removing the style before 3446 // touching the DOM tree. Then, we need to save and restore the 3447 // ranges only once. 3448 Maybe<AutoInlineStyleSetter> styleInverter; 3449 if (styleToRemove.IsInvertibleWithCSS()) { 3450 styleInverter.emplace(EditorInlineStyleAndValue::ToInvert(styleToRemove)); 3451 } 3452 for (OwningNonNull<nsRange>& selectionRange : selectionRanges.Ranges()) { 3453 AutoTrackDOMRange trackSelectionRange(RangeUpdaterRef(), &selectionRange); 3454 // If we're removing <a name>, we don't want to split ancestors because 3455 // the split fragment will keep working as named anchor. Therefore, we 3456 // need to remove all <a name> elements which the selection range even 3457 // partially contains. 3458 const EditorDOMRange range( 3459 styleToRemove.mHTMLProperty == nsGkAtoms::name 3460 ? GetExtendedRangeWrappingNamedAnchor( 3461 EditorRawDOMRange(selectionRange)) 3462 : GetExtendedRangeWrappingEntirelySelectedElements( 3463 EditorRawDOMRange(selectionRange))); 3464 if (NS_WARN_IF(!range.IsPositioned())) { 3465 continue; 3466 } 3467 3468 // Remove this style from ancestors of our range endpoints, splitting 3469 // them as appropriate 3470 Result<SplitRangeOffResult, nsresult> splitRangeOffResult = 3471 SplitAncestorStyledInlineElementsAtRangeEdges( 3472 range, styleToRemove, SplitAtEdges::eAllowToCreateEmptyContainer); 3473 if (MOZ_UNLIKELY(splitRangeOffResult.isErr())) { 3474 NS_WARNING( 3475 "HTMLEditor::SplitAncestorStyledInlineElementsAtRangeEdges() " 3476 "failed"); 3477 return splitRangeOffResult.unwrapErr(); 3478 } 3479 // There is AutoTransactionsConserveSelection, so we don't need to 3480 // update selection here. 3481 splitRangeOffResult.inspect().IgnoreCaretPointSuggestion(); 3482 3483 // XXX Modifying `range` means that we may modify ranges in `Selection`. 3484 // Is this intentional? Note that the range may be not in 3485 // `Selection` too. It seems that at least one of them is not 3486 // an unexpected case. 3487 const EditorDOMRange& splitRange = 3488 splitRangeOffResult.inspect().RangeRef(); 3489 if (NS_WARN_IF(!splitRange.IsPositioned())) { 3490 continue; 3491 } 3492 3493 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContentsToInvertStyle; 3494 { 3495 // Collect top level children in the range first. 3496 // TODO: Perhaps, HTMLEditUtils::IsSplittableNode should be used here 3497 // instead of EditorUtils::IsEditableContent. 3498 AutoTArray<OwningNonNull<nsIContent>, 64> arrayOfContentsAroundRange; 3499 if (splitRange.InSameContainer() && 3500 splitRange.StartRef().IsInTextNode()) { 3501 if (!EditorUtils::IsEditableContent( 3502 *splitRange.StartRef().ContainerAs<Text>(), 3503 EditorType::HTML)) { 3504 continue; 3505 } 3506 arrayOfContentsAroundRange.AppendElement( 3507 *splitRange.StartRef().ContainerAs<Text>()); 3508 } else if (splitRange.IsInTextNodes() && 3509 splitRange.InAdjacentSiblings()) { 3510 // Adjacent siblings are in a same element, so the editable state of 3511 // both text nodes are always same. 3512 if (!EditorUtils::IsEditableContent( 3513 *splitRange.StartRef().ContainerAs<Text>(), 3514 EditorType::HTML)) { 3515 continue; 3516 } 3517 arrayOfContentsAroundRange.AppendElement( 3518 *splitRange.StartRef().ContainerAs<Text>()); 3519 arrayOfContentsAroundRange.AppendElement( 3520 *splitRange.EndRef().ContainerAs<Text>()); 3521 } else { 3522 // Append first node if it's a text node but selected not entirely. 3523 if (splitRange.StartRef().IsInTextNode() && 3524 !splitRange.StartRef().IsStartOfContainer() && 3525 EditorUtils::IsEditableContent( 3526 *splitRange.StartRef().ContainerAs<Text>(), 3527 EditorType::HTML)) { 3528 arrayOfContentsAroundRange.AppendElement( 3529 *splitRange.StartRef().ContainerAs<Text>()); 3530 } 3531 // Append all entirely selected nodes. 3532 ContentSubtreeIterator subtreeIter; 3533 if (NS_SUCCEEDED( 3534 subtreeIter.Init(splitRange.StartRef().ToRawRangeBoundary(), 3535 splitRange.EndRef().ToRawRangeBoundary()))) { 3536 for (; !subtreeIter.IsDone(); subtreeIter.Next()) { 3537 nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode(); 3538 if (NS_WARN_IF(!node)) { 3539 return NS_ERROR_FAILURE; 3540 } 3541 if (node->IsContent() && 3542 EditorUtils::IsEditableContent(*node->AsContent(), 3543 EditorType::HTML)) { 3544 arrayOfContentsAroundRange.AppendElement(*node->AsContent()); 3545 } 3546 } 3547 } 3548 // Append last node if it's a text node but selected not entirely. 3549 if (!splitRange.InSameContainer() && 3550 splitRange.EndRef().IsInTextNode() && 3551 !splitRange.EndRef().IsEndOfContainer() && 3552 EditorUtils::IsEditableContent( 3553 *splitRange.EndRef().ContainerAs<Text>(), EditorType::HTML)) { 3554 arrayOfContentsAroundRange.AppendElement( 3555 *splitRange.EndRef().ContainerAs<Text>()); 3556 } 3557 } 3558 if (styleToRemove.IsInvertibleWithCSS()) { 3559 arrayOfContentsToInvertStyle.SetCapacity( 3560 arrayOfContentsAroundRange.Length()); 3561 } 3562 3563 for (OwningNonNull<nsIContent>& content : arrayOfContentsAroundRange) { 3564 // We should remove style from the element and its descendants. 3565 if (content->IsElement()) { 3566 Result<EditorDOMPoint, nsresult> removeStyleResult = 3567 RemoveStyleInside(MOZ_KnownLive(*content->AsElement()), 3568 styleToRemove, SpecifiedStyle::Preserve); 3569 if (MOZ_UNLIKELY(removeStyleResult.isErr())) { 3570 NS_WARNING("HTMLEditor::RemoveStyleInside() failed"); 3571 return removeStyleResult.unwrapErr(); 3572 } 3573 // There is AutoTransactionsConserveSelection, so we don't need to 3574 // update selection here. 3575 3576 // If the element was removed from the DOM tree by 3577 // RemoveStyleInside, we need to do nothing for it anymore. 3578 if (!content->GetParentNode()) { 3579 continue; 3580 } 3581 } 3582 3583 if (styleToRemove.IsInvertibleWithCSS()) { 3584 arrayOfContentsToInvertStyle.AppendElement(content); 3585 } 3586 } // for-loop for arrayOfContentsAroundRange 3587 } 3588 3589 auto FlushAndStopTrackingAndShrinkSelectionRange = 3590 [&]() MOZ_CAN_RUN_SCRIPT { 3591 trackSelectionRange.FlushAndStopTracking(); 3592 if (NS_WARN_IF(!selectionRange->IsPositioned())) { 3593 return; 3594 } 3595 EditorRawDOMRange range(selectionRange); 3596 nsINode* const commonAncestor = 3597 range.GetClosestCommonInclusiveAncestor(); 3598 // Shrink range for compatibility between browsers. 3599 nsIContent* const maybeNextContent = 3600 range.StartRef().IsInContentNode() && 3601 range.StartRef().IsEndOfContainer() 3602 ? AutoInlineStyleSetter::GetNextEditableInlineContent( 3603 *range.StartRef().ContainerAs<nsIContent>(), 3604 commonAncestor) 3605 : nullptr; 3606 nsIContent* const maybePreviousContent = 3607 range.EndRef().IsInContentNode() && 3608 range.EndRef().IsStartOfContainer() 3609 ? AutoInlineStyleSetter::GetPreviousEditableInlineContent( 3610 *range.EndRef().ContainerAs<nsIContent>(), 3611 commonAncestor) 3612 : nullptr; 3613 if (!maybeNextContent && !maybePreviousContent) { 3614 return; 3615 } 3616 const auto startPoint = 3617 maybeNextContent && 3618 maybeNextContent != selectionRange->GetStartContainer() 3619 ? HTMLEditUtils::GetDeepestEditableStartPointOf< 3620 EditorRawDOMPoint>( 3621 *maybeNextContent, 3622 {EditablePointOption::RecognizeInvisibleWhiteSpaces, 3623 EditablePointOption::StopAtComment}) 3624 : range.StartRef(); 3625 const auto endPoint = 3626 maybePreviousContent && maybePreviousContent != 3627 selectionRange->GetEndContainer() 3628 ? HTMLEditUtils::GetDeepestEditableEndPointOf< 3629 EditorRawDOMPoint>( 3630 *maybePreviousContent, 3631 {EditablePointOption::RecognizeInvisibleWhiteSpaces, 3632 EditablePointOption::StopAtComment}) 3633 : range.EndRef(); 3634 DebugOnly<nsresult> rvIgnored = selectionRange->SetStartAndEnd( 3635 startPoint.ToRawRangeBoundary(), endPoint.ToRawRangeBoundary()); 3636 NS_WARNING_ASSERTION( 3637 NS_SUCCEEDED(rvIgnored), 3638 "nsRange::SetStartAndEnd() failed, but ignored"); 3639 }; 3640 3641 if (arrayOfContentsToInvertStyle.IsEmpty()) { 3642 FlushAndStopTrackingAndShrinkSelectionRange(); 3643 continue; 3644 } 3645 MOZ_ASSERT(styleToRemove.IsInvertibleWithCSS()); 3646 3647 // If the style is specified in parent block and we can remove the 3648 // style with inserting new <span> element, we should do it. 3649 for (OwningNonNull<nsIContent>& content : arrayOfContentsToInvertStyle) { 3650 if (Element* element = Element::FromNode(content)) { 3651 // XXX Do we need to call this even when data node or something? If 3652 // so, for what? 3653 // MOZ_KnownLive because 'arrayOfContents' is guaranteed to 3654 // keep it alive. 3655 nsresult rv = styleInverter->InvertStyleIfApplied( 3656 *this, MOZ_KnownLive(*element)); 3657 if (NS_FAILED(rv)) { 3658 if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { 3659 NS_WARNING( 3660 "AutoInlineStyleSetter::InvertStyleIfApplied() failed"); 3661 return NS_ERROR_EDITOR_DESTROYED; 3662 } 3663 NS_WARNING( 3664 "AutoInlineStyleSetter::InvertStyleIfApplied() failed, but " 3665 "ignored"); 3666 } 3667 continue; 3668 } 3669 3670 // Unfortunately, all browsers don't join text nodes when removing a 3671 // style. Therefore, there may be multiple text nodes as adjacent 3672 // siblings. That's the reason why we need to handle text nodes in this 3673 // loop. 3674 if (Text* textNode = Text::FromNode(content)) { 3675 const uint32_t startOffset = 3676 content == splitRange.StartRef().GetContainer() 3677 ? splitRange.StartRef().Offset() 3678 : 0u; 3679 const uint32_t endOffset = 3680 content == splitRange.EndRef().GetContainer() 3681 ? splitRange.EndRef().Offset() 3682 : textNode->TextDataLength(); 3683 Result<SplitRangeOffFromNodeResult, nsresult> 3684 wrapTextInStyledElementResult = 3685 styleInverter->InvertStyleIfApplied( 3686 *this, MOZ_KnownLive(*textNode), startOffset, endOffset); 3687 if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) { 3688 NS_WARNING("AutoInlineStyleSetter::InvertStyleIfApplied() failed"); 3689 return wrapTextInStyledElementResult.unwrapErr(); 3690 } 3691 SplitRangeOffFromNodeResult unwrappedWrapTextInStyledElementResult = 3692 wrapTextInStyledElementResult.unwrap(); 3693 // There is AutoTransactionsConserveSelection, so we don't need to 3694 // update selection here. 3695 unwrappedWrapTextInStyledElementResult.IgnoreCaretPointSuggestion(); 3696 // If we've split the content, let's swap content in 3697 // arrayOfContentsToInvertStyle with the text node which is applied 3698 // the style. 3699 if (unwrappedWrapTextInStyledElementResult.DidSplit() && 3700 styleToRemove.IsInvertibleWithCSS()) { 3701 MOZ_ASSERT(unwrappedWrapTextInStyledElementResult 3702 .GetMiddleContentAs<Text>()); 3703 if (Text* styledTextNode = unwrappedWrapTextInStyledElementResult 3704 .GetMiddleContentAs<Text>()) { 3705 if (styledTextNode != content) { 3706 arrayOfContentsToInvertStyle.ReplaceElementAt( 3707 arrayOfContentsToInvertStyle.Length() - 1, 3708 OwningNonNull<nsIContent>(*styledTextNode)); 3709 } 3710 } 3711 } 3712 continue; 3713 } 3714 3715 // If the node is not an element nor a text node, it's invisible. 3716 // In this case, we don't need to make it wrapped in new element. 3717 } 3718 3719 // Finally, we should remove the style from all leaf text nodes if 3720 // they still have the style. 3721 AutoTArray<OwningNonNull<Text>, 32> leafTextNodes; 3722 for (const OwningNonNull<nsIContent>& content : 3723 arrayOfContentsToInvertStyle) { 3724 // XXX Should we ignore content which has already removed from the 3725 // DOM tree by the previous for-loop? 3726 if (content->IsElement()) { 3727 CollectEditableLeafTextNodes(*content->AsElement(), leafTextNodes); 3728 } 3729 } 3730 for (const OwningNonNull<Text>& textNode : leafTextNodes) { 3731 Result<SplitRangeOffFromNodeResult, nsresult> 3732 wrapTextInStyledElementResult = styleInverter->InvertStyleIfApplied( 3733 *this, MOZ_KnownLive(*textNode), 0, textNode->TextLength()); 3734 if (MOZ_UNLIKELY(wrapTextInStyledElementResult.isErr())) { 3735 NS_WARNING( 3736 "AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode() " 3737 "failed"); 3738 return wrapTextInStyledElementResult.unwrapErr(); 3739 } 3740 // There is AutoTransactionsConserveSelection, so we don't need to 3741 // update selection here. 3742 wrapTextInStyledElementResult.inspect().IgnoreCaretPointSuggestion(); 3743 } // for-loop of leafTextNodes 3744 3745 // styleInverter may have touched a part of the range. Therefore, we 3746 // cannot adjust the range without comparing DOM node position and 3747 // first/last touched positions, but it may be too expensive. I think 3748 // that shrinking only the tracked range boundaries must be enough in most 3749 // cases. 3750 FlushAndStopTrackingAndShrinkSelectionRange(); 3751 } // for-loop of selectionRanges 3752 } // for-loop of styles 3753 3754 MOZ_ASSERT(!selectionRanges.HasSavedRanges()); 3755 nsresult rv = selectionRanges.ApplyTo(SelectionRef()); 3756 if (NS_WARN_IF(Destroyed())) { 3757 return NS_ERROR_EDITOR_DESTROYED; 3758 } 3759 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3760 "AutoClonedSelectionRangeArray::ApplyTo() failed"); 3761 return rv; 3762 } 3763 3764 nsresult HTMLEditor::AutoInlineStyleSetter::InvertStyleIfApplied( 3765 HTMLEditor& aHTMLEditor, Element& aElement) { 3766 MOZ_ASSERT(IsStyleToInvert()); 3767 3768 Result<bool, nsresult> isRemovableParentStyleOrError = 3769 aHTMLEditor.IsRemovableParentStyleWithNewSpanElement(aElement, *this); 3770 if (MOZ_UNLIKELY(isRemovableParentStyleOrError.isErr())) { 3771 NS_WARNING("HTMLEditor::IsRemovableParentStyleWithNewSpanElement() failed"); 3772 return isRemovableParentStyleOrError.unwrapErr(); 3773 } 3774 if (!isRemovableParentStyleOrError.unwrap()) { 3775 // E.g., text-decoration cannot be override visually in children. 3776 // In such cases, we can do nothing. 3777 return NS_OK; 3778 } 3779 3780 // Wrap it into a new element, move it into direct child which has same style, 3781 // or specify the style to its parent. 3782 Result<CaretPoint, nsresult> pointToPutCaretOrError = 3783 ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle(aHTMLEditor, aElement); 3784 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 3785 NS_WARNING( 3786 "AutoInlineStyleSetter::" 3787 "ApplyStyleToNodeOrChildrenAndRemoveNestedSameStyle() failed"); 3788 return pointToPutCaretOrError.unwrapErr(); 3789 } 3790 // The caller must update `Selection` later so that we don't need this. 3791 pointToPutCaretOrError.unwrap().IgnoreCaretPointSuggestion(); 3792 return NS_OK; 3793 } 3794 3795 Result<SplitRangeOffFromNodeResult, nsresult> 3796 HTMLEditor::AutoInlineStyleSetter::InvertStyleIfApplied(HTMLEditor& aHTMLEditor, 3797 Text& aTextNode, 3798 uint32_t aStartOffset, 3799 uint32_t aEndOffset) { 3800 MOZ_ASSERT(IsStyleToInvert()); 3801 3802 Result<bool, nsresult> isRemovableParentStyleOrError = 3803 aHTMLEditor.IsRemovableParentStyleWithNewSpanElement(aTextNode, *this); 3804 if (MOZ_UNLIKELY(isRemovableParentStyleOrError.isErr())) { 3805 NS_WARNING("HTMLEditor::IsRemovableParentStyleWithNewSpanElement() failed"); 3806 return isRemovableParentStyleOrError.propagateErr(); 3807 } 3808 if (!isRemovableParentStyleOrError.unwrap()) { 3809 // E.g., text-decoration cannot be override visually in children. 3810 // In such cases, we can do nothing. 3811 return SplitRangeOffFromNodeResult(nullptr, &aTextNode, nullptr); 3812 } 3813 3814 // We need to use new `<span>` element or existing element if it's available 3815 // to overwrite parent style. 3816 Result<SplitRangeOffFromNodeResult, nsresult> wrapTextInStyledElementResult = 3817 SplitTextNodeAndApplyStyleToMiddleNode(aHTMLEditor, aTextNode, 3818 aStartOffset, aEndOffset); 3819 NS_WARNING_ASSERTION( 3820 wrapTextInStyledElementResult.isOk(), 3821 "AutoInlineStyleSetter::SplitTextNodeAndApplyStyleToMiddleNode() failed"); 3822 return wrapTextInStyledElementResult; 3823 } 3824 3825 Result<bool, nsresult> HTMLEditor::IsRemovableParentStyleWithNewSpanElement( 3826 nsIContent& aContent, const EditorInlineStyle& aStyle) const { 3827 // We don't support to remove all inline styles with this path. 3828 if (aStyle.IsStyleToClearAllInlineStyles()) { 3829 return false; 3830 } 3831 3832 // First check whether the style is invertible since this is the fastest 3833 // check. 3834 if (!aStyle.IsInvertibleWithCSS()) { 3835 return false; 3836 } 3837 3838 // If aContent is not an element and it's not in an element, it means that 3839 // aContent is disconnected non-element node. In this case, it's never 3840 // applied any styles which are invertible. 3841 const RefPtr<Element> element = aContent.GetAsElementOrParentElement(); 3842 if (MOZ_UNLIKELY(!element)) { 3843 return false; 3844 } 3845 3846 // If parent block has invertible style, we should remove the style with 3847 // creating new `<span>` element even in HTML mode because Chrome does it. 3848 if (!aStyle.IsCSSSettable(*element)) { 3849 return false; 3850 } 3851 nsAutoString emptyString; 3852 Result<bool, nsresult> isComputedCSSEquivalentToStyleOrError = 3853 CSSEditUtils::IsComputedCSSEquivalentTo(*this, *element, aStyle, 3854 emptyString); 3855 NS_WARNING_ASSERTION(isComputedCSSEquivalentToStyleOrError.isOk(), 3856 "CSSEditUtils::IsComputedCSSEquivalentTo() failed"); 3857 return isComputedCSSEquivalentToStyleOrError; 3858 } 3859 3860 void HTMLEditor::CollectEditableLeafTextNodes( 3861 Element& aElement, nsTArray<OwningNonNull<Text>>& aLeafTextNodes) const { 3862 for (nsIContent* child = aElement.GetFirstChild(); child; 3863 child = child->GetNextSibling()) { 3864 if (child->IsElement()) { 3865 CollectEditableLeafTextNodes(*child->AsElement(), aLeafTextNodes); 3866 continue; 3867 } 3868 if (child->IsText()) { 3869 aLeafTextNodes.AppendElement(*child->AsText()); 3870 } 3871 } 3872 } 3873 3874 nsresult HTMLEditor::IncreaseFontSizeAsAction(nsIPrincipal* aPrincipal) { 3875 AutoEditActionDataSetter editActionData(*this, EditAction::eIncrementFontSize, 3876 aPrincipal); 3877 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 3878 if (NS_FAILED(rv)) { 3879 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3880 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 3881 return EditorBase::ToGenericNSResult(rv); 3882 } 3883 3884 rv = IncrementOrDecrementFontSizeAsSubAction(FontSize::incr); 3885 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3886 "HTMLEditor::IncrementOrDecrementFontSizeAsSubAction(" 3887 "FontSize::incr) failed"); 3888 return EditorBase::ToGenericNSResult(rv); 3889 } 3890 3891 nsresult HTMLEditor::DecreaseFontSizeAsAction(nsIPrincipal* aPrincipal) { 3892 AutoEditActionDataSetter editActionData(*this, EditAction::eDecrementFontSize, 3893 aPrincipal); 3894 nsresult rv = editActionData.CanHandleAndMaybeDispatchBeforeInputEvent(); 3895 if (NS_FAILED(rv)) { 3896 NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, 3897 "CanHandleAndMaybeDispatchBeforeInputEvent(), failed"); 3898 return EditorBase::ToGenericNSResult(rv); 3899 } 3900 3901 rv = IncrementOrDecrementFontSizeAsSubAction(FontSize::decr); 3902 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 3903 "HTMLEditor::IncrementOrDecrementFontSizeAsSubAction(" 3904 "FontSize::decr) failed"); 3905 return EditorBase::ToGenericNSResult(rv); 3906 } 3907 3908 nsresult HTMLEditor::IncrementOrDecrementFontSizeAsSubAction( 3909 FontSize aIncrementOrDecrement) { 3910 MOZ_ASSERT(IsEditActionDataAvailable()); 3911 3912 // Committing composition and changing font size should be undone together. 3913 AutoPlaceholderBatch treatAsOneTransaction( 3914 *this, ScrollSelectionIntoView::Yes, __FUNCTION__); 3915 3916 DebugOnly<nsresult> rvIgnored = CommitComposition(); 3917 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 3918 "EditorBase::CommitComposition() failed, but ignored"); 3919 3920 // If selection is collapsed, set typing state 3921 if (SelectionRef().IsCollapsed()) { 3922 nsStaticAtom& bigOrSmallTagName = aIncrementOrDecrement == FontSize::incr 3923 ? *nsGkAtoms::big 3924 : *nsGkAtoms::small; 3925 3926 // Let's see in what kind of element the selection is 3927 if (!SelectionRef().RangeCount()) { 3928 return NS_OK; 3929 } 3930 const auto firstRangeStartPoint = 3931 EditorBase::GetFirstSelectionStartPoint<EditorRawDOMPoint>(); 3932 if (NS_WARN_IF(!firstRangeStartPoint.IsSet())) { 3933 return NS_OK; 3934 } 3935 Element* element = 3936 firstRangeStartPoint.GetContainerOrContainerParentElement(); 3937 if (NS_WARN_IF(!element)) { 3938 return NS_OK; 3939 } 3940 if (!HTMLEditUtils::CanNodeContain(*element, bigOrSmallTagName)) { 3941 return NS_OK; 3942 } 3943 3944 // Manipulating text attributes on a collapsed selection only sets state 3945 // for the next text insertion 3946 mPendingStylesToApplyToNewContent->PreserveStyle(bigOrSmallTagName, nullptr, 3947 u""_ns); 3948 return NS_OK; 3949 } 3950 3951 IgnoredErrorResult ignoredError; 3952 AutoEditSubActionNotifier startToHandleEditSubAction( 3953 *this, EditSubAction::eSetTextProperty, nsIEditor::eNext, ignoredError); 3954 if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { 3955 return ignoredError.StealNSResult(); 3956 } 3957 NS_WARNING_ASSERTION( 3958 !ignoredError.Failed(), 3959 "HTMLEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored"); 3960 3961 // TODO: We don't need AutoTransactionsConserveSelection here in the normal 3962 // cases, but removing this may cause the behavior with the legacy 3963 // mutation event listeners. We should try to delete this in a bug. 3964 AutoTransactionsConserveSelection dontChangeMySelection(*this); 3965 3966 AutoClonedSelectionRangeArray selectionRanges(SelectionRef()); 3967 MOZ_ALWAYS_TRUE(selectionRanges.SaveAndTrackRanges(*this)); 3968 for (const OwningNonNull<nsRange>& domRange : selectionRanges.Ranges()) { 3969 // TODO: We should stop extending the range outside ancestor blocks because 3970 // we don't need to do it for setting inline styles. However, here is 3971 // chrome only handling path. Therefore, we don't need to fix here 3972 // soon. 3973 const EditorDOMRange range(GetExtendedRangeWrappingEntirelySelectedElements( 3974 EditorRawDOMRange(domRange))); 3975 if (NS_WARN_IF(!range.IsPositioned())) { 3976 continue; 3977 } 3978 3979 if (range.InSameContainer() && range.StartRef().IsInTextNode()) { 3980 Result<CreateElementResult, nsresult> wrapInBigOrSmallElementResult = 3981 SetFontSizeOnTextNode( 3982 MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()), 3983 range.StartRef().Offset(), range.EndRef().Offset(), 3984 aIncrementOrDecrement); 3985 if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult.isErr())) { 3986 NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed"); 3987 return wrapInBigOrSmallElementResult.unwrapErr(); 3988 } 3989 // There is an AutoTransactionsConserveSelection instance so that we don't 3990 // need to update selection for this change. 3991 wrapInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion(); 3992 continue; 3993 } 3994 3995 // Not the easy case. Range not contained in single text node. There 3996 // are up to three phases here. There are all the nodes reported by the 3997 // subtree iterator to be processed. And there are potentially a 3998 // starting textnode and an ending textnode which are only partially 3999 // contained by the range. 4000 4001 // Let's handle the nodes reported by the iterator. These nodes are 4002 // entirely contained in the selection range. We build up a list of them 4003 // (since doing operations on the document during iteration would perturb 4004 // the iterator). 4005 4006 // Iterate range and build up array 4007 ContentSubtreeIterator subtreeIter; 4008 if (NS_SUCCEEDED(subtreeIter.Init(range.StartRef().ToRawRangeBoundary(), 4009 range.EndRef().ToRawRangeBoundary()))) { 4010 nsTArray<OwningNonNull<nsIContent>> arrayOfContents; 4011 for (; !subtreeIter.IsDone(); subtreeIter.Next()) { 4012 if (NS_WARN_IF(!subtreeIter.GetCurrentNode()->IsContent())) { 4013 return NS_ERROR_FAILURE; 4014 } 4015 OwningNonNull<nsIContent> content = 4016 *subtreeIter.GetCurrentNode()->AsContent(); 4017 4018 if (EditorUtils::IsEditableContent(content, EditorType::HTML)) { 4019 arrayOfContents.AppendElement(content); 4020 } 4021 } 4022 4023 // Now that we have the list, do the font size change on each node 4024 for (OwningNonNull<nsIContent>& content : arrayOfContents) { 4025 // MOZ_KnownLive because of bug 1622253 4026 Result<EditorDOMPoint, nsresult> fontChangeOnNodeResult = 4027 SetFontSizeWithBigOrSmallElement(MOZ_KnownLive(content), 4028 aIncrementOrDecrement); 4029 if (MOZ_UNLIKELY(fontChangeOnNodeResult.isErr())) { 4030 NS_WARNING("HTMLEditor::SetFontSizeWithBigOrSmallElement() failed"); 4031 return fontChangeOnNodeResult.unwrapErr(); 4032 } 4033 // There is an AutoTransactionsConserveSelection, so we don't need to 4034 // update selection here. 4035 } 4036 } 4037 // Now check the start and end parents of the range to see if they need 4038 // to be separately handled (they do if they are text nodes, due to how 4039 // the subtree iterator works - it will not have reported them). 4040 if (range.StartRef().IsInTextNode() && 4041 !range.StartRef().IsEndOfContainer() && 4042 EditorUtils::IsEditableContent(*range.StartRef().ContainerAs<Text>(), 4043 EditorType::HTML)) { 4044 Result<CreateElementResult, nsresult> wrapInBigOrSmallElementResult = 4045 SetFontSizeOnTextNode( 4046 MOZ_KnownLive(*range.StartRef().ContainerAs<Text>()), 4047 range.StartRef().Offset(), 4048 range.StartRef().ContainerAs<Text>()->TextDataLength(), 4049 aIncrementOrDecrement); 4050 if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult.isErr())) { 4051 NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed"); 4052 return wrapInBigOrSmallElementResult.unwrapErr(); 4053 } 4054 // There is an AutoTransactionsConserveSelection instance so that we 4055 // don't need to update selection for this change. 4056 wrapInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion(); 4057 } 4058 if (range.EndRef().IsInTextNode() && !range.EndRef().IsStartOfContainer() && 4059 EditorUtils::IsEditableContent(*range.EndRef().ContainerAs<Text>(), 4060 EditorType::HTML)) { 4061 Result<CreateElementResult, nsresult> wrapInBigOrSmallElementResult = 4062 SetFontSizeOnTextNode( 4063 MOZ_KnownLive(*range.EndRef().ContainerAs<Text>()), 0u, 4064 range.EndRef().Offset(), aIncrementOrDecrement); 4065 if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult.isErr())) { 4066 NS_WARNING("HTMLEditor::SetFontSizeOnTextNode() failed"); 4067 return wrapInBigOrSmallElementResult.unwrapErr(); 4068 } 4069 // There is an AutoTransactionsConserveSelection instance so that we 4070 // don't need to update selection for this change. 4071 wrapInBigOrSmallElementResult.inspect().IgnoreCaretPointSuggestion(); 4072 } 4073 } 4074 4075 MOZ_ASSERT(selectionRanges.HasSavedRanges()); 4076 selectionRanges.RestoreFromSavedRanges(); 4077 nsresult rv = selectionRanges.ApplyTo(SelectionRef()); 4078 if (NS_WARN_IF(Destroyed())) { 4079 return NS_ERROR_EDITOR_DESTROYED; 4080 } 4081 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), 4082 "AutoClonedSelectionRangeArray::ApplyTo() failed"); 4083 return rv; 4084 } 4085 4086 Result<CreateElementResult, nsresult> HTMLEditor::SetFontSizeOnTextNode( 4087 Text& aTextNode, uint32_t aStartOffset, uint32_t aEndOffset, 4088 FontSize aIncrementOrDecrement) { 4089 // Don't need to do anything if no characters actually selected 4090 if (aStartOffset == aEndOffset) { 4091 return CreateElementResult::NotHandled(); 4092 } 4093 4094 if (!aTextNode.GetParentNode() || 4095 !HTMLEditUtils::CanNodeContain(*aTextNode.GetParentNode(), 4096 *nsGkAtoms::big)) { 4097 return CreateElementResult::NotHandled(); 4098 } 4099 4100 aEndOffset = std::min(aTextNode.Length(), aEndOffset); 4101 4102 // Make the range an independent node. 4103 RefPtr<Text> textNodeForTheRange = &aTextNode; 4104 4105 EditorDOMPoint pointToPutCaret; 4106 { 4107 auto pointToPutCaretOrError = 4108 [&]() MOZ_CAN_RUN_SCRIPT -> Result<EditorDOMPoint, nsresult> { 4109 EditorDOMPoint pointToPutCaret; 4110 // Split at the end of the range. 4111 EditorDOMPoint atEnd(textNodeForTheRange, aEndOffset); 4112 if (!atEnd.IsEndOfContainer()) { 4113 // We need to split off back of text node 4114 Result<SplitNodeResult, nsresult> splitAtEndResult = 4115 SplitNodeWithTransaction(atEnd); 4116 if (MOZ_UNLIKELY(splitAtEndResult.isErr())) { 4117 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 4118 return splitAtEndResult.propagateErr(); 4119 } 4120 SplitNodeResult unwrappedSplitAtEndResult = splitAtEndResult.unwrap(); 4121 if (MOZ_UNLIKELY( 4122 !unwrappedSplitAtEndResult.HasCaretPointSuggestion())) { 4123 NS_WARNING( 4124 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret " 4125 "point"); 4126 return Err(NS_ERROR_FAILURE); 4127 } 4128 unwrappedSplitAtEndResult.MoveCaretPointTo(pointToPutCaret, *this, {}); 4129 MOZ_ASSERT_IF(AllowsTransactionsToChangeSelection(), 4130 pointToPutCaret.IsSet()); 4131 textNodeForTheRange = 4132 unwrappedSplitAtEndResult.GetPreviousContentAs<Text>(); 4133 MOZ_DIAGNOSTIC_ASSERT(textNodeForTheRange); 4134 } 4135 4136 // Split at the start of the range. 4137 EditorDOMPoint atStart(textNodeForTheRange, aStartOffset); 4138 if (!atStart.IsStartOfContainer()) { 4139 // We need to split off front of text node 4140 Result<SplitNodeResult, nsresult> splitAtStartResult = 4141 SplitNodeWithTransaction(atStart); 4142 if (MOZ_UNLIKELY(splitAtStartResult.isErr())) { 4143 NS_WARNING("HTMLEditor::SplitNodeWithTransaction() failed"); 4144 return splitAtStartResult.propagateErr(); 4145 } 4146 SplitNodeResult unwrappedSplitAtStartResult = 4147 splitAtStartResult.unwrap(); 4148 if (MOZ_UNLIKELY( 4149 !unwrappedSplitAtStartResult.HasCaretPointSuggestion())) { 4150 NS_WARNING( 4151 "HTMLEditor::SplitNodeWithTransaction() didn't suggest caret " 4152 "point"); 4153 return Err(NS_ERROR_FAILURE); 4154 } 4155 unwrappedSplitAtStartResult.MoveCaretPointTo(pointToPutCaret, *this, 4156 {}); 4157 MOZ_ASSERT_IF(AllowsTransactionsToChangeSelection(), 4158 pointToPutCaret.IsSet()); 4159 textNodeForTheRange = 4160 unwrappedSplitAtStartResult.GetNextContentAs<Text>(); 4161 MOZ_DIAGNOSTIC_ASSERT(textNodeForTheRange); 4162 } 4163 4164 return pointToPutCaret; 4165 }(); 4166 if (MOZ_UNLIKELY(pointToPutCaretOrError.isErr())) { 4167 // Don't warn here since it should be done in the lambda. 4168 return pointToPutCaretOrError.propagateErr(); 4169 } 4170 pointToPutCaret = pointToPutCaretOrError.unwrap(); 4171 } 4172 4173 // Look for siblings that are correct type of node 4174 nsStaticAtom* const bigOrSmallTagName = 4175 aIncrementOrDecrement == FontSize::incr ? nsGkAtoms::big 4176 : nsGkAtoms::small; 4177 nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling( 4178 *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode}); 4179 if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) { 4180 // Previous sib is already right kind of inline node; slide this over 4181 Result<MoveNodeResult, nsresult> moveTextNodeResult = 4182 MoveNodeToEndWithTransaction(*textNodeForTheRange, *sibling); 4183 if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) { 4184 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 4185 return moveTextNodeResult.propagateErr(); 4186 } 4187 MoveNodeResult unwrappedMoveTextNodeResult = moveTextNodeResult.unwrap(); 4188 unwrappedMoveTextNodeResult.MoveCaretPointTo( 4189 pointToPutCaret, *this, {SuggestCaret::OnlyIfHasSuggestion}); 4190 // XXX Should we return the new container? 4191 return CreateElementResult::NotHandled(std::move(pointToPutCaret)); 4192 } 4193 sibling = HTMLEditUtils::GetNextSibling( 4194 *textNodeForTheRange, {WalkTreeOption::IgnoreNonEditableNode}); 4195 if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) { 4196 // Following sib is already right kind of inline node; slide this over 4197 Result<MoveNodeResult, nsresult> moveTextNodeResult = 4198 MoveNodeWithTransaction(*textNodeForTheRange, 4199 EditorDOMPoint(sibling, 0u)); 4200 if (MOZ_UNLIKELY(moveTextNodeResult.isErr())) { 4201 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 4202 return moveTextNodeResult.propagateErr(); 4203 } 4204 MoveNodeResult unwrappedMoveTextNodeResult = moveTextNodeResult.unwrap(); 4205 unwrappedMoveTextNodeResult.MoveCaretPointTo( 4206 pointToPutCaret, *this, {SuggestCaret::OnlyIfHasSuggestion}); 4207 // XXX Should we return the new container? 4208 return CreateElementResult::NotHandled(std::move(pointToPutCaret)); 4209 } 4210 4211 // Else wrap the node inside font node with appropriate relative size 4212 Result<CreateElementResult, nsresult> wrapTextInBigOrSmallElementResult = 4213 InsertContainerWithTransaction(*textNodeForTheRange, 4214 MOZ_KnownLive(*bigOrSmallTagName)); 4215 if (wrapTextInBigOrSmallElementResult.isErr()) { 4216 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed"); 4217 return wrapTextInBigOrSmallElementResult; 4218 } 4219 CreateElementResult unwrappedWrapTextInBigOrSmallElementResult = 4220 wrapTextInBigOrSmallElementResult.unwrap(); 4221 unwrappedWrapTextInBigOrSmallElementResult.MoveCaretPointTo( 4222 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 4223 return CreateElementResult( 4224 unwrappedWrapTextInBigOrSmallElementResult.UnwrapNewNode(), 4225 std::move(pointToPutCaret)); 4226 } 4227 4228 Result<EditorDOMPoint, nsresult> HTMLEditor::SetFontSizeOfFontElementChildren( 4229 nsIContent& aContent, FontSize aIncrementOrDecrement) { 4230 // This routine looks for all the font nodes in the tree rooted by aNode, 4231 // including aNode itself, looking for font nodes that have the size attr 4232 // set. Any such nodes need to have big or small put inside them, since 4233 // they override any big/small that are above them. 4234 4235 // If this is a font node with size, put big/small inside it. 4236 if (aContent.IsHTMLElement(nsGkAtoms::font) && 4237 aContent.AsElement()->HasAttr(nsGkAtoms::size)) { 4238 EditorDOMPoint pointToPutCaret; 4239 4240 // Cycle through children and adjust relative font size. 4241 AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfContents; 4242 HTMLEditUtils::CollectAllChildren(aContent, arrayOfContents); 4243 for (const auto& child : arrayOfContents) { 4244 // MOZ_KnownLive because of bug 1622253 4245 Result<EditorDOMPoint, nsresult> setFontSizeOfChildResult = 4246 SetFontSizeWithBigOrSmallElement(MOZ_KnownLive(child), 4247 aIncrementOrDecrement); 4248 if (MOZ_UNLIKELY(setFontSizeOfChildResult.isErr())) { 4249 NS_WARNING("HTMLEditor::WrapContentInBigOrSmallElement() failed"); 4250 return setFontSizeOfChildResult; 4251 } 4252 if (setFontSizeOfChildResult.inspect().IsSet()) { 4253 pointToPutCaret = setFontSizeOfChildResult.unwrap(); 4254 } 4255 } 4256 4257 // WrapContentInBigOrSmallElement already calls us recursively, 4258 // so we don't need to check our children again. 4259 return pointToPutCaret; 4260 } 4261 4262 // Otherwise cycle through the children. 4263 EditorDOMPoint pointToPutCaret; 4264 AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfContents; 4265 HTMLEditUtils::CollectAllChildren(aContent, arrayOfContents); 4266 for (const auto& child : arrayOfContents) { 4267 // MOZ_KnownLive because of bug 1622253 4268 Result<EditorDOMPoint, nsresult> fontSizeChangeResult = 4269 SetFontSizeOfFontElementChildren(MOZ_KnownLive(child), 4270 aIncrementOrDecrement); 4271 if (MOZ_UNLIKELY(fontSizeChangeResult.isErr())) { 4272 NS_WARNING("HTMLEditor::SetFontSizeOfFontElementChildren() failed"); 4273 return fontSizeChangeResult; 4274 } 4275 if (fontSizeChangeResult.inspect().IsSet()) { 4276 pointToPutCaret = fontSizeChangeResult.unwrap(); 4277 } 4278 } 4279 4280 return pointToPutCaret; 4281 } 4282 4283 Result<EditorDOMPoint, nsresult> HTMLEditor::SetFontSizeWithBigOrSmallElement( 4284 nsIContent& aContent, FontSize aIncrementOrDecrement) { 4285 nsStaticAtom* const bigOrSmallTagName = 4286 aIncrementOrDecrement == FontSize::incr ? nsGkAtoms::big 4287 : nsGkAtoms::small; 4288 4289 // Is aContent the opposite of what we want? 4290 if ((aIncrementOrDecrement == FontSize::incr && 4291 aContent.IsHTMLElement(nsGkAtoms::small)) || 4292 (aIncrementOrDecrement == FontSize::decr && 4293 aContent.IsHTMLElement(nsGkAtoms::big))) { 4294 // First, populate any nested font elements that have the size attr set 4295 Result<EditorDOMPoint, nsresult> fontSizeChangeOfDescendantsResult = 4296 SetFontSizeOfFontElementChildren(aContent, aIncrementOrDecrement); 4297 if (MOZ_UNLIKELY(fontSizeChangeOfDescendantsResult.isErr())) { 4298 NS_WARNING("HTMLEditor::SetFontSizeOfFontElementChildren() failed"); 4299 return fontSizeChangeOfDescendantsResult; 4300 } 4301 EditorDOMPoint pointToPutCaret = fontSizeChangeOfDescendantsResult.unwrap(); 4302 // In that case, just unwrap the <big> or <small> element. 4303 Result<EditorDOMPoint, nsresult> unwrapBigOrSmallElementResult = 4304 RemoveContainerWithTransaction(MOZ_KnownLive(*aContent.AsElement())); 4305 if (MOZ_UNLIKELY(unwrapBigOrSmallElementResult.isErr())) { 4306 NS_WARNING("HTMLEditor::RemoveContainerWithTransaction() failed"); 4307 return unwrapBigOrSmallElementResult; 4308 } 4309 if (unwrapBigOrSmallElementResult.inspect().IsSet()) { 4310 pointToPutCaret = unwrapBigOrSmallElementResult.unwrap(); 4311 } 4312 return pointToPutCaret; 4313 } 4314 4315 if (HTMLEditUtils::CanNodeContain(*bigOrSmallTagName, aContent)) { 4316 // First, populate any nested font tags that have the size attr set 4317 Result<EditorDOMPoint, nsresult> fontSizeChangeOfDescendantsResult = 4318 SetFontSizeOfFontElementChildren(aContent, aIncrementOrDecrement); 4319 if (MOZ_UNLIKELY(fontSizeChangeOfDescendantsResult.isErr())) { 4320 NS_WARNING("HTMLEditor::SetFontSizeOfFontElementChildren() failed"); 4321 return fontSizeChangeOfDescendantsResult; 4322 } 4323 4324 EditorDOMPoint pointToPutCaret = fontSizeChangeOfDescendantsResult.unwrap(); 4325 4326 // Next, if next or previous is <big> or <small>, move aContent into it. 4327 nsCOMPtr<nsIContent> sibling = HTMLEditUtils::GetPreviousSibling( 4328 aContent, {WalkTreeOption::IgnoreNonEditableNode}); 4329 if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) { 4330 Result<MoveNodeResult, nsresult> moveNodeResult = 4331 MoveNodeToEndWithTransaction(aContent, *sibling); 4332 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 4333 NS_WARNING("HTMLEditor::MoveNodeToEndWithTransaction() failed"); 4334 return moveNodeResult.propagateErr(); 4335 } 4336 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 4337 unwrappedMoveNodeResult.MoveCaretPointTo( 4338 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 4339 return pointToPutCaret; 4340 } 4341 4342 sibling = HTMLEditUtils::GetNextSibling( 4343 aContent, {WalkTreeOption::IgnoreNonEditableNode}); 4344 if (sibling && sibling->IsHTMLElement(bigOrSmallTagName)) { 4345 Result<MoveNodeResult, nsresult> moveNodeResult = 4346 MoveNodeWithTransaction(aContent, EditorDOMPoint(sibling, 0u)); 4347 if (MOZ_UNLIKELY(moveNodeResult.isErr())) { 4348 NS_WARNING("HTMLEditor::MoveNodeWithTransaction() failed"); 4349 return moveNodeResult.propagateErr(); 4350 } 4351 MoveNodeResult unwrappedMoveNodeResult = moveNodeResult.unwrap(); 4352 unwrappedMoveNodeResult.MoveCaretPointTo( 4353 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 4354 return pointToPutCaret; 4355 } 4356 4357 // Otherwise, wrap aContent in new <big> or <small> 4358 Result<CreateElementResult, nsresult> wrapInBigOrSmallElementResult = 4359 InsertContainerWithTransaction(aContent, 4360 MOZ_KnownLive(*bigOrSmallTagName)); 4361 if (MOZ_UNLIKELY(wrapInBigOrSmallElementResult.isErr())) { 4362 NS_WARNING("HTMLEditor::InsertContainerWithTransaction() failed"); 4363 return Err(wrapInBigOrSmallElementResult.unwrapErr()); 4364 } 4365 CreateElementResult unwrappedWrapInBigOrSmallElementResult = 4366 wrapInBigOrSmallElementResult.unwrap(); 4367 MOZ_ASSERT(unwrappedWrapInBigOrSmallElementResult.GetNewNode()); 4368 unwrappedWrapInBigOrSmallElementResult.MoveCaretPointTo( 4369 pointToPutCaret, {SuggestCaret::OnlyIfHasSuggestion}); 4370 return pointToPutCaret; 4371 } 4372 4373 // none of the above? then cycle through the children. 4374 // MOOSE: we should group the children together if possible 4375 // into a single "big" or "small". For the moment they are 4376 // each getting their own. 4377 EditorDOMPoint pointToPutCaret; 4378 AutoTArray<OwningNonNull<nsIContent>, 32> arrayOfContents; 4379 HTMLEditUtils::CollectAllChildren(aContent, arrayOfContents); 4380 for (const auto& child : arrayOfContents) { 4381 // MOZ_KnownLive because of bug 1622253 4382 Result<EditorDOMPoint, nsresult> setFontSizeOfChildResult = 4383 SetFontSizeWithBigOrSmallElement(MOZ_KnownLive(child), 4384 aIncrementOrDecrement); 4385 if (MOZ_UNLIKELY(setFontSizeOfChildResult.isErr())) { 4386 NS_WARNING("HTMLEditor::SetFontSizeWithBigOrSmallElement() failed"); 4387 return setFontSizeOfChildResult; 4388 } 4389 if (setFontSizeOfChildResult.inspect().IsSet()) { 4390 pointToPutCaret = setFontSizeOfChildResult.unwrap(); 4391 } 4392 } 4393 4394 return pointToPutCaret; 4395 } 4396 4397 NS_IMETHODIMP HTMLEditor::GetFontFaceState(bool* aMixed, nsAString& outFace) { 4398 if (NS_WARN_IF(!aMixed)) { 4399 return NS_ERROR_INVALID_ARG; 4400 } 4401 4402 *aMixed = true; 4403 outFace.Truncate(); 4404 4405 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 4406 if (NS_WARN_IF(!editActionData.CanHandle())) { 4407 return NS_ERROR_NOT_INITIALIZED; 4408 } 4409 4410 bool first, any, all; 4411 4412 nsresult rv = GetInlinePropertyBase( 4413 EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::face), nullptr, &first, 4414 &any, &all, &outFace); 4415 if (NS_FAILED(rv)) { 4416 NS_WARNING( 4417 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::face) " 4418 "failed"); 4419 return EditorBase::ToGenericNSResult(rv); 4420 } 4421 if (any && !all) { 4422 return NS_OK; // mixed 4423 } 4424 if (all) { 4425 *aMixed = false; 4426 return NS_OK; 4427 } 4428 4429 // if there is no font face, check for tt 4430 rv = GetInlinePropertyBase(EditorInlineStyle(*nsGkAtoms::tt), nullptr, &first, 4431 &any, &all, nullptr); 4432 if (NS_FAILED(rv)) { 4433 NS_WARNING("HTMLEditor::GetInlinePropertyBase(nsGkAtoms::tt) failed"); 4434 return EditorBase::ToGenericNSResult(rv); 4435 } 4436 if (any && !all) { 4437 return NS_OK; // mixed 4438 } 4439 if (all) { 4440 *aMixed = false; 4441 outFace.AssignLiteral("tt"); 4442 } 4443 4444 if (!any) { 4445 // there was no font face attrs of any kind. We are in normal font. 4446 outFace.Truncate(); 4447 *aMixed = false; 4448 } 4449 return NS_OK; 4450 } 4451 4452 nsresult HTMLEditor::GetFontColorState(bool* aMixed, nsAString& aOutColor) { 4453 if (NS_WARN_IF(!aMixed)) { 4454 return NS_ERROR_INVALID_ARG; 4455 } 4456 4457 *aMixed = true; 4458 aOutColor.Truncate(); 4459 4460 AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); 4461 if (NS_WARN_IF(!editActionData.CanHandle())) { 4462 return NS_ERROR_NOT_INITIALIZED; 4463 } 4464 4465 bool first, any, all; 4466 nsresult rv = GetInlinePropertyBase( 4467 EditorInlineStyle(*nsGkAtoms::font, nsGkAtoms::color), nullptr, &first, 4468 &any, &all, &aOutColor); 4469 if (NS_FAILED(rv)) { 4470 NS_WARNING( 4471 "HTMLEditor::GetInlinePropertyBase(nsGkAtoms::font, nsGkAtoms::color) " 4472 "failed"); 4473 return EditorBase::ToGenericNSResult(rv); 4474 } 4475 4476 if (any && !all) { 4477 return NS_OK; // mixed 4478 } 4479 if (all) { 4480 *aMixed = false; 4481 return NS_OK; 4482 } 4483 4484 if (!any) { 4485 // there was no font color attrs of any kind.. 4486 aOutColor.Truncate(); 4487 *aMixed = false; 4488 } 4489 return NS_OK; 4490 } 4491 4492 // The return value is true only if the instance of the HTML editor we created 4493 // can handle CSS styles and if the CSS preference is checked. 4494 NS_IMETHODIMP HTMLEditor::GetIsCSSEnabled(bool* aIsCSSEnabled) { 4495 *aIsCSSEnabled = IsCSSEnabled(); 4496 return NS_OK; 4497 } 4498 4499 bool HTMLEditor::HasStyleOrIdOrClassAttribute(Element& aElement) { 4500 return aElement.HasNonEmptyAttr(nsGkAtoms::style) || 4501 aElement.HasNonEmptyAttr(nsGkAtoms::_class) || 4502 aElement.HasNonEmptyAttr(nsGkAtoms::id); 4503 } 4504 4505 } // namespace mozilla