gecko/dom/base/nsTextNode.cpp
Ehsan Akhgari e976e8f020 Bug 1103348 - Part 1: Correctly reset the direction of an ancestor that is still in the tree when a text node is removed; r=smontagu
This is essentially a better fix for bug 894137.  Relying on the aNullParent
argument to UnbindFromTree doesn't actually tell us if the dir=auto node
is still in the tree or not.  Instead, we can check the parent of the said
node and only reset the direction if the parent exists, which means that
the node is still in the tree.
2015-05-08 08:42:26 -04:00

301 lines
9.1 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/. */
/*
* Implementation of DOM Core's nsIDOMText node.
*/
#include "nsTextNode.h"
#include "mozilla/dom/TextBinding.h"
#include "nsContentUtils.h"
#include "mozilla/dom/DirectionalityUtils.h"
#include "nsIDOMEventListener.h"
#include "nsIDOMMutationEvent.h"
#include "nsIDocument.h"
#include "nsThreadUtils.h"
#include "nsStubMutationObserver.h"
#include "mozilla/IntegerPrintfMacros.h"
#ifdef DEBUG
#include "nsRange.h"
#endif
using namespace mozilla;
using namespace mozilla::dom;
/**
* class used to implement attr() generated content
*/
class nsAttributeTextNode final : public nsTextNode,
public nsStubMutationObserver
{
public:
NS_DECL_ISUPPORTS_INHERITED
nsAttributeTextNode(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo,
int32_t aNameSpaceID,
nsIAtom* aAttrName) :
nsTextNode(aNodeInfo),
mGrandparent(nullptr),
mNameSpaceID(aNameSpaceID),
mAttrName(aAttrName)
{
NS_ASSERTION(mNameSpaceID != kNameSpaceID_Unknown, "Must know namespace");
NS_ASSERTION(mAttrName, "Must have attr name");
}
virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
bool aCompileEventHandlers) override;
virtual void UnbindFromTree(bool aDeep = true,
bool aNullParent = true) override;
NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
virtual nsGenericDOMDataNode *CloneDataNode(mozilla::dom::NodeInfo *aNodeInfo,
bool aCloneText) const override
{
already_AddRefed<mozilla::dom::NodeInfo> ni =
nsRefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
nsAttributeTextNode *it = new nsAttributeTextNode(ni,
mNameSpaceID,
mAttrName);
if (it && aCloneText) {
it->mText = mText;
}
return it;
}
// Public method for the event to run
void UpdateText() {
UpdateText(true);
}
private:
virtual ~nsAttributeTextNode() {
NS_ASSERTION(!mGrandparent, "We were not unbound!");
}
// Update our text to our parent's current attr value
void UpdateText(bool aNotify);
// This doesn't need to be a strong pointer because it's only non-null
// while we're bound to the document tree, and it points to an ancestor
// so the ancestor must be bound to the document tree the whole time
// and can't be deleted.
nsIContent* mGrandparent;
// What attribute we're showing
int32_t mNameSpaceID;
nsCOMPtr<nsIAtom> mAttrName;
};
nsTextNode::~nsTextNode()
{
}
NS_IMPL_ISUPPORTS_INHERITED(nsTextNode, nsGenericDOMDataNode, nsIDOMNode,
nsIDOMText, nsIDOMCharacterData)
JSObject*
nsTextNode::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
{
return TextBinding::Wrap(aCx, this, aGivenProto);
}
bool
nsTextNode::IsNodeOfType(uint32_t aFlags) const
{
return !(aFlags & ~(eCONTENT | eTEXT | eDATA_NODE));
}
nsGenericDOMDataNode*
nsTextNode::CloneDataNode(mozilla::dom::NodeInfo *aNodeInfo, bool aCloneText) const
{
already_AddRefed<mozilla::dom::NodeInfo> ni = nsRefPtr<mozilla::dom::NodeInfo>(aNodeInfo).forget();
nsTextNode *it = new nsTextNode(ni);
if (aCloneText) {
it->mText = mText;
}
return it;
}
nsresult
nsTextNode::AppendTextForNormalize(const char16_t* aBuffer, uint32_t aLength,
bool aNotify, nsIContent* aNextSibling)
{
CharacterDataChangeInfo::Details details = {
CharacterDataChangeInfo::Details::eMerge, aNextSibling
};
return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify, &details);
}
nsresult
nsTextNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent, bool aCompileEventHandlers)
{
nsresult rv = nsGenericDOMDataNode::BindToTree(aDocument, aParent,
aBindingParent,
aCompileEventHandlers);
NS_ENSURE_SUCCESS(rv, rv);
SetDirectionFromNewTextNode(this);
return NS_OK;
}
void nsTextNode::UnbindFromTree(bool aDeep, bool aNullParent)
{
ResetDirectionSetByTextNode(this);
nsGenericDOMDataNode::UnbindFromTree(aDeep, aNullParent);
}
#ifdef DEBUG
void
nsTextNode::List(FILE* out, int32_t aIndent) const
{
int32_t index;
for (index = aIndent; --index >= 0; ) fputs(" ", out);
fprintf(out, "Text@%p", static_cast<const void*>(this));
fprintf(out, " flags=[%08x]", static_cast<unsigned int>(GetFlags()));
if (IsCommonAncestorForRangeInSelection()) {
typedef nsTHashtable<nsPtrHashKey<nsRange> > RangeHashTable;
RangeHashTable* ranges =
static_cast<RangeHashTable*>(GetProperty(nsGkAtoms::range));
fprintf(out, " ranges:%d", ranges ? ranges->Count() : 0);
}
fprintf(out, " primaryframe=%p", static_cast<void*>(GetPrimaryFrame()));
fprintf(out, " refcount=%" PRIuPTR "<", mRefCnt.get());
nsAutoString tmp;
ToCString(tmp, 0, mText.GetLength());
fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
fputs(">\n", out);
}
void
nsTextNode::DumpContent(FILE* out, int32_t aIndent, bool aDumpAll) const
{
if(aDumpAll) {
int32_t index;
for (index = aIndent; --index >= 0; ) fputs(" ", out);
nsAutoString tmp;
ToCString(tmp, 0, mText.GetLength());
if(!tmp.EqualsLiteral("\\n")) {
fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
if(aIndent) fputs("\n", out);
}
}
}
#endif
nsresult
NS_NewAttributeContent(nsNodeInfoManager *aNodeInfoManager,
int32_t aNameSpaceID, nsIAtom* aAttrName,
nsIContent** aResult)
{
NS_PRECONDITION(aNodeInfoManager, "Missing nodeInfoManager");
NS_PRECONDITION(aAttrName, "Must have an attr name");
NS_PRECONDITION(aNameSpaceID != kNameSpaceID_Unknown, "Must know namespace");
*aResult = nullptr;
already_AddRefed<mozilla::dom::NodeInfo> ni = aNodeInfoManager->GetTextNodeInfo();
nsAttributeTextNode* textNode = new nsAttributeTextNode(ni,
aNameSpaceID,
aAttrName);
if (!textNode) {
return NS_ERROR_OUT_OF_MEMORY;
}
NS_ADDREF(*aResult = textNode);
return NS_OK;
}
NS_IMPL_ISUPPORTS_INHERITED(nsAttributeTextNode, nsTextNode,
nsIMutationObserver)
nsresult
nsAttributeTextNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
nsIContent* aBindingParent,
bool aCompileEventHandlers)
{
NS_PRECONDITION(aParent && aParent->GetParent(),
"This node can't be a child of the document or of the document root");
nsresult rv = nsTextNode::BindToTree(aDocument, aParent,
aBindingParent, aCompileEventHandlers);
NS_ENSURE_SUCCESS(rv, rv);
NS_ASSERTION(!mGrandparent, "We were already bound!");
mGrandparent = aParent->GetParent();
mGrandparent->AddMutationObserver(this);
// Note that there is no need to notify here, since we have no
// frame yet at this point.
UpdateText(false);
return NS_OK;
}
void
nsAttributeTextNode::UnbindFromTree(bool aDeep, bool aNullParent)
{
// UnbindFromTree can be called anytime so we have to be safe.
if (mGrandparent) {
// aNullParent might not be true here, but we want to remove the
// mutation observer anyway since we only need it while we're
// in the document.
mGrandparent->RemoveMutationObserver(this);
mGrandparent = nullptr;
}
nsTextNode::UnbindFromTree(aDeep, aNullParent);
}
void
nsAttributeTextNode::AttributeChanged(nsIDocument* aDocument,
Element* aElement,
int32_t aNameSpaceID,
nsIAtom* aAttribute,
int32_t aModType)
{
if (aNameSpaceID == mNameSpaceID && aAttribute == mAttrName &&
aElement == mGrandparent) {
// Since UpdateText notifies, do it when it's safe to run script. Note
// that if we get unbound while the event is up that's ok -- we'll just
// have no grandparent when it fires, and will do nothing.
void (nsAttributeTextNode::*update)() = &nsAttributeTextNode::UpdateText;
nsCOMPtr<nsIRunnable> ev = NS_NewRunnableMethod(this, update);
nsContentUtils::AddScriptRunner(ev);
}
}
void
nsAttributeTextNode::NodeWillBeDestroyed(const nsINode* aNode)
{
NS_ASSERTION(aNode == static_cast<nsINode*>(mGrandparent), "Wrong node!");
mGrandparent = nullptr;
}
void
nsAttributeTextNode::UpdateText(bool aNotify)
{
if (mGrandparent) {
nsAutoString attrValue;
mGrandparent->GetAttr(mNameSpaceID, mAttrName, attrValue);
SetText(attrValue, aNotify);
}
}