EditorUtils.cpp (17710B)
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 "EditorUtils.h" 7 8 #include "EditorDOMPoint.h" // for EditorDOMPoint, EditorDOMRange, etc 9 #include "HTMLEditHelpers.h" // for MoveNodeResult 10 #include "HTMLEditUtils.h" // for HTMLEditUtils 11 #include "TextEditor.h" // for TextEditor 12 13 #include "mozilla/ComputedStyle.h" // for ComputedStyle 14 #include "mozilla/IntegerRange.h" // for IntegerRange 15 #include "mozilla/dom/Document.h" // for dom::Document 16 #include "mozilla/dom/Selection.h" // for dom::Selection 17 #include "mozilla/dom/Text.h" // for dom::Text 18 19 #include "nsComponentManagerUtils.h" // for do_CreateInstance 20 #include "nsContentUtils.h" // for nsContentUtils 21 #include "nsComputedDOMStyle.h" // for nsComputedDOMStyle 22 #include "nsError.h" // for NS_SUCCESS_* and NS_ERROR_* 23 #include "nsFrameSelection.h" // for nsFrameSelection 24 #include "nsIContent.h" // for nsIContent 25 #include "nsINode.h" // for nsINode 26 #include "nsITransferable.h" // for nsITransferable 27 #include "nsRange.h" // for nsRange 28 #include "nsStyleConsts.h" // for StyleWhiteSpace 29 #include "nsStyleStruct.h" // for nsStyleText, etc 30 31 namespace mozilla { 32 33 using namespace dom; 34 35 /****************************************************************************** 36 * some general purpose editor utils 37 *****************************************************************************/ 38 39 bool EditorUtils::IsDescendantOf(const nsINode& aNode, const nsINode& aParent, 40 EditorRawDOMPoint* aOutPoint /* = nullptr */) { 41 if (aOutPoint) { 42 aOutPoint->Clear(); 43 } 44 45 if (&aNode == &aParent) { 46 return false; 47 } 48 49 for (const nsINode* node = &aNode; node; node = node->GetParentNode()) { 50 if (node->GetParentNode() == &aParent) { 51 if (aOutPoint) { 52 MOZ_ASSERT(node->IsContent()); 53 aOutPoint->Set(node->AsContent()); 54 } 55 return true; 56 } 57 } 58 59 return false; 60 } 61 62 bool EditorUtils::IsDescendantOf(const nsINode& aNode, const nsINode& aParent, 63 EditorDOMPoint* aOutPoint) { 64 MOZ_ASSERT(aOutPoint); 65 aOutPoint->Clear(); 66 if (&aNode == &aParent) { 67 return false; 68 } 69 70 for (const nsINode* node = &aNode; node; node = node->GetParentNode()) { 71 if (node->GetParentNode() == &aParent) { 72 MOZ_ASSERT(node->IsContent()); 73 aOutPoint->Set(node->AsContent()); 74 return true; 75 } 76 } 77 78 return false; 79 } 80 81 // static 82 Maybe<std::pair<StyleWhiteSpaceCollapse, StyleTextWrapMode>> 83 EditorUtils::GetComputedWhiteSpaceStyles(const nsIContent& aContent) { 84 if (MOZ_UNLIKELY(!aContent.IsElement() && !aContent.GetParentElement())) { 85 return Nothing(); 86 } 87 RefPtr<const ComputedStyle> elementStyle = 88 nsComputedDOMStyle::GetComputedStyleNoFlush( 89 aContent.IsElement() ? aContent.AsElement() 90 : aContent.GetParentElement()); 91 if (NS_WARN_IF(!elementStyle)) { 92 return Nothing(); 93 } 94 const auto* styleText = elementStyle->StyleText(); 95 return Some( 96 std::pair(styleText->mWhiteSpaceCollapse, styleText->mTextWrapMode)); 97 } 98 99 // static 100 bool EditorUtils::IsWhiteSpacePreformatted(const nsIContent& aContent) { 101 // Look at the node (and its parent if it's not an element), and grab its 102 // ComputedStyle. 103 Element* element = aContent.GetAsElementOrParentElement(); 104 if (!element) { 105 return false; 106 } 107 108 RefPtr<const ComputedStyle> elementStyle = 109 nsComputedDOMStyle::GetComputedStyleNoFlush(element); 110 if (!elementStyle) { 111 // Consider nodes without a ComputedStyle to be NOT preformatted: 112 // For instance, this is true of JS tags inside the body (which show 113 // up as #text nodes but have no ComputedStyle). 114 return false; 115 } 116 117 return elementStyle->StyleText()->WhiteSpaceIsSignificant(); 118 } 119 120 // static 121 bool EditorUtils::IsNewLinePreformatted(const nsIContent& aContent) { 122 // Look at the node (and its parent if it's not an element), and grab its 123 // ComputedStyle. 124 Element* element = aContent.GetAsElementOrParentElement(); 125 if (!element) { 126 return false; 127 } 128 129 RefPtr<const ComputedStyle> elementStyle = 130 nsComputedDOMStyle::GetComputedStyleNoFlush(element); 131 if (!elementStyle) { 132 // Consider nodes without a ComputedStyle to be NOT preformatted: 133 // For instance, this is true of JS tags inside the body (which show 134 // up as #text nodes but have no ComputedStyle). 135 return false; 136 } 137 138 return elementStyle->StyleText()->NewlineIsSignificantStyle(); 139 } 140 141 // static 142 bool EditorUtils::IsOnlyNewLinePreformatted(const nsIContent& aContent) { 143 // Look at the node (and its parent if it's not an element), and grab its 144 // ComputedStyle. 145 Element* element = aContent.GetAsElementOrParentElement(); 146 if (!element) { 147 return false; 148 } 149 150 RefPtr<const ComputedStyle> elementStyle = 151 nsComputedDOMStyle::GetComputedStyleNoFlush(element); 152 if (!elementStyle) { 153 // Consider nodes without a ComputedStyle to be NOT preformatted: 154 // For instance, this is true of JS tags inside the body (which show 155 // up as #text nodes but have no ComputedStyle). 156 return false; 157 } 158 159 return elementStyle->StyleText()->mWhiteSpaceCollapse == 160 StyleWhiteSpaceCollapse::PreserveBreaks; 161 } 162 163 // static 164 Result<nsCOMPtr<nsITransferable>, nsresult> 165 EditorUtils::CreateTransferableForPlainText(const Document& aDocument) { 166 // Create generic Transferable for getting the data 167 nsresult rv; 168 nsCOMPtr<nsITransferable> transferable = 169 do_CreateInstance("@mozilla.org/widget/transferable;1", &rv); 170 if (NS_FAILED(rv)) { 171 NS_WARNING("do_CreateInstance() failed to create nsITransferable instance"); 172 return Err(rv); 173 } 174 175 if (!transferable) { 176 NS_WARNING("do_CreateInstance() returned nullptr, but ignored"); 177 return nsCOMPtr<nsITransferable>(); 178 } 179 180 DebugOnly<nsresult> rvIgnored = 181 transferable->Init(aDocument.GetLoadContext()); 182 NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), 183 "nsITransferable::Init() failed, but ignored"); 184 185 rvIgnored = transferable->AddDataFlavor(kTextMime); 186 NS_WARNING_ASSERTION( 187 NS_SUCCEEDED(rvIgnored), 188 "nsITransferable::AddDataFlavor(kTextMime) failed, but ignored"); 189 rvIgnored = transferable->AddDataFlavor(kMozTextInternal); 190 NS_WARNING_ASSERTION( 191 NS_SUCCEEDED(rvIgnored), 192 "nsITransferable::AddDataFlavor(kMozTextInternal) failed, but ignored"); 193 return transferable; 194 } 195 196 /****************************************************************************** 197 * mozilla::EditorDOMPointBase 198 *****************************************************************************/ 199 200 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharCollapsibleASCIISpace); 201 202 template <typename PT, typename CT> 203 bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleASCIISpace() const { 204 // \n can be not collapsible even when it's not treated as a preformatted line 205 // break. Therefore, we need to check whether the white-spaces are 206 // collapsible or not first. 207 if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) { 208 return false; 209 } 210 // Then, only \n may be preformatted. So, we need to check the case 211 // separately. 212 if (IsCharNewLine()) { 213 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>()); 214 } 215 return IsCharASCIISpace(); 216 } 217 218 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharCollapsibleNBSP); 219 220 template <typename PT, typename CT> 221 bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleNBSP() const { 222 // TODO: Perhaps, we should return false if neither previous char nor 223 // next char is collapsible white-space or NBSP. 224 return IsCharNBSP() && 225 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>()); 226 } 227 228 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, 229 IsCharCollapsibleASCIISpaceOrNBSP); 230 231 template <typename PT, typename CT> 232 bool EditorDOMPointBase<PT, CT>::IsCharCollapsibleASCIISpaceOrNBSP() const { 233 // \n can be not collapsible even when it's not treated as a preformatted line 234 // break. Therefore, we need to check whether the white-spaces are 235 // collapsible or not first. 236 if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) { 237 return false; 238 } 239 // Then, only \n may be preformatted. So, we need to check the case 240 // separately. 241 if (IsCharNewLine()) { 242 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>()); 243 } 244 return IsCharASCIISpaceOrNBSP(); 245 } 246 247 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD( 248 bool, IsPreviousCharCollapsibleASCIISpace); 249 250 template <typename PT, typename CT> 251 bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleASCIISpace() const { 252 // \n can be not collapsible even when it's not treated as a preformatted line 253 // break. Therefore, we need to check whether the white-spaces are 254 // collapsible or not first. 255 if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) { 256 return false; 257 } 258 // Then, only \n may be preformatted. So, we need to check the case 259 // separately. 260 if (IsPreviousCharNewLine()) { 261 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>()); 262 } 263 return IsPreviousCharASCIISpace(); 264 } 265 266 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, 267 IsPreviousCharCollapsibleNBSP); 268 269 template <typename PT, typename CT> 270 bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleNBSP() const { 271 return IsPreviousCharNBSP() && 272 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>()); 273 } 274 275 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD( 276 bool, IsPreviousCharCollapsibleASCIISpaceOrNBSP); 277 278 template <typename PT, typename CT> 279 bool EditorDOMPointBase<PT, CT>::IsPreviousCharCollapsibleASCIISpaceOrNBSP() 280 const { 281 // \n can be not collapsible even when it's not treated as a preformatted line 282 // break. Therefore, we need to check whether the white-spaces are 283 // collapsible or not first. 284 if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) { 285 return false; 286 } 287 // Then, only \n may be preformatted. So, we need to check the case 288 // separately. 289 if (IsPreviousCharNewLine()) { 290 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>()); 291 } 292 return IsPreviousCharASCIISpaceOrNBSP(); 293 } 294 295 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, 296 IsNextCharCollapsibleASCIISpace); 297 298 template <typename PT, typename CT> 299 bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleASCIISpace() const { 300 // \n can be not collapsible even when it's not treated as a preformatted line 301 // break. Therefore, we need to check whether the white-spaces are 302 // collapsible or not first. 303 if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) { 304 return false; 305 } 306 // Then, only \n may be preformatted. So, we need to check the case 307 // separately. 308 if (IsNextCharNewLine()) { 309 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>()); 310 } 311 return IsNextCharASCIISpace(); 312 } 313 314 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsNextCharCollapsibleNBSP); 315 316 template <typename PT, typename CT> 317 bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleNBSP() const { 318 return IsNextCharNBSP() && 319 !EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>()); 320 } 321 322 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD( 323 bool, IsNextCharCollapsibleASCIISpaceOrNBSP); 324 325 template <typename PT, typename CT> 326 bool EditorDOMPointBase<PT, CT>::IsNextCharCollapsibleASCIISpaceOrNBSP() const { 327 // \n can be not collapsible even when it's not treated as a preformatted line 328 // break. Therefore, we need to check whether the white-spaces are 329 // collapsible or not first. 330 if (EditorUtils::IsWhiteSpacePreformatted(*ContainerAs<Text>())) { 331 return false; 332 } 333 // Then, only \n may be preformatted. So, we need to check the case 334 // separately. 335 if (IsNextCharNewLine()) { 336 return !EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>()); 337 } 338 return IsNextCharASCIISpaceOrNBSP(); 339 } 340 341 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, IsCharPreformattedNewLine); 342 343 template <typename PT, typename CT> 344 bool EditorDOMPointBase<PT, CT>::IsCharPreformattedNewLine() const { 345 return IsCharNewLine() && 346 EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>()); 347 } 348 349 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD( 350 bool, IsCharPreformattedNewLineCollapsedWithWhiteSpaces); 351 352 template <typename PT, typename CT> 353 bool EditorDOMPointBase< 354 PT, CT>::IsCharPreformattedNewLineCollapsedWithWhiteSpaces() const { 355 return IsCharNewLine() && 356 EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs<Text>()); 357 } 358 359 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, 360 IsPreviousCharPreformattedNewLine); 361 362 template <typename PT, typename CT> 363 bool EditorDOMPointBase<PT, CT>::IsPreviousCharPreformattedNewLine() const { 364 return IsPreviousCharNewLine() && 365 EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>()); 366 } 367 368 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD( 369 bool, IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces); 370 371 template <typename PT, typename CT> 372 bool EditorDOMPointBase< 373 PT, CT>::IsPreviousCharPreformattedNewLineCollapsedWithWhiteSpaces() const { 374 return IsPreviousCharNewLine() && 375 EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs<Text>()); 376 } 377 378 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD(bool, 379 IsNextCharPreformattedNewLine); 380 381 template <typename PT, typename CT> 382 bool EditorDOMPointBase<PT, CT>::IsNextCharPreformattedNewLine() const { 383 return IsNextCharNewLine() && 384 EditorUtils::IsNewLinePreformatted(*ContainerAs<Text>()); 385 } 386 387 NS_INSTANTIATE_EDITOR_DOM_POINT_CONST_METHOD( 388 bool, IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces); 389 390 template <typename PT, typename CT> 391 bool EditorDOMPointBase< 392 PT, CT>::IsNextCharPreformattedNewLineCollapsedWithWhiteSpaces() const { 393 return IsNextCharNewLine() && 394 EditorUtils::IsOnlyNewLinePreformatted(*ContainerAs<Text>()); 395 } 396 397 template <typename PT, typename CT> 398 bool EditorDOMPointBase<PT, CT>::IsContainerEditableRoot() const { 399 if (MOZ_UNLIKELY(!mParent) || MOZ_UNLIKELY(!mParent->IsEditable()) || 400 NS_WARN_IF(mParent->IsInNativeAnonymousSubtree())) { 401 return false; 402 } 403 return HTMLEditUtils::ElementIsEditableRoot(*mParent); 404 } 405 406 /****************************************************************************** 407 * mozilla::EditorDOMRangeBase 408 *****************************************************************************/ 409 410 NS_INSTANTIATE_EDITOR_DOM_RANGE_CONST_METHOD(nsINode*, 411 GetClosestCommonInclusiveAncestor); 412 413 template <typename EditorDOMPointType> 414 nsINode* EditorDOMRangeBase< 415 EditorDOMPointType>::GetClosestCommonInclusiveAncestor() const { 416 if (NS_WARN_IF(!IsPositioned())) { 417 return nullptr; 418 } 419 return nsContentUtils::GetClosestCommonInclusiveAncestor( 420 mStart.GetContainer(), mEnd.GetContainer()); 421 } 422 423 /****************************************************************************** 424 * mozilla::CaretPoint 425 *****************************************************************************/ 426 427 nsresult CaretPoint::SuggestCaretPointTo( 428 EditorBase& aEditorBase, const SuggestCaretOptions& aOptions) const { 429 mHandledCaretPoint = true; 430 if (!mCaretPoint.IsSet()) { 431 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion)) { 432 return NS_OK; 433 } 434 NS_WARNING("There was no suggestion to put caret"); 435 return NS_ERROR_FAILURE; 436 } 437 if (aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt) && 438 !aEditorBase.AllowsTransactionsToChangeSelection()) { 439 return NS_OK; 440 } 441 nsresult rv = aEditorBase.CollapseSelectionTo(mCaretPoint); 442 if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) { 443 NS_WARNING( 444 "EditorBase::CollapseSelectionTo() caused destroying the editor"); 445 return NS_ERROR_EDITOR_DESTROYED; 446 } 447 return aOptions.contains(SuggestCaret::AndIgnoreTrivialError) && NS_FAILED(rv) 448 ? NS_SUCCESS_EDITOR_BUT_IGNORED_TRIVIAL_ERROR 449 : rv; 450 } 451 452 bool CaretPoint::CopyCaretPointTo(EditorDOMPoint& aPointToPutCaret, 453 const EditorBase& aEditorBase, 454 const SuggestCaretOptions& aOptions) const { 455 MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError)); 456 mHandledCaretPoint = true; 457 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) && 458 !mCaretPoint.IsSet()) { 459 return false; 460 } 461 if (aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt) && 462 !aEditorBase.AllowsTransactionsToChangeSelection()) { 463 return false; 464 } 465 aPointToPutCaret = mCaretPoint; 466 return true; 467 } 468 469 bool CaretPoint::MoveCaretPointTo(EditorDOMPoint& aPointToPutCaret, 470 const EditorBase& aEditorBase, 471 const SuggestCaretOptions& aOptions) { 472 MOZ_ASSERT(!aOptions.contains(SuggestCaret::AndIgnoreTrivialError)); 473 mHandledCaretPoint = true; 474 if (aOptions.contains(SuggestCaret::OnlyIfHasSuggestion) && 475 !mCaretPoint.IsSet()) { 476 return false; 477 } 478 if (aOptions.contains(SuggestCaret::OnlyIfTransactionsAllowedToDoIt) && 479 !aEditorBase.AllowsTransactionsToChangeSelection()) { 480 return false; 481 } 482 aPointToPutCaret = UnwrapCaretPoint(); 483 return true; 484 } 485 486 } // namespace mozilla