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