/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla XForms support. * * The Initial Developer of the Original Code is * Novell, Inc. * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Allan Beaufour * David Landwehr * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsXFormsMDGEngine.h" #include "nsIDOMDocument.h" #include "nsIDOMNodeList.h" #include "nsIDOMText.h" #include "nsIDOMNSXPathExpression.h" #include "nsIDOMXPathResult.h" #include "nsDeque.h" #include "nsXFormsUtils.h" #include "nsDOMError.h" #include "nsIDOMElement.h" #include "nsXFormsModelElement.h" #ifdef DEBUG //# define DEBUG_XF_MDG const char* gMIPNames[] = {"type", "r/o", "req", "rel", "calc", "const", "p3ptype" }; #endif /* ------------------------------------ */ /* --------- nsXFormsMDGNode ---------- */ /* ------------------------------------ */ nsXFormsMDGNode::nsXFormsMDGNode(nsIDOMNode *aNode, const ModelItemPropName aType) : mDirty (PR_TRUE), mHasExpr(PR_FALSE), mContextNode(aNode), mCount(0), mType(aType), mContextSize(0), mContextPosition(0), mDynFunc(PR_FALSE), mNext(nsnull) { MOZ_COUNT_CTOR(nsXFormsMDGNode); } nsXFormsMDGNode::~nsXFormsMDGNode() { MOZ_COUNT_DTOR(nsXFormsMDGNode); } void nsXFormsMDGNode::SetExpression(nsIDOMNSXPathExpression *aExpression, PRBool aDynFunc, PRInt32 aContextPosition, PRInt32 aContextSize) { mHasExpr = PR_TRUE; mDynFunc = aDynFunc; mExpression = aExpression; mContextPosition = aContextPosition; mContextSize = aContextSize; } PRBool nsXFormsMDGNode::HasExpr() const { return mHasExpr; } PRBool nsXFormsMDGNode::IsDirty() const { // A node is always dirty, if it depends on a dynamic function. return mDirty || mDynFunc; } void nsXFormsMDGNode::MarkDirty() { mDirty = PR_TRUE; } void nsXFormsMDGNode::MarkClean() { mDirty = PR_FALSE; } /* ------------------------------------ */ /* -------- nsXFormsMDGEngine --------- */ /* ------------------------------------ */ nsXFormsMDGEngine::nsXFormsMDGEngine() : mNodesInGraph(0) { MOZ_COUNT_CTOR(nsXFormsMDGEngine); } nsXFormsMDGEngine::~nsXFormsMDGEngine() { mNodeToMDG.Enumerate(DeleteLinkedNodes, nsnull); MOZ_COUNT_DTOR(nsXFormsMDGEngine); } nsresult nsXFormsMDGEngine::Init(nsXFormsModelElement *aModel) { nsresult rv = NS_ERROR_FAILURE; if (mNodeStates.Init() && mNodeToMDG.Init()) { rv = NS_OK; } mModel = aModel; return rv; } nsresult nsXFormsMDGEngine::AddMIP(ModelItemPropName aType, nsIDOMNSXPathExpression *aExpression, nsCOMArray *aDependencies, PRBool aDynFunc, nsIDOMNode *aContextNode, PRInt32 aContextPos, PRInt32 aContextSize) { NS_ENSURE_ARG(aContextNode); #ifdef DEBUG_XF_MDG nsAutoString nodename; aContextNode->GetNodeName(nodename); printf("nsXFormsMDGEngine::AddMIP(aContextNode=%s, aExpression=%p, aDependencies=|%d|,\n", NS_ConvertUTF16toUTF8(nodename).get(), (void*) aExpression, aDependencies ? aDependencies->Count() : 0); printf(" aContextPos=%d, aContextSize=%d, aType=%s, aDynFunc=%d)\n", aContextPos, aContextSize, gMIPNames[aType], aDynFunc); #endif nsXFormsMDGNode* newnode = GetNode(aContextNode, aType, PR_TRUE); if (!newnode) { return NS_ERROR_OUT_OF_MEMORY; } if (newnode->HasExpr()) { // MIP already in the graph. That is, there is already a MIP of the same // type for this node, which is illegal. // Example: return NS_ERROR_ABORT; } if (aExpression) { newnode->SetExpression(aExpression, aDynFunc, aContextPos, aContextSize); } // Add dependencies if (aDependencies) { nsCOMPtr dep_domnode; nsXFormsMDGNode* dep_gnode; for (PRInt32 i = 0; i < aDependencies->Count(); ++i) { dep_domnode = aDependencies->ObjectAt(i); if (!dep_domnode) { return NS_ERROR_NULL_POINTER; } #ifdef DEBUG_XF_MDG printf("\tDependency #%2d: %p\n", i, (void*) dep_domnode); #endif // Get calculate graph node for the dependency (only a calculate // property can influence another MIP). dep_gnode = GetNode(dep_domnode, eModel_calculate); if (!dep_gnode) { return NS_ERROR_OUT_OF_MEMORY; } if (dep_gnode->mType == aType && dep_gnode->mContextNode == aContextNode) { // Reference to itself, ignore continue; } // Add this node as a successor to the dependency (ie. the dependency // should be (re-)calculated before this node) dep_gnode->mSuc.AppendElement(newnode); newnode->mCount++; } } return NS_OK; } nsresult nsXFormsMDGEngine::MarkNodeAsChanged(nsIDOMNode* aContextNode) { return mMarkedNodes.AppendObject(aContextNode); } nsresult nsXFormsMDGEngine::HandleMarkedNodes(nsCOMArray *aArray) { NS_ENSURE_ARG_POINTER(aArray); // Handle nodes marked as changed for (PRInt32 i = 0; i < mMarkedNodes.Count(); ++i) { nsCOMPtr node = mMarkedNodes.ObjectAt(i); nsXFormsNodeState* ns = GetNCNodeState(node); NS_ENSURE_TRUE(ns, NS_ERROR_FAILURE); ns->Set(kFlags_ALL_DISPATCH, PR_TRUE); // Get the node, eMode_type == get any type of node nsXFormsMDGNode* n = GetNode(node, eModel_type, PR_FALSE); if (n) { while (n) { n->MarkDirty(); n = n->mNext; } } else { // Add constraint to trigger validation of node n = GetNode(node, eModel_constraint, PR_TRUE); if (!n) { return NS_ERROR_OUT_OF_MEMORY; } n->MarkDirty(); NS_ENSURE_TRUE(mGraph.AppendElement(n), NS_ERROR_OUT_OF_MEMORY); } NS_ENSURE_TRUE(aArray->AppendObjects(mMarkedNodes), NS_ERROR_OUT_OF_MEMORY); } mMarkedNodes.Clear(); return NS_OK; } #ifdef DEBUG_beaufour #include #include #include void nsXFormsMDGEngine::PrintDot(const char* aFile) { FILE *FD = stdout; if (aFile) { FD = fopen(aFile, "w"); } fprintf(FD, "digraph {\n"); for (PRInt32 i = 0; i < mGraph.Count(); ++i) { nsXFormsMDGNode* g = NS_STATIC_CAST(nsXFormsMDGNode*, mGraph[i]); if (g) { nsAutoString domNodeName; g->mContextNode->GetNodeName(domNodeName); if (g->IsDirty()) { fprintf(FD, "\t%s [color=red];\n", NS_ConvertUTF16toUTF8(domNodeName).get()); } for (PRInt32 j = 0; j < g->mSuc.Count(); ++j) { nsXFormsMDGNode* sucnode = NS_STATIC_CAST(nsXFormsMDGNode*, g->mSuc[j]); if (sucnode) { nsAutoString sucName; sucnode->mContextNode->GetNodeName(sucName); fprintf(FD, "\t%s -> %s [label=\"%s\"];\n", NS_ConvertUTF16toUTF8(sucName).get(), NS_ConvertUTF16toUTF8(domNodeName).get(), gMIPNames[sucnode->mType]); } } } } fprintf(FD, "}\n"); if (FD) { fclose(FD); } } #endif nsresult nsXFormsMDGEngine::Recalculate(nsCOMArray *aChangedNodes) { NS_ENSURE_ARG(aChangedNodes); #ifdef DEBUG_XF_MDG printf("nsXFormsMDGEngine::Recalculate(aChangedNodes=|%d|)\n", aChangedNodes->Count()); #endif // XXX: There's something wrong with the marking of nodes, as we assume that // recalculate will always be called first. bug 338146 nsresult rv = HandleMarkedNodes(aChangedNodes); NS_ENSURE_SUCCESS(rv, rv); PRBool res = PR_TRUE; mFirstCalculate = mJustRebuilt; #ifdef DEBUG_XF_MDG printf("\taChangedNodes: %d\n", aChangedNodes->Count()); printf("\tmNodeToMDG: %d\n", mNodeToMDG.Count()); printf("\tmNodeStates: %d\n", mNodeStates.Count()); printf("\tGraph nodes: %d\n", mGraph.Count()); #endif // Go through all dirty nodes in the graph nsXFormsMDGNode* g; for (PRInt32 i = 0; i < mGraph.Count(); ++i) { g = NS_STATIC_CAST(nsXFormsMDGNode*, mGraph[i]); if (!g) { NS_WARNING("nsXFormsMDGEngine::Calculate(): Empty node in graph!!!"); continue; } NS_ASSERTION(g->mCount == 0, "nsXFormsMDGEngine::Calculate(): Graph node with mCount != 0"); #ifdef DEBUG_XF_MDG nsAutoString domNodeName; g->mContextNode->GetNodeName(domNodeName); printf("\tNode #%d: This=%p, Dirty=%d, DynFunc=%d, Type=%d, Count=%d, Suc=%d, CSize=%d, CPos=%d, Next=%p, HasExpr=%d, domnode=%s\n", i, (void*) g, g->IsDirty(), g->mDynFunc, g->mType, g->mCount, g->mSuc.Count(), g->mContextSize, g->mContextPosition, (void*) g->mNext, g->HasExpr(), NS_ConvertUTF16toUTF8(domNodeName).get()); #endif // Ignore node if it is not dirty if (!g->IsDirty()) { continue; } nsXFormsNodeState* ns = GetNCNodeState(g->mContextNode); NS_ENSURE_TRUE(ns, NS_ERROR_FAILURE); PRBool constraint = PR_TRUE; PRBool conChanged; // Find MIP-type and handle it accordingly switch (g->mType) { case eModel_calculate: if (g->HasExpr()) { nsCOMPtr xpath_res; rv = g->mExpression->EvaluateWithContext(g->mContextNode, g->mContextPosition, g->mContextSize, nsIDOMXPathResult::STRING_TYPE, nsnull, getter_AddRefs(xpath_res)); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_STATE(xpath_res); nsAutoString nodeval; rv = xpath_res->GetStringValue(nodeval); NS_ENSURE_SUCCESS(rv, rv); rv = SetNodeValueInternal(g->mContextNode, nodeval, PR_FALSE, PR_TRUE); if (NS_SUCCEEDED(rv)) { NS_ENSURE_TRUE(aChangedNodes->AppendObject(g->mContextNode), NS_ERROR_FAILURE); } ns->Set(eFlag_DISPATCH_VALUE_CHANGED, PR_TRUE); } break; case eModel_constraint: if (g->HasExpr()) { rv = BooleanExpression(g, constraint); NS_ENSURE_SUCCESS(rv, rv); } conChanged = ns->IsConstraint() != constraint; // On the first calculate after a rebuild (mFirstCalculate) we also // add constraints to the set of changed nodes to trigger validation // of type information if present. if (conChanged || mFirstCalculate) { if (conChanged) { ns->Set(eFlag_CONSTRAINT, constraint); ns->Set(eFlag_DISPATCH_VALID_CHANGED, PR_TRUE); } NS_ENSURE_TRUE(aChangedNodes->AppendObject(g->mContextNode), NS_ERROR_FAILURE); } break; case eModel_readonly: if (g->HasExpr()) { rv = ComputeMIPWithInheritance(eFlag_READONLY, eFlag_DISPATCH_READONLY_CHANGED, eFlag_INHERITED_READONLY, g, aChangedNodes); NS_ENSURE_SUCCESS(rv, rv); } break; case eModel_relevant: if (g->HasExpr()) { rv = ComputeMIPWithInheritance(eFlag_RELEVANT, eFlag_DISPATCH_RELEVANT_CHANGED, eFlag_INHERITED_RELEVANT, g, aChangedNodes); NS_ENSURE_SUCCESS(rv, rv); } break; case eModel_required: if (g->HasExpr()) { PRBool didChange; rv = ComputeMIP(eFlag_REQUIRED, eFlag_DISPATCH_REQUIRED_CHANGED, g, didChange); NS_ENSURE_SUCCESS(rv, rv); if (didChange) { NS_ENSURE_TRUE(aChangedNodes->AppendObject(g->mContextNode), NS_ERROR_FAILURE); } } break; default: NS_ERROR("There was no expression which matched\n"); res = PR_FALSE; break; } // Mark successors dirty nsXFormsMDGNode* sucnode; for (PRInt32 j = 0; j < g->mSuc.Count(); ++j) { sucnode = NS_STATIC_CAST(nsXFormsMDGNode*, g->mSuc[j]); if (!sucnode) { NS_ERROR("nsXFormsMDGEngine::Calculate(): Node has NULL successor!"); return NS_ERROR_FAILURE; } sucnode->MarkDirty(); } g->MarkClean(); } nsXFormsUtils::MakeUniqueAndSort(aChangedNodes); #ifdef DEBUG_XF_MDG printf("\taChangedNodes: %d\n", aChangedNodes->Count()); printf("\tmNodeToMDG: %d\n", mNodeToMDG.Count()); printf("\tmNodeStates: %d\n", mNodeStates.Count()); printf("\tGraph nodes: %d\n", mGraph.Count()); #endif return res; } nsresult nsXFormsMDGEngine::Revalidate(nsCOMArray *aNodes) { NS_ENSURE_ARG(aNodes); NS_ENSURE_STATE(mModel); for (PRInt32 i = 0; i < aNodes->Count(); ++i) { nsCOMPtr node = aNodes->ObjectAt(i); nsXFormsNodeState* ns = GetNCNodeState(node); PRBool constraint; mModel->ValidateNode(node, &constraint); if (constraint != ns->IsConstraintSchema()) { ns->Set(eFlag_CONSTRAINT_SCHEMA, constraint); ns->Set(eFlag_DISPATCH_VALID_CHANGED, PR_TRUE); } } return NS_OK; } nsresult nsXFormsMDGEngine::Rebuild() { #ifdef DEBUG_XF_MDG printf("nsXFormsMDGEngine::Rebuild()\n"); #endif nsresult rv = NS_OK; mJustRebuilt = PR_TRUE; mFirstCalculate = PR_FALSE; mGraph.Clear(); mNodeStates.Clear(); nsDeque sortedNodes(nsnull); #ifdef DEBUG_XF_MDG printf("\tmNodesInGraph: %d\n", mNodesInGraph); printf("\tmNodeToMDG: %d\n", mNodeToMDG.Count()); printf("\tmNodeStates: %d\n", mNodeStates.Count()); #endif // Initial scan for nsXFormsMDGNodes with no dependencies (mCount == 0) PRUint32 entries = mNodeToMDG.EnumerateRead(AddStartNodes, &sortedNodes); if (entries != mNodeToMDG.Count()) { return NS_ERROR_OUT_OF_MEMORY; } #ifdef DEBUG_XF_MDG printf("\tStartNodes: %d\n", sortedNodes.GetSize()); #endif nsXFormsMDGNode* node; while ((node = NS_STATIC_CAST(nsXFormsMDGNode*, sortedNodes.Pop()))) { for (PRInt32 i = 0; i < node->mSuc.Count(); ++i) { nsXFormsMDGNode* sucNode = NS_STATIC_CAST(nsXFormsMDGNode*, node->mSuc[i]); NS_ASSERTION(sucNode, "XForms: NULL successor node"); sucNode->mCount--; if (sucNode->mCount == 0) { sortedNodes.Push(sucNode); } } node->MarkDirty(); if (!mGraph.AppendElement(node)) { return NS_ERROR_OUT_OF_MEMORY; } } #ifdef DEBUG_XF_MDG printf("\tmGraph.Count() = %2d\n", mGraph.Count()); printf("\tmNodesInGraph = %2d\n", mNodesInGraph); #endif if (mGraph.Count() != mNodesInGraph) { nsCOMPtr modelElement; if (mModel) { modelElement = mModel->GetDOMElement(); } nsXFormsUtils::ReportError(NS_LITERAL_STRING("MDGLoopError"), modelElement); rv = NS_ERROR_ABORT; } mNodesInGraph = 0; return rv; } nsresult nsXFormsMDGEngine::ClearDispatchFlags() { mJustRebuilt = PR_FALSE; return AndFlags(kFlags_NOT_DISPATCH) ? NS_OK : NS_ERROR_FAILURE; } nsresult nsXFormsMDGEngine::Clear() { #ifdef DEBUG_XF_MDG printf("nsXFormsMDGEngine::Clear()\n"); #endif nsresult rv = mNodeToMDG.Enumerate(DeleteLinkedNodes, nsnull); NS_ENSURE_SUCCESS(rv, rv); mNodeToMDG.Clear(); mNodeStates.Clear(); mGraph.Clear(); mNodesInGraph = 0; return NS_OK; } nsresult nsXFormsMDGEngine::SetNodeValue(nsIDOMNode *aContextNode, const nsAString &aNodeValue, PRBool *aNodeChanged) { return SetNodeValueInternal(aContextNode, aNodeValue, PR_TRUE, PR_FALSE, aNodeChanged); } nsresult nsXFormsMDGEngine::SetNodeValueInternal(nsIDOMNode *aContextNode, const nsAString &aNodeValue, PRBool aMarkNode, PRBool aIsCalculate, PRBool *aNodeChanged) { if (aNodeChanged) { *aNodeChanged = PR_FALSE; } const nsXFormsNodeState* ns = GetNodeState(aContextNode); NS_ENSURE_TRUE(ns, NS_ERROR_FAILURE); // If the node is read-only and not set by a @calculate MIP, // ignore the call if (ns->IsReadonly() && !aIsCalculate) { /// /// @todo Better feedback for readonly nodes? (XXX) return NS_OK; } nsCOMPtr childNode; PRUint16 nodeType; nsresult rv = aContextNode->GetNodeType(&nodeType); NS_ENSURE_SUCCESS(rv, rv); nsAutoString oldValue; nsXFormsUtils::GetNodeValue(aContextNode, oldValue); if (oldValue.Equals(aNodeValue)) { return NS_OK; } switch(nodeType) { case nsIDOMNode::ATTRIBUTE_NODE: case nsIDOMNode::TEXT_NODE: case nsIDOMNode::CDATA_SECTION_NODE: case nsIDOMNode::PROCESSING_INSTRUCTION_NODE: case nsIDOMNode::COMMENT_NODE: rv = aContextNode->SetNodeValue(aNodeValue); NS_ENSURE_SUCCESS(rv, rv); break; case nsIDOMNode::ELEMENT_NODE: rv = aContextNode->GetFirstChild(getter_AddRefs(childNode)); NS_ENSURE_SUCCESS(rv, rv); if (!childNode) { rv = CreateNewChild(aContextNode, aNodeValue); NS_ENSURE_SUCCESS(rv, rv); } else { PRUint16 childType; rv = childNode->GetNodeType(&childType); NS_ENSURE_SUCCESS(rv, rv); if (childType == nsIDOMNode::TEXT_NODE || childType == nsIDOMNode::CDATA_SECTION_NODE) { rv = childNode->SetNodeValue(aNodeValue); NS_ENSURE_SUCCESS(rv, rv); // Remove all leading text child nodes except first one (see // nsXFormsUtils::GetNodeValue method for motivation). nsCOMPtr siblingNode; while (true) { rv = childNode->GetNextSibling(getter_AddRefs(siblingNode)); NS_ENSURE_SUCCESS(rv, rv); if (!siblingNode) break; rv = siblingNode->GetNodeType(&childType); NS_ENSURE_SUCCESS(rv, rv); if (childType != nsIDOMNode::TEXT_NODE && childType != nsIDOMNode::CDATA_SECTION_NODE) { break; } nsCOMPtr stubNode; rv = aContextNode->RemoveChild(siblingNode, getter_AddRefs(stubNode)); NS_ENSURE_SUCCESS(rv, rv); } } else { // Not a text child, create a new one rv = CreateNewChild(aContextNode, aNodeValue, childNode); NS_ENSURE_SUCCESS(rv, rv); } } break; default: /// Unsupported nodeType /// @todo Should return more specific error? (XXX) return NS_ERROR_ILLEGAL_VALUE; break; } // NB: Never reached for Readonly nodes. if (aNodeChanged) { *aNodeChanged = PR_TRUE; } if (aMarkNode) { MarkNodeAsChanged(aContextNode); } return NS_OK; } nsresult nsXFormsMDGEngine::SetNodeContent(nsIDOMNode *aContextNode, nsIDOMNode *aContentEnvelope) { NS_ENSURE_ARG(aContextNode); NS_ENSURE_ARG(aContentEnvelope); // ok, this is tricky. This function will REPLACE the contents of // aContextNode with the a clone of the contents of aContentEnvelope. If // aContentEnvelope has no contents, then any contents that aContextNode // has will still be removed. const nsXFormsNodeState* ns = GetNodeState(aContextNode); NS_ENSURE_TRUE(ns, NS_ERROR_FAILURE); // If the node is read-only and not set by a @calculate MIP, // ignore the call if (ns->IsReadonly()) { /// /// @todo Better feedback for readonly nodes? (XXX) return NS_OK; } PRUint16 nodeType; nsresult rv = aContextNode->GetNodeType(&nodeType); NS_ENSURE_SUCCESS(rv, rv); if (nodeType != nsIDOMNode::ELEMENT_NODE) { // got to return something pretty unique that we can check down the road in // order to dispatch any error events return NS_ERROR_DOM_WRONG_TYPE_ERR; } // Need to determine if the contents of the context node and content envelope // are already the same. If so, we can avoid some unnecessary work. PRBool hasChildren1, hasChildren2, contentsEqual = PR_FALSE; nsresult rv1 = aContextNode->HasChildNodes(&hasChildren1); nsresult rv2 = aContentEnvelope->HasChildNodes(&hasChildren2); if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2) && hasChildren1 == hasChildren2) { // First test passed. Both have the same number of children nodes. if (hasChildren1) { nsCOMPtr children1, children2; PRUint32 childrenLength1, childrenLength2; rv1 = aContextNode->GetChildNodes(getter_AddRefs(children1)); rv2 = aContentEnvelope->GetChildNodes(getter_AddRefs(children2)); if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2) && children1 && children2) { // Both have child nodes. rv1 = children1->GetLength(&childrenLength1); rv2 = children2->GetLength(&childrenLength2); if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2) && (childrenLength1 == childrenLength2)) { // both have the same number of child nodes. Now checking to see if // each of the children are equal. for (PRUint32 i = 0; i < childrenLength1; ++i) { nsCOMPtr child1, child2; rv1 = children1->Item(i, getter_AddRefs(child1)); rv2 = children2->Item(i, getter_AddRefs(child2)); if (NS_FAILED(rv1) || NS_FAILED(rv2)) { // Unexpected error. Not as many children in the list as we // were told. return NS_ERROR_UNEXPECTED; } contentsEqual = nsXFormsUtils::AreNodesEqual(child1, child2, PR_TRUE); if (!contentsEqual) { break; } } } } } else { // neither have children contentsEqual = PR_TRUE; } } if (contentsEqual) { return NS_OK; } // remove any child nodes that aContextNode already contains nsCOMPtr resultNode; nsCOMPtr childList; rv = aContextNode->GetChildNodes(getter_AddRefs(childList)); NS_ENSURE_SUCCESS(rv, rv); if (childList) { PRUint32 length; rv = childList->GetLength(&length); NS_ENSURE_SUCCESS(rv, rv); for (PRInt32 i = length-1; i >= 0; i--) { nsCOMPtr childNode; rv = childList->Item(i, getter_AddRefs(childNode)); NS_ENSURE_SUCCESS(rv, rv); rv = aContextNode->RemoveChild(childNode, getter_AddRefs(resultNode)); NS_ENSURE_SUCCESS(rv, rv); } } // add contents of the envelope under aContextNode nsCOMPtr childNode; rv = aContentEnvelope->GetFirstChild(getter_AddRefs(childNode)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr document; rv = aContextNode->GetOwnerDocument(getter_AddRefs(document)); NS_ENSURE_STATE(document); while (childNode) { nsCOMPtr importedNode; rv = document->ImportNode(childNode, PR_TRUE, getter_AddRefs(importedNode)); NS_ENSURE_STATE(importedNode); rv = aContextNode->AppendChild(importedNode, getter_AddRefs(resultNode)); NS_ENSURE_SUCCESS(rv, rv); rv = childNode->GetNextSibling(getter_AddRefs(resultNode)); NS_ENSURE_SUCCESS(rv, rv); resultNode.swap(childNode); } // We already know that the contents have changed. Mark the node so that // a xforms-value-changed can be dispatched. MarkNodeAsChanged(aContextNode); return NS_OK; } const nsXFormsNodeState* nsXFormsMDGEngine::GetNodeState(nsIDOMNode *aContextNode) { return GetNCNodeState(aContextNode); } /**********************************************/ /* Private functions */ /**********************************************/ nsXFormsNodeState* nsXFormsMDGEngine::GetNCNodeState(nsIDOMNode *aContextNode) { nsXFormsNodeState* ns = nsnull; if (aContextNode && !mNodeStates.Get(aContextNode, &ns)) { ns = new nsXFormsNodeState(kFlags_DEFAULT | ((mJustRebuilt && mFirstCalculate) ? kFlags_INITIAL_DISPATCH : 0)); NS_ASSERTION(ns, "Could not create new nsXFormsNodeState"); if (!mNodeStates.Put(aContextNode, ns)) { NS_ERROR("Could not insert new nsXFormsNodeState"); delete ns; return nsnull; } aContextNode->AddRef(); // Do an initial type check, and set the validity state PRBool constraint; mModel->ValidateNode(aContextNode, &constraint); ns->Set(eFlag_CONSTRAINT_SCHEMA, constraint); } return ns; } nsresult nsXFormsMDGEngine::CreateNewChild(nsIDOMNode *aContextNode, const nsAString &aNodeValue, nsIDOMNode *aBeforeNode) { nsresult rv; nsCOMPtr document; rv = aContextNode->GetOwnerDocument(getter_AddRefs(document)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr textNode; rv = document->CreateTextNode(aNodeValue, getter_AddRefs(textNode)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr newNode; if (aBeforeNode) { rv = aContextNode->InsertBefore(textNode, aBeforeNode, getter_AddRefs(newNode)); } else { rv = aContextNode->AppendChild(textNode, getter_AddRefs(newNode)); } return rv; } PLDHashOperator nsXFormsMDGEngine::DeleteLinkedNodes(nsISupports *aKey, nsAutoPtr &aNode, void *aArg) { if (!aNode) { NS_WARNING("nsXFormsMDGEngine::DeleteLinkedNodes() called with aNode == nsnull!"); return PL_DHASH_STOP; } nsXFormsMDGNode* temp; nsXFormsMDGNode* next = aNode->mNext; while (next) { temp = next; next = next->mNext; delete temp; } return PL_DHASH_NEXT; } nsXFormsMDGNode* nsXFormsMDGEngine::GetNode(nsIDOMNode *aDomNode, ModelItemPropName aType, PRBool aCreate) { nsIDOMNode* nodeKey = aDomNode; nsXFormsMDGNode* nd = nsnull; #ifdef DEBUG_XF_MDG printf("nsXFormsMDGEngine::GetNode(aDomNode=%p, aType=%s, aCreate=%d)\n", (void*) nodeKey, gMIPNames[aType], aCreate); #endif // Find correct type if (mNodeToMDG.Get(nodeKey, &nd) && aType != eModel_type) { while (nd && aType != nd->mType) { nd = nd->mNext; } } // Eventually create node if (!nd && aCreate){ nd = new nsXFormsMDGNode(nodeKey, aType); if (!nd) { NS_ERROR("Could not allocate room for new nsXFormsMDGNode"); return nsnull; } #ifdef DEBUG_XF_MDG printf("\tNode not found, create new MDGNode '%p'\n", (void*) nd); #endif // Link to existing node nsXFormsMDGNode* nd_exists; if (mNodeToMDG.Get(nodeKey, &nd_exists)) { while (nd_exists->mNext) { nd_exists = nd_exists->mNext; } #ifdef DEBUG_XF_MDG printf("\tLinking to existing MDGNode '%p'\n", (void*) nd_exists); #endif nd_exists->mNext = nd; } else { if (!mNodeToMDG.Put(nodeKey, nd)) { delete nd; NS_ERROR("Could not insert new node in HashTable!"); return nsnull; } nodeKey->AddRef(); } mNodesInGraph++; } return nd; } PLDHashOperator nsXFormsMDGEngine::AddStartNodes(nsISupports *aKey, nsXFormsMDGNode *aNode, void *aDeque) { #ifdef DEBUG_XF_MDG printf("nsXFormsMDGEngine::AddStartNodes(aKey=n/a, aNode=%p, aDeque=%p)\n", (void*) aNode, aDeque); #endif nsDeque* deque = NS_STATIC_CAST(nsDeque*, aDeque); if (!deque) { NS_ERROR("nsXFormsMDGEngine::AddStartNodes called with NULL aDeque"); return PL_DHASH_STOP; } while (aNode) { if (aNode->mCount == 0) { /// /// @todo Is it not possible to check error condition? (XXX) deque->Push(aNode); } aNode = aNode->mNext; } return PL_DHASH_NEXT; } PLDHashOperator nsXFormsMDGEngine::AndFlag(nsISupports *aKey, nsAutoPtr &aState, void *aMask) { PRUint16* andMask = NS_STATIC_CAST(PRUint16*, aMask); if (!andMask) { return PL_DHASH_STOP; } *aState &= *andMask; return PL_DHASH_NEXT; } PRBool nsXFormsMDGEngine::AndFlags(PRUint16 aAndMask) { PRUint32 entries = mNodeStates.Enumerate(AndFlag, &aAndMask); return (entries == mNodeStates.Count()) ? PR_TRUE : PR_FALSE; } nsresult nsXFormsMDGEngine::BooleanExpression(nsXFormsMDGNode* aNode, PRBool& state) { NS_ENSURE_ARG_POINTER(aNode); NS_ENSURE_TRUE(aNode->mExpression, NS_ERROR_FAILURE); nsISupports* retval; nsresult rv; rv = aNode->mExpression->EvaluateWithContext(aNode->mContextNode, aNode->mContextPosition, aNode->mContextSize, nsIDOMXPathResult::BOOLEAN_TYPE, nsnull, &retval); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr xpath_res = do_QueryInterface(retval); NS_ENSURE_TRUE(xpath_res, NS_ERROR_FAILURE); rv = xpath_res->GetBooleanValue(&state); return rv; } nsresult nsXFormsMDGEngine::ComputeMIP(eFlag_t aStateFlag, eFlag_t aDispatchFlag, nsXFormsMDGNode *aNode, PRBool &aDidChange) { NS_ENSURE_ARG(aNode); aDidChange = PR_FALSE; if (!aNode->mExpression) return NS_OK; nsXFormsNodeState* ns = GetNCNodeState(aNode->mContextNode); NS_ENSURE_TRUE(ns, NS_ERROR_FAILURE); PRBool state; nsresult rv = BooleanExpression(aNode, state); NS_ENSURE_SUCCESS(rv, rv); PRBool cstate = ns->Test(aStateFlag); ns->Set(aStateFlag, state); aDidChange = (state != cstate) ? PR_TRUE : PR_FALSE; if (aDidChange) { ns->Set(aDispatchFlag, PR_TRUE); } return NS_OK; } nsresult nsXFormsMDGEngine::ComputeMIPWithInheritance(eFlag_t aStateFlag, eFlag_t aDispatchFlag, eFlag_t aInheritanceFlag, nsXFormsMDGNode *aNode, nsCOMArray *aSet) { nsresult rv; PRBool didChange; rv = ComputeMIP(aStateFlag, aDispatchFlag, aNode, didChange); NS_ENSURE_SUCCESS(rv, rv); if (didChange) { nsXFormsNodeState* ns = GetNCNodeState(aNode->mContextNode); NS_ENSURE_TRUE(ns, NS_ERROR_FAILURE); if ( !(aStateFlag == eFlag_READONLY && ns->Test(aInheritanceFlag)) || (aStateFlag == eFlag_RELEVANT && ns->Test(aInheritanceFlag)) ) { NS_ENSURE_TRUE(aSet->AppendObject(aNode->mContextNode), NS_ERROR_FAILURE); rv = AttachInheritance(aSet, aNode->mContextNode, ns->Test(aStateFlag), aStateFlag); } } return rv; } nsresult nsXFormsMDGEngine::AttachInheritance(nsCOMArray *aSet, nsIDOMNode *aSrc, PRBool aState, eFlag_t aStateFlag) { NS_ENSURE_ARG(aSrc); nsCOMPtr node; nsresult rv; PRBool updateNode = PR_FALSE; nsCOMPtr childList; rv = aSrc->GetChildNodes(getter_AddRefs(childList)); NS_ENSURE_SUCCESS(rv, rv); PRUint32 childCount; rv = childList->GetLength(&childCount); NS_ENSURE_SUCCESS(rv, rv); for (PRUint32 i = 0; i < childCount; i++) { rv = childList->Item(i, getter_AddRefs(node)); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(node, NS_ERROR_FAILURE); nsXFormsNodeState *ns = GetNCNodeState(node); NS_ENSURE_TRUE(ns, NS_ERROR_FAILURE); PRBool curState = ns->Test(aStateFlag); if (aStateFlag == eFlag_RELEVANT) { if (!aState) { // The nodes are getting irrelevant if (ns->Test(eFlag_INHERITED_RELEVANT) && curState) { ns->Set(eFlag_INHERITED_RELEVANT, PR_FALSE); ns->Set(eFlag_DISPATCH_RELEVANT_CHANGED, PR_TRUE); updateNode = PR_TRUE; } } else { // The nodes are becoming relevant if (curState) { // Relevant has changed from inheritance ns->Set(eFlag_DISPATCH_RELEVANT_CHANGED, PR_TRUE); ns->Set(eFlag_INHERITED_RELEVANT, PR_TRUE); updateNode = PR_TRUE; } } } else if (aStateFlag == eFlag_READONLY) { if (aState) { // The nodes are getting readonly if (!ns->Test(eFlag_INHERITED_READONLY) && curState == PR_FALSE) { ns->Set(eFlag_INHERITED_READONLY | eFlag_DISPATCH_READONLY_CHANGED, PR_TRUE); updateNode = PR_TRUE; } } else { // The nodes are getting readwrite if (curState) { ns->Set(eFlag_DISPATCH_READONLY_CHANGED, PR_TRUE); ns->Set(eFlag_INHERITED_READONLY, PR_FALSE); updateNode = PR_TRUE; } } } if (updateNode) { rv = AttachInheritance(aSet, node, aState, aStateFlag); NS_ENSURE_SUCCESS(rv, rv); NS_ENSURE_TRUE(aSet->AppendObject(node), NS_ERROR_FAILURE); updateNode = PR_FALSE; } } return NS_OK; } nsresult nsXFormsMDGEngine::Invalidate() { nsXFormsMDGNode* g; for (PRInt32 i = 0; i < mGraph.Count(); ++i) { g = NS_STATIC_CAST(nsXFormsMDGNode*, mGraph[i]); NS_ENSURE_TRUE(g, NS_ERROR_FAILURE); g->MarkDirty(); } return NS_OK; }