michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsXPathResult.h" michael@0: #include "txExprResult.h" michael@0: #include "txNodeSet.h" michael@0: #include "nsError.h" michael@0: #include "mozilla/dom/Attr.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "nsDOMClassInfoID.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsDOMString.h" michael@0: #include "txXPathTreeWalker.h" michael@0: #include "nsCycleCollectionParticipant.h" michael@0: michael@0: using namespace mozilla::dom; michael@0: michael@0: nsXPathResult::nsXPathResult() : mDocument(nullptr), michael@0: mCurrentPos(0), michael@0: mResultType(ANY_TYPE), michael@0: mInvalidIteratorState(true), michael@0: mBooleanResult(false), michael@0: mNumberResult(0) michael@0: { michael@0: } michael@0: michael@0: nsXPathResult::nsXPathResult(const nsXPathResult &aResult) michael@0: : mResult(aResult.mResult), michael@0: mResultNodes(aResult.mResultNodes), michael@0: mDocument(aResult.mDocument), michael@0: mCurrentPos(0), michael@0: mResultType(aResult.mResultType), michael@0: mContextNode(aResult.mContextNode), michael@0: mInvalidIteratorState(aResult.mInvalidIteratorState) michael@0: { michael@0: if (mDocument) { michael@0: mDocument->AddMutationObserver(this); michael@0: } michael@0: } michael@0: michael@0: nsXPathResult::~nsXPathResult() michael@0: { michael@0: RemoveObserver(); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(nsXPathResult) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXPathResult) michael@0: { michael@0: tmp->RemoveObserver(); michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXPathResult) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mResultNodes) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXPathResult) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXPathResult) michael@0: michael@0: DOMCI_DATA(XPathResult, nsXPathResult) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXPathResult) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMXPathResult) michael@0: NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIXPathResult) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMXPathResult) michael@0: NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(XPathResult) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: void michael@0: nsXPathResult::RemoveObserver() michael@0: { michael@0: if (mDocument) { michael@0: mDocument->RemoveMutationObserver(this); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPathResult::GetResultType(uint16_t *aResultType) michael@0: { michael@0: *aResultType = mResultType; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPathResult::GetNumberValue(double *aNumberValue) michael@0: { michael@0: if (mResultType != NUMBER_TYPE) { michael@0: return NS_ERROR_DOM_TYPE_ERR; michael@0: } michael@0: michael@0: *aNumberValue = mNumberResult; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPathResult::GetStringValue(nsAString &aStringValue) michael@0: { michael@0: if (mResultType != STRING_TYPE) { michael@0: return NS_ERROR_DOM_TYPE_ERR; michael@0: } michael@0: michael@0: aStringValue = mStringResult; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPathResult::GetBooleanValue(bool *aBooleanValue) michael@0: { michael@0: if (mResultType != BOOLEAN_TYPE) { michael@0: return NS_ERROR_DOM_TYPE_ERR; michael@0: } michael@0: michael@0: *aBooleanValue = mBooleanResult; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPathResult::GetSingleNodeValue(nsIDOMNode **aSingleNodeValue) michael@0: { michael@0: if (!isNode()) { michael@0: return NS_ERROR_DOM_TYPE_ERR; michael@0: } michael@0: michael@0: if (mResultNodes.Count() > 0) { michael@0: NS_ADDREF(*aSingleNodeValue = mResultNodes[0]); michael@0: } michael@0: else { michael@0: *aSingleNodeValue = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPathResult::GetInvalidIteratorState(bool *aInvalidIteratorState) michael@0: { michael@0: *aInvalidIteratorState = isIterator() && mInvalidIteratorState; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPathResult::GetSnapshotLength(uint32_t *aSnapshotLength) michael@0: { michael@0: if (!isSnapshot()) { michael@0: return NS_ERROR_DOM_TYPE_ERR; michael@0: } michael@0: michael@0: *aSnapshotLength = (uint32_t)mResultNodes.Count(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPathResult::IterateNext(nsIDOMNode **aResult) michael@0: { michael@0: if (!isIterator()) { michael@0: return NS_ERROR_DOM_TYPE_ERR; michael@0: } michael@0: michael@0: if (mDocument) { michael@0: mDocument->FlushPendingNotifications(Flush_Content); michael@0: } michael@0: michael@0: if (mInvalidIteratorState) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: if (mCurrentPos < (uint32_t)mResultNodes.Count()) { michael@0: NS_ADDREF(*aResult = mResultNodes[mCurrentPos++]); michael@0: } michael@0: else { michael@0: *aResult = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsXPathResult::SnapshotItem(uint32_t aIndex, nsIDOMNode **aResult) michael@0: { michael@0: if (!isSnapshot()) { michael@0: return NS_ERROR_DOM_TYPE_ERR; michael@0: } michael@0: michael@0: NS_IF_ADDREF(*aResult = mResultNodes.SafeObjectAt(aIndex)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXPathResult::NodeWillBeDestroyed(const nsINode* aNode) michael@0: { michael@0: nsCOMPtr kungFuDeathGrip(this); michael@0: // Set to null to avoid unregistring unnecessarily michael@0: mDocument = nullptr; michael@0: Invalidate(aNode->IsNodeOfType(nsINode::eCONTENT) ? michael@0: static_cast(aNode) : nullptr); michael@0: } michael@0: michael@0: void michael@0: nsXPathResult::CharacterDataChanged(nsIDocument* aDocument, michael@0: nsIContent *aContent, michael@0: CharacterDataChangeInfo* aInfo) michael@0: { michael@0: Invalidate(aContent); michael@0: } michael@0: michael@0: void michael@0: nsXPathResult::AttributeChanged(nsIDocument* aDocument, michael@0: Element* aElement, michael@0: int32_t aNameSpaceID, michael@0: nsIAtom* aAttribute, michael@0: int32_t aModType) michael@0: { michael@0: Invalidate(aElement); michael@0: } michael@0: michael@0: void michael@0: nsXPathResult::ContentAppended(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aFirstNewContent, michael@0: int32_t aNewIndexInContainer) michael@0: { michael@0: Invalidate(aContainer); michael@0: } michael@0: michael@0: void michael@0: nsXPathResult::ContentInserted(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer) michael@0: { michael@0: Invalidate(aContainer); michael@0: } michael@0: michael@0: void michael@0: nsXPathResult::ContentRemoved(nsIDocument* aDocument, michael@0: nsIContent* aContainer, michael@0: nsIContent* aChild, michael@0: int32_t aIndexInContainer, michael@0: nsIContent* aPreviousSibling) michael@0: { michael@0: Invalidate(aContainer); michael@0: } michael@0: michael@0: nsresult michael@0: nsXPathResult::SetExprResult(txAExprResult* aExprResult, uint16_t aResultType, michael@0: nsINode* aContextNode) michael@0: { michael@0: MOZ_ASSERT(aExprResult); michael@0: michael@0: if ((isSnapshot(aResultType) || isIterator(aResultType) || michael@0: isNode(aResultType)) && michael@0: aExprResult->getResultType() != txAExprResult::NODESET) { michael@0: // The DOM spec doesn't really say what should happen when reusing an michael@0: // XPathResult and an error is thrown. Let's not touch the XPathResult michael@0: // in that case. michael@0: return NS_ERROR_DOM_TYPE_ERR; michael@0: } michael@0: michael@0: mResultType = aResultType; michael@0: mContextNode = do_GetWeakReference(aContextNode); michael@0: michael@0: if (mDocument) { michael@0: mDocument->RemoveMutationObserver(this); michael@0: mDocument = nullptr; michael@0: } michael@0: michael@0: mResultNodes.Clear(); michael@0: michael@0: // XXX This will keep the recycler alive, should we clear it? michael@0: mResult = aExprResult; michael@0: switch (mResultType) { michael@0: case BOOLEAN_TYPE: michael@0: { michael@0: mBooleanResult = mResult->booleanValue(); michael@0: break; michael@0: } michael@0: case NUMBER_TYPE: michael@0: { michael@0: mNumberResult = mResult->numberValue(); michael@0: break; michael@0: } michael@0: case STRING_TYPE: michael@0: { michael@0: mResult->stringValue(mStringResult); michael@0: break; michael@0: } michael@0: default: michael@0: { michael@0: MOZ_ASSERT(isNode() || isIterator() || isSnapshot()); michael@0: } michael@0: } michael@0: michael@0: if (aExprResult->getResultType() == txAExprResult::NODESET) { michael@0: txNodeSet *nodeSet = static_cast(aExprResult); michael@0: nsCOMPtr node; michael@0: int32_t i, count = nodeSet->size(); michael@0: for (i = 0; i < count; ++i) { michael@0: txXPathNativeNode::getNode(nodeSet->get(i), getter_AddRefs(node)); michael@0: if (node) { michael@0: mResultNodes.AppendObject(node); michael@0: } michael@0: } michael@0: michael@0: if (count > 0) { michael@0: mResult = nullptr; michael@0: } michael@0: } michael@0: michael@0: if (!isIterator()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mInvalidIteratorState = false; michael@0: michael@0: if (mResultNodes.Count() > 0) { michael@0: // If we support the document() function in DOM-XPath we need to michael@0: // observe all documents that we have resultnodes in. michael@0: nsCOMPtr document; michael@0: mResultNodes[0]->GetOwnerDocument(getter_AddRefs(document)); michael@0: if (document) { michael@0: mDocument = do_QueryInterface(document); michael@0: } michael@0: else { michael@0: mDocument = do_QueryInterface(mResultNodes[0]); michael@0: } michael@0: michael@0: NS_ASSERTION(mDocument, "We need a document!"); michael@0: if (mDocument) { michael@0: mDocument->AddMutationObserver(this); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsXPathResult::Invalidate(const nsIContent* aChangeRoot) michael@0: { michael@0: nsCOMPtr contextNode = do_QueryReferent(mContextNode); michael@0: if (contextNode && aChangeRoot && aChangeRoot->GetBindingParent()) { michael@0: // If context node is in anonymous content, changes to michael@0: // non-anonymous content need to invalidate the XPathResult. If michael@0: // the changes are happening in a different anonymous trees, no michael@0: // invalidation should happen. michael@0: nsIContent* ctxBindingParent = nullptr; michael@0: if (contextNode->IsNodeOfType(nsINode::eCONTENT)) { michael@0: ctxBindingParent = michael@0: static_cast(contextNode.get()) michael@0: ->GetBindingParent(); michael@0: } else if (contextNode->IsNodeOfType(nsINode::eATTRIBUTE)) { michael@0: Element* parent = michael@0: static_cast(contextNode.get())->GetElement(); michael@0: if (parent) { michael@0: ctxBindingParent = parent->GetBindingParent(); michael@0: } michael@0: } michael@0: if (ctxBindingParent != aChangeRoot->GetBindingParent()) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: mInvalidIteratorState = true; michael@0: // Make sure nulling out mDocument is the last thing we do. michael@0: if (mDocument) { michael@0: mDocument->RemoveMutationObserver(this); michael@0: mDocument = nullptr; michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsXPathResult::GetExprResult(txAExprResult** aExprResult) michael@0: { michael@0: if (isIterator() && mInvalidIteratorState) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: if (mResult) { michael@0: NS_ADDREF(*aExprResult = mResult); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mResultNodes.Count() == 0) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: nsRefPtr nodeSet = new txNodeSet(nullptr); michael@0: if (!nodeSet) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: uint32_t i, count = mResultNodes.Count(); michael@0: for (i = 0; i < count; ++i) { michael@0: nsAutoPtr node(txXPathNativeNode::createXPathNode(mResultNodes[i])); michael@0: if (!node) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: nodeSet->append(*node); michael@0: } michael@0: michael@0: NS_ADDREF(*aExprResult = nodeSet); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsXPathResult::Clone(nsIXPathResult **aResult) michael@0: { michael@0: *aResult = nullptr; michael@0: michael@0: if (isIterator() && mInvalidIteratorState) { michael@0: return NS_ERROR_DOM_INVALID_STATE_ERR; michael@0: } michael@0: michael@0: nsCOMPtr result = new nsXPathResult(*this); michael@0: if (!result) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: result.swap(*aResult); michael@0: michael@0: return NS_OK; michael@0: }