/* -*- 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. PRUint32 separator = typeAttribute.FindChar(':'); if ((separator == kNotFound) || (separator == typeAttribute.Length())) return NS_ERROR_UNEXPECTED; 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; } // 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; }