EditorDOMAPIWrapper.h (33829B)
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 #ifndef EditorDOMAPIWrapper_h 7 #define EditorDOMAPIWrapper_h 8 9 #include "EditorBase.h" // for EditorBase 10 #include "HTMLEditor.h" // for HTMLEditor 11 12 #include "mozilla/Assertions.h" 13 #include "mozilla/Attributes.h" 14 #include "mozilla/ErrorResult.h" 15 #include "mozilla/dom/CharacterData.h" 16 #include "mozilla/dom/Element.h" 17 #include "nsAtom.h" 18 #include "nsDOMCSSDeclaration.h" 19 #include "nsIContent.h" 20 #include "nsIMutationObserver.h" 21 #include "nsINode.h" 22 #include "nsPrintfCString.h" 23 #include "nsString.h" 24 #include "nsStyledElement.h" 25 26 namespace mozilla { 27 28 static void MakeHumanFriendly(nsAutoString& aStr) { 29 aStr.ReplaceSubstring(u"\n", u"\\n"); 30 aStr.ReplaceSubstring(u"\r", u"\\r"); 31 aStr.ReplaceSubstring(u"\t", u"\\t"); 32 aStr.ReplaceSubstring(u"\f", u"\\f"); 33 aStr.ReplaceSubstring(u"\u00A0", u" "); 34 for (char16_t ch = 0; ch <= 0x20; ch++) { 35 aStr.ReplaceSubstring( 36 nsDependentSubstring(&ch, 1), 37 NS_ConvertASCIItoUTF16(nsPrintfCString("&#x%X04", ch))); 38 } 39 } 40 41 static void MakeHumanFriendly(nsAutoCString& aStr) { 42 aStr.ReplaceSubstring("\n", "\\n"); 43 aStr.ReplaceSubstring("\r", "\\r"); 44 aStr.ReplaceSubstring("\t", "\\t"); 45 aStr.ReplaceSubstring("\f", "\\f"); 46 aStr.ReplaceSubstring("\u00A0", " "); 47 for (char ch = 0; ch <= 0x20; ch++) { 48 aStr.ReplaceSubstring(nsDependentCSubstring(&ch, 1), 49 nsPrintfCString("&#x%X04", ch)); 50 } 51 } 52 53 class NodeToString : public nsAutoCString { 54 public: 55 explicit NodeToString(const nsINode* aNode) { 56 if (!aNode) { 57 Assign("null"); 58 return; 59 } 60 if (const dom::CharacterData* const characterData = 61 dom::CharacterData::FromNode(aNode)) { 62 nsAutoString data; 63 characterData->AppendTextTo(data); 64 if (data.Length() > 10) { 65 data.Truncate(10); 66 data.Append(u"..."); 67 } 68 MakeHumanFriendly(data); 69 Assign(nsPrintfCString("%s, data=\"%s\" (length=%zu)", 70 ToString(*characterData).c_str(), 71 NS_ConvertUTF16toUTF8(data).get(), data.Length())); 72 return; 73 } 74 Assign(ToString(*aNode).c_str()); 75 } 76 }; 77 78 class MarkSelectionAndShrinkLongString : public nsAutoString { 79 public: 80 MarkSelectionAndShrinkLongString(const nsAutoString& aString, 81 uint32_t aStartOffset, uint32_t aEndOffset) 82 : nsAutoString(aString) { 83 if (aStartOffset <= aString.Length() && aEndOffset <= aString.Length() && 84 aStartOffset <= aEndOffset) { 85 Insert(u']', aEndOffset); 86 Insert(u'[', aStartOffset); 87 if (aString.Length() > 30) { 88 if (aEndOffset + 10 <= Length()) { 89 Replace(aEndOffset + 6, Length(), u"..."); 90 } 91 if (aStartOffset > 8) { 92 Replace(0, aStartOffset - 5, u"..."); 93 } 94 } 95 } else if (aString.Length() > 30) { 96 Truncate(30); 97 Append(u"..."); 98 } 99 } 100 }; 101 102 /** 103 * The base class of wrappers of DOM API which modifies the DOM. Editor should 104 * update DOM via the following classes unless the node has not been connected 105 * to any document yet. 106 */ 107 class MOZ_STACK_CLASS AutoDOMAPIWrapperBase { 108 protected: 109 using CharacterData = dom::CharacterData; 110 using Element = dom::Element; 111 112 public: 113 // This method is available only while a subclass is calling a DOM API. 114 [[nodiscard]] virtual bool IsExpectedContentAppended( 115 nsIContent* aFirstNewContent) const { 116 return false; 117 } 118 // This method is available only while a subclass is calling a DOM API. 119 [[nodiscard]] virtual bool IsExpectedContentInserted( 120 nsIContent* aChild) const { 121 return false; 122 } 123 // This method is available only while a subclass is calling a DOM API. 124 [[nodiscard]] virtual bool IsExpectedContentWillBeRemoved( 125 nsIContent* aChild) const { 126 return false; 127 } 128 // This method is available only while a subclass is calling a DOM API. 129 [[nodiscard]] virtual bool IsExpectedAttributeChanged( 130 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, 131 AttrModType aModType, const nsAttrValue* aOldValue) const { 132 return false; 133 } 134 // This method is available only while a subclass is calling a DOM API. 135 [[nodiscard]] virtual bool IsExpectedCharacterDataChanged( 136 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const { 137 return false; 138 } 139 140 enum class DOMAPI { 141 // AutoNodeAPIWrapper 142 nsINode_AppendChild, 143 nsINode_InsertBefore, 144 nsINode_Remove, 145 nsINode_RemoveChild, 146 // AutoElementAttrAPIWrapper 147 Element_SetAttr, 148 Element_UnsetAttr, 149 // AutoCharacterDataAPIWrapper 150 CharacterData_DeleteData, 151 CharacterData_InsertData, 152 CharacterData_ReplaceData, 153 CharacterData_SetData, 154 // AutoCSSDeclarationAPIWrapper 155 CSSDeclaration_SetProperty, 156 CSSDeclaration_RemoveProperty, 157 }; 158 159 friend std::ostream& operator<<(std::ostream& aStream, DOMAPI aType) { 160 switch (aType) { 161 case DOMAPI::nsINode_AppendChild: 162 return aStream << "nsINode::AppendChild"; 163 case DOMAPI::nsINode_InsertBefore: 164 return aStream << "nsINode::InsertBefore"; 165 case DOMAPI::nsINode_Remove: 166 return aStream << "nsINode::Remove"; 167 case DOMAPI::nsINode_RemoveChild: 168 return aStream << "nsINode::RemoveChild"; 169 case DOMAPI::Element_SetAttr: 170 return aStream << "Element::SetAttr"; 171 case DOMAPI::Element_UnsetAttr: 172 return aStream << "Element::UnsetAttr"; 173 case DOMAPI::CharacterData_DeleteData: 174 return aStream << "CharacterData::DeleteData"; 175 case DOMAPI::CharacterData_InsertData: 176 return aStream << "CharacterData::InsertData"; 177 case DOMAPI::CharacterData_ReplaceData: 178 return aStream << "CharacterData::ReplaceData"; 179 case DOMAPI::CharacterData_SetData: 180 return aStream << "CharacterData::SetData"; 181 case DOMAPI::CSSDeclaration_SetProperty: 182 return aStream << "nsICSSDeclaration::SetProperty"; 183 case DOMAPI::CSSDeclaration_RemoveProperty: 184 return aStream << "nsICSSDeclaration::DeleteProperty"; 185 default: 186 MOZ_ASSERT_UNREACHABLE("Invalid DOMAPI value"); 187 return aStream << "<invalid value>"; 188 } 189 } 190 191 [[nodiscard]] DOMAPI Type() const { return *mType; } 192 193 #ifdef DEBUG 194 virtual ~AutoDOMAPIWrapperBase() { MOZ_ASSERT(mType.isSome()); } 195 #endif 196 197 protected: 198 MOZ_CAN_RUN_SCRIPT explicit AutoDOMAPIWrapperBase(EditorBase& aEditorBase) 199 : mEditorBase(aEditorBase) {} 200 201 class MOZ_STACK_CLASS AutoNotifyEditorOfAPICall final { 202 public: 203 MOZ_CAN_RUN_SCRIPT AutoNotifyEditorOfAPICall(AutoDOMAPIWrapperBase& aBase, 204 DOMAPI aCallingAPI) 205 : mBase(aBase) { 206 aBase.mType.emplace(aCallingAPI); 207 if (HTMLEditor* const htmlEditor = mBase.mEditorBase.GetAsHTMLEditor()) { 208 mPrevBase = htmlEditor->OnDOMAPICallStart(mBase); 209 } else { 210 mPrevBase = nullptr; 211 } 212 } 213 ~AutoNotifyEditorOfAPICall() { 214 if (HTMLEditor* const htmlEditor = mBase.mEditorBase.GetAsHTMLEditor()) { 215 htmlEditor->OnDOMAPICallEnd(mPrevBase); 216 } 217 } 218 219 private: 220 const AutoDOMAPIWrapperBase& mBase; 221 const AutoDOMAPIWrapperBase* mPrevBase; 222 }; 223 224 friend std::ostream& operator<<(std::ostream& aStream, 225 const AutoDOMAPIWrapperBase& aWrapperBase); 226 227 MOZ_KNOWN_LIVE EditorBase& mEditorBase; 228 Maybe<DOMAPI> mType; 229 }; 230 231 /** 232 * Wrapper class of nsINode::AppendChild, nsINode::InsertBefore, nsINode::Remove 233 * and nsINode::RemoveChild. 234 */ 235 class MOZ_STACK_CLASS AutoNodeAPIWrapper : public AutoDOMAPIWrapperBase { 236 public: 237 static AutoNodeAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) { 238 switch (aBase->Type()) { 239 case DOMAPI::nsINode_AppendChild: 240 case DOMAPI::nsINode_InsertBefore: 241 case DOMAPI::nsINode_Remove: 242 case DOMAPI::nsINode_RemoveChild: 243 return static_cast<AutoNodeAPIWrapper*>(aBase); 244 default: 245 return nullptr; 246 } 247 } 248 static AutoNodeAPIWrapper* FromBaseOrNull(AutoDOMAPIWrapperBase* aBase) { 249 return aBase ? FromBase(aBase) : nullptr; 250 } 251 static const AutoNodeAPIWrapper* FromBase( 252 const AutoDOMAPIWrapperBase* aBase) { 253 return FromBase(const_cast<AutoDOMAPIWrapperBase*>(aBase)); 254 } 255 static const AutoNodeAPIWrapper* FromBaseOrNull( 256 const AutoDOMAPIWrapperBase* aBase) { 257 return FromBaseOrNull(const_cast<AutoDOMAPIWrapperBase*>(aBase)); 258 } 259 260 MOZ_CAN_RUN_SCRIPT AutoNodeAPIWrapper(EditorBase& aEditorBase, nsINode& aNode) 261 : AutoDOMAPIWrapperBase(aEditorBase), mNode(&aNode) {} 262 263 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult AppendChild(nsIContent& aChild) { 264 mChild = &aChild; 265 AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_AppendChild); 266 IgnoredErrorResult error; 267 MOZ_KnownLive(mNode)->AppendChild(aChild, error); 268 error.WouldReportJSException(); 269 if (NS_WARN_IF(mEditorBase.Destroyed())) { 270 return NS_ERROR_EDITOR_DESTROYED; 271 } 272 if (MOZ_UNLIKELY(error.Failed())) { 273 NS_WARNING("nsINode::AppendChild() failed"); 274 return error.StealNSResult(); 275 } 276 return NS_OK; 277 } 278 279 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 280 InsertBefore(nsIContent& aChild, nsIContent* aReferenceChild) { 281 mChild = &aChild; 282 mReference = aReferenceChild; 283 AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_InsertBefore); 284 IgnoredErrorResult error; 285 MOZ_KnownLive(mNode)->InsertBefore(aChild, aReferenceChild, error); 286 error.WouldReportJSException(); 287 if (NS_WARN_IF(mEditorBase.Destroyed())) { 288 return NS_ERROR_EDITOR_DESTROYED; 289 } 290 if (MOZ_UNLIKELY(error.Failed())) { 291 NS_WARNING("nsINode::InsertBefore() failed"); 292 return error.StealNSResult(); 293 } 294 return NS_OK; 295 } 296 297 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult RemoveChild(nsIContent& aChild) { 298 mChild = &aChild; 299 AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_RemoveChild); 300 IgnoredErrorResult error; 301 MOZ_KnownLive(mNode)->RemoveChild(aChild, error); 302 error.WouldReportJSException(); 303 if (NS_WARN_IF(mEditorBase.Destroyed())) { 304 return NS_ERROR_EDITOR_DESTROYED; 305 } 306 if (MOZ_UNLIKELY(error.Failed())) { 307 NS_WARNING("nsINode::RemoveChild() failed"); 308 return error.StealNSResult(); 309 } 310 return NS_OK; 311 } 312 313 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult Remove() { 314 mChild = nsIContent::FromNode(mNode); 315 MOZ_ASSERT(mChild); 316 mNode = mChild->GetParentNode(); 317 AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::nsINode_Remove); 318 MOZ_KnownLive(mChild)->Remove(); 319 if (NS_WARN_IF(mEditorBase.Destroyed())) { 320 return NS_ERROR_EDITOR_DESTROYED; 321 } 322 return NS_OK; 323 } 324 325 [[nodiscard]] bool IsExpectedResult() const { 326 switch (Type()) { 327 case DOMAPI::nsINode_AppendChild: 328 case DOMAPI::nsINode_InsertBefore: 329 return mChild->GetParentNode() == mNode && 330 mChild->GetNextSibling() == mReference; 331 case DOMAPI::nsINode_RemoveChild: 332 case DOMAPI::nsINode_Remove: 333 return !mChild->GetParentNode(); 334 default: 335 MOZ_ASSERT_UNREACHABLE("Instantiated with wrong type?"); 336 return false; 337 } 338 } 339 340 [[nodiscard]] bool IsExpectedContentAppended( 341 nsIContent* aFirstNewContent) const override { 342 return (Type() == DOMAPI::nsINode_AppendChild || 343 Type() == DOMAPI::nsINode_InsertBefore) && 344 aFirstNewContent == mChild && IsExpectedResult(); 345 } 346 [[nodiscard]] bool IsExpectedContentInserted( 347 nsIContent* aChild) const override { 348 return (Type() == DOMAPI::nsINode_AppendChild || 349 Type() == DOMAPI::nsINode_InsertBefore) && 350 aChild == mChild && IsExpectedResult(); 351 } 352 [[nodiscard]] bool IsExpectedContentWillBeRemoved( 353 nsIContent* aChild) const override { 354 if ((Type() == DOMAPI::nsINode_RemoveChild || 355 Type() == DOMAPI::nsINode_Remove) && 356 aChild == mChild && aChild->GetParentNode() == mNode) { 357 return true; 358 } 359 return (Type() == DOMAPI::nsINode_AppendChild || 360 Type() == DOMAPI::nsINode_InsertBefore) && 361 aChild == mChild; 362 } 363 364 friend std::ostream& operator<<(std::ostream& aStream, 365 const AutoNodeAPIWrapper& aWrapper) { 366 aStream << aWrapper.Type() << "("; 367 switch (aWrapper.Type()) { 368 case DOMAPI::nsINode_AppendChild: 369 aStream << "parent: " << NodeToString(aWrapper.mNode).get() 370 << ", new child: " << NodeToString(aWrapper.mChild).get(); 371 break; 372 case DOMAPI::nsINode_InsertBefore: 373 aStream << "parent: " << NodeToString(aWrapper.mNode).get() 374 << ", new child: " << NodeToString(aWrapper.mChild).get() 375 << ", reference node: " 376 << NodeToString(aWrapper.mReference).get(); 377 break; 378 case DOMAPI::nsINode_Remove: 379 case DOMAPI::nsINode_RemoveChild: 380 aStream << "parent: " << NodeToString(aWrapper.mNode).get() 381 << ", removing node: " << NodeToString(aWrapper.mChild).get(); 382 break; 383 default: 384 break; 385 } 386 return aStream << ")"; 387 } 388 389 protected: 390 // nullptr if nsINode::Remove() is called when no parent. 391 nsINode* mNode; 392 nsIContent* mChild = nullptr; 393 nsIContent* mReference = nullptr; 394 }; 395 396 /** 397 * Wrapper class of Element::SetAttr and Element::UnsetAttr. 398 */ 399 class MOZ_STACK_CLASS AutoElementAttrAPIWrapper : public AutoDOMAPIWrapperBase { 400 public: 401 static AutoElementAttrAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) { 402 switch (aBase->Type()) { 403 case DOMAPI::Element_SetAttr: 404 case DOMAPI::Element_UnsetAttr: 405 return static_cast<AutoElementAttrAPIWrapper*>(aBase); 406 default: 407 return nullptr; 408 } 409 } 410 static AutoElementAttrAPIWrapper* FromBaseOrNull( 411 AutoDOMAPIWrapperBase* aBase) { 412 return aBase ? FromBase(aBase) : nullptr; 413 } 414 static const AutoElementAttrAPIWrapper* FromBase( 415 const AutoDOMAPIWrapperBase* aBase) { 416 return FromBase(const_cast<AutoDOMAPIWrapperBase*>(aBase)); 417 } 418 static const AutoElementAttrAPIWrapper* FromBaseOrNull( 419 const AutoDOMAPIWrapperBase* aBase) { 420 return FromBaseOrNull(const_cast<AutoDOMAPIWrapperBase*>(aBase)); 421 } 422 423 MOZ_CAN_RUN_SCRIPT AutoElementAttrAPIWrapper(EditorBase& aEditorBase, 424 Element& aElement) 425 : AutoDOMAPIWrapperBase(aEditorBase), mElement(aElement) {} 426 427 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetAttr(nsAtom* aAttr, 428 const nsAString& aNewValue, 429 bool aNotify) { 430 mAttr = aAttr; 431 mNewValuePtr = &aNewValue; 432 AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::Element_SetAttr); 433 nsresult rv = 434 mElement.SetAttr(kNameSpaceID_None, aAttr, aNewValue, aNotify); 435 // Don't keep storing the pointer, nobody can guarantee the lifetime. 436 mNewValuePtr = nullptr; 437 NS_WARNING_ASSERTION( 438 NS_SUCCEEDED(rv), 439 nsPrintfCString( 440 "Element::SetAttr(kNameSpaceID_None, %s, %s, %s) failed", 441 nsAutoAtomCString(mAttr).get(), 442 NS_ConvertUTF16toUTF8(aNewValue).get(), TrueOrFalse(aNotify)) 443 .get()); 444 return rv; 445 } 446 447 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult UnsetAttr(nsAtom* aAttr, 448 bool aNotify) { 449 mAttr = aAttr; 450 AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::Element_UnsetAttr); 451 nsresult rv = mElement.UnsetAttr(kNameSpaceID_None, mAttr, aNotify); 452 NS_WARNING_ASSERTION( 453 NS_SUCCEEDED(rv), 454 nsPrintfCString("Element::UnsetAttr(kNameSpaceID_None, %s, %s) failed", 455 nsAutoAtomCString(mAttr).get(), TrueOrFalse(aNotify)) 456 .get()); 457 return rv; 458 } 459 460 [[nodiscard]] bool IsExpectedResult(const nsAString& aExpectedValue) const { 461 switch (Type()) { 462 case DOMAPI::Element_SetAttr: { 463 nsAutoString value; 464 const bool hasAttr = mElement.GetAttr(kNameSpaceID_None, mAttr, value); 465 return hasAttr && value == aExpectedValue; 466 } 467 case DOMAPI::Element_UnsetAttr: 468 return !mElement.HasAttr(kNameSpaceID_None, mAttr); 469 default: 470 MOZ_ASSERT_UNREACHABLE("Instantiated with wrong type?"); 471 return false; 472 } 473 } 474 475 [[nodiscard]] virtual bool IsExpectedAttributeChanged( 476 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, 477 AttrModType aModType, const nsAttrValue* aOldValue) const { 478 switch (Type()) { 479 case DOMAPI::Element_SetAttr: 480 return IsAdditionOrModification(aModType) && aElement == &mElement && 481 aNameSpaceID == kNameSpaceID_None && aAttribute == mAttr && 482 mNewValuePtr && IsExpectedResult(*mNewValuePtr); 483 case DOMAPI::Element_UnsetAttr: 484 return aModType == AttrModType::Removal && aElement == &mElement && 485 aNameSpaceID == kNameSpaceID_None && aAttribute == mAttr; 486 default: 487 return false; 488 } 489 } 490 491 friend std::ostream& operator<<(std::ostream& aStream, 492 const AutoElementAttrAPIWrapper& aWrapper) { 493 aStream << aWrapper.Type() 494 << "(element: " << NodeToString(&aWrapper.mElement).get() 495 << ", attr: " << nsAutoAtomCString(aWrapper.mAttr).get(); 496 switch (aWrapper.Type()) { 497 case DOMAPI::Element_SetAttr: { 498 MOZ_ASSERT(aWrapper.mNewValuePtr); 499 nsAutoString newValue(aWrapper.mNewValuePtr ? *aWrapper.mNewValuePtr 500 : EmptyString()); 501 MakeHumanFriendly(newValue); 502 aStream << ", new value=\"" << NS_ConvertUTF16toUTF8(newValue).get() 503 << "\""; 504 break; 505 } 506 case DOMAPI::Element_UnsetAttr: 507 default: 508 break; 509 } 510 return aStream << ")"; 511 } 512 513 protected: 514 MOZ_KNOWN_LIVE Element& mElement; 515 nsAtom* mAttr = nullptr; 516 // For avoiding to copy the string, we store the given string pointer only 517 // while calling the API because it's enough to check whether checking 518 // mutations are expected ones or not. 519 const nsAString* mNewValuePtr = nullptr; 520 }; 521 522 /** 523 * Wrapper class of CharacterData::DeleteData, CharacterData::InsertData, 524 * CharacterData::ReplaceData and CharacterData::SetData. 525 */ 526 class MOZ_STACK_CLASS AutoCharacterDataAPIWrapper 527 : public AutoDOMAPIWrapperBase { 528 public: 529 static AutoCharacterDataAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) { 530 switch (aBase->Type()) { 531 case DOMAPI::CharacterData_DeleteData: 532 case DOMAPI::CharacterData_InsertData: 533 case DOMAPI::CharacterData_ReplaceData: 534 case DOMAPI::CharacterData_SetData: 535 return static_cast<AutoCharacterDataAPIWrapper*>(aBase); 536 default: 537 return nullptr; 538 } 539 } 540 static AutoCharacterDataAPIWrapper* FromBaseOrNull( 541 AutoDOMAPIWrapperBase* aBase) { 542 return aBase ? FromBase(aBase) : nullptr; 543 } 544 static const AutoCharacterDataAPIWrapper* FromBase( 545 const AutoDOMAPIWrapperBase* aBase) { 546 return FromBase(const_cast<AutoDOMAPIWrapperBase*>(aBase)); 547 } 548 static const AutoCharacterDataAPIWrapper* FromBaseOrNull( 549 const AutoDOMAPIWrapperBase* aBase) { 550 return FromBaseOrNull(const_cast<AutoDOMAPIWrapperBase*>(aBase)); 551 } 552 553 MOZ_CAN_RUN_SCRIPT AutoCharacterDataAPIWrapper(EditorBase& aEditorBase, 554 CharacterData& aNode) 555 : AutoDOMAPIWrapperBase(aEditorBase), mCharacterData(aNode) {} 556 557 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult DeleteData(uint32_t aOffset, 558 uint32_t aLength) { 559 mOffset = aOffset; 560 mReplaceLength = aLength; 561 AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CharacterData_DeleteData); 562 IgnoredErrorResult error; 563 mCharacterData.DeleteData(mOffset, mReplaceLength, error); 564 if (NS_WARN_IF(mEditorBase.Destroyed())) { 565 return NS_ERROR_EDITOR_DESTROYED; 566 } 567 if (MOZ_UNLIKELY(error.Failed())) { 568 NS_WARNING("CharacterData::DeleteData() failed"); 569 return error.StealNSResult(); 570 } 571 return NS_OK; 572 } 573 574 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult InsertData(uint32_t aOffset, 575 const nsAString& aData) { 576 mOffset = aOffset; 577 mDataPtr = &aData; 578 AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CharacterData_InsertData); 579 IgnoredErrorResult error; 580 mCharacterData.InsertData(mOffset, aData, error); 581 // Don't keep storing the pointer, nobody can guarantee the lifetime. 582 mDataPtr = nullptr; 583 if (NS_WARN_IF(mEditorBase.Destroyed())) { 584 return NS_ERROR_EDITOR_DESTROYED; 585 } 586 if (MOZ_UNLIKELY(error.Failed())) { 587 NS_WARNING("CharacterData::InsertData() failed"); 588 return error.StealNSResult(); 589 } 590 return NS_OK; 591 } 592 593 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult ReplaceData( 594 uint32_t aOffset, uint32_t aReplaceLength, const nsAString& aData) { 595 mOffset = aOffset; 596 mReplaceLength = aReplaceLength; 597 mDataPtr = &aData; 598 AutoNotifyEditorOfAPICall notifier(*this, 599 DOMAPI::CharacterData_ReplaceData); 600 IgnoredErrorResult error; 601 mCharacterData.ReplaceData(mOffset, mReplaceLength, aData, error); 602 // Don't keep storing the pointer, nobody can guarantee the lifetime. 603 mDataPtr = nullptr; 604 if (NS_WARN_IF(mEditorBase.Destroyed())) { 605 return NS_ERROR_EDITOR_DESTROYED; 606 } 607 if (MOZ_UNLIKELY(error.Failed())) { 608 NS_WARNING("CharacterData::ReplaceData() failed"); 609 return error.StealNSResult(); 610 } 611 return NS_OK; 612 } 613 614 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult SetData(const nsAString& aData) { 615 mDataPtr = &aData; 616 AutoNotifyEditorOfAPICall notifier(*this, DOMAPI::CharacterData_SetData); 617 IgnoredErrorResult error; 618 mCharacterData.SetData(aData, error); 619 // Don't keep storing the pointer, nobody can guarantee the lifetime. 620 mDataPtr = nullptr; 621 if (NS_WARN_IF(mEditorBase.Destroyed())) { 622 return NS_ERROR_EDITOR_DESTROYED; 623 } 624 if (MOZ_UNLIKELY(error.Failed())) { 625 NS_WARNING("CharacterData::SetData() failed"); 626 return error.StealNSResult(); 627 } 628 return NS_OK; 629 } 630 631 /** 632 * Be aware, this may be too slow for the normal path. This should be used 633 * by debugging code like assertions or logging code. 634 * 635 * @param aExpectedData Specify the data which you call an above API 636 * calling method. 637 */ 638 [[nodiscard]] bool IsExpectedResult(const nsAString& aExpectedData) const { 639 switch (Type()) { 640 case DOMAPI::CharacterData_DeleteData: 641 // XXX We don't check whether the final data is expected one because 642 // we need to store the original value or the expected value, but that 643 // may require a big buffer if the text node has long text. 644 return mCharacterData.TextDataLength() >= mOffset; 645 case DOMAPI::CharacterData_InsertData: 646 case DOMAPI::CharacterData_ReplaceData: { 647 if (MOZ_UNLIKELY(mCharacterData.TextDataLength() < 648 mOffset + aExpectedData.Length())) { 649 return false; 650 } 651 // Let's check only the new data is expected value. 652 nsAutoString data; 653 mCharacterData.GetData(data); 654 return Substring(data, mOffset, aExpectedData.Length()) == 655 aExpectedData; 656 } 657 case DOMAPI::CharacterData_SetData: { 658 if (MOZ_UNLIKELY(mCharacterData.TextDataLength() != 659 aExpectedData.Length())) { 660 return false; 661 } 662 // We can check strictly only in this case. However, getting the 663 // value may be slow if the text node has long text. 664 nsAutoString data; 665 mCharacterData.GetData(data); 666 return data == aExpectedData; 667 } 668 default: 669 MOZ_ASSERT_UNREACHABLE("Instantiated with wrong type?"); 670 return false; 671 } 672 } 673 674 [[nodiscard]] virtual bool IsExpectedCharacterDataChanged( 675 nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const { 676 return aContent == &mCharacterData && aInfo.mChangeStart == mOffset && 677 aInfo.LengthOfRemovedText() == mReplaceLength && mDataPtr && 678 aInfo.mReplaceLength == mDataPtr->Length() && !aInfo.mDetails && 679 IsExpectedResult(*mDataPtr); 680 } 681 682 friend std::ostream& operator<<(std::ostream& aStream, 683 const AutoCharacterDataAPIWrapper& aWrapper) { 684 nsAutoString data; 685 aWrapper.mCharacterData.AppendTextTo(data); 686 MarkSelectionAndShrinkLongString shrunkenData( 687 data, aWrapper.mOffset, aWrapper.mOffset + aWrapper.mReplaceLength); 688 MakeHumanFriendly(shrunkenData); 689 aStream << aWrapper.Type() << "(node: " << aWrapper.mCharacterData 690 << ", data=\"" << NS_ConvertUTF16toUTF8(shrunkenData).get() 691 << "\" (length=" << data.Length() 692 << "), offset: " << aWrapper.mOffset 693 << ", replace length: " << aWrapper.mReplaceLength; 694 switch (aWrapper.Type()) { 695 case DOMAPI::CharacterData_DeleteData: 696 break; 697 case DOMAPI::CharacterData_InsertData: 698 case DOMAPI::CharacterData_ReplaceData: 699 case DOMAPI::CharacterData_SetData: { 700 MOZ_ASSERT(aWrapper.mDataPtr); 701 nsAutoString newData(aWrapper.mDataPtr ? *aWrapper.mDataPtr 702 : EmptyString()); 703 MakeHumanFriendly(newData); 704 aStream << ", inserting data=\"" << NS_ConvertUTF16toUTF8(newData).get() 705 << "\" (length=" 706 << (aWrapper.mDataPtr ? aWrapper.mDataPtr->Length() : 0u) 707 << ")"; 708 break; 709 } 710 default: 711 break; 712 } 713 return aStream << ")"; 714 } 715 716 protected: 717 MOZ_KNOWN_LIVE CharacterData& mCharacterData; 718 uint32_t mOffset = 0; 719 uint32_t mReplaceLength = 0; 720 // For avoiding to copy the string, we store the given string pointer only 721 // while calling the API because it's enough to check whether checking 722 // mutations are expected ones or not. 723 const nsAString* mDataPtr = nullptr; 724 }; 725 726 /** 727 * Wrapper class of nsICSSDeclaration::SetProperty and 728 * nsICSSDeclaration::RemoveProperty which modifies `style` attribute. 729 */ 730 class MOZ_STACK_CLASS AutoCSSDeclarationAPIWrapper 731 : public AutoDOMAPIWrapperBase { 732 public: 733 static AutoCSSDeclarationAPIWrapper* FromBase(AutoDOMAPIWrapperBase* aBase) { 734 switch (aBase->Type()) { 735 case DOMAPI::CSSDeclaration_SetProperty: 736 case DOMAPI::CSSDeclaration_RemoveProperty: 737 return static_cast<AutoCSSDeclarationAPIWrapper*>(aBase); 738 default: 739 return nullptr; 740 } 741 } 742 static AutoCSSDeclarationAPIWrapper* FromBaseOrNull( 743 AutoDOMAPIWrapperBase* aBase) { 744 return aBase ? FromBase(aBase) : nullptr; 745 } 746 static const AutoCSSDeclarationAPIWrapper* FromBase( 747 const AutoDOMAPIWrapperBase* aBase) { 748 return FromBase(const_cast<AutoDOMAPIWrapperBase*>(aBase)); 749 } 750 static const AutoCSSDeclarationAPIWrapper* FromBaseOrNull( 751 const AutoDOMAPIWrapperBase* aBase) { 752 return FromBaseOrNull(const_cast<AutoDOMAPIWrapperBase*>(aBase)); 753 } 754 755 MOZ_CAN_RUN_SCRIPT AutoCSSDeclarationAPIWrapper( 756 EditorBase& aEditorBase, nsStyledElement& aStyledElement, 757 nsDOMCSSDeclaration* aDeclaration = nullptr) 758 : AutoDOMAPIWrapperBase(aEditorBase), 759 mStyledElement(aStyledElement), 760 mCSSDeclaration(aDeclaration ? *aDeclaration 761 : *aStyledElement.Style()) {} 762 763 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 764 SetProperty(const nsACString& aPropertyName, const nsACString& aValues, 765 const nsACString& aPriority) { 766 mPropertyNamePtr = &aPropertyName; 767 mValuesPtr = &aValues; 768 mPriorityPtr = &aPriority; 769 AutoNotifyEditorOfAPICall notifier(*this, 770 DOMAPI::CSSDeclaration_SetProperty); 771 IgnoredErrorResult error; 772 mCSSDeclaration->SetProperty(aPropertyName, aValues, aPriority, error); 773 // Don't keep storing the pointers, nobody can guarantee the lifetime. 774 mPropertyNamePtr = mValuesPtr = mPriorityPtr = nullptr; 775 if (MOZ_UNLIKELY(error.Failed())) { 776 NS_WARNING( 777 nsPrintfCString("nsICSSDeclaration::SetProperty(\"%s\", \"%s\", " 778 "\"%s\") failed (mStyledElement=%s)", 779 PromiseFlatCString(aPropertyName).get(), 780 PromiseFlatCString(aValues).get(), 781 PromiseFlatCString(aPriority).get(), 782 ToString(mStyledElement).c_str()) 783 .get()); 784 return error.StealNSResult(); 785 } 786 return NS_OK; 787 } 788 789 [[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult 790 RemoveProperty(const nsACString& aPropertyName) { 791 mPropertyNamePtr = &aPropertyName; 792 AutoNotifyEditorOfAPICall notifier(*this, 793 DOMAPI::CSSDeclaration_RemoveProperty); 794 IgnoredErrorResult error; 795 mCSSDeclaration->RemoveProperty(aPropertyName, mRemovedValue, error); 796 // Don't keep storing the pointers, nobody can guarantee the lifetime. 797 mPropertyNamePtr = mValuesPtr = mPriorityPtr = nullptr; 798 if (MOZ_UNLIKELY(error.Failed())) { 799 NS_WARNING( 800 nsPrintfCString("nsICSSDeclaration::RemoveProperty(\"%s\") failed " 801 "(mStyledElement=%s, removed value=\"%s\")", 802 PromiseFlatCString(aPropertyName).get(), 803 ToString(mStyledElement).c_str(), mRemovedValue.get()) 804 .get()); 805 return error.StealNSResult(); 806 } 807 return NS_OK; 808 } 809 810 [[nodiscard]] const nsAutoCString& RemovedValueRef() const { 811 MOZ_ASSERT(Type() == DOMAPI::CSSDeclaration_RemoveProperty); 812 return mRemovedValue; 813 } 814 815 [[nodiscard]] virtual bool IsExpectedAttributeChanged( 816 Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute, 817 AttrModType aModType, const nsAttrValue* aOldValue) const { 818 // XXX We don't check the style value is expected one because it requires 819 // to store the original value and compute the expected new value. 820 return aAttribute == nsGkAtoms::style && 821 aNameSpaceID == kNameSpaceID_None && aElement == &mStyledElement && 822 IsAdditionOrRemoval(aModType); 823 } 824 825 friend std::ostream& operator<<( 826 std::ostream& aStream, const AutoCSSDeclarationAPIWrapper& aWrapper) { 827 MOZ_ASSERT(aWrapper.mPropertyNamePtr); 828 aStream << aWrapper.Type() 829 << "(element: " << NodeToString(&aWrapper.mStyledElement).get() 830 << ", property: \"" 831 << (aWrapper.mPropertyNamePtr 832 ? PromiseFlatCString(*aWrapper.mPropertyNamePtr).get() 833 : "") 834 << "\""; 835 switch (aWrapper.Type()) { 836 case DOMAPI::CSSDeclaration_SetProperty: { 837 MOZ_ASSERT(aWrapper.mValuesPtr); 838 nsAutoCString values(aWrapper.mValuesPtr ? *aWrapper.mValuesPtr 839 : EmptyCString()); 840 MakeHumanFriendly(values); 841 aStream << ", values=\"" << values.get() << "\", priority=\"" 842 << (aWrapper.mPriorityPtr 843 ? PromiseFlatCString(*aWrapper.mPriorityPtr).get() 844 : "") 845 << "\""; 846 break; 847 } 848 case DOMAPI::Element_UnsetAttr: 849 default: 850 break; 851 } 852 return aStream << ")"; 853 } 854 855 protected: 856 MOZ_KNOWN_LIVE nsStyledElement& mStyledElement; 857 MOZ_KNOWN_LIVE const OwningNonNull<nsDOMCSSDeclaration> mCSSDeclaration; 858 nsAutoCString mRemovedValue; 859 // For avoiding to copy the strings, we store the given string pointers only 860 // while calling the API because it's enough to check whether checking 861 // mutations are expected ones or not. 862 const nsACString* mPropertyNamePtr = nullptr; 863 const nsACString* mValuesPtr = nullptr; 864 const nsACString* mPriorityPtr = nullptr; 865 }; 866 867 inline std::ostream& operator<<(std::ostream& aStream, 868 const AutoDOMAPIWrapperBase& aWrapperBase) { 869 switch (aWrapperBase.Type()) { 870 case AutoDOMAPIWrapperBase::DOMAPI::nsINode_AppendChild: 871 case AutoDOMAPIWrapperBase::DOMAPI::nsINode_InsertBefore: 872 case AutoDOMAPIWrapperBase::DOMAPI::nsINode_Remove: 873 case AutoDOMAPIWrapperBase::DOMAPI::nsINode_RemoveChild: { 874 const auto* runner = AutoNodeAPIWrapper::FromBase(&aWrapperBase); 875 return aStream << *runner; 876 } 877 case AutoDOMAPIWrapperBase::DOMAPI::Element_SetAttr: 878 case AutoDOMAPIWrapperBase::DOMAPI::Element_UnsetAttr: { 879 const auto* runner = AutoElementAttrAPIWrapper::FromBase(&aWrapperBase); 880 return aStream << *runner; 881 } 882 case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_DeleteData: 883 case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_InsertData: 884 case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_ReplaceData: 885 case AutoDOMAPIWrapperBase::DOMAPI::CharacterData_SetData: { 886 const auto* runner = AutoCharacterDataAPIWrapper::FromBase(&aWrapperBase); 887 return aStream << *runner; 888 } 889 case AutoDOMAPIWrapperBase::DOMAPI::CSSDeclaration_SetProperty: 890 case AutoDOMAPIWrapperBase::DOMAPI::CSSDeclaration_RemoveProperty: { 891 const auto* runner = 892 AutoCSSDeclarationAPIWrapper::FromBase(&aWrapperBase); 893 return aStream << *runner; 894 } 895 default: 896 MOZ_ASSERT_UNREACHABLE("Invalid DOMAPI value"); 897 return aStream << "<The wrapper has invalid DOMAPI value>"; 898 } 899 } 900 901 } // namespace mozilla 902 903 #endif // #ifndef EditorDOMAPIWrapper_h