Bug 1386110 - Use a smart pointer to reliably de-register NAC regardless of how it goes away. r=masayuki, a=lizzard
☠☠ backed out by ce1a65cec332 ☠ ☠
authorBobby Holley <bobbyholley@gmail.com>
Wed, 02 Aug 2017 12:37:44 -0700
changeset 423458 60c17d37ab38886b0a0d7aca8f0c44a1b8d5a0e5
parent 423457 6efea60c6783a8d0bdac12150a7acbc5ba91f5e5
child 423459 87c5252d5e6255d528704a5afd68e2906054fc8d
push id1517
push userjlorenzo@mozilla.com
push dateThu, 14 Sep 2017 16:50:54 +0000
treeherdermozilla-release@3b41fd564418 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki, lizzard
bugs1386110
milestone56.0
Bug 1386110 - Use a smart pointer to reliably de-register NAC regardless of how it goes away. r=masayuki, a=lizzard MozReview-Commit-ID: HTSu5BjxD8I
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
editor/libeditor/HTMLAbsPositionEditor.cpp
editor/libeditor/HTMLAnonymousNodeEditor.cpp
editor/libeditor/HTMLEditor.h
editor/libeditor/HTMLEditorObjectResizer.cpp
editor/libeditor/HTMLInlineTableEditor.cpp
editor/libeditor/ManualNAC.h
editor/libeditor/moz.build
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -61,16 +61,17 @@
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozilla/Likely.h"
+#include "mozilla/ManualNAC.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/dom/Selection.h"
 #include "mozilla/TextEvents.h"
 #include "nsArrayUtils.h"
 #include "nsAString.h"
 #include "nsAttrName.h"
 #include "nsAttrValue.h"
@@ -10343,17 +10344,17 @@ nsContentUtils::AppendNativeAnonymousChi
       primaryFrame->AppendOwnedAnonBoxes(ownedAnonBoxes);
       for (nsIFrame::OwnedAnonBox& box : ownedAnonBoxes) {
         MOZ_ASSERT(box.mAnonBoxFrame->GetContent() == aContent);
         AppendNativeAnonymousChildrenFromFrame(box.mAnonBoxFrame, aKids, aFlags);
       }
     }
 
     // Get manually created NAC (editor resize handles, etc.).
-    if (auto nac = static_cast<ManualNAC*>(
+    if (auto nac = static_cast<ManualNACArray*>(
           aContent->GetProperty(nsGkAtoms::manualNACProperty))) {
       aKids.AppendElements(*nac);
     }
   }
 
   // The root scroll frame is not the primary frame of the root element.
   // Detect and handle this case.
   if (!(aFlags & nsIContent::eSkipDocumentLevelNativeAnonymousContent) &&
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -192,25 +192,16 @@ struct EventNameMapping
   // True if mAtom is possibly used by special SVG/SMIL events, but
   // mMessage is eUnidentifiedEvent. See EventNameList.h
   bool mMaybeSpecialSVGorSMILEvent;
 };
 
 typedef bool (*CallOnRemoteChildFunction) (mozilla::dom::TabParent* aTabParent,
                                            void* aArg);
 
-namespace mozilla {
-// 16 seems to be the maximum number of manual NAC nodes that editor
-// creates for a given element.
-//
-// These need to be manually removed by the machinery that sets the NAC,
-// otherwise we'll leak.
-typedef AutoTArray<RefPtr<mozilla::dom::Element>,16> ManualNAC;
-}
-
 class nsContentUtils
 {
   friend class nsAutoScriptBlockerSuppressNodeRemoved;
   typedef mozilla::dom::Element Element;
   typedef mozilla::TimeDuration TimeDuration;
 
 public:
   static nsresult Init();
--- a/editor/libeditor/HTMLAbsPositionEditor.cpp
+++ b/editor/libeditor/HTMLAbsPositionEditor.cpp
@@ -224,33 +224,33 @@ HTMLEditor::GetElementZIndex(nsIDOMEleme
   if (!zIndexStr.EqualsLiteral("auto")) {
     nsresult errorCode;
     *aZindex = zIndexStr.ToInteger(&errorCode);
   }
 
   return NS_OK;
 }
 
-already_AddRefed<Element>
+ManualNACPtr
 HTMLEditor::CreateGrabber(nsINode* aParentNode)
 {
   // let's create a grabber through the element factory
-  RefPtr<Element> ret =
+  ManualNACPtr ret =
     CreateAnonymousElement(nsGkAtoms::span, GetAsDOMNode(aParentNode),
                            NS_LITERAL_STRING("mozGrabber"), false);
   if (NS_WARN_IF(!ret)) {
     return nullptr;
   }
 
   // add the mouse listener so we can detect a click on a resizer
   nsCOMPtr<nsIDOMEventTarget> evtTarget = do_QueryInterface(ret);
   evtTarget->AddEventListener(NS_LITERAL_STRING("mousedown"),
                               mEventListener, false);
 
-  return ret.forget();
+  return ret;
 }
 
 NS_IMETHODIMP
 HTMLEditor::RefreshGrabber()
 {
   NS_ENSURE_TRUE(mAbsolutelyPositionedObject, NS_ERROR_NULL_POINTER);
 
   nsresult rv =
@@ -284,20 +284,18 @@ HTMLEditor::HideGrabber()
   NS_ENSURE_TRUE(mGrabber, NS_ERROR_NULL_POINTER);
 
   // get the presshell's document observer interface.
   nsCOMPtr<nsIPresShell> ps = GetPresShell();
   // We allow the pres shell to be null; when it is, we presume there
   // are no document observers to notify, but we still want to
   // UnbindFromTree.
 
-  DeleteRefToAnonymousNode(mGrabber, ps);
-  mGrabber = nullptr;
-  DeleteRefToAnonymousNode(mPositioningShadow, ps);
-  mPositioningShadow = nullptr;
+  DeleteRefToAnonymousNode(Move(mGrabber), ps);
+  DeleteRefToAnonymousNode(Move(mPositioningShadow), ps);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLEditor::ShowGrabberOnElement(nsIDOMElement* aElement)
 {
   nsCOMPtr<Element> element = do_QueryInterface(aElement);
@@ -386,17 +384,17 @@ HTMLEditor::GrabberClicked()
 
 nsresult
 HTMLEditor::EndMoving()
 {
   if (mPositioningShadow) {
     nsCOMPtr<nsIPresShell> ps = GetPresShell();
     NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
 
-    DeleteRefToAnonymousNode(mPositioningShadow, ps);
+    DeleteRefToAnonymousNode(Move(mPositioningShadow), ps);
 
     mPositioningShadow = nullptr;
   }
   nsCOMPtr<nsIDOMEventTarget> piTarget = GetDOMEventTarget();
 
   if (piTarget && mMouseMotionListenerP) {
     DebugOnly<nsresult> rv =
       piTarget->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
--- a/editor/libeditor/HTMLAnonymousNodeEditor.cpp
+++ b/editor/libeditor/HTMLAnonymousNodeEditor.cpp
@@ -156,17 +156,17 @@ ElementDeletionObserver::NodeWillBeDestr
     mNativeAnonNode->RemoveMutationObserver(this);
     mNativeAnonNode->UnbindFromTree();
     mNativeAnonNode = nullptr;
   }
 
   NS_RELEASE_THIS();
 }
 
-already_AddRefed<Element>
+ManualNACPtr
 HTMLEditor::CreateAnonymousElement(nsIAtom* aTag,
                                    nsIDOMNode* aParentNode,
                                    const nsAString& aAnonClass,
                                    bool aIsCreatedHidden)
 {
   if (NS_WARN_IF(!aParentNode)) {
     return nullptr;
   }
@@ -183,63 +183,55 @@ HTMLEditor::CreateAnonymousElement(nsIAt
 
   // Get the pres shell
   nsCOMPtr<nsIPresShell> ps = GetPresShell();
   if (NS_WARN_IF(!ps)) {
     return nullptr;
   }
 
   // Create a new node through the element factory
-  RefPtr<Element> newContent = CreateHTMLContent(aTag);
-  if (NS_WARN_IF(!newContent)) {
+  RefPtr<Element> newContentRaw = CreateHTMLContent(aTag);
+  if (NS_WARN_IF(!newContentRaw)) {
     return nullptr;
   }
 
   // add the "hidden" class if needed
   if (aIsCreatedHidden) {
     nsresult rv =
-      newContent->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
-                          NS_LITERAL_STRING("hidden"), true);
+      newContentRaw->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+                             NS_LITERAL_STRING("hidden"), true);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return nullptr;
     }
   }
 
   // add an _moz_anonclass attribute if needed
   if (!aAnonClass.IsEmpty()) {
     nsresult rv =
-      newContent->SetAttr(kNameSpaceID_None, nsGkAtoms::_moz_anonclass,
-                          aAnonClass, true);
+      newContentRaw->SetAttr(kNameSpaceID_None, nsGkAtoms::_moz_anonclass,
+                             aAnonClass, true);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return nullptr;
     }
   }
 
   {
     nsAutoScriptBlocker scriptBlocker;
 
     // establish parenthood of the element
-    newContent->SetIsNativeAnonymousRoot();
+    newContentRaw->SetIsNativeAnonymousRoot();
     nsresult rv =
-      newContent->BindToTree(doc, parentContent, parentContent, true);
+      newContentRaw->BindToTree(doc, parentContent, parentContent, true);
     if (NS_FAILED(rv)) {
-      newContent->UnbindFromTree();
+      newContentRaw->UnbindFromTree();
       return nullptr;
     }
   }
 
-  // Record the NAC on the element, so that AllChildrenIterator can find it.
-  auto nac = static_cast<ManualNAC*>(
-      parentContent->GetProperty(nsGkAtoms::manualNACProperty));
-  if (!nac) {
-    nac = new ManualNAC();
-    parentContent->SetProperty(nsGkAtoms::manualNACProperty, nac,
-                               nsINode::DeleteProperty<ManualNAC>);
-  }
-  nac->AppendElement(newContent);
+  ManualNACPtr newContent(newContentRaw.forget());
 
   // Must style the new element, otherwise the PostRecreateFramesFor call
   // below will do nothing.
   if (ServoStyleSet* styleSet = ps->StyleSet()->GetAsServo()) {
     // Sometimes editor likes to append anonymous content to elements
     // in display:none subtrees, so avoid styling in those cases.
     if (styleSet->MayTraverseFrom(newContent)) {
       styleSet->StyleNewSubtree(newContent);
@@ -259,37 +251,37 @@ HTMLEditor::CreateAnonymousElement(nsIAt
   // sort of ok.
   newContent->SetProperty(nsGkAtoms::restylableAnonymousNode,
 			  reinterpret_cast<void*>(true));
 #endif // DEBUG
 
   // display the element
   ps->PostRecreateFramesFor(newContent);
 
-  return newContent.forget();
+  return Move(newContent);
 }
 
 // Removes event listener and calls DeleteRefToAnonymousNode.
 void
 HTMLEditor::RemoveListenerAndDeleteRef(const nsAString& aEvent,
                                        nsIDOMEventListener* aListener,
                                        bool aUseCapture,
-                                       Element* aElement,
+                                       ManualNACPtr aElement,
                                        nsIPresShell* aShell)
 {
   nsCOMPtr<nsIDOMEventTarget> evtTarget(do_QueryInterface(aElement));
   if (evtTarget) {
     evtTarget->RemoveEventListener(aEvent, aListener, aUseCapture);
   }
-  DeleteRefToAnonymousNode(aElement, aShell);
+  DeleteRefToAnonymousNode(Move(aElement), aShell);
 }
 
 // Deletes all references to an anonymous element
 void
-HTMLEditor::DeleteRefToAnonymousNode(nsIContent* aContent,
+HTMLEditor::DeleteRefToAnonymousNode(ManualNACPtr aContent,
                                      nsIPresShell* aShell)
 {
   // call ContentRemoved() for the anonymous content
   // node so its references get removed from the frame manager's
   // undisplay map, and its layout frames get destroyed!
 
   if (NS_WARN_IF(!aContent)) {
     return;
@@ -323,29 +315,17 @@ HTMLEditor::DeleteRefToAnonymousNode(nsI
                                   parentContent, aContent, -1,
                                   aContent->GetPreviousSibling());
       if (document) {
         docObserver->EndUpdate(document, UPDATE_CONTENT_MODEL);
       }
     }
   }
 
-  // Remove reference from the parent element.
-  auto nac = static_cast<mozilla::ManualNAC*>(
-      parentContent->GetProperty(nsGkAtoms::manualNACProperty));
-  // nsIDocument::AdoptNode might remove all properties before destroying
-  // editor.  So we have to consider that NAC could be already removed.
-  if (nac) {
-    nac->RemoveElement(aContent);
-    if (nac->IsEmpty()) {
-      parentContent->DeleteProperty(nsGkAtoms::manualNACProperty);
-    }
-  }
-
-  aContent->UnbindFromTree();
+  // The ManualNACPtr destructor will invoke UnbindFromTree.
 }
 
 // The following method is mostly called by a selection listener. When a
 // selection change is notified, the method is called to check if resizing
 // handles, a grabber and/or inline table editing UI need to be displayed
 // or refreshed
 NS_IMETHODIMP
 HTMLEditor::CheckSelectionStateForAnonymousButtons(nsISelection* aSelection)
--- a/editor/libeditor/HTMLEditor.h
+++ b/editor/libeditor/HTMLEditor.h
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_HTMLEditor_h
 #define mozilla_HTMLEditor_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/CSSEditUtils.h"
+#include "mozilla/ManualNAC.h"
 #include "mozilla/StyleSheet.h"
 #include "mozilla/TextEditor.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/File.h"
 
 #include "nsAttrName.h"
 #include "nsCOMPtr.h"
@@ -852,19 +853,19 @@ protected:
   // an array for holding default style settings
   nsTArray<PropItem*> mDefaultStyles;
 
 protected:
   // ANONYMOUS UTILS
   void RemoveListenerAndDeleteRef(const nsAString& aEvent,
                                   nsIDOMEventListener* aListener,
                                   bool aUseCapture,
-                                  Element* aElement,
+                                  ManualNACPtr aElement,
                                   nsIPresShell* aShell);
-  void DeleteRefToAnonymousNode(nsIContent* aContent,
+  void DeleteRefToAnonymousNode(ManualNACPtr aContent,
                                 nsIPresShell* aShell);
 
   nsresult ShowResizersInner(nsIDOMElement *aResizedElement);
 
   /**
    * Returns the offset of an element's frame to its absolute containing block.
    */
   nsresult GetElementOrigin(nsIDOMElement* aElement,
@@ -895,29 +896,29 @@ protected:
   bool mIsMoving;
 
   bool mSnapToGridEnabled;
 
   // inline table editing
   bool mIsInlineTableEditingEnabled;
 
   // resizing
-  nsCOMPtr<Element> mTopLeftHandle;
-  nsCOMPtr<Element> mTopHandle;
-  nsCOMPtr<Element> mTopRightHandle;
-  nsCOMPtr<Element> mLeftHandle;
-  nsCOMPtr<Element> mRightHandle;
-  nsCOMPtr<Element> mBottomLeftHandle;
-  nsCOMPtr<Element> mBottomHandle;
-  nsCOMPtr<Element> mBottomRightHandle;
+  ManualNACPtr mTopLeftHandle;
+  ManualNACPtr mTopHandle;
+  ManualNACPtr mTopRightHandle;
+  ManualNACPtr mLeftHandle;
+  ManualNACPtr mRightHandle;
+  ManualNACPtr mBottomLeftHandle;
+  ManualNACPtr mBottomHandle;
+  ManualNACPtr mBottomRightHandle;
 
   nsCOMPtr<Element> mActivatedHandle;
 
-  nsCOMPtr<Element> mResizingShadow;
-  nsCOMPtr<Element> mResizingInfo;
+  ManualNACPtr mResizingShadow;
+  ManualNACPtr mResizingInfo;
 
   nsCOMPtr<Element> mResizedObject;
 
   nsCOMPtr<nsIDOMEventListener>  mMouseMotionListenerP;
   nsCOMPtr<nsISelectionListener> mSelectionListenerP;
   nsCOMPtr<nsIDOMEventListener>  mResizeEventListenerP;
 
   int32_t mOriginalX;
@@ -938,28 +939,27 @@ protected:
   int32_t mWidthIncrementFactor;
   int32_t mHeightIncrementFactor;
 
   int8_t  mInfoXIncrement;
   int8_t  mInfoYIncrement;
 
   nsresult SetAllResizersPosition();
 
-  already_AddRefed<Element> CreateResizer(int16_t aLocation,
-                                          nsIDOMNode* aParentNode);
+  ManualNACPtr CreateResizer(int16_t aLocation, nsIDOMNode* aParentNode);
   void SetAnonymousElementPosition(int32_t aX, int32_t aY,
                                    Element* aResizer);
 
-  already_AddRefed<Element> CreateShadow(nsIDOMNode* aParentNode,
-                                         nsIDOMElement* aOriginalObject);
+  ManualNACPtr CreateShadow(nsIDOMNode* aParentNode,
+                            nsIDOMElement* aOriginalObject);
   nsresult SetShadowPosition(Element* aShadow, Element* aOriginalObject,
                              int32_t aOriginalObjectX,
                              int32_t aOriginalObjectY);
 
-  already_AddRefed<Element> CreateResizingInfo(nsIDOMNode* aParentNode);
+  ManualNACPtr CreateResizingInfo(nsIDOMNode* aParentNode);
   nsresult SetResizingInfoPosition(int32_t aX, int32_t aY,
                                    int32_t aW, int32_t aH);
 
   int32_t GetNewResizingIncrement(int32_t aX, int32_t aY, int32_t aID);
   nsresult StartResizing(nsIDOMElement* aHandle);
   int32_t GetNewResizingX(int32_t aX, int32_t aY);
   int32_t GetNewResizingY(int32_t aX, int32_t aY);
   int32_t GetNewResizingWidth(int32_t aX, int32_t aY);
@@ -978,41 +978,41 @@ protected:
   int32_t mPositionedObjectHeight;
 
   int32_t mPositionedObjectMarginLeft;
   int32_t mPositionedObjectMarginTop;
   int32_t mPositionedObjectBorderLeft;
   int32_t mPositionedObjectBorderTop;
 
   nsCOMPtr<Element> mAbsolutelyPositionedObject;
-  nsCOMPtr<Element> mGrabber;
-  nsCOMPtr<Element> mPositioningShadow;
+  ManualNACPtr mGrabber;
+  ManualNACPtr mPositioningShadow;
 
   int32_t mGridSize;
 
-  already_AddRefed<Element> CreateGrabber(nsINode* aParentNode);
+  ManualNACPtr CreateGrabber(nsINode* aParentNode);
   nsresult StartMoving(nsIDOMElement* aHandle);
   nsresult SetFinalPosition(int32_t aX, int32_t aY);
   void AddPositioningOffset(int32_t& aX, int32_t& aY);
   void SnapToGrid(int32_t& newX, int32_t& newY);
   nsresult GrabberClicked();
   nsresult EndMoving();
   nsresult CheckPositionedElementBGandFG(nsIDOMElement* aElement,
                                          nsAString& aReturn);
 
   // inline table editing
   nsCOMPtr<nsIDOMElement> mInlineEditedCell;
 
-  RefPtr<Element> mAddColumnBeforeButton;
-  RefPtr<Element> mRemoveColumnButton;
-  RefPtr<Element> mAddColumnAfterButton;
+  ManualNACPtr mAddColumnBeforeButton;
+  ManualNACPtr mRemoveColumnButton;
+  ManualNACPtr mAddColumnAfterButton;
 
-  RefPtr<Element> mAddRowBeforeButton;
-  RefPtr<Element> mRemoveRowButton;
-  RefPtr<Element> mAddRowAfterButton;
+  ManualNACPtr mAddRowBeforeButton;
+  ManualNACPtr mRemoveRowButton;
+  ManualNACPtr mAddRowAfterButton;
 
   void AddMouseClickListener(Element* aElement);
   void RemoveMouseClickListener(Element* aElement);
 
   nsCOMPtr<nsILinkHandler> mLinkHandler;
 
   ParagraphSeparator mDefaultParagraphSeparator;
 
@@ -1048,18 +1048,17 @@ private:
    * @param aTag             [IN] desired type of the element to create
    * @param aParentNode      [IN] the parent node of the created anonymous
    *                              element
    * @param aAnonClass       [IN] contents of the _moz_anonclass attribute
    * @param aIsCreatedHidden [IN] a boolean specifying if the class "hidden"
    *                              is to be added to the created anonymous
    *                              element
    */
-  already_AddRefed<Element> CreateAnonymousElement(
-                              nsIAtom* aTag,
-                              nsIDOMNode* aParentNode,
-                              const nsAString& aAnonClass,
-                              bool aIsCreatedHidden);
+  ManualNACPtr CreateAnonymousElement(nsIAtom* aTag,
+                                      nsIDOMNode* aParentNode,
+                                      const nsAString& aAnonClass,
+                                      bool aIsCreatedHidden);
 };
 
 } // namespace mozilla
 
 #endif // #ifndef mozilla_HTMLEditor_h
--- a/editor/libeditor/HTMLEditorObjectResizer.cpp
+++ b/editor/libeditor/HTMLEditorObjectResizer.cpp
@@ -126,21 +126,21 @@ ResizerMouseMotionListener::HandleEvent(
 
   return NS_OK;
 }
 
 /******************************************************************************
  * mozilla::HTMLEditor
  ******************************************************************************/
 
-already_AddRefed<Element>
+ManualNACPtr
 HTMLEditor::CreateResizer(int16_t aLocation,
                           nsIDOMNode* aParentNode)
 {
-  RefPtr<Element> ret =
+  ManualNACPtr ret =
     CreateAnonymousElement(nsGkAtoms::span,
                            aParentNode,
                            NS_LITERAL_STRING("mozResizer"),
                            false);
   if (NS_WARN_IF(!ret)) {
     return nullptr;
   }
 
@@ -177,44 +177,41 @@ HTMLEditor::CreateResizer(int16_t aLocat
     case nsIHTMLObjectResizer::eBottomRight:
       locationStr = kBottomRight;
       break;
   }
 
   nsresult rv =
     ret->SetAttr(kNameSpaceID_None, nsGkAtoms::anonlocation, locationStr, true);
   NS_ENSURE_SUCCESS(rv, nullptr);
-  return ret.forget();
+  return Move(ret);
 }
 
-already_AddRefed<Element>
+ManualNACPtr
 HTMLEditor::CreateShadow(nsIDOMNode* aParentNode,
                          nsIDOMElement* aOriginalObject)
 {
   // let's create an image through the element factory
   nsCOMPtr<nsIAtom> name;
   if (HTMLEditUtils::IsImage(aOriginalObject)) {
     name = nsGkAtoms::img;
   } else {
     name = nsGkAtoms::span;
   }
-  RefPtr<Element> ret =
-    CreateAnonymousElement(name, aParentNode,
-                           NS_LITERAL_STRING("mozResizingShadow"), true);
-  return ret.forget();
+
+  return CreateAnonymousElement(name, aParentNode,
+                                NS_LITERAL_STRING("mozResizingShadow"), true);
 }
 
-already_AddRefed<Element>
+ManualNACPtr
 HTMLEditor::CreateResizingInfo(nsIDOMNode* aParentNode)
 {
   // let's create an info box through the element factory
-  RefPtr<Element> ret =
-    CreateAnonymousElement(nsGkAtoms::span, aParentNode,
-                           NS_LITERAL_STRING("mozResizingInfo"), true);
-  return ret.forget();
+  return CreateAnonymousElement(nsGkAtoms::span, aParentNode,
+                                NS_LITERAL_STRING("mozResizingInfo"), true);
 }
 
 nsresult
 HTMLEditor::SetAllResizersPosition()
 {
   NS_ENSURE_TRUE(mTopLeftHandle, NS_ERROR_FAILURE);
 
   int32_t x = mResizedObjectX;
@@ -381,54 +378,44 @@ HTMLEditor::HideResizers()
   nsCOMPtr<nsIPresShell> ps = GetPresShell();
   // We allow the pres shell to be null; when it is, we presume there
   // are no document observers to notify, but we still want to
   // UnbindFromTree.
 
   NS_NAMED_LITERAL_STRING(mousedown, "mousedown");
 
   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
-                             mTopLeftHandle, ps);
-  mTopLeftHandle = nullptr;
+                             Move(mTopLeftHandle), ps);
 
   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
-                             mTopHandle, ps);
-  mTopHandle = nullptr;
+                             Move(mTopHandle), ps);
 
   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
-                             mTopRightHandle, ps);
-  mTopRightHandle = nullptr;
+                             Move(mTopRightHandle), ps);
 
   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
-                             mLeftHandle, ps);
-  mLeftHandle = nullptr;
+                             Move(mLeftHandle), ps);
 
   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
-                             mRightHandle, ps);
-  mRightHandle = nullptr;
+                             Move(mRightHandle), ps);
 
   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
-                             mBottomLeftHandle, ps);
-  mBottomLeftHandle = nullptr;
+                             Move(mBottomLeftHandle), ps);
 
   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
-                             mBottomHandle, ps);
-  mBottomHandle = nullptr;
+                             Move(mBottomHandle), ps);
 
   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
-                             mBottomRightHandle, ps);
-  mBottomRightHandle = nullptr;
+                             Move(mBottomRightHandle), ps);
 
   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
-                             mResizingShadow, ps);
-  mResizingShadow = nullptr;
+                             Move(mResizingShadow), ps);
 
   RemoveListenerAndDeleteRef(mousedown, mEventListener, true,
-                             mResizingInfo, ps);
-  mResizingInfo = nullptr;
+                             Move(mResizingInfo), ps);
 
   if (mActivatedHandle) {
     mActivatedHandle->UnsetAttr(kNameSpaceID_None, nsGkAtoms::_moz_activated,
                                 true);
     mActivatedHandle = nullptr;
   }
 
   // don't forget to remove the listeners !
--- a/editor/libeditor/HTMLInlineTableEditor.cpp
+++ b/editor/libeditor/HTMLInlineTableEditor.cpp
@@ -105,28 +105,22 @@ HTMLEditor::HideInlineTableEditingUI()
   RemoveMouseClickListener(mAddRowAfterButton);
 
   // get the presshell's document observer interface.
   nsCOMPtr<nsIPresShell> ps = GetPresShell();
   // We allow the pres shell to be null; when it is, we presume there
   // are no document observers to notify, but we still want to
   // UnbindFromTree.
 
-  DeleteRefToAnonymousNode(mAddColumnBeforeButton, ps);
-  mAddColumnBeforeButton = nullptr;
-  DeleteRefToAnonymousNode(mRemoveColumnButton, ps);
-  mRemoveColumnButton = nullptr;
-  DeleteRefToAnonymousNode(mAddColumnAfterButton, ps);
-  mAddColumnAfterButton = nullptr;
-  DeleteRefToAnonymousNode(mAddRowBeforeButton, ps);
-  mAddRowBeforeButton = nullptr;
-  DeleteRefToAnonymousNode(mRemoveRowButton, ps);
-  mRemoveRowButton = nullptr;
-  DeleteRefToAnonymousNode(mAddRowAfterButton, ps);
-  mAddRowAfterButton = nullptr;
+  DeleteRefToAnonymousNode(Move(mAddColumnBeforeButton), ps);
+  DeleteRefToAnonymousNode(Move(mRemoveColumnButton), ps);
+  DeleteRefToAnonymousNode(Move(mAddColumnAfterButton), ps);
+  DeleteRefToAnonymousNode(Move(mAddRowBeforeButton), ps);
+  DeleteRefToAnonymousNode(Move(mRemoveRowButton), ps);
+  DeleteRefToAnonymousNode(Move(mAddRowAfterButton), ps);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLEditor::DoInlineTableEditingAction(nsIDOMElement* aElement)
 {
   NS_ENSURE_ARG_POINTER(aElement);
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/ManualNAC.h
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ManualNAC_h
+#define mozilla_ManualNAC_h
+
+#include "mozilla/dom/Element.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+// 16 seems to be the maximum number of manual Native Anonymous Content (NAC)
+// nodes that editor creates for a given element.
+//
+// These need to be manually removed by the machinery that sets the NAC,
+// otherwise we'll leak.
+typedef AutoTArray<RefPtr<dom::Element>, 16> ManualNACArray;
+
+/**
+ * Smart pointer class to own "manual" Native Anonymous Content, and perform
+ * the necessary registration and deregistration on the parent element.
+ */
+class ManualNACPtr final
+{
+public:
+  ManualNACPtr() {}
+  MOZ_IMPLICIT ManualNACPtr(decltype(nullptr)) {}
+  explicit ManualNACPtr(already_AddRefed<Element> aNewNAC)
+    : mPtr(aNewNAC)
+  {
+    if (!mPtr) {
+      return;
+    }
+
+    // Record the NAC on the element, so that AllChildrenIterator can find it.
+    nsIContent* parentContent = mPtr->GetParent();
+    auto nac = static_cast<ManualNACArray*>(
+        parentContent->GetProperty(nsGkAtoms::manualNACProperty));
+    if (!nac) {
+      nac = new ManualNACArray();
+      parentContent->SetProperty(nsGkAtoms::manualNACProperty, nac,
+                                 nsINode::DeleteProperty<ManualNACArray>);
+    }
+    nac->AppendElement(mPtr);
+  }
+
+  // We use move semantics, and delete the copy-constructor and operator=.
+  ManualNACPtr(ManualNACPtr&& aOther) : mPtr(aOther.mPtr.forget()) {}
+  ManualNACPtr(ManualNACPtr& aOther) = delete;
+  ManualNACPtr& operator=(ManualNACPtr&& aOther)
+  {
+    mPtr = aOther.mPtr.forget();
+    return *this;
+  }
+  ManualNACPtr& operator=(ManualNACPtr& aOther) = delete;
+
+  ~ManualNACPtr() { Reset(); }
+
+  void Reset()
+  {
+    if (!mPtr) {
+      return;
+    }
+
+    RefPtr<Element> ptr = mPtr.forget();
+    nsIContent* parentContent = ptr->GetParent();
+    if (!parentContent) {
+      NS_WARNING("Potentially leaking manual NAC");
+      return;
+    }
+
+    // Remove reference from the parent element.
+    auto nac = static_cast<mozilla::ManualNACArray*>(
+        parentContent->GetProperty(nsGkAtoms::manualNACProperty));
+    // nsIDocument::AdoptNode might remove all properties before destroying
+    // editor.  So we have to consider that NAC could be already removed.
+    if (nac) {
+      nac->RemoveElement(ptr);
+      if (nac->IsEmpty()) {
+        parentContent->DeleteProperty(nsGkAtoms::manualNACProperty);
+      }
+    }
+
+    ptr->UnbindFromTree();
+  }
+
+  Element* get() const { return mPtr.get(); }
+  Element* operator->() const { return get(); }
+  operator Element*() const &
+  {
+    return get();
+  }
+
+private:
+  RefPtr<Element> mPtr;
+};
+
+} // namespace mozilla
+
+inline void
+ImplCycleCollectionUnlink(mozilla::ManualNACPtr& field)
+{
+  field.Reset();
+}
+
+inline void
+ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& callback,
+                            const mozilla::ManualNACPtr& field,
+                            const char* name,
+                            uint32_t flags = 0)
+{
+  CycleCollectionNoteChild(callback, field.get(), name, flags);
+}
+
+#endif // #ifndef mozilla_ManualNAC_h
--- a/editor/libeditor/moz.build
+++ b/editor/libeditor/moz.build
@@ -20,16 +20,17 @@ EXPORTS += [
 EXPORTS.mozilla += [
     'ChangeStyleTransaction.h',
     'CSSEditUtils.h',
     'EditorBase.h',
     'EditorController.h',
     'EditorUtils.h',
     'EditTransactionBase.h',
     'HTMLEditor.h',
+    'ManualNAC.h',
     'SelectionState.h',
     'TextEditor.h',
     'TextEditRules.h',
 ]
 
 UNIFIED_SOURCES += [
     'ChangeAttributeTransaction.cpp',
     'ChangeStyleTransaction.cpp',