|
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 /* |
|
7 * Implementation of DOM Core's nsIDOMText node. |
|
8 */ |
|
9 |
|
10 #include "nsTextNode.h" |
|
11 #include "mozilla/dom/TextBinding.h" |
|
12 #include "nsContentUtils.h" |
|
13 #include "mozilla/dom/DirectionalityUtils.h" |
|
14 #include "nsIDOMEventListener.h" |
|
15 #include "nsIDOMMutationEvent.h" |
|
16 #include "nsIDocument.h" |
|
17 #include "nsThreadUtils.h" |
|
18 #include "nsStubMutationObserver.h" |
|
19 #include "mozilla/IntegerPrintfMacros.h" |
|
20 #ifdef DEBUG |
|
21 #include "nsRange.h" |
|
22 #endif |
|
23 |
|
24 using namespace mozilla; |
|
25 using namespace mozilla::dom; |
|
26 |
|
27 /** |
|
28 * class used to implement attr() generated content |
|
29 */ |
|
30 class nsAttributeTextNode MOZ_FINAL : public nsTextNode, |
|
31 public nsStubMutationObserver |
|
32 { |
|
33 public: |
|
34 NS_DECL_ISUPPORTS_INHERITED |
|
35 |
|
36 nsAttributeTextNode(already_AddRefed<nsINodeInfo>& aNodeInfo, |
|
37 int32_t aNameSpaceID, |
|
38 nsIAtom* aAttrName) : |
|
39 nsTextNode(aNodeInfo), |
|
40 mGrandparent(nullptr), |
|
41 mNameSpaceID(aNameSpaceID), |
|
42 mAttrName(aAttrName) |
|
43 { |
|
44 NS_ASSERTION(mNameSpaceID != kNameSpaceID_Unknown, "Must know namespace"); |
|
45 NS_ASSERTION(mAttrName, "Must have attr name"); |
|
46 } |
|
47 |
|
48 virtual ~nsAttributeTextNode() { |
|
49 NS_ASSERTION(!mGrandparent, "We were not unbound!"); |
|
50 } |
|
51 |
|
52 virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent, |
|
53 nsIContent* aBindingParent, |
|
54 bool aCompileEventHandlers); |
|
55 virtual void UnbindFromTree(bool aDeep = true, |
|
56 bool aNullParent = true); |
|
57 |
|
58 NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED |
|
59 NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED |
|
60 |
|
61 virtual nsGenericDOMDataNode *CloneDataNode(nsINodeInfo *aNodeInfo, |
|
62 bool aCloneText) const |
|
63 { |
|
64 already_AddRefed<nsINodeInfo> ni = |
|
65 nsCOMPtr<nsINodeInfo>(aNodeInfo).forget(); |
|
66 nsAttributeTextNode *it = new nsAttributeTextNode(ni, |
|
67 mNameSpaceID, |
|
68 mAttrName); |
|
69 if (it && aCloneText) { |
|
70 it->mText = mText; |
|
71 } |
|
72 |
|
73 return it; |
|
74 } |
|
75 |
|
76 // Public method for the event to run |
|
77 void UpdateText() { |
|
78 UpdateText(true); |
|
79 } |
|
80 |
|
81 private: |
|
82 // Update our text to our parent's current attr value |
|
83 void UpdateText(bool aNotify); |
|
84 |
|
85 // This doesn't need to be a strong pointer because it's only non-null |
|
86 // while we're bound to the document tree, and it points to an ancestor |
|
87 // so the ancestor must be bound to the document tree the whole time |
|
88 // and can't be deleted. |
|
89 nsIContent* mGrandparent; |
|
90 // What attribute we're showing |
|
91 int32_t mNameSpaceID; |
|
92 nsCOMPtr<nsIAtom> mAttrName; |
|
93 }; |
|
94 |
|
95 nsTextNode::~nsTextNode() |
|
96 { |
|
97 } |
|
98 |
|
99 NS_IMPL_ISUPPORTS_INHERITED(nsTextNode, nsGenericDOMDataNode, nsIDOMNode, |
|
100 nsIDOMText, nsIDOMCharacterData) |
|
101 |
|
102 JSObject* |
|
103 nsTextNode::WrapNode(JSContext *aCx) |
|
104 { |
|
105 return TextBinding::Wrap(aCx, this); |
|
106 } |
|
107 |
|
108 bool |
|
109 nsTextNode::IsNodeOfType(uint32_t aFlags) const |
|
110 { |
|
111 return !(aFlags & ~(eCONTENT | eTEXT | eDATA_NODE)); |
|
112 } |
|
113 |
|
114 nsGenericDOMDataNode* |
|
115 nsTextNode::CloneDataNode(nsINodeInfo *aNodeInfo, bool aCloneText) const |
|
116 { |
|
117 already_AddRefed<nsINodeInfo> ni = nsCOMPtr<nsINodeInfo>(aNodeInfo).forget(); |
|
118 nsTextNode *it = new nsTextNode(ni); |
|
119 if (aCloneText) { |
|
120 it->mText = mText; |
|
121 } |
|
122 |
|
123 return it; |
|
124 } |
|
125 |
|
126 nsresult |
|
127 nsTextNode::AppendTextForNormalize(const char16_t* aBuffer, uint32_t aLength, |
|
128 bool aNotify, nsIContent* aNextSibling) |
|
129 { |
|
130 CharacterDataChangeInfo::Details details = { |
|
131 CharacterDataChangeInfo::Details::eMerge, aNextSibling |
|
132 }; |
|
133 return SetTextInternal(mText.GetLength(), 0, aBuffer, aLength, aNotify, &details); |
|
134 } |
|
135 |
|
136 nsresult |
|
137 nsTextNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent, |
|
138 nsIContent* aBindingParent, bool aCompileEventHandlers) |
|
139 { |
|
140 nsresult rv = nsGenericDOMDataNode::BindToTree(aDocument, aParent, |
|
141 aBindingParent, |
|
142 aCompileEventHandlers); |
|
143 NS_ENSURE_SUCCESS(rv, rv); |
|
144 |
|
145 SetDirectionFromNewTextNode(this); |
|
146 |
|
147 return NS_OK; |
|
148 } |
|
149 |
|
150 void nsTextNode::UnbindFromTree(bool aDeep, bool aNullParent) |
|
151 { |
|
152 ResetDirectionSetByTextNode(this, aNullParent); |
|
153 |
|
154 nsGenericDOMDataNode::UnbindFromTree(aDeep, aNullParent); |
|
155 } |
|
156 |
|
157 #ifdef DEBUG |
|
158 void |
|
159 nsTextNode::List(FILE* out, int32_t aIndent) const |
|
160 { |
|
161 int32_t index; |
|
162 for (index = aIndent; --index >= 0; ) fputs(" ", out); |
|
163 |
|
164 fprintf(out, "Text@%p", static_cast<const void*>(this)); |
|
165 fprintf(out, " flags=[%08x]", static_cast<unsigned int>(GetFlags())); |
|
166 if (IsCommonAncestorForRangeInSelection()) { |
|
167 typedef nsTHashtable<nsPtrHashKey<nsRange> > RangeHashTable; |
|
168 RangeHashTable* ranges = |
|
169 static_cast<RangeHashTable*>(GetProperty(nsGkAtoms::range)); |
|
170 fprintf(out, " ranges:%d", ranges ? ranges->Count() : 0); |
|
171 } |
|
172 fprintf(out, " primaryframe=%p", static_cast<void*>(GetPrimaryFrame())); |
|
173 fprintf(out, " refcount=%" PRIuPTR "<", mRefCnt.get()); |
|
174 |
|
175 nsAutoString tmp; |
|
176 ToCString(tmp, 0, mText.GetLength()); |
|
177 fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out); |
|
178 |
|
179 fputs(">\n", out); |
|
180 } |
|
181 |
|
182 void |
|
183 nsTextNode::DumpContent(FILE* out, int32_t aIndent, bool aDumpAll) const |
|
184 { |
|
185 if(aDumpAll) { |
|
186 int32_t index; |
|
187 for (index = aIndent; --index >= 0; ) fputs(" ", out); |
|
188 |
|
189 nsAutoString tmp; |
|
190 ToCString(tmp, 0, mText.GetLength()); |
|
191 |
|
192 if(!tmp.EqualsLiteral("\\n")) { |
|
193 fputs(NS_LossyConvertUTF16toASCII(tmp).get(), out); |
|
194 if(aIndent) fputs("\n", out); |
|
195 } |
|
196 } |
|
197 } |
|
198 #endif |
|
199 |
|
200 nsresult |
|
201 NS_NewAttributeContent(nsNodeInfoManager *aNodeInfoManager, |
|
202 int32_t aNameSpaceID, nsIAtom* aAttrName, |
|
203 nsIContent** aResult) |
|
204 { |
|
205 NS_PRECONDITION(aNodeInfoManager, "Missing nodeInfoManager"); |
|
206 NS_PRECONDITION(aAttrName, "Must have an attr name"); |
|
207 NS_PRECONDITION(aNameSpaceID != kNameSpaceID_Unknown, "Must know namespace"); |
|
208 |
|
209 *aResult = nullptr; |
|
210 |
|
211 already_AddRefed<nsINodeInfo> ni = aNodeInfoManager->GetTextNodeInfo(); |
|
212 |
|
213 nsAttributeTextNode* textNode = new nsAttributeTextNode(ni, |
|
214 aNameSpaceID, |
|
215 aAttrName); |
|
216 if (!textNode) { |
|
217 return NS_ERROR_OUT_OF_MEMORY; |
|
218 } |
|
219 |
|
220 NS_ADDREF(*aResult = textNode); |
|
221 |
|
222 return NS_OK; |
|
223 } |
|
224 |
|
225 NS_IMPL_ISUPPORTS_INHERITED(nsAttributeTextNode, nsTextNode, |
|
226 nsIMutationObserver) |
|
227 |
|
228 nsresult |
|
229 nsAttributeTextNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent, |
|
230 nsIContent* aBindingParent, |
|
231 bool aCompileEventHandlers) |
|
232 { |
|
233 NS_PRECONDITION(aParent && aParent->GetParent(), |
|
234 "This node can't be a child of the document or of the document root"); |
|
235 |
|
236 nsresult rv = nsTextNode::BindToTree(aDocument, aParent, |
|
237 aBindingParent, aCompileEventHandlers); |
|
238 NS_ENSURE_SUCCESS(rv, rv); |
|
239 |
|
240 NS_ASSERTION(!mGrandparent, "We were already bound!"); |
|
241 mGrandparent = aParent->GetParent(); |
|
242 mGrandparent->AddMutationObserver(this); |
|
243 |
|
244 // Note that there is no need to notify here, since we have no |
|
245 // frame yet at this point. |
|
246 UpdateText(false); |
|
247 |
|
248 return NS_OK; |
|
249 } |
|
250 |
|
251 void |
|
252 nsAttributeTextNode::UnbindFromTree(bool aDeep, bool aNullParent) |
|
253 { |
|
254 // UnbindFromTree can be called anytime so we have to be safe. |
|
255 if (mGrandparent) { |
|
256 // aNullParent might not be true here, but we want to remove the |
|
257 // mutation observer anyway since we only need it while we're |
|
258 // in the document. |
|
259 mGrandparent->RemoveMutationObserver(this); |
|
260 mGrandparent = nullptr; |
|
261 } |
|
262 nsTextNode::UnbindFromTree(aDeep, aNullParent); |
|
263 } |
|
264 |
|
265 void |
|
266 nsAttributeTextNode::AttributeChanged(nsIDocument* aDocument, |
|
267 Element* aElement, |
|
268 int32_t aNameSpaceID, |
|
269 nsIAtom* aAttribute, |
|
270 int32_t aModType) |
|
271 { |
|
272 if (aNameSpaceID == mNameSpaceID && aAttribute == mAttrName && |
|
273 aElement == mGrandparent) { |
|
274 // Since UpdateText notifies, do it when it's safe to run script. Note |
|
275 // that if we get unbound while the event is up that's ok -- we'll just |
|
276 // have no grandparent when it fires, and will do nothing. |
|
277 void (nsAttributeTextNode::*update)() = &nsAttributeTextNode::UpdateText; |
|
278 nsCOMPtr<nsIRunnable> ev = NS_NewRunnableMethod(this, update); |
|
279 nsContentUtils::AddScriptRunner(ev); |
|
280 } |
|
281 } |
|
282 |
|
283 void |
|
284 nsAttributeTextNode::NodeWillBeDestroyed(const nsINode* aNode) |
|
285 { |
|
286 NS_ASSERTION(aNode == static_cast<nsINode*>(mGrandparent), "Wrong node!"); |
|
287 mGrandparent = nullptr; |
|
288 } |
|
289 |
|
290 void |
|
291 nsAttributeTextNode::UpdateText(bool aNotify) |
|
292 { |
|
293 if (mGrandparent) { |
|
294 nsAutoString attrValue; |
|
295 mGrandparent->GetAttr(mNameSpaceID, mAttrName, attrValue); |
|
296 SetText(attrValue, aNotify); |
|
297 } |
|
298 } |
|
299 |