/* -*- 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 * IBM Corporation. * Portions created by the Initial Developer are Copyright (C) 2004 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Brian Ryner * Allan Beaufour * Darin Fisher * * 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 "nsXFormsModelElement.h" #include "nsIXTFGenericElementWrapper.h" #include "nsMemory.h" #include "nsIDOMElement.h" #include "nsIDOM3Node.h" #include "nsIDOMNodeList.h" #include "nsString.h" #include "nsIDocument.h" #include "nsXFormsAtoms.h" #include "nsINameSpaceManager.h" #include "nsIServiceManager.h" #include "nsINodeInfo.h" #include "nsIDOMEvent.h" #include "nsIDOMDOMImplementation.h" #include "nsIDOMXMLDocument.h" #include "nsIDOMEventReceiver.h" #include "nsIDOMXPathResult.h" #include "nsIXFormsXPathEvaluator.h" #include "nsIDOMXPathNSResolver.h" #include "nsIDOMXPathExpression.h" #include "nsIScriptGlobalObject.h" #include "nsIContent.h" #include "nsIURL.h" #include "nsNetUtil.h" #include "nsIXFormsControl.h" #include "nsXFormsTypes.h" #include "nsXFormsXPathParser.h" #include "nsXFormsXPathAnalyzer.h" #include "nsIInstanceElementPrivate.h" #include "nsXFormsUtils.h" #include "nsXFormsSchemaValidator.h" #include "nsISchemaLoader.h" #include "nsISchema.h" #include "nsAutoPtr.h" #include "nsArray.h" #ifdef DEBUG //#define DEBUG_MODEL #endif //------------------------------------------------------------------------------ // Helper function for using XPath to locate an element by // matching its "id" attribute. This is necessary since is // treated as an ordinary XML data node without an "ID" attribute. static void GetSchemaElementById(nsIDOMElement *contextNode, const nsString &id, nsIDOMElement **resultNode) { // search for an element with the given "id" attribute, and then verify // that the element is in the XML Schema namespace. nsAutoString expr; expr.AssignLiteral("//*[@id=\""); expr.Append(id); expr.AppendLiteral("\"]"); nsCOMPtr xpRes = nsXFormsUtils::EvaluateXPath(expr, contextNode, contextNode, nsIDOMXPathResult::FIRST_ORDERED_NODE_TYPE); if (xpRes) { nsCOMPtr node; xpRes->GetSingleNodeValue(getter_AddRefs(node)); if (node) { nsAutoString ns; node->GetNamespaceURI(ns); if (ns.EqualsLiteral(NS_NAMESPACE_XML_SCHEMA)) CallQueryInterface(node, resultNode); } } } //------------------------------------------------------------------------------ static void DeleteVoidArray(void *aObject, nsIAtom *aPropertyName, void *aPropertyValue, void *aData) { delete NS_STATIC_CAST(nsVoidArray *, aPropertyValue); } static nsresult AddToModelList(nsIDOMDocument *domDoc, nsXFormsModelElement *model) { nsCOMPtr doc = do_QueryInterface(domDoc); nsVoidArray *models = NS_STATIC_CAST(nsVoidArray *, doc->GetProperty(nsXFormsAtoms::modelListProperty)); if (!models) { models = new nsVoidArray(16); if (!models) return NS_ERROR_OUT_OF_MEMORY; doc->SetProperty(nsXFormsAtoms::modelListProperty, models, DeleteVoidArray); } models->AppendElement(model); return NS_OK; } static void RemoveFromModelList(nsIDOMDocument *domDoc, nsXFormsModelElement *model) { nsCOMPtr doc = do_QueryInterface(domDoc); nsVoidArray *models = NS_STATIC_CAST(nsVoidArray *, doc->GetProperty(nsXFormsAtoms::modelListProperty)); if (models) models->RemoveElement(model); } static const nsVoidArray * GetModelList(nsIDOMDocument *domDoc) { nsCOMPtr doc = do_QueryInterface(domDoc); return NS_STATIC_CAST(nsVoidArray *, doc->GetProperty(nsXFormsAtoms::modelListProperty)); } //------------------------------------------------------------------------------ static const nsIID sScriptingIIDs[] = { NS_IDOMELEMENT_IID, NS_IDOMEVENTTARGET_IID, NS_IDOM3NODE_IID, NS_IXFORMSMODELELEMENT_IID }; static nsIAtom* sModelPropsList[eModel__count]; nsXFormsModelElement::nsXFormsModelElement() : mElement(nsnull), mSchemaCount(0), mSchemaTotal(0), mPendingInstanceCount(0), mDocumentLoaded(PR_FALSE) { } NS_IMPL_ISUPPORTS_INHERITED5(nsXFormsModelElement, nsXFormsStubElement, nsIXFormsModelElement, nsIModelElementPrivate, nsISchemaLoadListener, nsIWebServiceErrorHandler, nsIDOMEventListener) NS_IMETHODIMP nsXFormsModelElement::OnDestroyed() { RemoveModelFromDocument(); mElement = nsnull; mSchemas = nsnull; return NS_OK; } void nsXFormsModelElement::RemoveModelFromDocument() { mDocumentLoaded = PR_FALSE; nsCOMPtr domDoc; mElement->GetOwnerDocument(getter_AddRefs(domDoc)); if (!domDoc) return; RemoveFromModelList(domDoc, this); nsCOMPtr targ = do_QueryInterface(domDoc); if (targ) targ->RemoveEventListener(NS_LITERAL_STRING("DOMContentLoaded"), this, PR_TRUE); } NS_IMETHODIMP nsXFormsModelElement::GetScriptingInterfaces(PRUint32 *aCount, nsIID ***aArray) { return nsXFormsUtils::CloneScriptingInterfaces(sScriptingIIDs, NS_ARRAY_LENGTH(sScriptingIIDs), aCount, aArray); } NS_IMETHODIMP nsXFormsModelElement::WillChangeDocument(nsIDOMDocument* aNewDocument) { RemoveModelFromDocument(); return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::DocumentChanged(nsIDOMDocument* aNewDocument) { if (!aNewDocument) return NS_OK; AddToModelList(aNewDocument, this); nsCOMPtr targ = do_QueryInterface(aNewDocument); if (targ) targ->AddEventListener(NS_LITERAL_STRING("DOMContentLoaded"), this, PR_TRUE); return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::DoneAddingChildren() { // We wait until all children are added to dispatch xforms-model-construct, // since the model may have an action handler for this event. nsresult rv = nsXFormsUtils::DispatchEvent(mElement, eEvent_ModelConstruct); NS_ENSURE_SUCCESS(rv, rv); // xforms-model-construct is not cancellable, so always proceed. // We continue here rather than doing this in HandleEvent since we know // it only makes sense to perform this default action once. // (XForms 4.2.1) // 1. load xml schemas nsAutoString schemaList; mElement->GetAttribute(NS_LITERAL_STRING("schema"), schemaList); if (!schemaList.IsEmpty()) { NS_ENSURE_TRUE(mSchemas, NS_ERROR_FAILURE); // Parse the whitespace-separated list. nsCOMPtr content = do_QueryInterface(mElement); nsRefPtr baseURI = content->GetBaseURI(); nsCStringArray schemas; schemas.ParseString(NS_ConvertUTF16toUTF8(schemaList).get(), " \t\r\n"); // Increase by 1 to prevent OnLoad from calling FinishConstruction mSchemaTotal = schemas.Count(); for (PRInt32 i=0; i newURI; NS_NewURI(getter_AddRefs(newURI), *schemas[i], nsnull, baseURI); nsCOMPtr newURL = do_QueryInterface(newURI); if (!newURL) { rv = NS_ERROR_UNEXPECTED; } else { // This code is copied from nsXMLEventsManager for extracting an // element ID from an xsd:anyURI link. nsCAutoString ref; newURL->GetRef(ref); newURL->SetRef(EmptyCString()); PRBool equals = PR_FALSE; newURL->Equals(baseURI, &equals); if (equals) { // We will not be able to locate the element using the // getElementById function defined on our document when // is treated as an ordinary XML data node. So, we employ XPath to // locate it for us. NS_ConvertUTF8toUTF16 id(ref); nsCOMPtr el; GetSchemaElementById(mElement, id, getter_AddRefs(el)); if (!el) { // Perhaps the element appears after the // element in the document, so we'll defer loading it until the // document has finished loading. mPendingInlineSchemas.AppendString(id); } else { nsCOMPtr schema; // no need to observe errors via the callback. instead, rely on // this method returning a failure code when it encounters errors. rv = mSchemas->ProcessSchemaElement(el, nsnull, getter_AddRefs(schema)); if (NS_SUCCEEDED(rv)) mSchemaCount++; } } else { nsCAutoString uriSpec; newURI->GetSpec(uriSpec); rv = mSchemas->LoadAsync(NS_ConvertUTF8toUTF16(uriSpec), this); } } if (NS_FAILED(rv)) { // this is a fatal error nsXFormsUtils::DispatchEvent(mElement, eEvent_LinkException); return NS_OK; } } } // 2. construct an XPath data model from inline or external initial instance // data. This is done by our child instance elements as they are inserted // into the document, and all of the instances will be processed by this // point. // schema and external instance data loads should delay document onload if (IsComplete()) { // No need to fire refresh event if we assume that all UI controls // appear later in the document. NS_ASSERTION(!mDocumentLoaded, "document should not be loaded yet"); return FinishConstruction(); } return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::HandleDefault(nsIDOMEvent *aEvent, PRBool *aHandled) { *aHandled = PR_TRUE; nsAutoString type; aEvent->GetType(type); if (type.EqualsLiteral("xforms-refresh")) { Refresh(); } else if (type.EqualsLiteral("xforms-revalidate")) { Revalidate(); } else if (type.EqualsLiteral("xforms-recalculate")) { Recalculate(); } else if (type.EqualsLiteral("xforms-rebuild")) { Rebuild(); } else if (type.EqualsLiteral("xforms-reset")) { #ifdef DEBUG printf("nsXFormsModelElement::Reset()\n"); #endif } else { *aHandled = PR_FALSE; } return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::OnCreated(nsIXTFGenericElementWrapper *aWrapper) { aWrapper->SetNotificationMask(nsIXTFElement::NOTIFY_WILL_CHANGE_DOCUMENT | nsIXTFElement::NOTIFY_DOCUMENT_CHANGED | nsIXTFElement::NOTIFY_DONE_ADDING_CHILDREN | nsIXTFElement::NOTIFY_HANDLE_DEFAULT); nsCOMPtr node; aWrapper->GetElementNode(getter_AddRefs(node)); // It's ok to keep a weak pointer to mElement. mElement will have an // owning reference to this object, so as long as we null out mElement in // OnDestroyed, it will always be valid. mElement = node; NS_ASSERTION(mElement, "Wrapper is not an nsIDOMElement, we'll crash soon"); nsresult rv = mMDG.Init(this); NS_ENSURE_SUCCESS(rv, rv); mSchemas = do_GetService(NS_SCHEMALOADER_CONTRACTID); return NS_OK; } // nsIXFormsModelElement NS_IMETHODIMP nsXFormsModelElement::GetInstanceDocument(const nsAString& aInstanceID, nsIDOMDocument **aDocument) { NS_ENSURE_ARG_POINTER(aDocument); *aDocument = FindInstanceDocument(aInstanceID).get(); // transfer reference return *aDocument ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsXFormsModelElement::Rebuild() { #ifdef DEBUG printf("nsXFormsModelElement::Rebuild()\n"); #endif // TODO: Clear graph and re-attach elements // 1 . Clear graph // mMDG.Clear(); // 2. Re-attach all elements // 3. Rebuild graph return mMDG.Rebuild(); } NS_IMETHODIMP nsXFormsModelElement::Recalculate() { #ifdef DEBUG printf("nsXFormsModelElement::Recalculate()\n"); #endif return mMDG.Recalculate(&mChangedNodes); } void nsXFormsModelElement::DispatchEvents(nsIXFormsControl *aControl, nsIDOMNode *aNode) { nsCOMPtr element; aControl->GetElement(getter_AddRefs(element)); const nsXFormsNodeState* ns = mMDG.GetNodeState(aNode); if (!element || !ns) { #ifdef DEBUG_beaufour printf("nsXFormsModelElement::DispatchEvents(): Could not get element or node state for node!\n"); #endif return; } if (ns->ShouldDispatchValid()) { nsXFormsUtils::DispatchEvent(element, ns->IsValid() ? eEvent_Valid : eEvent_Invalid); } if (ns->ShouldDispatchReadonly()) { nsXFormsUtils::DispatchEvent(element, ns->IsReadonly() ? eEvent_Readonly : eEvent_Readwrite); } if (ns->ShouldDispatchRequired()) { nsXFormsUtils::DispatchEvent(element, ns->IsRequired() ? eEvent_Required : eEvent_Optional); } if (ns->ShouldDispatchRelevant()) { nsXFormsUtils::DispatchEvent(element, ns->IsRelevant() ? eEvent_Enabled : eEvent_Disabled); } if (ns->ShouldDispatchValueChanged()) { nsXFormsUtils::DispatchEvent(element, eEvent_ValueChanged); } } NS_IMETHODIMP nsXFormsModelElement::Revalidate() { #ifdef DEBUG printf("nsXFormsModelElement::Revalidate()\n"); #endif /// @note Prerequisite: Both changed nodes and dependencies are sorted in /// ascending order! #ifdef DEBUG_MODEL printf("Changed nodes:\n"); for (PRInt32 j = 0; j < mChangedNodes.Count(); ++j) { nsCOMPtr node = mChangedNodes.GetNode(j); nsAutoString name; node->GetNodeName(name); printf("\t%s\n", NS_ConvertUCS2toUTF8(name).get()); } #endif // Iterate over all form controls PRInt32 controlCount = mFormControls.Count(); for (PRInt32 i = 0; i < controlCount; ++i) { nsIXFormsControl* control = NS_STATIC_CAST(nsIXFormsControl*, mFormControls[i]); /// @todo If a control is removed because of previous control has been /// refreshed, we do, obviously, not need to refresh it. So mFormControls /// should have weak bindings to the controls I guess? (XXX) /// /// This could happen for \s for example. if (!control) { continue; } // Get bound node nsCOMPtr boundNode; control->GetBoundNode(getter_AddRefs(boundNode)); // Get dependencies nsCOMPtr deps; control->GetDependencies(getter_AddRefs(deps)); PRUint32 depCount = 0; if (deps) { deps->GetLength(&depCount); } #ifdef DEBUG_MODEL nsCOMPtr controlElement; control->GetElement(getter_AddRefs(controlElement)); if (controlElement) { printf("Checking control: "); //DBG_TAGINFO(controlElement); nsAutoString boundName; if (boundNode) boundNode->GetNodeName(boundName); printf("\tBound to: '%s', dependencies: %d\n", NS_ConvertUCS2toUTF8(boundName).get(), depCount); } #endif nsCOMPtr curDep, curChanged; PRUint32 depPos = 0; /// @bug This should be set to PR_FALSE! (XXX) /// Setting it to PR_TRUE rebinds all controls all the time /// @see https://bugzilla.mozilla.org/show_bug.cgi?id=278368 PRBool rebind = PR_TRUE; PRBool refresh = PR_FALSE; for (PRInt32 j = 0; j < mChangedNodes.Count(); ++j) { curChanged = mChangedNodes.GetNode(j); if (curChanged == boundNode) { refresh = PR_TRUE; // We cannot break here, as we need to to check for any changed // dependencies } if (depPos == depCount) { continue; } while (depPos < depCount && (void*) curChanged > (void*) curDep) { curDep = do_QueryElementAt(deps, depPos); ++depPos; } if (curDep == curChanged) { rebind = PR_TRUE; break; } } #ifdef DEBUG_MODEL printf("\trebind: %d, refresh: %d\n", rebind, refresh); #endif if (rebind) { control->Bind(); control->GetBoundNode(getter_AddRefs(boundNode)); } if (rebind || refresh) { DispatchEvents(control, boundNode); /// /// @todo Should be moved to Refresh() (XXX) control->Refresh(); } } mChangedNodes.Clear(); mMDG.ClearDispatchFlags(); return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::Refresh() { #ifdef DEBUG printf("nsXFormsModelElement::Refresh()\n"); #endif /// @todo Any refreshing is for the moment done in Revalidate(), so we do /// not need to do anything here. But the refreshing part should probably /// be moved to Refresh()... (XXX) return NS_OK; } // nsISchemaLoadListener NS_IMETHODIMP nsXFormsModelElement::OnLoad(nsISchema* aSchema) { mSchemaCount++; if (IsComplete()) { nsresult rv = FinishConstruction(); NS_ENSURE_SUCCESS(rv, rv); nsXFormsUtils::DispatchEvent(mElement, eEvent_Refresh); MaybeNotifyCompletion(); } return NS_OK; } // nsIWebServiceErrorHandler NS_IMETHODIMP nsXFormsModelElement::OnError(nsresult aStatus, const nsAString &aStatusMessage) { nsXFormsUtils::DispatchEvent(mElement, eEvent_LinkException); return NS_OK; } // nsIDOMEventListener NS_IMETHODIMP nsXFormsModelElement::HandleEvent(nsIDOMEvent* aEvent) { nsAutoString type; aEvent->GetType(type); if (!type.EqualsLiteral("DOMContentLoaded")) return NS_OK; mDocumentLoaded = PR_TRUE; if (mPendingInlineSchemas.Count() > 0) { nsCOMPtr el; nsresult rv; for (PRInt32 i=0; i schema; // no need to observe errors via the callback. instead, rely on // this method returning a failure code when it encounters errors. rv = mSchemas->ProcessSchemaElement(el, nsnull, getter_AddRefs(schema)); if (NS_SUCCEEDED(rv)) mSchemaCount++; } if (NS_FAILED(rv)) { // this is a fatal error nsXFormsUtils::DispatchEvent(mElement, eEvent_LinkException); return NS_OK; } } if (IsComplete()) { rv = FinishConstruction(); NS_ENSURE_SUCCESS(rv, rv); nsXFormsUtils::DispatchEvent(mElement, eEvent_Refresh); } mPendingInlineSchemas.Clear(); } // We may still be waiting on external documents to load. MaybeNotifyCompletion(); return NS_OK; } // nsIModelElementPrivate NS_IMETHODIMP nsXFormsModelElement::AddFormControl(nsIXFormsControl *aControl) { if (mFormControls.IndexOf(aControl) == -1) mFormControls.AppendElement(aControl); return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::RemoveFormControl(nsIXFormsControl *aControl) { mFormControls.RemoveElement(aControl); return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::GetTypeForControl(nsIXFormsControl *aControl, nsISchemaType **aType) { *aType = nsnull; return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::InstanceLoadStarted() { ++mPendingInstanceCount; return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::InstanceLoadFinished(PRBool aSuccess) { --mPendingInstanceCount; if (!aSuccess) { nsXFormsUtils::DispatchEvent(mElement, eEvent_LinkException); } else if (IsComplete()) { nsresult rv = FinishConstruction(); if (NS_SUCCEEDED(rv)) { nsXFormsUtils::DispatchEvent(mElement, eEvent_Refresh); MaybeNotifyCompletion(); } } return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::FindInstanceElement(const nsAString &aID, nsIInstanceElementPrivate **aElement) { *aElement = nsnull; nsCOMPtr children; mElement->GetChildNodes(getter_AddRefs(children)); if (!children) return NS_OK; PRUint32 childCount = 0; children->GetLength(&childCount); nsCOMPtr node; nsCOMPtr element; nsAutoString id; for (PRUint32 i = 0; i < childCount; ++i) { children->Item(i, getter_AddRefs(node)); NS_ASSERTION(node, "incorrect NodeList length?"); element = do_QueryInterface(node); if (!element) continue; element->GetAttribute(NS_LITERAL_STRING("id"), id); if (aID.IsEmpty() || aID.Equals(id)) { CallQueryInterface(element, aElement); if (*aElement) break; } } return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::SetNodeValue(nsIDOMNode *aContextNode, const nsAString &aNodeValue, PRBool *aNodeChanged) { return mMDG.SetNodeValue(aContextNode, aNodeValue, PR_TRUE, aNodeChanged); } NS_IMETHODIMP nsXFormsModelElement::GetNodeValue(nsIDOMNode *aContextNode, nsAString &aNodeValue) { return mMDG.GetNodeValue(aContextNode, aNodeValue); } NS_IMETHODIMP nsXFormsModelElement::ValidateNode(nsIDOMNode *aInstanceNode, PRBool *aResult) { NS_ENSURE_ARG_POINTER(aResult); nsresult rv; nsAutoString typeAttribute; nsCOMPtr nodeElem = do_QueryInterface(aInstanceNode, &rv); NS_ENSURE_SUCCESS(rv, rv); nodeElem->GetAttributeNS(NS_LITERAL_STRING(NS_NAMESPACE_XML_SCHEMA_INSTANCE), NS_LITERAL_STRING("type"), typeAttribute); // split type (ns:type) into namespace and type. PRInt32 separator = typeAttribute.FindChar(':'); if ((separator == kNotFound) || ((PRUint32) separator == typeAttribute.Length())) return NS_ERROR_UNEXPECTED; // xxx send error to console nsAutoString schemaTypeName; nsAutoString schemaTypePrefix; nsAutoString schemaTypeNamespace; schemaTypePrefix.Assign(Substring(typeAttribute, 0, separator)); schemaTypeName.Assign(Substring(typeAttribute, ++separator, typeAttribute.Length())); // get the namespace url from the prefix nsCOMPtr domNode3 = do_QueryInterface(mElement, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = domNode3->LookupNamespaceURI(schemaTypePrefix, schemaTypeNamespace); NS_ENSURE_SUCCESS(rv, rv); nsXFormsSchemaValidator validator; nsCOMPtr schema = do_QueryInterface(mSchemas); validator.LoadSchema(schema); nsAutoString value; nsXFormsUtils::GetNodeValue(aInstanceNode, value); PRBool isValid = validator.ValidateString(value, schemaTypeName, schemaTypeNamespace); *aResult = isValid; return NS_OK; } /* Validates an instance data subtree for submission by recursively walking it. It makes sure that all nodes are valid, and that all required nodes have an XForms value. It skips non-relevant nodes and their children, since those won't be submitted per the spec. */ NS_IMETHODIMP nsXFormsModelElement::ValidateInstanceDataForSubmission(nsIDOMNode *aInstanceDataNode, PRBool *aResult) { NS_ENSURE_ARG_POINTER(aResult); NS_ENSURE_ARG_POINTER(aInstanceDataNode); nsresult rv; PRBool isValid = *aResult = PR_FALSE; const nsXFormsNodeState* ns; ns = mMDG.GetNodeState(aInstanceDataNode); NS_ENSURE_STATE(ns); PRBool isRequired = ns->IsRequired(); PRBool isNodeRelevant = ns->IsRelevant(); PRBool isNodeValid = ns->IsValid(); // if not relevant, don't handle subtree if (!isNodeRelevant) { *aResult = PR_TRUE; return NS_OK; } // if invalid abort. if (!isNodeValid) { *aResult = PR_FALSE; return NS_OK; } PRBool hasChildren; rv = aInstanceDataNode->HasChildNodes(&hasChildren); NS_ENSURE_SUCCESS(rv, rv); // if required and no children, its empty and thus invalid. if (isRequired && !hasChildren) { *aResult = PR_FALSE; return NS_OK; } nsCOMPtr node; nsCOMPtr children; rv = aInstanceDataNode->GetChildNodes(getter_AddRefs(children)); NS_ENSURE_SUCCESS(rv, rv); PRUint32 length; children->GetLength(&length); PRBool foundElementNode = PR_FALSE; PRUint16 run = 0; PRBool done = PR_FALSE; PRUint16 nodeType; while (!done && (run < length)) { // If there is an nsIDOMNode::ELEMENT_NODE child, we recurse. children->Item(run, getter_AddRefs(node)); node->GetNodeType(&nodeType); if (nodeType == nsIDOMNode::ELEMENT_NODE) { rv = ValidateInstanceDataForSubmission(node, &isValid); NS_ENSURE_SUCCESS(rv, rv); if (!isValid) done = PR_TRUE; foundElementNode = PR_TRUE; } ++run; } /* If all children are text nodes and the node is required, check its nodevalue. XXX: it is unclear what happens for an required node that has both element and text nodes as children or that only has element nodes as children. Different XForms processors seem to handle it differently. Currently, if a required node has no text children it will pass as GetNodeValue won't get called. */ if (!foundElementNode) { if (isRequired) { nsAutoString value; nsXFormsUtils::GetNodeValue(node, value); isValid = !value.IsEmpty(); } else { isValid = PR_TRUE; } } *aResult = isValid; return rv; } // internal methods already_AddRefed nsXFormsModelElement::FindInstanceDocument(const nsAString &aID) { nsCOMPtr instance; nsXFormsModelElement::FindInstanceElement(aID, getter_AddRefs(instance)); nsIDOMDocument *doc = nsnull; if (instance) instance->GetDocument(&doc); // addrefs return doc; } nsresult nsXFormsModelElement::FinishConstruction() { // 3. if applicable, initialize P3P // 4. construct instance data from initial instance data. apply all // elements in document order. // we get the instance data from our instance child nodes nsCOMPtr firstInstanceDoc = FindInstanceDocument(EmptyString()); if (!firstInstanceDoc) return NS_OK; nsCOMPtr firstInstanceRoot; firstInstanceDoc->GetDocumentElement(getter_AddRefs(firstInstanceRoot)); nsresult rv; nsCOMPtr xpath = do_CreateInstance("@mozilla.org/dom/xforms-xpath-evaluator;1", &rv); NS_ENSURE_TRUE(xpath, rv); nsCOMPtr children; mElement->GetChildNodes(getter_AddRefs(children)); PRUint32 childCount = 0; if (children) children->GetLength(&childCount); nsAutoString namespaceURI, localName; for (PRUint32 i = 0; i < childCount; ++i) { nsCOMPtr child; children->Item(i, getter_AddRefs(child)); NS_ASSERTION(child, "there can't be null items in the NodeList!"); child->GetLocalName(localName); if (localName.EqualsLiteral("bind")) { child->GetNamespaceURI(namespaceURI); if (namespaceURI.EqualsLiteral(NS_NAMESPACE_XFORMS)) { rv = ProcessBind(xpath, firstInstanceRoot, 1, 1, nsCOMPtr(do_QueryInterface(child))); if (NS_FAILED(rv)) { return NS_OK; } } } } // 5. dispatch xforms-rebuild, xforms-recalculate, xforms-revalidate nsXFormsUtils::DispatchEvent(mElement, eEvent_Rebuild); nsXFormsUtils::DispatchEvent(mElement, eEvent_Recalculate); nsXFormsUtils::DispatchEvent(mElement, eEvent_Revalidate); // We're done initializing this model. return NS_OK; } void nsXFormsModelElement::MaybeNotifyCompletion() { nsCOMPtr domDoc; mElement->GetOwnerDocument(getter_AddRefs(domDoc)); const nsVoidArray *models = GetModelList(domDoc); if (!models) { NS_NOTREACHED("no model list property"); return; } PRInt32 i; // Nothing to be done if any model is incomplete or hasn't seen // DOMContentLoaded. for (i = 0; i < models->Count(); ++i) { nsXFormsModelElement *model = NS_STATIC_CAST(nsXFormsModelElement *, models->ElementAt(i)); if (!model->mDocumentLoaded || !model->IsComplete()) return; } // Okay, dispatch xforms-model-construct-done and xforms-ready events! for (i = 0; i < models->Count(); ++i) { nsXFormsModelElement *model = NS_STATIC_CAST(nsXFormsModelElement *, models->ElementAt(i)); nsXFormsUtils::DispatchEvent(model->mElement, eEvent_ModelConstructDone); nsXFormsUtils::DispatchEvent(model->mElement, eEvent_Ready); } } nsresult nsXFormsModelElement::ProcessBind(nsIXFormsXPathEvaluator *aEvaluator, nsIDOMNode *aContextNode, PRInt32 aContextPosition, PRInt32 aContextSize, nsIDOMElement *aBindElement) { // Get the model item properties specified by this \. nsCOMPtr props[eModel__count]; nsAutoString propStrings[eModel__count]; nsresult rv; nsAutoString attrStr; for (int i = 0; i < eModel__count; ++i) { sModelPropsList[i]->ToString(attrStr); aBindElement->GetAttribute(attrStr, propStrings[i]); if (!propStrings[i].IsEmpty() && i != eModel_type && i != eModel_p3ptype) { rv = aEvaluator->CreateExpression(propStrings[i], aBindElement, getter_AddRefs(props[i])); if (NS_FAILED(rv)) { nsXFormsUtils::DispatchEvent(mElement, eEvent_ComputeException); return rv; } } } // Find the nodeset that this bind applies to. nsCOMPtr result; nsAutoString expr; aBindElement->GetAttribute(NS_LITERAL_STRING("nodeset"), expr); if (expr.IsEmpty()) { expr = NS_LITERAL_STRING("."); } /// /// @todo use aContextSize and aContextPosition in evaluation (XXX) rv = aEvaluator->Evaluate(expr, aContextNode, aBindElement, nsIDOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE, nsnull, getter_AddRefs(result)); if (NS_FAILED(rv)) { nsXFormsUtils::DispatchEvent(mElement, eEvent_BindingException); return rv; } NS_ENSURE_STATE(result); PRUint32 snapLen; rv = result->GetSnapshotLength(&snapLen); NS_ENSURE_SUCCESS(rv, rv); // Iterate over resultset nsXFormsMDGSet set; nsCOMPtr node; PRUint32 snapItem; for (snapItem = 0; snapItem < snapLen; ++snapItem) { rv = result->SnapshotItem(snapItem, getter_AddRefs(node)); NS_ENSURE_SUCCESS(rv, rv); if (!node) { NS_WARNING("nsXFormsModelElement::ProcessBind(): Empty node in result set."); continue; } // Apply MIPs nsXFormsXPathParser parser; nsXFormsXPathAnalyzer analyzer(aEvaluator, aBindElement); PRBool multiMIP = PR_FALSE; for (int j = 0; j < eModel__count; ++j) { if (propStrings[j].IsEmpty()) continue; // type and p3ptype are applied as attributes on the instance node if (j == eModel_type || j == eModel_p3ptype) { nsCOMPtr nodeElem = do_QueryInterface(node, &rv); if (NS_FAILED(rv)) { NS_WARNING("nsXFormsModelElement::ProcessBind(): Node is not nsIDOMElement!\n"); continue; } // Check whether attribute already exists if (j == eModel_type) { rv = nodeElem->HasAttributeNS(NS_LITERAL_STRING(NS_NAMESPACE_XML_SCHEMA_INSTANCE), NS_LITERAL_STRING("type"), &multiMIP); } else { rv = nodeElem->HasAttributeNS(NS_LITERAL_STRING(NS_NAMESPACE_XFORMS), NS_LITERAL_STRING("p3ptype"), &multiMIP); } NS_ENSURE_SUCCESS(rv, rv); // It is an error to set a MIP twice, so break and emit an exception. if (multiMIP) { break; } // Set attribute if (j == eModel_type) { rv = nodeElem->SetAttributeNS(NS_LITERAL_STRING(NS_NAMESPACE_XML_SCHEMA_INSTANCE), NS_LITERAL_STRING("type"), propStrings[j]); NS_ENSURE_SUCCESS(rv, rv); // Inform MDG that it needs to check type. The only arguments // actually used are |eModel_constraint| and |node|. rv = mMDG.AddMIP(eModel_constraint, nsnull, nsnull, PR_FALSE, node, 1, 1); } else { rv = nodeElem->SetAttributeNS(NS_LITERAL_STRING(NS_NAMESPACE_XFORMS), NS_LITERAL_STRING("p3ptype"), propStrings[j]); } NS_ENSURE_SUCCESS(rv, rv); } else { // the rest of the MIPs are given to the MDG nsCOMPtr expr = props[j]; // Get node dependencies nsAutoPtr xNode(parser.Parse(propStrings[j])); set.Clear(); rv = analyzer.Analyze(node, xNode, expr, &propStrings[j], &set); NS_ENSURE_SUCCESS(rv, rv); // Insert into MDG rv = mMDG.AddMIP((ModelItemPropName) j, expr, &set, parser.UsesDynamicFunc(), node, snapItem + 1, snapLen); // if the call results in NS_ERROR_ABORT the page has tried to set a // MIP twice, break and emit an exception. if (rv == NS_ERROR_ABORT) { multiMIP = PR_TRUE; break; } NS_ENSURE_SUCCESS(rv, rv); } } // If the attribute is already there, the page sets a MIP twice // which is illegal, and should result in an xforms-binding-exception. // @see http://www.w3.org/TR/xforms/slice4.html#evt-modelConstruct // (item 4, c) if (multiMIP) { nsXFormsUtils::DispatchEvent(aBindElement, eEvent_BindingException); return NS_ERROR_FAILURE; } // Now evaluate any child \ elements. nsCOMPtr children; aBindElement->GetChildNodes(getter_AddRefs(children)); if (children) { PRUint32 childCount = 0; children->GetLength(&childCount); nsCOMPtr child; nsAutoString value; for (PRUint32 k = 0; k < childCount; ++k) { children->Item(k, getter_AddRefs(child)); if (child) { child->GetLocalName(value); if (!value.EqualsLiteral("bind")) continue; child->GetNamespaceURI(value); if (!value.EqualsLiteral(NS_NAMESPACE_XFORMS)) continue; rv = ProcessBind(aEvaluator, node, snapItem + 1, snapLen, nsCOMPtr(do_QueryInterface(child))); NS_ENSURE_SUCCESS(rv, rv); } } } } return NS_OK; } /* static */ void nsXFormsModelElement::Startup() { sModelPropsList[eModel_type] = nsXFormsAtoms::type; sModelPropsList[eModel_readonly] = nsXFormsAtoms::readonly; sModelPropsList[eModel_required] = nsXFormsAtoms::required; sModelPropsList[eModel_relevant] = nsXFormsAtoms::relevant; sModelPropsList[eModel_calculate] = nsXFormsAtoms::calculate; sModelPropsList[eModel_constraint] = nsXFormsAtoms::constraint; sModelPropsList[eModel_p3ptype] = nsXFormsAtoms::p3ptype; } nsresult NS_NewXFormsModelElement(nsIXTFElement **aResult) { *aResult = new nsXFormsModelElement(); if (!*aResult) return NS_ERROR_OUT_OF_MEMORY; NS_ADDREF(*aResult); return NS_OK; }