1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /*
8 * Implementation of DOM Core's Text node.
9 */
10
11 #include "nsTextNode.h"
12 #include "mozilla/dom/TextBinding.h"
13 #include "nsContentUtils.h"
14 #include "mozilla/dom/DirectionalityUtils.h"
15 #include "mozilla/dom/Document.h"
16 #include "nsThreadUtils.h"
17 #include "nsStubMutationObserver.h"
18 #include "mozilla/IntegerPrintfMacros.h"
19 #ifdef DEBUG
20 # include "nsRange.h"
21 #endif
22
23 using namespace mozilla;
24 using namespace mozilla::dom;
25
26 /**
27 * class used to implement attr() generated content
28 */
29 class nsAttributeTextNode final : public nsTextNode,
30 public nsStubMutationObserver {
31 public:
32 NS_DECL_ISUPPORTS_INHERITED
33
nsAttributeTextNode(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo,int32_t aNameSpaceID,nsAtom * aAttrName)34 nsAttributeTextNode(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
35 int32_t aNameSpaceID, nsAtom* aAttrName)
36 : nsTextNode(std::move(aNodeInfo)),
37 mGrandparent(nullptr),
38 mNameSpaceID(aNameSpaceID),
39 mAttrName(aAttrName) {
40 NS_ASSERTION(mNameSpaceID != kNameSpaceID_Unknown, "Must know namespace");
41 NS_ASSERTION(mAttrName, "Must have attr name");
42 }
43
44 virtual nsresult BindToTree(BindContext&, nsINode& aParent) override;
45 virtual void UnbindFromTree(bool aNullParent = true) override;
46
47 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
48 NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
49
CloneDataNode(mozilla::dom::NodeInfo * aNodeInfo,bool aCloneText) const50 virtual already_AddRefed<CharacterData> CloneDataNode(
51 mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const override {
52 RefPtr<nsAttributeTextNode> it = new (aNodeInfo->NodeInfoManager())
53 nsAttributeTextNode(do_AddRef(aNodeInfo), mNameSpaceID, mAttrName);
54 if (aCloneText) {
55 it->mText = mText;
56 }
57
58 return it.forget();
59 }
60
61 // Public method for the event to run
UpdateText()62 void UpdateText() { UpdateText(true); }
63
64 private:
~nsAttributeTextNode()65 virtual ~nsAttributeTextNode() {
66 NS_ASSERTION(!mGrandparent, "We were not unbound!");
67 }
68
69 // Update our text to our parent's current attr value
70 void UpdateText(bool aNotify);
71
72 // This doesn't need to be a strong pointer because it's only non-null
73 // while we're bound to the document tree, and it points to an ancestor
74 // so the ancestor must be bound to the document tree the whole time
75 // and can't be deleted.
76 Element* mGrandparent;
77 // What attribute we're showing
78 int32_t mNameSpaceID;
79 RefPtr<nsAtom> mAttrName;
80 };
81
82 nsTextNode::~nsTextNode() = default;
83
84 // Use the CC variant of this, even though this class does not define
85 // a new CC participant, to make QIing to the CC interfaces faster.
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(nsTextNode,CharacterData)86 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(nsTextNode, CharacterData)
87
88 JSObject* nsTextNode::WrapNode(JSContext* aCx,
89 JS::Handle<JSObject*> aGivenProto) {
90 return Text_Binding::Wrap(aCx, this, aGivenProto);
91 }
92
IsNodeOfType(uint32_t aFlags) const93 bool nsTextNode::IsNodeOfType(uint32_t aFlags) const { return false; }
94
CloneDataNode(mozilla::dom::NodeInfo * aNodeInfo,bool aCloneText) const95 already_AddRefed<CharacterData> nsTextNode::CloneDataNode(
96 mozilla::dom::NodeInfo* aNodeInfo, bool aCloneText) const {
97 RefPtr<nsTextNode> it =
98 new (aNodeInfo->NodeInfoManager()) nsTextNode(do_AddRef(aNodeInfo));
99 if (aCloneText) {
100 it->mText = mText;
101 }
102
103 return it.forget();
104 }
105
AppendTextForNormalize(const char16_t * aBuffer,uint32_t aLength,bool aNotify,nsIContent * aNextSibling)106 nsresult nsTextNode::AppendTextForNormalize(const char16_t* aBuffer,
107 uint32_t aLength, bool aNotify,
108 nsIContent* aNextSibling) {
109 CharacterDataChangeInfo::Details details = {
110 CharacterDataChangeInfo::Details::eMerge, aNextSibling};
111 return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify,
112 &details);
113 }
114
BindToTree(BindContext & aContext,nsINode & aParent)115 nsresult nsTextNode::BindToTree(BindContext& aContext, nsINode& aParent) {
116 nsresult rv = CharacterData::BindToTree(aContext, aParent);
117 NS_ENSURE_SUCCESS(rv, rv);
118
119 SetDirectionFromNewTextNode(this);
120
121 return NS_OK;
122 }
123
UnbindFromTree(bool aNullParent)124 void nsTextNode::UnbindFromTree(bool aNullParent) {
125 ResetDirectionSetByTextNode(this);
126
127 CharacterData::UnbindFromTree(aNullParent);
128 }
129
130 #ifdef DEBUG
List(FILE * out,int32_t aIndent) const131 void nsTextNode::List(FILE* out, int32_t aIndent) const {
132 int32_t index;
133 for (index = aIndent; --index >= 0;) fputs(" ", out);
134
135 fprintf(out, "Text@%p", static_cast<const void*>(this));
136 fprintf(out, " flags=[%08x]", static_cast<unsigned int>(GetFlags()));
137 if (IsClosestCommonInclusiveAncestorForRangeInSelection()) {
138 const LinkedList<nsRange>* ranges =
139 GetExistingClosestCommonInclusiveAncestorRanges();
140 int32_t count = 0;
141 if (ranges) {
142 // Can't use range-based iteration on a const LinkedList, unfortunately.
143 for (const nsRange* r = ranges->getFirst(); r; r = r->getNext()) {
144 ++count;
145 }
146 }
147 fprintf(out, " ranges:%d", count);
148 }
149 fprintf(out, " primaryframe=%p", static_cast<void*>(GetPrimaryFrame()));
150 fprintf(out, " refcount=%" PRIuPTR "<", mRefCnt.get());
151
152 nsAutoString tmp;
153 ToCString(tmp, 0, mText.GetLength());
154 fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
155
156 fputs(">\n", out);
157 }
158
DumpContent(FILE * out,int32_t aIndent,bool aDumpAll) const159 void nsTextNode::DumpContent(FILE* out, int32_t aIndent, bool aDumpAll) const {
160 if (aDumpAll) {
161 int32_t index;
162 for (index = aIndent; --index >= 0;) fputs(" ", out);
163
164 nsAutoString tmp;
165 ToCString(tmp, 0, mText.GetLength());
166
167 if (!tmp.EqualsLiteral("\\n")) {
168 fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out);
169 if (aIndent) fputs("\n", out);
170 }
171 }
172 }
173 #endif
174
NS_NewAttributeContent(nsNodeInfoManager * aNodeInfoManager,int32_t aNameSpaceID,nsAtom * aAttrName,nsIContent ** aResult)175 nsresult NS_NewAttributeContent(nsNodeInfoManager* aNodeInfoManager,
176 int32_t aNameSpaceID, nsAtom* aAttrName,
177 nsIContent** aResult) {
178 MOZ_ASSERT(aNodeInfoManager, "Missing nodeInfoManager");
179 MOZ_ASSERT(aAttrName, "Must have an attr name");
180 MOZ_ASSERT(aNameSpaceID != kNameSpaceID_Unknown, "Must know namespace");
181
182 *aResult = nullptr;
183
184 RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfoManager->GetTextNodeInfo();
185
186 RefPtr<nsAttributeTextNode> textNode = new (aNodeInfoManager)
187 nsAttributeTextNode(ni.forget(), aNameSpaceID, aAttrName);
188 textNode.forget(aResult);
189
190 return NS_OK;
191 }
192
NS_IMPL_ISUPPORTS_INHERITED(nsAttributeTextNode,nsTextNode,nsIMutationObserver)193 NS_IMPL_ISUPPORTS_INHERITED(nsAttributeTextNode, nsTextNode,
194 nsIMutationObserver)
195
196 nsresult nsAttributeTextNode::BindToTree(BindContext& aContext,
197 nsINode& aParent) {
198 MOZ_ASSERT(aParent.IsContent() && aParent.GetParent(),
199 "This node can't be a child of the document or of "
200 "the document root");
201
202 nsresult rv = nsTextNode::BindToTree(aContext, aParent);
203 NS_ENSURE_SUCCESS(rv, rv);
204
205 NS_ASSERTION(!mGrandparent, "We were already bound!");
206 mGrandparent = aParent.GetParent()->AsElement();
207 mGrandparent->AddMutationObserver(this);
208
209 // Note that there is no need to notify here, since we have no
210 // frame yet at this point.
211 UpdateText(false);
212
213 return NS_OK;
214 }
215
UnbindFromTree(bool aNullParent)216 void nsAttributeTextNode::UnbindFromTree(bool aNullParent) {
217 // UnbindFromTree can be called anytime so we have to be safe.
218 if (mGrandparent) {
219 // aNullParent might not be true here, but we want to remove the
220 // mutation observer anyway since we only need it while we're
221 // in the document.
222 mGrandparent->RemoveMutationObserver(this);
223 mGrandparent = nullptr;
224 }
225 nsTextNode::UnbindFromTree(aNullParent);
226 }
227
AttributeChanged(Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)228 void nsAttributeTextNode::AttributeChanged(Element* aElement,
229 int32_t aNameSpaceID,
230 nsAtom* aAttribute, int32_t aModType,
231 const nsAttrValue* aOldValue) {
232 if (aNameSpaceID == mNameSpaceID && aAttribute == mAttrName &&
233 aElement == mGrandparent) {
234 // Since UpdateText notifies, do it when it's safe to run script. Note
235 // that if we get unbound while the event is up that's ok -- we'll just
236 // have no grandparent when it fires, and will do nothing.
237 void (nsAttributeTextNode::*update)() = &nsAttributeTextNode::UpdateText;
238 nsContentUtils::AddScriptRunner(NewRunnableMethod(
239 "nsAttributeTextNode::AttributeChanged", this, update));
240 }
241 }
242
NodeWillBeDestroyed(const nsINode * aNode)243 void nsAttributeTextNode::NodeWillBeDestroyed(const nsINode* aNode) {
244 NS_ASSERTION(aNode == static_cast<nsINode*>(mGrandparent), "Wrong node!");
245 mGrandparent = nullptr;
246 }
247
UpdateText(bool aNotify)248 void nsAttributeTextNode::UpdateText(bool aNotify) {
249 if (mGrandparent) {
250 nsAutoString attrValue;
251 mGrandparent->GetAttr(mNameSpaceID, mAttrName, attrValue);
252 SetText(attrValue, aNotify);
253 }
254 }
255