1 /* -*- Mode: C++; tab-width: 4; 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 #include "XPathResult.h"
7 #include "txExprResult.h"
8 #include "txNodeSet.h"
9 #include "nsError.h"
10 #include "mozilla/dom/Attr.h"
11 #include "mozilla/dom/Element.h"
12 #include "nsDOMString.h"
13 #include "txXPathTreeWalker.h"
14 #include "nsCycleCollectionParticipant.h"
15 #include "mozilla/dom/XPathResultBinding.h"
16
17 namespace mozilla {
18 namespace dom {
19
XPathResult(nsINode * aParent)20 XPathResult::XPathResult(nsINode* aParent)
21 : mParent(aParent),
22 mDocument(nullptr),
23 mCurrentPos(0),
24 mResultType(ANY_TYPE),
25 mInvalidIteratorState(true),
26 mBooleanResult(false),
27 mNumberResult(0) {}
28
XPathResult(const XPathResult & aResult)29 XPathResult::XPathResult(const XPathResult& aResult)
30 : mParent(aResult.mParent),
31 mResult(aResult.mResult),
32 mResultNodes(aResult.mResultNodes.Clone()),
33 mDocument(aResult.mDocument),
34 mContextNode(aResult.mContextNode),
35 mCurrentPos(0),
36 mResultType(aResult.mResultType),
37 mInvalidIteratorState(aResult.mInvalidIteratorState) {
38 if (mDocument) {
39 mDocument->AddMutationObserver(this);
40 }
41 }
42
~XPathResult()43 XPathResult::~XPathResult() { RemoveObserver(); }
44
45 NS_IMPL_CYCLE_COLLECTION_CLASS(XPathResult)
46
NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(XPathResult)47 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(XPathResult)
48 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(XPathResult)
49 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
50 NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) { tmp->RemoveObserver(); }
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)51 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument)
52 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
53 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(XPathResult)
54 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent)
55 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument)
56 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResultNodes)
57 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
58
59 NS_IMPL_CYCLE_COLLECTING_ADDREF(XPathResult)
60 NS_IMPL_CYCLE_COLLECTING_RELEASE(XPathResult)
61
62 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(XPathResult)
63 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
64 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
65 NS_INTERFACE_MAP_ENTRY(nsIXPathResult)
66 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXPathResult)
67 NS_INTERFACE_MAP_END
68
69 JSObject* XPathResult::WrapObject(JSContext* aCx,
70 JS::Handle<JSObject*> aGivenProto) {
71 return XPathResult_Binding::Wrap(aCx, this, aGivenProto);
72 }
73
RemoveObserver()74 void XPathResult::RemoveObserver() {
75 if (mDocument) {
76 mDocument->RemoveMutationObserver(this);
77 }
78 }
79
IterateNext(ErrorResult & aRv)80 nsINode* XPathResult::IterateNext(ErrorResult& aRv) {
81 if (!isIterator()) {
82 aRv.ThrowTypeError("Result is not an iterator");
83 return nullptr;
84 }
85
86 if (mDocument) {
87 mDocument->FlushPendingNotifications(FlushType::Content);
88 }
89
90 if (mInvalidIteratorState) {
91 aRv.ThrowInvalidStateError(
92 "The document has been mutated since the result was returned");
93 return nullptr;
94 }
95
96 return mResultNodes.SafeElementAt(mCurrentPos++);
97 }
98
NodeWillBeDestroyed(const nsINode * aNode)99 void XPathResult::NodeWillBeDestroyed(const nsINode* aNode) {
100 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
101 // Set to null to avoid unregistring unnecessarily
102 mDocument = nullptr;
103 Invalidate(aNode->IsContent() ? aNode->AsContent() : nullptr);
104 }
105
CharacterDataChanged(nsIContent * aContent,const CharacterDataChangeInfo &)106 void XPathResult::CharacterDataChanged(nsIContent* aContent,
107 const CharacterDataChangeInfo&) {
108 Invalidate(aContent);
109 }
110
AttributeChanged(Element * aElement,int32_t aNameSpaceID,nsAtom * aAttribute,int32_t aModType,const nsAttrValue * aOldValue)111 void XPathResult::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
112 nsAtom* aAttribute, int32_t aModType,
113 const nsAttrValue* aOldValue) {
114 Invalidate(aElement);
115 }
116
ContentAppended(nsIContent * aFirstNewContent)117 void XPathResult::ContentAppended(nsIContent* aFirstNewContent) {
118 Invalidate(aFirstNewContent->GetParent());
119 }
120
ContentInserted(nsIContent * aChild)121 void XPathResult::ContentInserted(nsIContent* aChild) {
122 Invalidate(aChild->GetParent());
123 }
124
ContentRemoved(nsIContent * aChild,nsIContent * aPreviousSibling)125 void XPathResult::ContentRemoved(nsIContent* aChild,
126 nsIContent* aPreviousSibling) {
127 Invalidate(aChild->GetParent());
128 }
129
SetExprResult(txAExprResult * aExprResult,uint16_t aResultType,nsINode * aContextNode,ErrorResult & aRv)130 void XPathResult::SetExprResult(txAExprResult* aExprResult,
131 uint16_t aResultType, nsINode* aContextNode,
132 ErrorResult& aRv) {
133 MOZ_ASSERT(aExprResult);
134
135 if ((isSnapshot(aResultType) || isIterator(aResultType) ||
136 isNode(aResultType)) &&
137 aExprResult->getResultType() != txAExprResult::NODESET) {
138 // The DOM spec doesn't really say what should happen when reusing an
139 // XPathResult and an error is thrown. Let's not touch the XPathResult
140 // in that case.
141 aRv.ThrowTypeError("Result type mismatch");
142 return;
143 }
144
145 mResultType = aResultType;
146 mContextNode = do_GetWeakReference(aContextNode);
147
148 if (mDocument) {
149 mDocument->RemoveMutationObserver(this);
150 mDocument = nullptr;
151 }
152
153 mResultNodes.Clear();
154
155 // XXX This will keep the recycler alive, should we clear it?
156 mResult = aExprResult;
157 switch (mResultType) {
158 case BOOLEAN_TYPE: {
159 mBooleanResult = mResult->booleanValue();
160 break;
161 }
162 case NUMBER_TYPE: {
163 mNumberResult = mResult->numberValue();
164 break;
165 }
166 case STRING_TYPE: {
167 mResult->stringValue(mStringResult);
168 break;
169 }
170 default: {
171 MOZ_ASSERT(isNode() || isIterator() || isSnapshot());
172 }
173 }
174
175 if (aExprResult->getResultType() == txAExprResult::NODESET) {
176 txNodeSet* nodeSet = static_cast<txNodeSet*>(aExprResult);
177 int32_t i, count = nodeSet->size();
178 for (i = 0; i < count; ++i) {
179 nsINode* node = txXPathNativeNode::getNode(nodeSet->get(i));
180 mResultNodes.AppendElement(node);
181 }
182
183 if (count > 0) {
184 mResult = nullptr;
185 }
186 }
187
188 if (!isIterator()) {
189 return;
190 }
191
192 mCurrentPos = 0;
193 mInvalidIteratorState = false;
194
195 if (!mResultNodes.IsEmpty()) {
196 // If we support the document() function in DOM-XPath we need to
197 // observe all documents that we have resultnodes in.
198 mDocument = mResultNodes[0]->OwnerDoc();
199 NS_ASSERTION(mDocument, "We need a document!");
200 if (mDocument) {
201 mDocument->AddMutationObserver(this);
202 }
203 }
204 }
205
Invalidate(const nsIContent * aChangeRoot)206 void XPathResult::Invalidate(const nsIContent* aChangeRoot) {
207 nsCOMPtr<nsINode> contextNode = do_QueryReferent(mContextNode);
208 // If the changes are happening in a different anonymous trees, no
209 // invalidation should happen.
210 if (contextNode && aChangeRoot &&
211 !nsContentUtils::IsInSameAnonymousTree(contextNode, aChangeRoot)) {
212 return;
213 }
214
215 mInvalidIteratorState = true;
216 // Make sure nulling out mDocument is the last thing we do.
217 if (mDocument) {
218 mDocument->RemoveMutationObserver(this);
219 mDocument = nullptr;
220 }
221 }
222
GetExprResult(txAExprResult ** aExprResult)223 nsresult XPathResult::GetExprResult(txAExprResult** aExprResult) {
224 if (isIterator() && mInvalidIteratorState) {
225 return NS_ERROR_DOM_INVALID_STATE_ERR;
226 }
227
228 if (mResult) {
229 NS_ADDREF(*aExprResult = mResult);
230
231 return NS_OK;
232 }
233
234 if (mResultNodes.IsEmpty()) {
235 return NS_ERROR_DOM_INVALID_STATE_ERR;
236 }
237
238 RefPtr<txNodeSet> nodeSet = new txNodeSet(nullptr);
239 uint32_t i, count = mResultNodes.Length();
240 for (i = 0; i < count; ++i) {
241 UniquePtr<txXPathNode> node(
242 txXPathNativeNode::createXPathNode(mResultNodes[i]));
243 if (!node) {
244 return NS_ERROR_OUT_OF_MEMORY;
245 }
246
247 nodeSet->append(*node);
248 }
249
250 NS_ADDREF(*aExprResult = nodeSet);
251
252 return NS_OK;
253 }
254
Clone(nsIXPathResult ** aResult)255 nsresult XPathResult::Clone(nsIXPathResult** aResult) {
256 *aResult = nullptr;
257
258 if (isIterator() && mInvalidIteratorState) {
259 return NS_ERROR_DOM_INVALID_STATE_ERR;
260 }
261
262 NS_ADDREF(*aResult = new XPathResult(*this));
263
264 return NS_OK;
265 }
266
267 } // namespace dom
268 } // namespace mozilla
269