/* -*- 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 * * 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 "nsIDOMDocumentEvent.h" #include "nsIDOMEvent.h" #include "nsIDOMEventTarget.h" #include "nsIDOMElement.h" #include "nsIDOM3Node.h" #include "nsString.h" #include "nsIDocument.h" #include "nsXFormsAtoms.h" #include "nsINameSpaceManager.h" #include "nsIServiceManager.h" #include "nsINodeInfo.h" #include "nsIDOMDOMImplementation.h" #include "nsIDOMXMLDocument.h" #include "nsIDOMEventReceiver.h" #include "nsIDOMXPathResult.h" #include "nsIDOMXPathEvaluator.h" #include "nsIDOMXPathNSResolver.h" #include "nsIDOMXPathExpression.h" #include "nsIDOM3EventTarget.h" #include "nsIDOMEventGroup.h" #include "nsIDOMNSUIEvent.h" #include "nsISchemaLoader.h" #include "nsAutoPtr.h" static const nsIID sScriptingIIDs[] = { NS_IDOMELEMENT_IID, NS_IDOMEVENTTARGET_IID, NS_IDOM3NODE_IID, NS_IXFORMSMODELELEMENT_IID }; struct EventData { const char *name; PRBool canCancel; PRBool canBubble; }; enum { eEvent_ModelConstruct, eEvent_ModelConstructDone, eEvent_Ready, eEvent_ModelDestruct, eEvent_Rebuild, eEvent_Refresh, eEvent_Revalidate, eEvent_Recalculate, eEvent_Reset, eEvent_BindingException, eEvent_LinkException, eEvent_LinkError, eEvent_ComputeExeception }; static const EventData sModelEvents[] = { { "xforms-model-construct", PR_FALSE, PR_TRUE }, { "xforms-model-construct-done", PR_FALSE, PR_TRUE }, { "xforms-ready", PR_FALSE, PR_TRUE }, { "xforms-model-destruct", PR_FALSE, PR_TRUE }, { "xforms-rebuild", PR_TRUE, PR_TRUE }, { "xforms-refresh", PR_TRUE, PR_TRUE }, { "xforms-revalidate", PR_TRUE, PR_TRUE }, { "xforms-recalculate", PR_TRUE, PR_TRUE }, { "xforms-reset", PR_TRUE, PR_TRUE }, { "xforms-binding-exception", PR_FALSE, PR_TRUE }, { "xforms-link-exception", PR_FALSE, PR_TRUE }, { "xforms-link-error", PR_FALSE, PR_TRUE }, { "xforms-compute-exception", PR_FALSE, PR_TRUE } }; enum ModelItemPropName { eModel_type, eModel_readonly, eModel_required, eModel_relevant, eModel_calculate, eModel_constraint, eModel_p3ptype, eModel__count }; static nsIAtom* sModelPropsList[eModel__count]; struct nsXFormsModelElement::ModelItemProperties { nsCOMPtr properties[eModel__count]; }; nsXFormsModelElement::nsXFormsModelElement() : mSchemaCount(0), mInstanceDataLoaded(PR_FALSE) { } NS_IMPL_ADDREF(nsXFormsModelElement) NS_IMPL_RELEASE(nsXFormsModelElement) NS_INTERFACE_MAP_BEGIN(nsXFormsModelElement) NS_INTERFACE_MAP_ENTRY(nsIXTFElement) NS_INTERFACE_MAP_ENTRY(nsIXTFGenericElement) NS_INTERFACE_MAP_ENTRY(nsIXFormsModelElement) NS_INTERFACE_MAP_ENTRY(nsISchemaLoadListener) NS_INTERFACE_MAP_ENTRY(nsIDOMLoadListener) NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXTFElement) NS_INTERFACE_MAP_END NS_IMETHODIMP nsXFormsModelElement::OnDestroyed() { nsCOMPtr receiver = do_QueryInterface(mContent); NS_ASSERTION(receiver, "xml elements must be event receivers"); nsCOMPtr systemGroup; receiver->GetSystemEventGroup(getter_AddRefs(systemGroup)); NS_ASSERTION(systemGroup, "system event group must exist"); nsCOMPtr targ = do_QueryInterface(mContent); for (unsigned int i = 0; i < NS_ARRAY_LENGTH(sModelEvents); ++i) { targ->RemoveGroupedEventListener(NS_ConvertUTF8toUTF16(sModelEvents[i].name), this, PR_FALSE, systemGroup); } return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::GetElementType(PRUint32 *aType) { *aType = ELEMENT_TYPE_GENERIC_ELEMENT; return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::GetIsAttributeHandler(PRBool *aIsHandler) { *aIsHandler = PR_FALSE; return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::GetScriptingInterfaces(PRUint32 *aCount, nsIID ***aArray) { PRUint32 count = NS_ARRAY_LENGTH(sScriptingIIDs); nsIID **iids = NS_STATIC_CAST(nsIID**, nsMemory::Alloc(count * sizeof(nsIID*))); if (!iids) { return NS_ERROR_OUT_OF_MEMORY; } for (PRUint32 i = 0; i < count; ++i) { iids[i] = NS_STATIC_CAST(nsIID*, nsMemory::Clone(&sScriptingIIDs[i], sizeof(nsIID))); if (!iids[i]) { for (PRUint32 j = 0; j < i; ++j) nsMemory::Free(iids[j]); nsMemory::Free(iids); return NS_ERROR_OUT_OF_MEMORY; } } *aArray = iids; *aCount = count; return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::GetNotificationMask(PRUint32 *aMask) { *aMask = 0; return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::WillChangeDocument(nsISupports* aNewDocument) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::DocumentChanged(nsISupports* aNewDocument) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::WillChangeParent(nsISupports* aNewParent) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::ParentChanged(nsISupports* aNewParent) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::WillInsertChild(nsISupports* aChild, PRUint32 aIndex) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::ChildInserted(nsISupports* aChild, PRUint32 aIndex) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::WillAppendChild(nsISupports* aChild) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::ChildAppended(nsISupports* aChild) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::WillRemoveChild(PRUint32 aIndex) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::ChildRemoved(PRUint32 aIndex) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::WillSetAttribute(nsIAtom *aName, const nsAString &aNewValue) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::AttributeSet(nsIAtom *aName, const nsAString &aNewValue) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::WillUnsetAttribute(nsIAtom *aName) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::AttributeUnset(nsIAtom *aName) { 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 = DispatchEvent(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; mContent->GetAttr(kNameSpaceID_None, nsXFormsAtoms::schema, schemaList); if (!schemaList.IsEmpty()) { nsCOMPtr loader = do_GetService(NS_SCHEMALOADER_CONTRACTID); NS_ENSURE_TRUE(loader, NS_ERROR_FAILURE); // Parse the space-separated list. PRUint32 offset = 0; nsRefPtr baseURI = mContent->GetBaseURI(); while (1) { ++mSchemaCount; PRInt32 index = schemaList.FindChar(PRUnichar(' '), offset); rv = loader->LoadAsync(Substring(schemaList, offset, index - offset), baseURI, this); if (NS_FAILED(rv)) { DispatchEvent(eEvent_LinkException); // this is a fatal error return NS_OK; } if (index == -1) break; offset = index + 1; } } // 2. construct an XPath data model from inline or external initial instance // data. // XXX the spec says there can be any number of nodes, but // I can't see how it makes sense to have more than one per model. PRUint32 childCount = mContent->GetChildCount(); for (PRUint32 i = 0; i < childCount; ++i) { nsIContent *child = mContent->GetChildAt(i); nsINodeInfo *ni = child->GetNodeInfo(); if (ni && ni->Equals(nsXFormsAtoms::instance, kNameSpaceID_XForms)) { // Create a document which will hold the live instance data. nsCOMPtr domDoc = do_QueryInterface(child->GetDocument()); nsCOMPtr domImpl; domDoc->GetImplementation(getter_AddRefs(domImpl)); nsAutoString src; child->GetAttr(kNameSpaceID_None, nsXFormsAtoms::src, src); rv = domImpl->CreateDocument(EmptyString(), EmptyString(), nsnull, getter_AddRefs(mInstanceDocument)); NS_ENSURE_SUCCESS(rv, rv); if (src.IsEmpty()) { // No src means we should use the inline instance data, using the // first child element of the instance node as the root. PRUint32 instanceChildCount = child->GetChildCount(); nsCOMPtr root; for (PRUint32 j = 0; j < instanceChildCount; ++j) { nsIContent *node = child->GetChildAt(j); if (node->IsContentOfType(nsIContent::eELEMENT)) { root = do_QueryInterface(node); break; } } if (root) { nsCOMPtr newNode; rv = mInstanceDocument->ImportNode(root, PR_TRUE, getter_AddRefs(newNode)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr nodeReturn; rv = mInstanceDocument->AppendChild(newNode, getter_AddRefs(nodeReturn)); NS_ENSURE_SUCCESS(rv, rv); mInstanceDataLoaded = PR_TRUE; } } else { // We're using external instance data, so we need to load // the data into a new document which becomes our live instance data. // XXX We need to come up with a mechanism so that the content sink // can use xmlns declarations in effect for the to resolve // tag prefixes in the external instance data. // Hook up load an error listeners so we'll know when the document // is done loading. nsCOMPtr rec = do_QueryInterface(mInstanceDocument); rec->AddEventListenerByIID(this, NS_GET_IID(nsIDOMLoadListener)); nsCOMPtr xmlDoc = do_QueryInterface(mInstanceDocument); PRBool success; xmlDoc->Load(src, &success); if (!success) { DispatchEvent(eEvent_LinkException); return NS_OK; } } break; } } if (IsComplete()) { return FinishConstruction(); } return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::OnCreated(nsIXTFGenericElementWrapper *aWrapper) { nsCOMPtr node; aWrapper->GetElementNode(getter_AddRefs(node)); mContent = do_QueryInterface(node); NS_ASSERTION(mContent, "Wrapper is not an nsIContent, we'll crash soon"); return NS_OK; } // nsIXFormsModelElement NS_IMETHODIMP nsXFormsModelElement::GetInstanceDocument(const nsAString& aInstanceID, nsIDOMDocument **aDocument) { if (!mInstanceDocument) return NS_ERROR_FAILURE; // what sort of exception should this be? NS_ADDREF(*aDocument = mInstanceDocument); return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::Rebuild() { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::Recalculate() { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::Revalidate() { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::Refresh() { return NS_OK; } // nsISchemaLoadListener NS_IMETHODIMP nsXFormsModelElement::OnLoad(nsISchema* aSchema) { mSchemas.AppendObject(aSchema); if (IsComplete()) { return FinishConstruction(); } return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::OnError(PRInt32 aStatus, const nsAString &aStatusMessage) { DispatchEvent(eEvent_LinkException); return NS_OK; } // nsIDOMEventListener NS_IMETHODIMP nsXFormsModelElement::HandleEvent(nsIDOMEvent* aEvent) { nsCOMPtr evt = do_QueryInterface(aEvent); NS_ASSERTION(evt, "event should implement nsIDOMNSUIEvent"); PRBool defaultPrevented; evt->GetPreventDefault(&defaultPrevented); if (defaultPrevented) return NS_OK; nsAutoString type; aEvent->GetType(type); if (type.EqualsASCII("xforms-refresh")) { // refresh } else if (type.EqualsASCII("xforms-revalidate")) { // revalidate } else if (type.EqualsASCII("xforms-recalculate")) { // recalculate } else if (type.EqualsASCII("xforms-rebuild")) { // rebuild } else if (type.EqualsASCII("xforms-reset")) { // reset } return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::Load(nsIDOMEvent* aEvent) { mInstanceDataLoaded = PR_TRUE; if (IsComplete()) { return FinishConstruction(); } return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::BeforeUnload(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::Unload(nsIDOMEvent* aEvent) { return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::Abort(nsIDOMEvent* aEvent) { DispatchEvent(eEvent_LinkException); return NS_OK; } NS_IMETHODIMP nsXFormsModelElement::Error(nsIDOMEvent* aEvent) { DispatchEvent(eEvent_LinkException); return NS_OK; } // internal methods nsresult nsXFormsModelElement::FinishConstruction() { // 3. if applicable, initialize P3P // 4. construct instance data from initial instance data. apply all // elements in document order. // The instance data is in our mInstanceDocument. PRUint32 childCount = mContent->GetChildCount(); nsCOMPtr xpath = do_QueryInterface(mInstanceDocument); for (PRUint32 i = 0; i < childCount; ++i) { nsIContent *child = mContent->GetChildAt(i); nsINodeInfo *ni = child->GetNodeInfo(); if (ni && ni->Equals(nsXFormsAtoms::bind, kNameSpaceID_XForms)) { if (!ProcessBind(xpath, child)) { DispatchEvent(eEvent_BindingException); return NS_OK; } } } // 5. dispatch xforms-rebuild, xforms-recalculate, xforms-revalidate // First hook up our event listener so we invoke the default action for // these events. We listen on the system event group so that we can check // whether preventDefault() was called by any content listeners. nsCOMPtr receiver = do_QueryInterface(mContent); NS_ASSERTION(receiver, "xml elements must be event receivers"); nsCOMPtr systemGroup; receiver->GetSystemEventGroup(getter_AddRefs(systemGroup)); NS_ASSERTION(systemGroup, "system event group must exist"); nsCOMPtr targ = do_QueryInterface(mContent); for (unsigned int i = 0; i < NS_ARRAY_LENGTH(sModelEvents); ++i) { targ->AddGroupedEventListener(NS_ConvertUTF8toUTF16(sModelEvents[i].name), this, PR_FALSE, systemGroup); } DispatchEvent(eEvent_Rebuild); DispatchEvent(eEvent_Recalculate); DispatchEvent(eEvent_Revalidate); // mark this model as initialized return NS_OK; } static void ReleaseExpr(void *aElement, nsIAtom *aPropertyName, void *aPropertyValue, void *aData) { nsIDOMXPathExpression *expr = NS_STATIC_CAST(nsIDOMXPathExpression*, aElement); NS_RELEASE(expr); } PRBool nsXFormsModelElement::ProcessBind(nsIDOMXPathEvaluator *aEvaluator, nsIContent *aBindElement) { // Get the expression for the nodes that this applies to. nsAutoString expr; aBindElement->GetAttr(kNameSpaceID_None, nsXFormsAtoms::nodeset, expr); if (expr.IsEmpty()) return PR_TRUE; nsCOMPtr resolver; aEvaluator->CreateNSResolver(nsCOMPtr(do_QueryInterface(aBindElement)), getter_AddRefs(resolver)); // Get the model item properties specified by this . nsCOMPtr props[eModel__count]; nsAutoString exprString; PRInt32 propCount = 0; nsresult rv = NS_OK; for (int i = 0; i < eModel__count; ++i) { if (aBindElement->GetAttr(kNameSpaceID_None, sModelPropsList[i], exprString) != NS_CONTENT_ATTR_NOT_THERE) { rv = aEvaluator->CreateExpression(exprString, resolver, getter_AddRefs(props[i])); if (NS_FAILED(rv)) return PR_FALSE; ++propCount; } } if (propCount == 0) return PR_TRUE; // successful, but nothing to do nsCOMPtr result; rv = aEvaluator->Evaluate(expr, mInstanceDocument, resolver, nsIDOMXPathResult::ORDERED_NODE_ITERATOR_TYPE, nsnull, getter_AddRefs(result)); if (NS_FAILED(rv)) return PR_FALSE; nsCOMPtr node; while (NS_SUCCEEDED(result->IterateNext(getter_AddRefs(node))) && node) { // We must check whether the properties already exist on the node. for (int j = 0; j < eModel__count; ++j) { if (props[j]) { nsCOMPtr content = do_QueryInterface(node); nsIDOMXPathExpression *expr = props[j]; NS_ADDREF(expr); rv = content->SetProperty(sModelPropsList[j], expr, ReleaseExpr); if (rv == NS_PROPTABLE_PROP_OVERWRITTEN) { return PR_FALSE; } } } } return PR_TRUE; } nsresult nsXFormsModelElement::DispatchEvent(unsigned int aEvent) { const EventData *data = &sModelEvents[aEvent]; nsCOMPtr event; nsCOMPtr doc = do_QueryInterface(mContent->GetDocument()); doc->CreateEvent(NS_LITERAL_STRING("Events"), getter_AddRefs(event)); NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); event->InitEvent(NS_ConvertUTF8toUTF16(data->name), data->canBubble, data->canCancel); nsCOMPtr target = do_QueryInterface(mContent); PRBool cancelled; return target->DispatchEvent(event, &cancelled); } /* 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; }