tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

HTMLEditorMutationObserver.cpp (21588B)


      1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* This Source Code Form is subject to the terms of the Mozilla Public
      3 * License, v. 2.0. If a copy of the MPL was not distributed with this
      4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      5 
      6 #include "HTMLEditor.h"
      7 
      8 #include "EditAction.h"
      9 #include "EditorDOMAPIWrapper.h"
     10 #include "EditorUtils.h"
     11 #include "HTMLEditorNestedClasses.h"
     12 
     13 #include "mozilla/Assertions.h"
     14 #include "mozilla/Attributes.h"
     15 #include "mozilla/DebugOnly.h"
     16 #include "mozilla/IMEStateManager.h"
     17 #include "mozilla/Logging.h"
     18 #include "mozilla/Maybe.h"
     19 #include "mozilla/RefPtr.h"
     20 #include "mozilla/dom/AncestorIterator.h"
     21 #include "mozilla/dom/Element.h"
     22 #include "mozInlineSpellChecker.h"
     23 #include "nsContentUtils.h"
     24 #include "nsIContent.h"
     25 #include "nsIContentInlines.h"
     26 #include "nsIMutationObserver.h"
     27 #include "nsINode.h"
     28 #include "nsRange.h"
     29 #include "nsThreadUtils.h"
     30 
     31 namespace mozilla {
     32 
     33 using namespace dom;
     34 
     35 /******************************************************************************
     36 * DOM mutation logger
     37 ******************************************************************************/
     38 
     39 // - HTMLEditorMutation:3: Logging only mutations in editable containers which
     40 // is not expected.
     41 // - HTMLEditorMutation:4: Logging only mutations in editable containers which
     42 // is either expected or not expected.
     43 // - HTMLEditorMutation:5: Logging any mutations including in
     44 // non-editable containers.
     45 LazyLogModule gHTMLEditorMutationLog("HTMLEditorMutation");
     46 
     47 LogLevel HTMLEditor::MutationLogLevelOf(
     48    nsIContent* aContent,
     49    const CharacterDataChangeInfo* aCharacterDataChangeInfo,
     50    DOMMutationType aDOMMutationType) const {
     51  // Should be called only when the "info" level is enabled at least since we
     52  // shouldn't add any new unnecessary calls in the hot paths when the logging
     53  // is disabled.
     54  MOZ_ASSERT(MOZ_LOG_TEST(gHTMLEditorMutationLog, LogLevel::Info));
     55 
     56  if (MOZ_UNLIKELY(!aContent->IsInComposedDoc())) {
     57    return LogLevel::Disabled;
     58  }
     59  Element* const containerElement = aContent->GetAsElementOrParentElement();
     60  if (!containerElement || !containerElement->IsEditable()) {
     61    return MOZ_LOG_TEST(gHTMLEditorMutationLog, LogLevel::Verbose)
     62               ? LogLevel::Verbose
     63               : LogLevel::Disabled;
     64  }
     65  if (!mRunningDOMAPIWrapper) {
     66    return LogLevel::Info;
     67  }
     68  switch (aDOMMutationType) {
     69    case DOMMutationType::ContentAppended:
     70      return mRunningDOMAPIWrapper->IsExpectedContentAppended(aContent)
     71                 ? LogLevel::Debug
     72                 : LogLevel::Info;
     73    case DOMMutationType::ContentInserted:
     74      return mRunningDOMAPIWrapper->IsExpectedContentInserted(aContent)
     75                 ? LogLevel::Debug
     76                 : LogLevel::Info;
     77    case DOMMutationType::ContentWillBeRemoved:
     78      return mRunningDOMAPIWrapper->IsExpectedContentWillBeRemoved(aContent)
     79                 ? LogLevel::Debug
     80                 : LogLevel::Info;
     81    case DOMMutationType::CharacterDataChanged:
     82      MOZ_ASSERT(aCharacterDataChangeInfo);
     83      return mRunningDOMAPIWrapper->IsExpectedCharacterDataChanged(
     84                 aContent, *aCharacterDataChangeInfo)
     85                 ? LogLevel::Debug
     86                 : LogLevel::Info;
     87    default:
     88      MOZ_ASSERT_UNREACHABLE("Invalid DOMMutationType value");
     89      return LogLevel::Disabled;
     90  }
     91 }
     92 
     93 // - HTMLEditorAttrMutation:3: Logging only mutations of editable element which
     94 // is not expected.
     95 // - HTMLEditorAttrMutation:4: Logging only mutations of editable element which
     96 // is either expected or not expected.
     97 // - HTMLEditorAttrMutation:5: Logging any mutations including non-editable
     98 // elements' attributes.
     99 LazyLogModule gHTMLEditorAttrMutationLog("HTMLEditorAttrMutation");
    100 
    101 LogLevel HTMLEditor::AttrMutationLogLevelOf(
    102    Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
    103    AttrModType aModType, const nsAttrValue* aOldValue) const {
    104  // Should be called only when the "info" level is enabled at least since we
    105  // shouldn't add any new unnecessary calls in the hot paths when the logging
    106  // is disabled.
    107  MOZ_ASSERT(MOZ_LOG_TEST(gHTMLEditorAttrMutationLog, LogLevel::Info));
    108  if (MOZ_UNLIKELY(!aElement->IsInComposedDoc())) {
    109    return LogLevel::Disabled;
    110  }
    111  if (!aElement->IsEditable()) {
    112    return MOZ_LOG_TEST(gHTMLEditorAttrMutationLog, LogLevel::Verbose)
    113               ? LogLevel::Verbose
    114               : LogLevel::Disabled;
    115  }
    116  if (!mRunningDOMAPIWrapper) {
    117    return LogLevel::Info;
    118  }
    119  return mRunningDOMAPIWrapper->IsExpectedAttributeChanged(
    120             aElement, aNameSpaceID, aAttribute, aModType, aOldValue)
    121             ? LogLevel::Debug
    122             : LogLevel::Info;
    123 }
    124 
    125 void HTMLEditor::MaybeLogContentAppended(nsIContent* aFirstNewContent) const {
    126  const LogLevel logLevel = MutationLogLevelOf(
    127      aFirstNewContent, nullptr, DOMMutationType::ContentAppended);
    128  if (logLevel == LogLevel::Disabled) {
    129    return;
    130  }
    131  MOZ_LOG(
    132      gHTMLEditorMutationLog, logLevel,
    133      ("%p %s ContentAppended: %s (previousSibling=%s, nextSibling=%s)", this,
    134       logLevel == LogLevel::Debug ? "HTMLEditor  " : "SomebodyElse",
    135       NodeToString(aFirstNewContent).get(),
    136       NodeToString(aFirstNewContent ? aFirstNewContent->GetPreviousSibling()
    137                                     : nullptr)
    138           .get(),
    139       NodeToString(aFirstNewContent ? aFirstNewContent->GetNextSibling()
    140                                     : nullptr)
    141           .get()));
    142 }
    143 
    144 void HTMLEditor::MaybeLogContentInserted(nsIContent* aChild) const {
    145  const LogLevel logLevel =
    146      MutationLogLevelOf(aChild, nullptr, DOMMutationType::ContentInserted);
    147  if (logLevel == LogLevel::Disabled) {
    148    return;
    149  }
    150  MOZ_LOG(gHTMLEditorMutationLog, logLevel,
    151          ("%p %s ContentInserted: %s (previousSibling=%s, nextSibling=%s)",
    152           this, logLevel == LogLevel::Debug ? "HTMLEditor  " : "SomebodyElse",
    153           NodeToString(aChild).get(),
    154           NodeToString(aChild ? aChild->GetPreviousSibling() : nullptr).get(),
    155           NodeToString(aChild ? aChild->GetNextSibling() : nullptr).get()));
    156 }
    157 
    158 void HTMLEditor::MaybeLogContentWillBeRemoved(nsIContent* aChild) const {
    159  const LogLevel logLevel = MutationLogLevelOf(
    160      aChild, nullptr, DOMMutationType::ContentWillBeRemoved);
    161  if (logLevel == LogLevel::Disabled) {
    162    return;
    163  }
    164  MOZ_LOG(
    165      gHTMLEditorMutationLog, logLevel,
    166      ("%p %s ContentWillBeRemoved: %s (previousSibling=%s, nextSibling=%s)",
    167       this, logLevel == LogLevel::Debug ? "HTMLEditor  " : "SomebodyElse",
    168       NodeToString(aChild).get(),
    169       NodeToString(aChild ? aChild->GetPreviousSibling() : nullptr).get(),
    170       NodeToString(aChild ? aChild->GetNextSibling() : nullptr).get()));
    171 }
    172 
    173 void HTMLEditor::MaybeLogCharacterDataChanged(
    174    nsIContent* aContent, const CharacterDataChangeInfo& aInfo) const {
    175  const LogLevel logLevel = MutationLogLevelOf(
    176      aContent, &aInfo, DOMMutationType::CharacterDataChanged);
    177  if (logLevel == LogLevel::Disabled) {
    178    return;
    179  }
    180  nsAutoString data;
    181  aContent->GetCharacterDataBuffer()->AppendTo(data);
    182  MarkSelectionAndShrinkLongString shrunkenData(
    183      data, aInfo.mChangeStart, aInfo.mChangeStart + aInfo.mReplaceLength);
    184  MakeHumanFriendly(shrunkenData);
    185  MOZ_LOG(
    186      gHTMLEditorMutationLog, logLevel,
    187      ("%p %s CharacterDataChanged: %s, data=\"%s\", (length=%u), aInfo=%s",
    188       this, logLevel == LogLevel::Debug ? "HTMLEditor  " : "SomebodyElse",
    189       ToString(*aContent).c_str(), NS_ConvertUTF16toUTF8(shrunkenData).get(),
    190       aContent->Length(), ToString(aInfo).c_str()));
    191 }
    192 
    193 void HTMLEditor::MaybeLogAttributeChanged(Element* aElement,
    194                                          int32_t aNameSpaceID,
    195                                          nsAtom* aAttribute,
    196                                          AttrModType aModType,
    197                                          const nsAttrValue* aOldValue) const {
    198  const LogLevel logLevel = AttrMutationLogLevelOf(
    199      aElement, aNameSpaceID, aAttribute, aModType, aOldValue);
    200  if (logLevel == LogLevel::Disabled) {
    201    return;
    202  }
    203  nsAutoString value;
    204  aElement->GetAttr(aAttribute, value);
    205  MOZ_LOG(
    206      gHTMLEditorAttrMutationLog, logLevel,
    207      ("%p %s AttributeChanged: %s of %s %s", this,
    208       logLevel == LogLevel::Debug ? "HTMLEditor  " : "SomebodyElse",
    209       nsAutoAtomCString(aAttribute).get(), NodeToString(aElement).get(),
    210       aModType == AttrModType::Removal
    211           ? "Removed"
    212           : nsPrintfCString("to \"%s\"", NS_ConvertUTF16toUTF8(value).get())
    213                 .get()));
    214 }
    215 
    216 /******************************************************************************
    217 * mozilla::HTMLEditor - Start/end of a DOM API call to modify the DOM
    218 ******************************************************************************/
    219 
    220 const AutoDOMAPIWrapperBase* HTMLEditor::OnDOMAPICallStart(
    221    const AutoDOMAPIWrapperBase& aWrapperBase) {
    222  const AutoDOMAPIWrapperBase* const prevRunner = mRunningDOMAPIWrapper;
    223  mRunningDOMAPIWrapper = &aWrapperBase;
    224  MOZ_LOG(gHTMLEditorMutationLog, LogLevel::Warning,
    225          (">>>> %p Calling DOM API: %s", this,
    226           ToString(*mRunningDOMAPIWrapper).c_str()));
    227  return prevRunner;
    228 }
    229 
    230 void HTMLEditor::OnDOMAPICallEnd(const AutoDOMAPIWrapperBase* aPrevWrapper) {
    231  MOZ_LOG(gHTMLEditorMutationLog, LogLevel::Warning,
    232          ("<<<< %p Called DOM API: %s", this,
    233           ToString(mRunningDOMAPIWrapper->Type()).c_str()));
    234  mRunningDOMAPIWrapper = aPrevWrapper;
    235 }
    236 
    237 /******************************************************************************
    238 * mozilla::HTMLEditor - mutation observers/handlers
    239 ******************************************************************************/
    240 
    241 void HTMLEditor::NotifyRootChanged() {
    242  MOZ_ASSERT(mPendingRootElementUpdatedRunner,
    243             "HTMLEditor::NotifyRootChanged() should be called via a runner");
    244  mPendingRootElementUpdatedRunner = nullptr;
    245 
    246  nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
    247 
    248  AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing);
    249  if (NS_WARN_IF(!editActionData.CanHandle())) {
    250    return;
    251  }
    252 
    253  RemoveEventListeners();
    254  nsresult rv = InstallEventListeners();
    255  if (NS_FAILED(rv)) {
    256    NS_WARNING("HTMLEditor::InstallEventListeners() failed, but ignored");
    257    return;
    258  }
    259 
    260  UpdateRootElement();
    261 
    262  if (MOZ_LIKELY(mRootElement)) {
    263    rv = MaybeCollapseSelectionAtFirstEditableNode(false);
    264    if (NS_FAILED(rv)) {
    265      NS_WARNING(
    266          "HTMLEditor::MaybeCollapseSelectionAtFirstEditableNode(false) "
    267          "failed, "
    268          "but ignored");
    269      return;
    270    }
    271 
    272    // When this editor has focus, we need to reset the selection limiter to
    273    // new root.  Otherwise, that is going to be done when this gets focus.
    274    nsCOMPtr<nsINode> node = GetFocusedNode();
    275    if (node) {
    276      DebugOnly<nsresult> rvIgnored = InitializeSelection(*node);
    277      NS_WARNING_ASSERTION(
    278          NS_SUCCEEDED(rvIgnored),
    279          "EditorBase::InitializeSelection() failed, but ignored");
    280    }
    281 
    282    SyncRealTimeSpell();
    283  }
    284 
    285  RefPtr<Element> newRootElement(mRootElement);
    286  IMEStateManager::OnUpdateHTMLEditorRootElement(*this, newRootElement);
    287 }
    288 
    289 // nsStubMutationObserver::ContentAppended override
    290 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentAppended(
    291    nsIContent* aFirstNewContent, const ContentAppendInfo&) {
    292  DoContentInserted(aFirstNewContent, ContentNodeIs::Appended);
    293 }
    294 
    295 // nsStubMutationObserver::ContentInserted override
    296 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentInserted(
    297    nsIContent* aChild, const ContentInsertInfo&) {
    298  DoContentInserted(aChild, ContentNodeIs::Inserted);
    299 }
    300 
    301 bool HTMLEditor::IsInObservedSubtree(nsIContent* aChild) {
    302  if (!aChild) {
    303    return false;
    304  }
    305 
    306  // FIXME(emilio, bug 1596856): This should probably work if the root is in the
    307  // same shadow tree as the child, probably? I don't know what the
    308  // contenteditable-in-shadow-dom situation is.
    309  if (Element* root = GetRoot()) {
    310    // To be super safe here, check both ChromeOnlyAccess and NAC / Shadow DOM.
    311    // That catches (also unbound) native anonymous content and ShadowDOM.
    312    if (root->ChromeOnlyAccess() != aChild->ChromeOnlyAccess() ||
    313        root->IsInNativeAnonymousSubtree() !=
    314            aChild->IsInNativeAnonymousSubtree() ||
    315        root->IsInShadowTree() != aChild->IsInShadowTree()) {
    316      return false;
    317    }
    318  }
    319 
    320  return !aChild->ChromeOnlyAccess() && !aChild->IsInShadowTree() &&
    321         !aChild->IsInNativeAnonymousSubtree();
    322 }
    323 
    324 bool HTMLEditor::ShouldReplaceRootElement() const {
    325  if (!mRootElement) {
    326    // If we don't know what is our root element, we should find our root.
    327    return true;
    328  }
    329 
    330  // If we temporary set document root element to mRootElement, but there is
    331  // body element now, we should replace the root element by the body element.
    332  return mRootElement != GetBodyElement();
    333 }
    334 
    335 void HTMLEditor::DoContentInserted(nsIContent* aChild,
    336                                   ContentNodeIs aContentNodeIs) {
    337  MOZ_ASSERT(aChild);
    338  nsINode* container = aChild->GetParentNode();
    339  MOZ_ASSERT(container);
    340 
    341  if (!IsInObservedSubtree(aChild)) {
    342    return;
    343  }
    344 
    345  if (MOZ_LOG_TEST(gHTMLEditorMutationLog, LogLevel::Info)) {
    346    if (aContentNodeIs == ContentNodeIs::Appended) {
    347      MaybeLogContentAppended(aChild);
    348    } else {
    349      MOZ_ASSERT(aContentNodeIs == ContentNodeIs::Inserted);
    350      MaybeLogContentInserted(aChild);
    351    }
    352  }
    353 
    354  // XXX Why do we need this? This method is a helper of mutation observer.
    355  //     So, the callers of mutation observer should guarantee that this won't
    356  //     be deleted at least during the call.
    357  RefPtr<HTMLEditor> kungFuDeathGrip(this);
    358 
    359  // Do not create AutoEditActionDataSetter here because it grabs `Selection`,
    360  // but that appear in the profile. If you need to create to it in some cases,
    361  // you should do it in the minimum scope.
    362 
    363  if (ShouldReplaceRootElement()) {
    364    // Forget maybe disconnected root element right now because nobody should
    365    // work with it.
    366    mRootElement = nullptr;
    367    if (mPendingRootElementUpdatedRunner) {
    368      return;
    369    }
    370    mPendingRootElementUpdatedRunner = NewRunnableMethod(
    371        "HTMLEditor::NotifyRootChanged", this, &HTMLEditor::NotifyRootChanged);
    372    nsContentUtils::AddScriptRunner(
    373        do_AddRef(mPendingRootElementUpdatedRunner));
    374    return;
    375  }
    376 
    377  // We don't need to handle our own modifications
    378  if (!GetTopLevelEditSubAction() && container->IsEditable()) {
    379    if (EditorUtils::IsPaddingBRElementForEmptyEditor(*aChild)) {
    380      // Ignore insertion of the padding <br> element.
    381      return;
    382    }
    383    nsresult rv = RunOrScheduleOnModifyDocument();
    384    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    385      return;
    386    }
    387    NS_WARNING_ASSERTION(
    388        NS_SUCCEEDED(rv),
    389        "HTMLEditor::RunOrScheduleOnModifyDocument() failed, but ignored");
    390 
    391    // Update spellcheck for only the newly-inserted node (bug 743819)
    392    if (mInlineSpellChecker) {
    393      nsIContent* endContent = aChild;
    394      if (aContentNodeIs == ContentNodeIs::Appended) {
    395        nsIContent* child = nullptr;
    396        for (child = aChild; child; child = child->GetNextSibling()) {
    397          if (child->InclusiveDescendantMayNeedSpellchecking(this)) {
    398            break;
    399          }
    400        }
    401        if (!child) {
    402          // No child needed spellchecking, return.
    403          return;
    404        }
    405 
    406        // Maybe more than 1 child was appended.
    407        endContent = container->GetLastChild();
    408      } else if (!aChild->InclusiveDescendantMayNeedSpellchecking(this)) {
    409        return;
    410      }
    411 
    412      RefPtr<nsRange> range = nsRange::Create(aChild);
    413      range->SelectNodesInContainer(container, aChild, endContent);
    414      DebugOnly<nsresult> rvIgnored =
    415          mInlineSpellChecker->SpellCheckRange(range);
    416      NS_WARNING_ASSERTION(
    417          rvIgnored == NS_ERROR_NOT_INITIALIZED || NS_SUCCEEDED(rvIgnored),
    418          "mozInlineSpellChecker::SpellCheckRange() failed, but ignored");
    419    }
    420  }
    421 }
    422 
    423 // nsStubMutationObserver::ContentWillBeRemoved override
    424 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::ContentWillBeRemoved(
    425    nsIContent* aChild, const ContentRemoveInfo&) {
    426  if (mLastCollapsibleWhiteSpaceAppendedTextNode == aChild) {
    427    mLastCollapsibleWhiteSpaceAppendedTextNode = nullptr;
    428  }
    429 
    430  if (!IsInObservedSubtree(aChild)) {
    431    return;
    432  }
    433 
    434  if (MOZ_LOG_TEST(gHTMLEditorMutationLog, LogLevel::Info)) {
    435    MaybeLogContentWillBeRemoved(aChild);
    436  }
    437 
    438  // XXX Why do we need to do this?  This method is a mutation observer's
    439  //     method.  Therefore, the caller should guarantee that this won't be
    440  //     deleted during the call.
    441  RefPtr<HTMLEditor> kungFuDeathGrip(this);
    442 
    443  // Do not create AutoEditActionDataSetter here because it grabs `Selection`,
    444  // but that appear in the profile. If you need to create to it in some cases,
    445  // you should do it in the minimum scope.
    446 
    447  // FYI: mRootElement may be the <body> of the document or the root element.
    448  // Therefore, we don't need to check it across shadow DOM boundaries.
    449  if (mRootElement && mRootElement->IsInclusiveDescendantOf(aChild)) {
    450    // Forget the disconnected root element right now because nobody should work
    451    // with it.
    452    mRootElement = nullptr;
    453    if (mPendingRootElementUpdatedRunner) {
    454      return;
    455    }
    456    mPendingRootElementUpdatedRunner = NewRunnableMethod(
    457        "HTMLEditor::NotifyRootChanged", this, &HTMLEditor::NotifyRootChanged);
    458    nsContentUtils::AddScriptRunner(
    459        do_AddRef(mPendingRootElementUpdatedRunner));
    460    return;
    461  }
    462 
    463  // We don't need to handle our own modifications
    464  if (!GetTopLevelEditSubAction() && aChild->GetParentNode()->IsEditable()) {
    465    if (aChild && EditorUtils::IsPaddingBRElementForEmptyEditor(*aChild)) {
    466      // Ignore removal of the padding <br> element for empty editor.
    467      return;
    468    }
    469 
    470    nsresult rv = RunOrScheduleOnModifyDocument(aChild);
    471    if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    472      return;
    473    }
    474    NS_WARNING_ASSERTION(
    475        NS_SUCCEEDED(rv),
    476        "HTMLEditor::RunOrScheduleOnModifyDocument() failed, but ignored");
    477  }
    478 }
    479 
    480 // nsStubMutationObserver::CharacterDataChanged override
    481 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::CharacterDataChanged(
    482    nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
    483  if (!IsInObservedSubtree(aContent)) {
    484    return;
    485  }
    486  if (MOZ_LOG_TEST(gHTMLEditorMutationLog, LogLevel::Info)) {
    487    MaybeLogCharacterDataChanged(aContent, aInfo);
    488  }
    489  if (!mInlineSpellChecker || !aContent->IsEditable() ||
    490      GetTopLevelEditSubAction() != EditSubAction::eNone) {
    491    return;
    492  }
    493 
    494  nsIContent* parent = aContent->GetParent();
    495  if (!parent || !parent->InclusiveDescendantMayNeedSpellchecking(this)) {
    496    return;
    497  }
    498 
    499  RefPtr<nsRange> range = nsRange::Create(aContent);
    500  range->SelectNodesInContainer(parent, aContent, aContent);
    501  DebugOnly<nsresult> rvIgnored = mInlineSpellChecker->SpellCheckRange(range);
    502 }
    503 
    504 nsresult HTMLEditor::RunOrScheduleOnModifyDocument(
    505    const nsIContent* aContentWillBeRemoved /* = nullptr */) {
    506  if (mPendingDocumentModifiedRunner) {
    507    return NS_OK;  // We've already posted same runnable into the queue.
    508  }
    509  mPendingDocumentModifiedRunner = new DocumentModifiedEvent(*this);
    510  nsContentUtils::AddScriptRunner(do_AddRef(mPendingDocumentModifiedRunner));
    511  // Be aware, if OnModifyDocument() may be called synchronously, the
    512  // editor might have been destroyed here.
    513  return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
    514 }
    515 
    516 // nsStubMutationObserver::AttributeChanged override
    517 MOZ_CAN_RUN_SCRIPT_BOUNDARY void HTMLEditor::AttributeChanged(
    518    Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
    519    AttrModType aModType, const nsAttrValue* aOldValue) {
    520  if (MOZ_LOG_TEST(gHTMLEditorAttrMutationLog, LogLevel::Info) &&
    521      IsInObservedSubtree(aElement)) {
    522    MaybeLogAttributeChanged(aElement, aNameSpaceID, aAttribute, aModType,
    523                             aOldValue);
    524  }
    525 }
    526 
    527 nsresult HTMLEditor::OnModifyDocument(const DocumentModifiedEvent& aRunner) {
    528  MOZ_ASSERT(mPendingDocumentModifiedRunner,
    529             "HTMLEditor::OnModifyDocument() should be called via a runner");
    530  MOZ_ASSERT(&aRunner == mPendingDocumentModifiedRunner);
    531  mPendingDocumentModifiedRunner = nullptr;
    532 
    533  Maybe<AutoEditActionDataSetter> editActionData;
    534  if (!IsEditActionDataAvailable()) {
    535    editActionData.emplace(*this,
    536                           EditAction::eCreatePaddingBRElementForEmptyEditor);
    537    if (NS_WARN_IF(!editActionData->CanHandle())) {
    538      return NS_ERROR_NOT_AVAILABLE;
    539    }
    540  }
    541 
    542  // EnsureNoPaddingBRElementForEmptyEditor() below may cause a flush, which
    543  // could destroy the editor
    544  nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
    545 
    546  // Delete our padding <br> element for empty editor, if we have one, since
    547  // the document might not be empty any more.
    548  nsresult rv = EnsureNoPaddingBRElementForEmptyEditor();
    549  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    550    return rv;
    551  }
    552  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
    553                       "EditorBase::EnsureNoPaddingBRElementForEmptyEditor() "
    554                       "failed, but ignored");
    555 
    556  // Try to recreate the padding <br> element for empty editor if needed.
    557  rv = MaybeCreatePaddingBRElementForEmptyEditor();
    558  if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
    559    return NS_ERROR_EDITOR_DESTROYED;
    560  }
    561  NS_WARNING_ASSERTION(
    562      NS_SUCCEEDED(rv),
    563      "EditorBase::MaybeCreatePaddingBRElementForEmptyEditor() failed");
    564 
    565  return rv;
    566 }
    567 
    568 }  // namespace mozilla