Bug 1386110 - Use a smart pointer to reliably de-register NAC regardless of how it goes away. r=masayuki

MozReview-Commit-ID: HTSu5BjxD8I
This commit is contained in:
Bobby Holley 2017-08-02 12:37:44 -07:00
Родитель 693c0c370f
Коммит 1bd3bc937c
9 изменённых файлов: 198 добавлений и 130 удалений

Просмотреть файл

@ -66,6 +66,7 @@
#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"
@ -10348,7 +10349,7 @@ nsContentUtils::AppendNativeAnonymousChildren(
}
// 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);
}

Просмотреть файл

@ -197,15 +197,6 @@ struct EventNameMapping
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;

Просмотреть файл

@ -229,11 +229,11 @@ HTMLEditor::GetElementZIndex(nsIDOMElement* aElement,
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)) {
@ -245,7 +245,7 @@ HTMLEditor::CreateGrabber(nsINode* aParentNode)
evtTarget->AddEventListener(NS_LITERAL_STRING("mousedown"),
mEventListener, false);
return ret.forget();
return ret;
}
NS_IMETHODIMP
@ -289,10 +289,8 @@ HTMLEditor::HideGrabber()
// 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;
}
@ -391,7 +389,7 @@ HTMLEditor::EndMoving()
nsCOMPtr<nsIPresShell> ps = GetPresShell();
NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
DeleteRefToAnonymousNode(mPositioningShadow, ps);
DeleteRefToAnonymousNode(Move(mPositioningShadow), ps);
mPositioningShadow = nullptr;
}

Просмотреть файл

@ -161,7 +161,7 @@ ElementDeletionObserver::NodeWillBeDestroyed(const nsINode* aNode)
NS_RELEASE_THIS();
}
already_AddRefed<Element>
ManualNACPtr
HTMLEditor::CreateAnonymousElement(nsIAtom* aTag,
nsIDOMNode* aParentNode,
const nsAString& aAnonClass,
@ -196,16 +196,16 @@ HTMLEditor::CreateAnonymousElement(nsIAtom* aTag,
}
// 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;
}
@ -214,8 +214,8 @@ HTMLEditor::CreateAnonymousElement(nsIAtom* aTag,
// 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;
}
@ -225,24 +225,16 @@ HTMLEditor::CreateAnonymousElement(nsIAtom* aTag,
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.
@ -268,7 +260,7 @@ HTMLEditor::CreateAnonymousElement(nsIAtom* aTag,
// display the element
ps->PostRecreateFramesFor(newContent);
return newContent.forget();
return Move(newContent);
}
// Removes event listener and calls DeleteRefToAnonymousNode.
@ -276,19 +268,19 @@ 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
@ -332,19 +324,7 @@ HTMLEditor::DeleteRefToAnonymousNode(nsIContent* aContent,
}
}
// 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

Просмотреть файл

@ -8,6 +8,7 @@
#include "mozilla/Attributes.h"
#include "mozilla/CSSEditUtils.h"
#include "mozilla/ManualNAC.h"
#include "mozilla/StyleSheet.h"
#include "mozilla/TextEditor.h"
#include "mozilla/UniquePtr.h"
@ -857,9 +858,9 @@ protected:
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);
@ -900,19 +901,19 @@ protected:
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;
@ -943,18 +944,17 @@ protected:
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);
@ -983,12 +983,12 @@ protected:
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);
@ -1001,13 +1001,13 @@ protected:
// 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);
@ -1053,11 +1053,10 @@ private:
* 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

Просмотреть файл

@ -131,11 +131,11 @@ ResizerMouseMotionListener::HandleEvent(nsIDOMEvent* aMouseEvent)
* 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"),
@ -182,10 +182,10 @@ HTMLEditor::CreateResizer(int16_t aLocation,
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)
{
@ -196,20 +196,17 @@ HTMLEditor::CreateShadow(nsIDOMNode* aParentNode,
} 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
@ -386,44 +383,34 @@ HTMLEditor::HideResizers()
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,

Просмотреть файл

@ -110,18 +110,12 @@ HTMLEditor::HideInlineTableEditingUI()
// 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;
}

Просмотреть файл

@ -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

Просмотреть файл

@ -25,6 +25,7 @@ EXPORTS.mozilla += [
'EditorUtils.h',
'EditTransactionBase.h',
'HTMLEditor.h',
'ManualNAC.h',
'SelectionState.h',
'TextEditor.h',
'TextEditRules.h',