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