pjs/extensions/xforms/nsXFormsModelElement.cpp

2636 строки
77 KiB
C++

/* -*- 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 <bryner@brianryner.com>
* Allan Beaufour <abeaufour@novell.com>
* Darin Fisher <darin@meer.net>
* Olli Pettay <Olli.Pettay@helsinki.fi>
*
* 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 "nsIDOMEvent.h"
#include "nsIDOMDOMImplementation.h"
#include "nsIDOMXMLDocument.h"
#include "nsIDOMEventReceiver.h"
#include "nsIDOMXPathResult.h"
#include "nsIXFormsXPathEvaluator.h"
#include "nsIDOMXPathNSResolver.h"
#include "nsIDOMNSXPathExpression.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 "nsIXFormsUIWidget.h"
#include "nsIAttribute.h"
#include "nsISchemaLoader.h"
#include "nsISchema.h"
#include "nsAutoPtr.h"
#include "nsArray.h"
#include "nsIDOMDocumentXBL.h"
#include "nsIProgrammingLanguage.h"
#include "nsDOMError.h"
#define XFORMS_LAZY_INSTANCE_BINDING \
"chrome://xforms/content/xforms.xml#xforms-lazy-instance"
#ifdef DEBUG
//#define DEBUG_MODEL
#endif
//------------------------------------------------------------------------------
// Helper function for using XPath to locate an <xsd:schema> element by
// matching its "id" attribute. This is necessary since <xsd:schema> 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<nsIDOMXPathResult> xpRes =
nsXFormsUtils::EvaluateXPath(expr,
contextNode,
contextNode,
nsIDOMXPathResult::FIRST_ORDERED_NODE_TYPE);
if (xpRes) {
nsCOMPtr<nsIDOMNode> 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<nsIDocument> 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<nsIDocument> 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<nsIDocument> doc = do_QueryInterface(domDoc);
return NS_STATIC_CAST(nsVoidArray *,
doc->GetProperty(nsXFormsAtoms::modelListProperty));
}
static void
SupportsDtorFunc(void *aObject, nsIAtom *aPropertyName,
void *aPropertyValue, void *aData)
{
nsISupports *propertyValue = NS_STATIC_CAST(nsISupports*, aPropertyValue);
NS_IF_RELEASE(propertyValue);
}
//------------------------------------------------------------------------------
// --- nsXFormsControlListItem ---
nsXFormsControlListItem::iterator::iterator()
: mCur(0)
{
}
nsXFormsControlListItem::iterator::iterator(const nsXFormsControlListItem::iterator& aCopy)
: mCur(aCopy.mCur)
{
mStack = aCopy.mStack;
}
nsXFormsControlListItem::iterator
nsXFormsControlListItem::iterator::operator=(nsXFormsControlListItem* aCnt)
{
mCur = aCnt;
return *this;
}
bool
nsXFormsControlListItem::iterator::operator!=(const nsXFormsControlListItem* aCnt)
{
return mCur != aCnt;
}
nsXFormsControlListItem::iterator
nsXFormsControlListItem::iterator::operator++()
{
if (!mCur)
return *this;
if (mCur->mFirstChild) {
if (!mCur->mNextSibling) {
mCur = mCur->mFirstChild;
return *this;
}
mStack.AppendElement(mCur->mFirstChild);
}
if (mCur->mNextSibling) {
mCur = mCur->mNextSibling;
} else if (mStack.Count()) {
mCur = (nsXFormsControlListItem*) mStack[mStack.Count() - 1];
mStack.RemoveElementAt(mStack.Count() - 1);
} else {
mCur = nsnull;
}
return *this;
}
nsXFormsControlListItem*
nsXFormsControlListItem::iterator::operator*()
{
return mCur;
}
nsXFormsControlListItem::nsXFormsControlListItem(nsIXFormsControl* aControl)
: mNode(aControl),
mNextSibling(nsnull),
mFirstChild(nsnull)
{
}
nsXFormsControlListItem::~nsXFormsControlListItem()
{
Clear();
}
nsXFormsControlListItem::nsXFormsControlListItem(const nsXFormsControlListItem& aCopy)
: mNode(aCopy.mNode)
{
if (aCopy.mNextSibling) {
mNextSibling = new nsXFormsControlListItem(*aCopy.mNextSibling);
NS_WARN_IF_FALSE(mNextSibling, "could not new?!");
} else {
mNextSibling = nsnull;
}
if (aCopy.mFirstChild) {
mFirstChild = new nsXFormsControlListItem(*aCopy.mFirstChild);
NS_WARN_IF_FALSE(mFirstChild, "could not new?!");
} else {
mFirstChild = nsnull;
}
}
void
nsXFormsControlListItem::Clear()
{
if (mFirstChild) {
mFirstChild->Clear();
NS_ASSERTION(!(mFirstChild->mFirstChild || mFirstChild->mNextSibling),
"child did not clear members!!");
delete mFirstChild;
mFirstChild = nsnull;
}
if (mNextSibling) {
mNextSibling->Clear();
NS_ASSERTION(!(mNextSibling->mFirstChild || mNextSibling->mNextSibling),
"sibling did not clear members!!");
delete mNextSibling;
mNextSibling = nsnull;
}
if (mNode)
mNode = nsnull;
}
nsresult
nsXFormsControlListItem::AddControl(nsIXFormsControl *aControl,
nsIXFormsControl *aParent)
{
// Four insertion posibilities:
// 1) Delegate to first child from root node
if (!mNode && mFirstChild) {
return mFirstChild->AddControl(aControl, aParent);
}
// 2) control with no parent
if (!aParent) {
nsXFormsControlListItem* newNode = new nsXFormsControlListItem(aControl);
NS_ENSURE_STATE(newNode);
// Empty tree (we have already checked mFirstChild)
if (!mNode) {
mFirstChild = newNode;
return NS_OK;
}
if (mNextSibling) {
newNode->mNextSibling = mNextSibling;
}
mNextSibling = newNode;
#ifdef DEBUG
nsXFormsControlListItem* next = newNode->mNextSibling;
while (next) {
NS_ASSERTION(aControl != next->mNode,
"Node already in tree!!");
next = next->mNextSibling;
}
#endif
return NS_OK;
}
// Locate parent
nsXFormsControlListItem* parentControl = FindControl(aParent);
NS_ASSERTION(parentControl, "Parent not found?!");
// 3) parentControl has a first child, insert as sibling to that
if (parentControl->mFirstChild) {
return parentControl->mFirstChild->AddControl(aControl, nsnull);
}
// 4) first child for parentControl
nsXFormsControlListItem* newNode = new nsXFormsControlListItem(aControl);
NS_ENSURE_STATE(newNode);
parentControl->mFirstChild = newNode;
return NS_OK;
}
nsresult
nsXFormsControlListItem::RemoveControl(nsIXFormsControl *aControl,
PRBool &aRemoved)
{
nsXFormsControlListItem* deleteMe = nsnull;
aRemoved = PR_FALSE;
// Try children
if (mFirstChild) {
// The control to remove is our first child
if (mFirstChild->mNode == aControl) {
deleteMe = mFirstChild;
// Fix siblings
if (deleteMe->mNextSibling) {
mFirstChild = deleteMe->mNextSibling;
deleteMe->mNextSibling = nsnull;
} else {
mFirstChild = nsnull;
}
// Fix children
if (deleteMe->mFirstChild) {
if (!mFirstChild) {
mFirstChild = deleteMe->mFirstChild;
} else {
nsXFormsControlListItem *insertPos = mFirstChild;
while (insertPos->mNextSibling) {
insertPos = insertPos->mNextSibling;
}
insertPos->mNextSibling = deleteMe->mFirstChild;
}
deleteMe->mFirstChild = nsnull;
}
} else {
// Run through children
nsresult rv = mFirstChild->RemoveControl(aControl, aRemoved);
NS_ENSURE_SUCCESS(rv, rv);
if (aRemoved)
return rv;
}
}
// Try siblings
if (!deleteMe && mNextSibling) {
if (mNextSibling->mNode == aControl) {
deleteMe = mNextSibling;
// Fix siblings
if (deleteMe->mNextSibling) {
mNextSibling = deleteMe->mNextSibling;
deleteMe->mNextSibling = nsnull;
} else {
mNextSibling = nsnull;
}
// Fix children
if (deleteMe->mFirstChild) {
if (!mNextSibling) {
mNextSibling = deleteMe->mFirstChild;
} else {
nsXFormsControlListItem *insertPos = mNextSibling;
while (insertPos->mNextSibling) {
insertPos = insertPos->mNextSibling;
}
insertPos->mNextSibling = deleteMe->mFirstChild;
}
deleteMe->mFirstChild = nsnull;
}
} else {
// run through siblings
return mNextSibling->RemoveControl(aControl, aRemoved);
}
}
if (deleteMe) {
NS_ASSERTION(!(deleteMe->mNextSibling),
"Deleted control should not have siblings!");
NS_ASSERTION(!(deleteMe->mFirstChild),
"Deleted control should not have children!");
delete deleteMe;
aRemoved = PR_TRUE;
}
return NS_OK;
}
nsXFormsControlListItem*
nsXFormsControlListItem::FindControl(nsIXFormsControl *aControl)
{
if (!aControl)
return nsnull;
// this should only be false for the root
if (mNode) {
// XXX: *sigh* pointer comparision of nsIXFormsControl would be nice...
nsCOMPtr<nsIDOMElement> el1, el2;
aControl->GetElement(getter_AddRefs(el1));
mNode->GetElement(getter_AddRefs(el2));
if (el1 == el2)
return this;
}
nsXFormsControlListItem* cur = nsnull;
if (mFirstChild) {
cur = mFirstChild->FindControl(aControl);
}
if (!cur && mNextSibling) {
cur = mNextSibling->FindControl(aControl);
}
return cur;
}
already_AddRefed<nsIXFormsControl>
nsXFormsControlListItem::Control()
{
nsIXFormsControl* res = nsnull;
if (mNode)
NS_ADDREF(res = mNode);
NS_WARN_IF_FALSE(res, "Returning nsnull for a control. Bad sign.");
return res;
}
nsXFormsControlListItem*
nsXFormsControlListItem::begin()
{
// handle root
if (!mNode)
return mFirstChild;
return this;
}
nsXFormsControlListItem*
nsXFormsControlListItem::end()
{
return nsnull;
}
//------------------------------------------------------------------------------
static const nsIID sScriptingIIDs[] = {
NS_IDOMELEMENT_IID,
NS_IDOMEVENTTARGET_IID,
NS_IDOM3NODE_IID,
NS_IXFORMSMODELELEMENT_IID,
NS_IXFORMSNSMODELELEMENT_IID
};
static nsIAtom* sModelPropsList[eModel__count];
// This can be nsVoidArray because elements will remove
// themselves from the list if they are deleted during refresh.
static nsVoidArray* sPostRefreshList = nsnull;
static PRInt32 sRefreshing = 0;
nsPostRefresh::nsPostRefresh()
{
#ifdef DEBUG_smaug
printf("nsPostRefresh\n");
#endif
++sRefreshing;
}
nsPostRefresh::~nsPostRefresh()
{
#ifdef DEBUG_smaug
printf("~nsPostRefresh\n");
#endif
--sRefreshing;
if (sPostRefreshList && !sRefreshing) {
while (sPostRefreshList->Count()) {
// Iterating this way because refresh can lead to
// additions/deletions in sPostRefreshList.
// Iterating from last to first saves possibly few memcopies,
// see nsVoidArray::RemoveElementsAt().
PRInt32 last = sPostRefreshList->Count() - 1;
nsIXFormsControl* control =
NS_STATIC_CAST(nsIXFormsControl*, sPostRefreshList->ElementAt(last));
sPostRefreshList->RemoveElementAt(last);
if (control)
control->Refresh();
}
delete sPostRefreshList;
sPostRefreshList = nsnull;
}
}
const nsVoidArray*
nsPostRefresh::PostRefreshList()
{
return sPostRefreshList;
}
nsresult
nsXFormsModelElement::NeedsPostRefresh(nsIXFormsControl* aControl)
{
if (sRefreshing) {
if (!sPostRefreshList) {
sPostRefreshList = new nsVoidArray();
NS_ENSURE_TRUE(sPostRefreshList, NS_ERROR_OUT_OF_MEMORY);
}
if (sPostRefreshList->IndexOf(aControl) < 0) {
sPostRefreshList->AppendElement(aControl);
}
} else {
// We are not refreshing any models, so the control
// can be refreshed immediately.
aControl->Refresh();
}
return NS_OK;
}
void
nsXFormsModelElement::CancelPostRefresh(nsIXFormsControl* aControl)
{
if (sPostRefreshList)
sPostRefreshList->RemoveElement(aControl);
}
nsXFormsModelElement::nsXFormsModelElement()
: mElement(nsnull),
mFormControls(nsnull),
mSchemaCount(0),
mSchemaTotal(0),
mPendingInstanceCount(0),
mDocumentLoaded(PR_FALSE),
mNeedsRefresh(PR_FALSE),
mInstancesInitialized(PR_FALSE),
mReadyHandled(PR_FALSE),
mInstanceDocuments(nsnull),
mLazyModel(PR_FALSE),
mConstructDoneHandled(PR_FALSE)
{
}
NS_INTERFACE_MAP_BEGIN(nsXFormsModelElement)
NS_INTERFACE_MAP_ENTRY(nsIXFormsModelElement)
NS_INTERFACE_MAP_ENTRY(nsIXFormsNSModelElement)
NS_INTERFACE_MAP_ENTRY(nsIModelElementPrivate)
NS_INTERFACE_MAP_ENTRY(nsISchemaLoadListener)
NS_INTERFACE_MAP_ENTRY(nsIWebServiceErrorHandler)
NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
NS_INTERFACE_MAP_ENTRY(nsIXFormsContextControl)
NS_INTERFACE_MAP_END_INHERITING(nsXFormsStubElement)
NS_IMPL_ADDREF_INHERITED(nsXFormsModelElement, nsXFormsStubElement)
NS_IMPL_RELEASE_INHERITED(nsXFormsModelElement, nsXFormsStubElement)
NS_IMETHODIMP
nsXFormsModelElement::OnDestroyed()
{
RemoveModelFromDocument();
mElement = nsnull;
mSchemas = nsnull;
if (mInstanceDocuments)
mInstanceDocuments->DropReferences();
mFormControls.Clear();
return NS_OK;
}
void
nsXFormsModelElement::RemoveModelFromDocument()
{
mDocumentLoaded = PR_FALSE;
nsCOMPtr<nsIDOMDocument> domDoc;
mElement->GetOwnerDocument(getter_AddRefs(domDoc));
if (!domDoc)
return;
RemoveFromModelList(domDoc, this);
nsCOMPtr<nsIDOMEventTarget> targ = do_QueryInterface(domDoc);
if (targ) {
targ->RemoveEventListener(NS_LITERAL_STRING("DOMContentLoaded"), this, PR_TRUE);
nsCOMPtr<nsIDOMWindowInternal> window;
nsXFormsUtils::GetWindowFromDocument(domDoc, getter_AddRefs(window));
targ = do_QueryInterface(window);
if (targ) {
targ->RemoveEventListener(NS_LITERAL_STRING("unload"), 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<nsIDOMEventTarget> targ = do_QueryInterface(aNewDocument);
if (targ) {
targ->AddEventListener(NS_LITERAL_STRING("DOMContentLoaded"), this, PR_TRUE);
nsCOMPtr<nsIDOMWindowInternal> window;
nsXFormsUtils::GetWindowFromDocument(aNewDocument, getter_AddRefs(window));
targ = do_QueryInterface(window);
if (targ) {
targ->AddEventListener(NS_LITERAL_STRING("unload"), this, PR_TRUE);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::DoneAddingChildren()
{
return InitializeInstances();
}
nsresult
nsXFormsModelElement::InitializeInstances()
{
if (mInstancesInitialized || !mElement) {
return NS_OK;
}
mInstancesInitialized = PR_TRUE;
nsCOMPtr<nsIDOMNodeList> children;
mElement->GetChildNodes(getter_AddRefs(children));
PRUint32 childCount = 0;
if (children) {
children->GetLength(&childCount);
}
for (PRUint32 i = 0; i < childCount; ++i) {
nsCOMPtr<nsIDOMNode> child;
children->Item(i, getter_AddRefs(child));
if (nsXFormsUtils::IsXFormsElement(child, NS_LITERAL_STRING("instance"))) {
nsCOMPtr<nsIInstanceElementPrivate> instance(do_QueryInterface(child));
if (instance) {
instance->Initialize();
}
}
}
// (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<nsIContent> content = do_QueryInterface(mElement);
nsRefPtr<nsIURI> baseURI = content->GetBaseURI();
nsRefPtr<nsIURI> docURI = content->GetOwnerDoc() ?
content->GetOwnerDoc()->GetDocumentURI() : nsnull;
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<mSchemaTotal; ++i) {
nsresult rv = NS_OK;
nsCOMPtr<nsIURI> newURI;
NS_NewURI(getter_AddRefs(newURI), *schemas[i], nsnull, baseURI);
nsCOMPtr<nsIURL> 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(docURI, &equals);
if (equals) {
// We will not be able to locate the <xsd:schema> element using the
// getElementById function defined on our document when <xsd:schema>
// is treated as an ordinary XML data node. So, we employ XPath to
// locate it for us.
NS_ConvertUTF8toUTF16 id(ref);
nsCOMPtr<nsIDOMElement> el;
GetSchemaElementById(mElement, id, getter_AddRefs(el));
if (!el) {
// Perhaps the <xsd:schema> element appears after the <xforms:model>
// element in the document, so we'll defer loading it until the
// document has finished loading.
mPendingInlineSchemas.AppendString(id);
} else {
nsCOMPtr<nsISchema> 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::ReportError(NS_LITERAL_STRING("schemaLoadError"), mElement);
nsXFormsUtils::DispatchEvent(mElement, eEvent_LinkException);
rv = NS_OK;
if (!nsXFormsUtils::HandleFatalError(mElement,
NS_LITERAL_STRING("XFormsLinkException"))) {
rv = NS_ERROR_FAILURE;
}
return rv;
}
}
}
// If all of the children are added and there aren't any instance elements,
// yet, then we need to make sure that one is ready in case the form author
// is using lazy authoring.
// Lazy <xforms:intance> element is created in anonymous content using XBL.
NS_ENSURE_STATE(mInstanceDocuments);
PRUint32 instCount;
mInstanceDocuments->GetLength(&instCount);
if (!instCount) {
#ifdef DEBUG
printf("Creating lazy instance\n");
#endif
nsCOMPtr<nsIDOMDocument> domDoc;
mElement->GetOwnerDocument(getter_AddRefs(domDoc));
nsCOMPtr<nsIDOMDocumentXBL> xblDoc(do_QueryInterface(domDoc));
if (xblDoc) {
nsresult rv =
xblDoc->AddBinding(mElement,
NS_LITERAL_STRING(XFORMS_LAZY_INSTANCE_BINDING));
NS_ENSURE_SUCCESS(rv, rv);
mInstanceDocuments->GetLength(&instCount);
nsCOMPtr<nsIDOMNodeList> list;
xblDoc->GetAnonymousNodes(mElement, getter_AddRefs(list));
if (list) {
PRUint32 childCount = 0;
if (list) {
list->GetLength(&childCount);
}
for (PRUint32 i = 0; i < childCount; ++i) {
nsCOMPtr<nsIDOMNode> item;
list->Item(i, getter_AddRefs(item));
nsCOMPtr<nsIInstanceElementPrivate> instance =
do_QueryInterface(item);
if (instance) {
rv = instance->Initialize();
NS_ENSURE_SUCCESS(rv, rv);
mLazyModel = PR_TRUE;
break;
}
}
}
}
NS_WARN_IF_FALSE(mLazyModel, "Installing lazy instance didn't succeed!");
}
// (XForms 4.2.1 - cont)
// 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)
{
if (!nsXFormsUtils::EventHandlingAllowed(aEvent, mElement))
return NS_OK;
*aHandled = PR_TRUE;
nsAutoString type;
aEvent->GetType(type);
nsresult rv = NS_OK;
if (type.EqualsASCII(sXFormsEventsEntries[eEvent_Refresh].name)) {
rv = Refresh();
} else if (type.EqualsASCII(sXFormsEventsEntries[eEvent_Revalidate].name)) {
rv = Revalidate();
} else if (type.EqualsASCII(sXFormsEventsEntries[eEvent_Recalculate].name)) {
rv = Recalculate();
} else if (type.EqualsASCII(sXFormsEventsEntries[eEvent_Rebuild].name)) {
rv = Rebuild();
} else if (type.EqualsASCII(sXFormsEventsEntries[eEvent_ModelConstructDone].name)) {
rv = ConstructDone();
mConstructDoneHandled = PR_TRUE;
} else if (type.EqualsASCII(sXFormsEventsEntries[eEvent_Reset].name)) {
Reset();
} else if (type.EqualsASCII(sXFormsEventsEntries[eEvent_BindingException].name)) {
*aHandled = nsXFormsUtils::HandleFatalError(mElement,
NS_LITERAL_STRING("XFormsBindingException"));
} else {
*aHandled = PR_FALSE;
}
NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
"nsXFormsModelElement::HandleDefault() failed!\n");
return rv;
}
nsresult
nsXFormsModelElement::ConstructDone()
{
nsresult rv = InitializeControls();
NS_ENSURE_SUCCESS(rv, rv);
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<nsIDOMElement> 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_CreateInstance(NS_SCHEMALOADER_CONTRACTID);
mInstanceDocuments = new nsXFormsModelInstanceDocuments();
NS_ASSERTION(mInstanceDocuments, "could not create mInstanceDocuments?!");
// Initialize hash tables
NS_ENSURE_TRUE(mNodeToType.Init(), NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_TRUE(mNodeToP3PType.Init(), NS_ERROR_OUT_OF_MEMORY);
return NS_OK;
}
// nsIXFormsModelElement
NS_IMETHODIMP
nsXFormsModelElement::GetInstanceDocuments(nsIDOMNodeList **aDocuments)
{
NS_ENSURE_STATE(mInstanceDocuments);
NS_ENSURE_ARG_POINTER(aDocuments);
NS_ADDREF(*aDocuments = mInstanceDocuments);
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::GetInstanceDocument(const nsAString& aInstanceID,
nsIDOMDocument **aDocument)
{
NS_ENSURE_ARG_POINTER(aDocument);
*aDocument = FindInstanceDocument(aInstanceID).get(); // transfer reference
if (*aDocument) {
return NS_OK;
}
const nsPromiseFlatString& flat = PromiseFlatString(aInstanceID);
const PRUnichar *strings[] = { flat.get() };
nsXFormsUtils::ReportError(aInstanceID.IsEmpty() ?
NS_LITERAL_STRING("defInstanceNotFound") :
NS_LITERAL_STRING("instanceNotFound"),
strings, 1, mElement, nsnull);
return NS_ERROR_DOM_NOT_FOUND_ERR;
}
NS_IMETHODIMP
nsXFormsModelElement::Rebuild()
{
#ifdef DEBUG
printf("nsXFormsModelElement::Rebuild()\n");
#endif
// 1 . Clear graph
nsresult rv;
rv = mMDG.Clear();
NS_ENSURE_SUCCESS(rv, rv);
// Clear any type information
NS_ENSURE_TRUE(mNodeToType.IsInitialized() && mNodeToP3PType.IsInitialized(),
NS_ERROR_FAILURE);
mNodeToType.Clear();
mNodeToP3PType.Clear();
// 2. Process bind elements
rv = ProcessBindElements();
NS_ENSURE_SUCCESS(rv, rv);
// 3. Re-attach all elements
if (mDocumentLoaded) { // if it's not during initializing phase
nsXFormsControlListItem::iterator it;
for (it = mFormControls.begin(); it != mFormControls.end(); ++it) {
nsCOMPtr<nsIXFormsControl> control = (*it)->Control();
NS_ASSERTION(control, "mFormControls has null control?!");
// run bind to reset mBoundNode for all of the model's controls
control->Bind();
}
// Triggers a refresh of all controls
mNeedsRefresh = PR_TRUE;
}
// 4. Rebuild graph
return mMDG.Rebuild();
}
NS_IMETHODIMP
nsXFormsModelElement::Recalculate()
{
#ifdef DEBUG
printf("nsXFormsModelElement::Recalculate()\n");
#endif
return mMDG.Recalculate(&mChangedNodes);
}
void
nsXFormsModelElement::SetSingleState(nsIDOMElement *aElement,
PRBool aState,
nsXFormsEvent aOnEvent)
{
nsXFormsEvent event = aState ? aOnEvent : (nsXFormsEvent) (aOnEvent + 1);
// Dispatch event
nsXFormsUtils::DispatchEvent(aElement, event);
}
nsresult
nsXFormsModelElement::SetStatesInternal(nsIXFormsControl *aControl,
nsIDOMNode *aNode,
PRBool aDispatchEvents)
{
NS_ENSURE_ARG(aControl);
if (!aNode)
return NS_OK;
nsCOMPtr<nsIDOMElement> element;
aControl->GetElement(getter_AddRefs(element));
NS_ENSURE_STATE(element);
nsCOMPtr<nsIXTFElementWrapper> xtfWrap(do_QueryInterface(element));
NS_ENSURE_STATE(xtfWrap);
const nsXFormsNodeState *ns = mMDG.GetNodeState(aNode);
NS_ENSURE_STATE(ns);
nsresult rv = xtfWrap->SetIntrinsicState(ns->GetIntrinsicState());
NS_ENSURE_SUCCESS(rv, rv);
if (!aDispatchEvents)
return NS_OK;
if (ns->ShouldDispatchValid()) {
SetSingleState(element, ns->IsValid(), eEvent_Valid);
}
if (ns->ShouldDispatchReadonly()) {
SetSingleState(element, ns->IsReadonly(), eEvent_Readonly);
}
if (ns->ShouldDispatchRequired()) {
SetSingleState(element, ns->IsRequired(), eEvent_Required);
}
if (ns->ShouldDispatchRelevant()) {
SetSingleState(element, ns->IsRelevant(), eEvent_Enabled);
}
if (ns->ShouldDispatchValueChanged()) {
nsXFormsUtils::DispatchEvent(element, eEvent_ValueChanged);
}
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::Revalidate()
{
#ifdef DEBUG
printf("nsXFormsModelElement::Revalidate()\n");
#endif
#ifdef DEBUG_MODEL
printf("[%s] Changed nodes:\n", __TIME__);
for (PRInt32 j = 0; j < mChangedNodes.Count(); ++j) {
nsCOMPtr<nsIDOMNode> node = mChangedNodes[j];
nsAutoString name;
node->GetNodeName(name);
printf("\t%s [%p]\n",
NS_ConvertUTF16toUTF8(name).get(),
(void*) node);
}
#endif
// Revalidate nodes
mMDG.Revalidate(&mChangedNodes);
return NS_OK;
}
nsresult
nsXFormsModelElement::RefreshSubTree(nsXFormsControlListItem *aCurrent,
PRBool aForceRebind)
{
nsresult rv;
while (aCurrent) {
nsCOMPtr<nsIXFormsControl> control(aCurrent->Control());
NS_ASSERTION(control, "A tree node without a control?!");
// Get bound node
nsCOMPtr<nsIDOMNode> boundNode;
control->GetBoundNode(getter_AddRefs(boundNode));
PRBool rebind = aForceRebind;
PRBool refresh = PR_FALSE;
PRBool rebindChildren = PR_FALSE;
#ifdef DEBUG_MODEL
nsCOMPtr<nsIDOMElement> controlElement;
control->GetElement(getter_AddRefs(controlElement));
printf("rebind: %d, mNeedsRefresh: %d, rebindChildren: %d\n",
rebind, mNeedsRefresh, rebindChildren);
if (controlElement) {
printf("Checking control: ");
//DBG_TAGINFO(controlElement);
}
#endif
if (mNeedsRefresh || rebind) {
refresh = PR_TRUE;
} else {
PRBool usesModelBinding = PR_FALSE;
control->GetUsesModelBinding(&usesModelBinding);
#ifdef DEBUG_MODEL
printf("usesModelBinding: %d\n", usesModelBinding);
#endif
nsCOMArray<nsIDOMNode> *deps = nsnull;
if (usesModelBinding) {
if (!boundNode) {
// If a control uses a model binding, but has no bound node a
// rebuild is the only thing that'll (eventually) change it
aCurrent = aCurrent->NextSibling();
continue;
}
} else {
// Get dependencies
control->GetDependencies(&deps);
}
PRUint32 depCount = deps ? deps->Count() : 0;
#ifdef DEBUG_MODEL
nsAutoString boundName;
if (boundNode)
boundNode->GetNodeName(boundName);
printf("\tDependencies: %d, Bound to: '%s' [%p]\n",
depCount,
NS_ConvertUTF16toUTF8(boundName).get(),
(void*) boundNode);
nsAutoString depNodeName;
for (PRUint32 t = 0; t < depCount; ++t) {
nsCOMPtr<nsIDOMNode> tmpdep = deps->ObjectAt(t);
if (tmpdep) {
tmpdep->GetNodeName(depNodeName);
printf("\t\t%s [%p]\n",
NS_ConvertUTF16toUTF8(depNodeName).get(),
(void*) tmpdep);
}
}
#endif
nsCOMPtr<nsIDOM3Node> curChanged;
// Iterator over changed nodes. Checking for rebind, too. If it ever
// becomes true due to some condition below, we can stop this testing
// since any control that needs to rebind will also refresh.
for (PRInt32 j = 0; j < mChangedNodes.Count() && !rebind; ++j) {
curChanged = do_QueryInterface(mChangedNodes[j]);
// Check whether the bound node is dirty. If so, we need to refresh the
// control (get updated node value from the bound node)
if (!refresh && boundNode) {
curChanged->IsSameNode(boundNode, &refresh);
// Two ways to go here. Keep in mind that controls using model
// binding expressions never needs to have dependencies checked as
// they only rebind on xforms-rebuild
if (refresh && usesModelBinding) {
// 1) If the control needs a refresh, and uses model bindings,
// we can stop checking here
break;
}
if (refresh || usesModelBinding) {
// 2) If either the control needs a refresh or it uses a model
// binding we can continue to next changed node
continue;
}
}
// Check whether any dependencies are dirty. If so, we need to rebind
// the control (re-evaluate it's binding expression)
for (PRUint32 k = 0; k < depCount; ++k) {
/// @note beaufour: I'm not too happy about this ...
/// O(mChangedNodes.Count() * deps->Count()), but using the pointers
/// for sorting and comparing does not work...
curChanged->IsSameNode(deps->ObjectAt(k), &rebind);
if (rebind)
// We need to rebind the control, no need to check any more
break;
}
}
#ifdef DEBUG_MODEL
printf("\trebind: %d, refresh: %d\n", rebind, refresh);
#endif
}
// Handle rebinding
if (rebind) {
nsCOMPtr<nsIDOMNode> oldBoundNode;
control->GetBoundNode(getter_AddRefs(oldBoundNode));
rv = control->Bind();
NS_ENSURE_SUCCESS(rv, rv);
control->GetBoundNode(getter_AddRefs(boundNode));
rebindChildren = (oldBoundNode != boundNode);
}
// Handle refreshing
if (rebind || refresh) {
rv = SetStatesInternal(control, boundNode);
NS_ENSURE_SUCCESS(rv, rv);
control->Refresh();
// XXX: we should really check the return result, but f.x. select1
// returns error because of no widget...? so we should ensure that an
// error is only returned when there actually is an error, and we should
// report that on the console... possibly we should then continue,
// instead of bailing totally.
// NS_ENSURE_SUCCESS(rv, rv);
}
// Refresh children
rv = RefreshSubTree(aCurrent->FirstChild(), rebindChildren);
NS_ENSURE_SUCCESS(rv, rv);
aCurrent = aCurrent->NextSibling();
}
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::Refresh()
{
#ifdef DEBUG
printf("nsXFormsModelElement::Refresh()\n");
#endif
nsPostRefresh postRefresh = nsPostRefresh();
if (!mDocumentLoaded) {
return NS_OK;
}
// Kick off refreshing on root node
nsresult rv = RefreshSubTree(mFormControls.FirstChild(), PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
// Clear refresh structures
mChangedNodes.Clear();
mNeedsRefresh = PR_FALSE;
mMDG.ClearDispatchFlags();
return NS_OK;
}
// nsISchemaLoadListener
NS_IMETHODIMP
nsXFormsModelElement::OnLoad(nsISchema* aSchema)
{
mSchemaCount++;
if (IsComplete()) {
nsresult rv = FinishConstruction();
NS_ENSURE_SUCCESS(rv, rv);
MaybeNotifyCompletion();
}
return NS_OK;
}
// nsIWebServiceErrorHandler
NS_IMETHODIMP
nsXFormsModelElement::OnError(nsresult aStatus,
const nsAString &aStatusMessage)
{
nsXFormsUtils::ReportError(NS_LITERAL_STRING("schemaLoadError"), mElement);
nsXFormsUtils::DispatchEvent(mElement, eEvent_LinkException);
return NS_OK;
}
// nsIDOMEventListener
NS_IMETHODIMP
nsXFormsModelElement::HandleEvent(nsIDOMEvent* aEvent)
{
if (!nsXFormsUtils::EventHandlingAllowed(aEvent, mElement))
return NS_OK;
nsAutoString type;
aEvent->GetType(type);
if (type.EqualsLiteral("DOMContentLoaded")) {
return HandleLoad(aEvent);
}else if (type.EqualsLiteral("unload")) {
return HandleUnload(aEvent);
}
return NS_OK;
}
// nsIModelElementPrivate
NS_IMETHODIMP
nsXFormsModelElement::AddFormControl(nsIXFormsControl *aControl,
nsIXFormsControl *aParent)
{
NS_ENSURE_ARG(aControl);
return mFormControls.AddControl(aControl, aParent);
}
NS_IMETHODIMP
nsXFormsModelElement::RemoveFormControl(nsIXFormsControl *aControl)
{
NS_ENSURE_ARG(aControl);
PRBool removed;
nsresult rv = mFormControls.RemoveControl(aControl, removed);
NS_WARN_IF_FALSE(removed,
"Tried to remove control that was not in the model");
return rv;
}
NS_IMETHODIMP
nsXFormsModelElement::GetTypeForControl(nsIXFormsControl *aControl,
nsISchemaType **aType)
{
NS_ENSURE_ARG_POINTER(aType);
*aType = nsnull;
nsCOMPtr<nsIDOMNode> boundNode;
aControl->GetBoundNode(getter_AddRefs(boundNode));
if (!boundNode) {
// if the control isn't bound to instance data, it doesn't make sense to
// return a type. It is perfectly valid for there to be no bound node,
// so no need to use an NS_ENSURE_xxx macro, either.
return NS_ERROR_FAILURE;
}
nsAutoString schemaTypeName, schemaTypeNamespace;
nsresult rv = GetTypeAndNSFromNode(boundNode, schemaTypeName,
schemaTypeNamespace);
NS_ENSURE_SUCCESS(rv, rv);
nsXFormsSchemaValidator validator;
nsCOMPtr<nsISchemaCollection> schemaColl = do_QueryInterface(mSchemas);
if (schemaColl) {
nsCOMPtr<nsISchema> schema;
schemaColl->GetSchema(schemaTypeNamespace, getter_AddRefs(schema));
// if no schema found, then we will only handle built-in types.
if (schema)
validator.LoadSchema(schema);
}
return validator.GetType(schemaTypeName, schemaTypeNamespace, aType);
}
/* static */ nsresult
nsXFormsModelElement::GetTypeAndNSFromNode(nsIDOMNode *aInstanceData,
nsAString &aType, nsAString &aNSUri)
{
nsresult rv = GetTypeFromNode(aInstanceData, aType, aNSUri);
if (rv == NS_ERROR_NOT_AVAILABLE) {
// if there is no type assigned, then assume that the type is 'string'
aNSUri.Assign(NS_LITERAL_STRING(NS_NAMESPACE_XML_SCHEMA));
aType.Assign(NS_LITERAL_STRING("string"));
rv = NS_OK;
}
return rv;
}
NS_IMETHODIMP
nsXFormsModelElement::InstanceLoadStarted()
{
++mPendingInstanceCount;
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::InstanceLoadFinished(PRBool aSuccess)
{
if (!aSuccess) {
// This will leave mPendingInstanceCount in an invalid state, which is
// exactly what we want, because this is a fatal error, and processing
// should stop. If we decrease mPendingInstanceCount, the model would
// finish construction, which is wrong.
nsXFormsUtils::ReportError(NS_LITERAL_STRING("instanceLoadError"), mElement);
nsXFormsUtils::DispatchEvent(mElement, eEvent_LinkException);
return NS_OK;
}
--mPendingInstanceCount;
if (IsComplete()) {
nsresult rv = FinishConstruction();
if (NS_SUCCEEDED(rv)) {
MaybeNotifyCompletion();
}
}
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::FindInstanceElement(const nsAString &aID,
nsIInstanceElementPrivate **aElement)
{
NS_ENSURE_STATE(mInstanceDocuments);
*aElement = nsnull;
PRUint32 instCount;
mInstanceDocuments->GetLength(&instCount);
if (instCount) {
nsCOMPtr<nsIDOMElement> element;
nsAutoString id;
for (PRUint32 i = 0; i < instCount; ++i) {
nsIInstanceElementPrivate* instEle = mInstanceDocuments->GetInstanceAt(i);
instEle->GetElement(getter_AddRefs(element));
if (aID.IsEmpty()) {
NS_ADDREF(instEle);
*aElement = instEle;
break;
} else if (!element) {
// this should only happen if the instance on the list is lazy authored
// and as far as I can tell, a lazy authored instance should be the
// first (and only) instance in the model and unable to have an ID.
// But that isn't clear to me reading the spec, so for now
// we'll play it safe in case the WG more clearly defines lazy authoring
// in the future.
continue;
}
element->GetAttribute(NS_LITERAL_STRING("id"), id);
if (aID.Equals(id)) {
NS_ADDREF(instEle);
*aElement = instEle;
break;
}
}
}
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::SetNodeValue(nsIDOMNode *aContextNode,
const nsAString &aNodeValue,
PRBool *aNodeChanged)
{
return mMDG.SetNodeValue(aContextNode,
aNodeValue,
aNodeChanged);
}
NS_IMETHODIMP
nsXFormsModelElement::GetNodeValue(nsIDOMNode *aContextNode,
nsAString &aNodeValue)
{
return mMDG.GetNodeValue(aContextNode,
aNodeValue);
}
NS_IMETHODIMP
nsXFormsModelElement::SetNodeContent(nsIDOMNode *aContextNode,
nsIDOMNode *aNodeContent,
PRBool *aNodeChanged)
{
return mMDG.SetNodeContent(aContextNode,
aNodeContent,
aNodeChanged);
}
NS_IMETHODIMP
nsXFormsModelElement::ValidateNode(nsIDOMNode *aInstanceNode, PRBool *aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
nsAutoString schemaTypeName, schemaTypeNamespace;
nsresult rv = GetTypeAndNSFromNode(aInstanceNode, schemaTypeName,
schemaTypeNamespace);
NS_ENSURE_SUCCESS(rv, rv);
nsXFormsSchemaValidator validator;
nsCOMPtr<nsISchemaCollection> schemaColl = do_QueryInterface(mSchemas);
if (schemaColl) {
nsCOMPtr<nsISchema> schema;
schemaColl->GetSchema(schemaTypeNamespace, getter_AddRefs(schema));
// if no schema found, then we will only handle built-in types.
if (schema)
validator.LoadSchema(schema);
}
nsAutoString value;
nsXFormsUtils::GetNodeValue(aInstanceNode, value);
PRBool isValid = validator.ValidateString(value, schemaTypeName, schemaTypeNamespace);
*aResult = isValid;
return NS_OK;
}
nsresult
nsXFormsModelElement::ValidateDocument(nsIDOMDocument *aInstanceDocument,
PRBool *aResult)
{
NS_ENSURE_ARG_POINTER(aResult);
NS_ENSURE_ARG(aInstanceDocument);
/*
This will process the instance document and check for schema validity. It
will mark nodes in the document with their schema types using nsIProperty
until it hits a structural schema validation error. So if the instance
document's XML structure is invalid, don't expect type properties to be
set.
Note that if the structure is fine but some simple types nodes (nodes
that contain text only) are invalid (say one has a empty nodeValue but
should be a date), the schema validator will continue processing and add
the type properties. Schema validation will return false at the end.
*/
nsCOMPtr<nsIDOMElement> element;
nsresult rv = aInstanceDocument->GetDocumentElement(getter_AddRefs(element));
NS_ENSURE_SUCCESS(rv, rv);
// get namespace from node
nsAutoString nsuri;
element->GetNamespaceURI(nsuri);
nsCOMPtr<nsISchemaCollection> schemaColl = do_QueryInterface(mSchemas);
NS_ENSURE_STATE(schemaColl);
nsCOMPtr<nsISchema> schema;
schemaColl->GetSchema(nsuri, getter_AddRefs(schema));
if (!schema) {
// No schema found, so nothing to validate
*aResult = PR_TRUE;
return NS_OK;
}
nsXFormsSchemaValidator validator;
validator.LoadSchema(schema);
// Validate will validate the node and its subtree, as per the schema
// specification.
*aResult = validator.Validate(element);
return NS_OK;
}
/*
* SUBMIT_SERIALIZE_NODE - node is to be serialized
* SUBMIT_SKIP_NODE - node is not to be serialized
* SUBMIT_ABORT_SUBMISSION - abort submission (invalid node or empty required node)
*/
NS_IMETHODIMP
nsXFormsModelElement::HandleInstanceDataNode(nsIDOMNode *aInstanceDataNode, unsigned short *aResult)
{
// abort by default
*aResult = SUBMIT_ABORT_SUBMISSION;
const nsXFormsNodeState* ns;
ns = mMDG.GetNodeState(aInstanceDataNode);
NS_ENSURE_STATE(ns);
if (!ns->IsRelevant()) {
// not relevant, thus skip
*aResult = SUBMIT_SKIP_NODE;
} else if (ns->IsRequired()) {
// required and has a value, continue
nsAutoString value;
nsXFormsUtils::GetNodeValue(aInstanceDataNode, value);
if (!value.IsEmpty() && ns->IsValid())
*aResult = SUBMIT_SERIALIZE_NODE;
} else if (ns->IsValid()) {
// valid
*aResult = SUBMIT_SERIALIZE_NODE;
}
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::GetLazyAuthored(PRBool *aLazyInstance)
{
*aLazyInstance = mLazyModel;
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::GetIsReady(PRBool *aIsReady)
{
*aIsReady = mReadyHandled;
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::GetTypeFromNode(nsIDOMNode *aInstanceData,
nsAString &aType,
nsAString &aNSUri)
{
// aInstanceData could be an instance data node or it could be an attribute
// on an instance data node (basically the node that a control is bound to).
nsString *typeVal = nsnull;
// Get type stored directly on instance node
nsAutoString typeAttribute;
nsCOMPtr<nsIDOMElement> nodeElem(do_QueryInterface(aInstanceData));
if (nodeElem) {
nodeElem->GetAttributeNS(NS_LITERAL_STRING(NS_NAMESPACE_XML_SCHEMA_INSTANCE),
NS_LITERAL_STRING("type"), typeAttribute);
if (!typeAttribute.IsEmpty()) {
typeVal = &typeAttribute;
}
}
// If there was no type information on the node itself, check for a type
// bound to the node via \<xforms:bind\>
if (!typeVal && !mNodeToType.Get(aInstanceData, &typeVal)) {
// check if schema validation left us a nsISchemaType*
nsCOMPtr<nsIContent> content = do_QueryInterface(aInstanceData);
if (content) {
nsISchemaType *type;
nsCOMPtr<nsIAtom> myAtom = do_GetAtom("xsdtype");
type = NS_STATIC_CAST(nsISchemaType *, content->GetProperty(myAtom));
if (type) {
type->GetName(aType);
type->GetTargetNamespace(aNSUri);
return NS_OK;
}
}
// No type information found
return NS_ERROR_NOT_AVAILABLE;
}
// split type (ns:type) into namespace and type.
nsAutoString prefix;
PRInt32 separator = typeVal->FindChar(':');
if ((PRUint32) separator == (typeVal->Length() - 1)) {
const PRUnichar *strings[] = { typeVal->get() };
nsXFormsUtils::ReportError(NS_LITERAL_STRING("missingTypeName"), strings, 1,
mElement, nsnull);
return NS_ERROR_UNEXPECTED;
}
if (separator == kNotFound) {
// no namespace prefix, which is valid;
prefix = EmptyString();
aType.Assign(*typeVal);
} else {
prefix.Assign(Substring(*typeVal, 0, separator));
aType.Assign(Substring(*typeVal, ++separator, typeVal->Length()));
}
if (prefix.IsEmpty()) {
aNSUri = EmptyString();
return NS_OK;
}
// get the namespace url from the prefix using instance data node
nsresult rv;
nsCOMPtr<nsIDOM3Node> domNode3 = do_QueryInterface(aInstanceData, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = domNode3->LookupNamespaceURI(prefix, aNSUri);
if (DOMStringIsNull(aNSUri)) {
// if not found using instance data node, use <xf:instance> node
nsCOMPtr<nsIDOMNode> instanceNode;
rv = nsXFormsUtils::GetInstanceNodeForData(aInstanceData,
getter_AddRefs(instanceNode));
NS_ENSURE_SUCCESS(rv, rv);
domNode3 = do_QueryInterface(instanceNode, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = domNode3->LookupNamespaceURI(prefix, aNSUri);
}
return rv;
}
// nsIXFormsContextControl
NS_IMETHODIMP
nsXFormsModelElement::SetContext(nsIDOMNode *aContextNode,
PRInt32 aContextPosition,
PRInt32 aContextSize)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
nsXFormsModelElement::GetContext(nsAString &aModelID,
nsIDOMNode **aContextNode,
PRInt32 *aContextPosition,
PRInt32 *aContextSize)
{
// Adding the nsIXFormsContextControl interface to model to allow
// submission elements to call our binding evaluation methods, like
// EvaluateNodeBinding. If GetContext can get called outside of the binding
// codepath, then this MIGHT lead to problems.
NS_ENSURE_ARG(aContextSize);
NS_ENSURE_ARG(aContextPosition);
*aContextNode = nsnull;
// better get the stuff most likely to fail out of the way first. No sense
// changing the other values that we are returning unless this is successful.
nsresult rv = NS_ERROR_FAILURE;
// Anybody (like a submission element) asking a model element for its context
// for XPath expressions will want the root node of the default instance
// document
nsCOMPtr<nsIDOMDocument> firstInstanceDoc =
FindInstanceDocument(EmptyString());
NS_ENSURE_TRUE(firstInstanceDoc, rv);
nsCOMPtr<nsIDOMElement> firstInstanceRoot;
rv = firstInstanceDoc->GetDocumentElement(getter_AddRefs(firstInstanceRoot));
NS_ENSURE_TRUE(firstInstanceRoot, rv);
nsCOMPtr<nsIDOMNode>rootNode = do_QueryInterface(firstInstanceRoot);
rootNode.swap(*aContextNode);
// found the context, so can finish up assinging the rest of the values that
// we are returning
*aContextPosition = 1;
*aContextSize = 1;
nsAutoString id;
mElement->GetAttribute(NS_LITERAL_STRING("id"), id);
aModelID.Assign(id);
return NS_OK;
}
// internal methods
already_AddRefed<nsIDOMDocument>
nsXFormsModelElement::FindInstanceDocument(const nsAString &aID)
{
nsCOMPtr<nsIInstanceElementPrivate> instance;
nsXFormsModelElement::FindInstanceElement(aID, getter_AddRefs(instance));
nsIDOMDocument *doc = nsnull;
if (instance) {
instance->GetInstanceDocument(&doc); // addrefs
}
return doc;
}
nsresult
nsXFormsModelElement::ProcessBindElements()
{
// ProcessBindElements() will go through each xforms:bind element in
// document order and apply all of the Model Item Properties to the
// instance items in the nodeset. This information will also be entered
// in the Master Dependency Graph. Most of this work is done in the
// ProcessBind() method.
nsCOMPtr<nsIDOMDocument> firstInstanceDoc =
FindInstanceDocument(EmptyString());
if (!firstInstanceDoc)
return NS_OK;
nsCOMPtr<nsIDOMElement> firstInstanceRoot;
firstInstanceDoc->GetDocumentElement(getter_AddRefs(firstInstanceRoot));
nsresult rv;
nsCOMPtr<nsIXFormsXPathEvaluator> xpath =
do_CreateInstance("@mozilla.org/dom/xforms-xpath-evaluator;1", &rv);
NS_ENSURE_TRUE(xpath, rv);
nsCOMPtr<nsIDOMNodeList> 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<nsIDOMNode> 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<nsIDOMElement>(do_QueryInterface(child)),
PR_TRUE);
if (NS_FAILED(rv)) {
return NS_OK;
}
}
}
}
return NS_OK;
}
void
nsXFormsModelElement::Reset()
{
BackupOrRestoreInstanceData(PR_TRUE);
nsXFormsUtils::DispatchEvent(mElement, eEvent_Rebuild);
nsXFormsUtils::DispatchEvent(mElement, eEvent_Recalculate);
nsXFormsUtils::DispatchEvent(mElement, eEvent_Revalidate);
nsXFormsUtils::DispatchEvent(mElement, eEvent_Refresh);
}
// This function will restore all of the model's instance data to it's original
// state if the supplied boolean is PR_TRUE. If it is PR_FALSE, this function
// will cause this model's instance data to be backed up.
void
nsXFormsModelElement::BackupOrRestoreInstanceData(PRBool restore)
{
if (!mInstanceDocuments)
return;
PRUint32 instCount;
mInstanceDocuments->GetLength(&instCount);
if (instCount) {
for (PRUint32 i = 0; i < instCount; ++i) {
nsIInstanceElementPrivate *instance =
mInstanceDocuments->GetInstanceAt(i);
// Don't know what to do with error if we get one.
// Restore/BackupOriginalDocument will already output warnings.
if(restore) {
instance->RestoreOriginalDocument();
}
else {
instance->BackupOriginalDocument();
}
}
}
}
nsresult
nsXFormsModelElement::FinishConstruction()
{
// process inline schemas that aren't referenced via the schema attribute
nsCOMPtr<nsIDOMNodeList> children;
mElement->GetChildNodes(getter_AddRefs(children));
if (children) {
PRUint32 childCount = 0;
children->GetLength(&childCount);
nsCOMPtr<nsIDOMNode> node;
nsCOMPtr<nsIDOMElement> element;
nsAutoString nsURI, localName, targetNamespace;
for (PRUint32 i = 0; i < childCount; ++i) {
children->Item(i, getter_AddRefs(node));
element = do_QueryInterface(node);
if (!element)
continue;
node->GetNamespaceURI(nsURI);
node->GetLocalName(localName);
if (nsURI.EqualsLiteral(NS_NAMESPACE_XML_SCHEMA) &&
localName.EqualsLiteral("schema")) {
// we don't have to check if the schema was already added because
// nsSchemaLoader::ProcessSchemaElement takes care of that.
nsCOMPtr<nsISchema> schema;
nsresult rv = mSchemas->ProcessSchemaElement(element, nsnull,
getter_AddRefs(schema));
if (!NS_SUCCEEDED(rv)) {
nsXFormsUtils::ReportError(NS_LITERAL_STRING("schemaProcessError"), node);
}
}
}
}
// (XForms 4.2.1 - cont)
// 3. if applicable, initialize P3P
// 4. construct instance data from initial instance data. apply all
// <bind> elements in document order.
// we get the instance data from our instance child nodes
// We're done initializing this model.
// 5. Perform an xforms-rebuild, xforms-recalculate, and xforms-revalidate in
// sequence, for this model element. (The xforms-refresh is not performed
// since the user interface has not yet been initialized).
nsXFormsUtils::DispatchEvent(mElement, eEvent_Rebuild);
nsXFormsUtils::DispatchEvent(mElement, eEvent_Recalculate);
nsXFormsUtils::DispatchEvent(mElement, eEvent_Revalidate);
return NS_OK;
}
nsresult
nsXFormsModelElement::InitializeControls()
{
#ifdef DEBUG
printf("nsXFormsModelElement::InitializeControls()\n");
#endif
nsPostRefresh postRefresh = nsPostRefresh();
nsXFormsControlListItem::iterator it;
nsresult rv;
for (it = mFormControls.begin(); it != mFormControls.end(); ++it) {
// Get control
nsCOMPtr<nsIXFormsControl> control = (*it)->Control();
NS_ASSERTION(control, "mFormControls has null control?!");
#ifdef DEBUG_MODEL
printf("\tControl (%p): ", (void*) control);
nsCOMPtr<nsIDOMElement> controlElement;
control->GetElement(getter_AddRefs(controlElement));
// DBG_TAGINFO(controlElement);
#endif
// Rebind
rv = control->Bind();
NS_ENSURE_SUCCESS(rv, rv);
// Get bound node
nsCOMPtr<nsIDOMNode> boundNode;
rv = control->GetBoundNode(getter_AddRefs(boundNode));
NS_ENSURE_SUCCESS(rv, rv);
// Set MIP states on control
rv = SetStatesInternal(control, boundNode, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
// Refresh controls
rv = control->Refresh();
NS_ENSURE_SUCCESS(rv, rv);
}
mChangedNodes.Clear();
return NS_OK;
}
void
nsXFormsModelElement::MaybeNotifyCompletion()
{
nsCOMPtr<nsIDOMDocument> 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);
}
// validate the instance documents becauar we want schemaValidation to add
// schema type properties from the schema file unto our instance document
// elements. We don't care about the validation results.
if (mInstanceDocuments) {
PRUint32 instCount;
mInstanceDocuments->GetLength(&instCount);
if (instCount) {
nsCOMPtr<nsIDOMDocument> document;
for (PRUint32 i = 0; i < instCount; ++i) {
nsIInstanceElementPrivate* instEle = mInstanceDocuments->GetInstanceAt(i);
nsCOMPtr<nsIXFormsNSInstanceElement> NSInstEle(instEle);
NSInstEle->GetInstanceDocument(getter_AddRefs(document));
NS_ASSERTION(document, "nsIXFormsNSInstanceElement::GetInstanceDocument returned null?!");
if (document) {
PRBool isValid = PR_FALSE;
ValidateDocument(document, &isValid);
if (!isValid) {
nsCOMPtr<nsIDOMElement> instanceElement;
instEle->GetElement(getter_AddRefs(instanceElement));
nsXFormsUtils::ReportError(NS_LITERAL_STRING("instDocumentInvalid"),
instanceElement);
}
}
}
}
}
nsXFormsModelElement::ProcessDeferredBinds(domDoc);
nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
if (doc) {
PRUint32 loadingMessages = NS_PTR_TO_UINT32(
doc->GetProperty(nsXFormsAtoms::externalMessagesProperty));
if (loadingMessages) {
// if we are still waiting for external messages to load, then put off
// the xforms-ready until a model in the document is notified that they
// are finished loading
return;
}
}
for (i = 0; i < models->Count(); ++i) {
nsXFormsModelElement *model =
NS_STATIC_CAST(nsXFormsModelElement *, models->ElementAt(i));
model->BackupOrRestoreInstanceData(PR_FALSE);
model->mReadyHandled = PR_TRUE;
nsXFormsUtils::DispatchEvent(model->mElement, eEvent_Ready);
}
}
nsresult
nsXFormsModelElement::ProcessBind(nsIXFormsXPathEvaluator *aEvaluator,
nsIDOMNode *aContextNode,
PRInt32 aContextPosition,
PRInt32 aContextSize,
nsIDOMElement *aBindElement,
PRBool aIsOuter)
{
// Get the model item properties specified by this \<bind\>.
nsCOMPtr<nsIDOMNSXPathExpression> props[eModel__count];
nsAutoString propStrings[eModel__count];
nsresult rv;
nsAutoString attrStr;
for (PRUint32 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)) {
const PRUnichar *strings[] = { propStrings[i].get() };
nsXFormsUtils::ReportError(NS_LITERAL_STRING("mipParseError"),
strings, 1, aBindElement, aBindElement);
nsXFormsUtils::DispatchEvent(mElement, eEvent_ComputeException);
return rv;
}
}
}
// Find the nodeset that this bind applies to.
nsCOMPtr<nsIDOMXPathResult> result;
nsAutoString expr;
aBindElement->GetAttribute(NS_LITERAL_STRING("nodeset"), expr);
if (expr.IsEmpty()) {
expr = NS_LITERAL_STRING(".");
}
rv = aEvaluator->Evaluate(expr, aContextNode, aContextPosition, aContextSize,
aBindElement,
nsIDOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE,
nsnull, getter_AddRefs(result));
if (NS_FAILED(rv)) {
if (rv == NS_ERROR_DOM_INVALID_EXPRESSION_ERR) {
// the xpath expression isn't valid xpath
const nsPromiseFlatString& flat = PromiseFlatString(expr);
const PRUnichar *strings[] = { flat.get() };
nsXFormsUtils::ReportError(NS_LITERAL_STRING("exprParseError"),
strings, 1, aBindElement, nsnull);
nsXFormsUtils::DispatchEvent(mElement, eEvent_ComputeException);
} else {
#ifdef DEBUG
printf("xforms-binding-exception: XPath Evaluation failed\n");
#endif
const PRUnichar *strings[] = { expr.get() };
nsXFormsUtils::ReportError(NS_LITERAL_STRING("nodesetEvaluateError"),
strings, 1, aBindElement, aBindElement);
nsXFormsUtils::DispatchEvent(mElement, eEvent_BindingException);
}
return rv;
}
NS_ENSURE_STATE(result);
// If this is an outer bind, store the nodeset, as controls binding to this
// bind will need this.
if (aIsOuter) {
nsCOMPtr<nsIContent> content(do_QueryInterface(aBindElement));
NS_ASSERTION(content, "nsIDOMElement not implementing nsIContent?!");
rv = content->SetProperty(nsXFormsAtoms::bind, result,
SupportsDtorFunc);
NS_ENSURE_SUCCESS(rv, rv);
// addref, circumventing nsDerivedSave
NS_ADDREF(NS_STATIC_CAST(nsIDOMXPathResult*, result));
}
PRUint32 snapLen;
rv = result->GetSnapshotLength(&snapLen);
NS_ENSURE_SUCCESS(rv, rv);
// Iterate over resultset
nsCOMArray<nsIDOMNode> deps;
nsCOMPtr<nsIDOMNode> 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 (PRUint32 j = 0; j < eModel__count; ++j) {
if (propStrings[j].IsEmpty())
continue;
// type and p3ptype are stored as properties on the instance node
if (j == eModel_type || j == eModel_p3ptype) {
nsClassHashtable<nsISupportsHashKey, nsString> *table;
table = j == eModel_type ? &mNodeToType : &mNodeToP3PType;
NS_ENSURE_TRUE(table->IsInitialized(), NS_ERROR_FAILURE);
// Check for existing value
if (table->Get(node, nsnull)) {
multiMIP = PR_TRUE;
break;
}
// Insert value
nsAutoPtr<nsString> newString(new nsString(propStrings[j]));
NS_ENSURE_TRUE(newString, NS_ERROR_OUT_OF_MEMORY);
NS_ENSURE_TRUE(table->Put(node, newString), NS_ERROR_OUT_OF_MEMORY);
// string is succesfully stored in the table, we should not dealloc it
newString.forget();
if (j == eModel_type) {
// 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);
NS_ENSURE_SUCCESS(rv, rv);
}
} else {
// the rest of the MIPs are given to the MDG
nsCOMPtr<nsIDOMNSXPathExpression> expr = props[j];
// Get node dependencies
nsAutoPtr<nsXFormsXPathNode> xNode(parser.Parse(propStrings[j]));
deps.Clear();
rv = analyzer.Analyze(node, xNode, expr, &propStrings[j], &deps,
snapItem + 1, snapLen, PR_FALSE);
NS_ENSURE_SUCCESS(rv, rv);
// Insert into MDG
rv = mMDG.AddMIP((ModelItemPropName) j,
expr,
&deps,
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) {
#ifdef DEBUG
printf("xforms-binding-exception: Multiple MIPs on same node!");
#endif
nsXFormsUtils::ReportError(NS_LITERAL_STRING("multiMIPError"),
aBindElement);
nsXFormsUtils::DispatchEvent(aBindElement,
eEvent_BindingException);
return NS_ERROR_FAILURE;
}
// Now evaluate any child \<bind\> elements.
nsCOMPtr<nsIDOMNodeList> children;
aBindElement->GetChildNodes(getter_AddRefs(children));
if (children) {
PRUint32 childCount = 0;
children->GetLength(&childCount);
nsCOMPtr<nsIDOMNode> 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<nsIDOMElement>(do_QueryInterface(child)));
NS_ENSURE_SUCCESS(rv, rv);
}
}
}
}
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelElement::SetStates(nsIXFormsControl *aControl, nsIDOMNode *aBoundNode)
{
return SetStatesInternal(aControl, aBoundNode, PR_FALSE);
}
nsresult
nsXFormsModelElement::AddInstanceElement(nsIInstanceElementPrivate *aInstEle)
{
NS_ENSURE_STATE(mInstanceDocuments);
mInstanceDocuments->AddInstance(aInstEle);
return NS_OK;
}
nsresult
nsXFormsModelElement::RemoveInstanceElement(nsIInstanceElementPrivate *aInstEle)
{
NS_ENSURE_STATE(mInstanceDocuments);
mInstanceDocuments->RemoveInstance(aInstEle);
return NS_OK;
}
nsresult
nsXFormsModelElement::MessageLoadFinished()
{
// This is our signal that all external message links have been tested. If
// we were waiting for this to send out xforms-ready, then now is the time.
// if this document hasn't processed xforms-model-construct-done, yet (which
// must precede xforms-ready), then we'll send out the xforms-ready later
// as part of our normal handling. If we've already become ready, then this
// event was probably generated by a change in the src attribute on the
// message element. Ignore it in that case.
if (!mConstructDoneHandled || mReadyHandled) {
return NS_OK;
}
nsCOMPtr<nsIDOMDocument> domDoc;
mElement->GetOwnerDocument(getter_AddRefs(domDoc));
const nsVoidArray *models = GetModelList(domDoc);
nsCOMPtr<nsIDocument>doc = do_QueryInterface(domDoc);
nsCOMArray<nsIXFormsControlBase> *deferredBindList =
NS_STATIC_CAST(nsCOMArray<nsIXFormsControlBase> *,
doc->GetProperty(nsXFormsAtoms::deferredBindListProperty));
// if we've already gotten the xforms-model-construct-done event and not
// yet the xforms-ready, we've hit a window where we may still be
// processing the deferred control binding. If so, we'll leave now and
// leave it to MaybeNotifyCompletion to generate the xforms-ready event.
if (deferredBindList) {
return NS_OK;
}
// if we reached here, then we had to wait on sending out the xforms-ready
// events until the external messages were tested. Now we are finally
// ready to send out xforms-ready to all of the models.
for (int i = 0; i < models->Count(); ++i) {
nsXFormsModelElement *model =
NS_STATIC_CAST(nsXFormsModelElement *, models->ElementAt(i));
model->mReadyHandled = PR_TRUE;
nsXFormsUtils::DispatchEvent(model->mElement, eEvent_Ready);
}
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;
}
already_AddRefed<nsIDOMElement>
nsXFormsModelElement::GetDOMElement()
{
nsIDOMElement* element = nsnull;
NS_IF_ADDREF(element = mElement);
return element;
}
static void
DeleteBindList(void *aObject,
nsIAtom *aPropertyName,
void *aPropertyValue,
void *aData)
{
delete NS_STATIC_CAST(nsCOMArray<nsIXFormsControl> *, aPropertyValue);
}
/* static */ nsresult
nsXFormsModelElement::DeferElementBind(nsIDOMDocument *aDoc,
nsIXFormsControlBase *aControl)
{
nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
if (!doc || !aControl) {
return NS_ERROR_FAILURE;
}
nsCOMArray<nsIXFormsControlBase> *deferredBindList =
NS_STATIC_CAST(nsCOMArray<nsIXFormsControlBase> *,
doc->GetProperty(nsXFormsAtoms::deferredBindListProperty));
if (!deferredBindList) {
deferredBindList = new nsCOMArray<nsIXFormsControlBase>(16);
NS_ENSURE_TRUE(deferredBindList, NS_ERROR_OUT_OF_MEMORY);
doc->SetProperty(nsXFormsAtoms::deferredBindListProperty, deferredBindList,
DeleteBindList);
}
// always append to the end of the list. We need to keep the elements in
// document order when we process the binds later. Otherwise we have trouble
// when an element is trying to bind and should use its parent as a context
// for the xpath evaluation but the parent isn't bound yet.
deferredBindList->AppendObject(aControl);
return NS_OK;
}
/* static */ void
nsXFormsModelElement::ProcessDeferredBinds(nsIDOMDocument *aDoc)
{
nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
if (!doc) {
return;
}
nsPostRefresh postRefresh = nsPostRefresh();
doc->SetProperty(nsXFormsAtoms::readyForBindProperty, doc);
nsCOMArray<nsIXFormsControlBase> *deferredBindList =
NS_STATIC_CAST(nsCOMArray<nsIXFormsControlBase> *,
doc->GetProperty(nsXFormsAtoms::deferredBindListProperty));
if (deferredBindList) {
for (int i = 0; i < deferredBindList->Count(); ++i) {
nsIXFormsControlBase *base = deferredBindList->ObjectAt(i);
if (base) {
base->Bind();
base->Refresh();
}
}
doc->DeleteProperty(nsXFormsAtoms::deferredBindListProperty);
}
}
nsresult
nsXFormsModelElement::HandleLoad(nsIDOMEvent* aEvent)
{
if (!mInstancesInitialized) {
// XXX This is for Bug 308106. In Gecko 1.8 DoneAddingChildren is not
// called in XUL if the element doesn't have any child nodes.
InitializeInstances();
}
mDocumentLoaded = PR_TRUE;
// dispatch xforms-model-construct, xforms-rebuild, xforms-recalculate,
// xforms-revalidate
// We wait until DOMContentLoaded to dispatch xforms-model-construct,
// since the model may have an action handler for this event and Mozilla
// doesn't register XML Event listeners until the document is loaded.
// xforms-model-construct is not cancellable, so always proceed.
nsXFormsUtils::DispatchEvent(mElement, eEvent_ModelConstruct);
if (mPendingInlineSchemas.Count() > 0) {
nsCOMPtr<nsIDOMElement> el;
nsresult rv;
for (PRInt32 i=0; i<mPendingInlineSchemas.Count(); ++i) {
GetSchemaElementById(mElement, *mPendingInlineSchemas[i],
getter_AddRefs(el));
if (!el) {
rv = NS_ERROR_UNEXPECTED;
} else {
nsCOMPtr<nsISchema> 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 (XXX)
nsXFormsUtils::ReportError(NS_LITERAL_STRING("schemaLoadError"), mElement);
nsXFormsUtils::DispatchEvent(mElement, eEvent_LinkException);
return NS_OK;
}
}
if (IsComplete()) {
rv = FinishConstruction();
NS_ENSURE_SUCCESS(rv, rv);
}
mPendingInlineSchemas.Clear();
}
// We may still be waiting on external documents to load.
MaybeNotifyCompletion();
return NS_OK;
}
nsresult
nsXFormsModelElement::HandleUnload(nsIDOMEvent* aEvent)
{
// due to fastback changes, had to move this notification out from under
// model's WillChangeDocument override.
return nsXFormsUtils::DispatchEvent(mElement, eEvent_ModelDestruct);
}
nsresult
NS_NewXFormsModelElement(nsIXTFElement **aResult)
{
*aResult = new nsXFormsModelElement();
if (!*aResult)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*aResult);
return NS_OK;
}
// ---------------------------- //
// nsXFormsModelInstanceDocuments
NS_IMPL_ISUPPORTS2(nsXFormsModelInstanceDocuments, nsIDOMNodeList, nsIClassInfo)
nsXFormsModelInstanceDocuments::nsXFormsModelInstanceDocuments()
: mInstanceList(16)
{
}
NS_IMETHODIMP
nsXFormsModelInstanceDocuments::GetLength(PRUint32* aLength)
{
*aLength = mInstanceList.Count();
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelInstanceDocuments::Item(PRUint32 aIndex, nsIDOMNode** aReturn)
{
*aReturn = nsnull;
nsIInstanceElementPrivate* instance = mInstanceList.SafeObjectAt(aIndex);
if (instance) {
nsCOMPtr<nsIDOMDocument> doc;
if (NS_SUCCEEDED(instance->GetInstanceDocument(getter_AddRefs(doc))) && doc) {
NS_ADDREF(*aReturn = doc);
}
}
return NS_OK;
}
nsIInstanceElementPrivate*
nsXFormsModelInstanceDocuments::GetInstanceAt(PRUint32 aIndex)
{
return mInstanceList.ObjectAt(aIndex);
}
void
nsXFormsModelInstanceDocuments::AddInstance(nsIInstanceElementPrivate *aInst)
{
// always append to the end of the list. We need to keep the elements in
// document order since the first instance element is the default instance
// document for the model.
mInstanceList.AppendObject(aInst);
}
void
nsXFormsModelInstanceDocuments::RemoveInstance(nsIInstanceElementPrivate *aInst)
{
mInstanceList.RemoveObject(aInst);
}
void
nsXFormsModelInstanceDocuments::DropReferences()
{
mInstanceList.Clear();
}
// nsIClassInfo implementation
static const nsIID sInstScriptingIIDs[] = {
NS_IDOMNODELIST_IID
};
NS_IMETHODIMP
nsXFormsModelInstanceDocuments::GetInterfaces(PRUint32 *aCount,
nsIID * **aArray)
{
return
nsXFormsUtils::CloneScriptingInterfaces(sInstScriptingIIDs,
NS_ARRAY_LENGTH(sInstScriptingIIDs),
aCount, aArray);
}
NS_IMETHODIMP
nsXFormsModelInstanceDocuments::GetHelperForLanguage(PRUint32 language,
nsISupports **_retval)
{
*_retval = nsnull;
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelInstanceDocuments::GetContractID(char * *aContractID)
{
*aContractID = nsnull;
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelInstanceDocuments::GetClassDescription(char * *aClassDescription)
{
*aClassDescription = nsnull;
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelInstanceDocuments::GetClassID(nsCID * *aClassID)
{
*aClassID = nsnull;
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelInstanceDocuments::GetImplementationLanguage(PRUint32 *aLang)
{
*aLang = nsIProgrammingLanguage::CPLUSPLUS;
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelInstanceDocuments::GetFlags(PRUint32 *aFlags)
{
*aFlags = nsIClassInfo::DOM_OBJECT;
return NS_OK;
}
NS_IMETHODIMP
nsXFormsModelInstanceDocuments::GetClassIDNoAlloc(nsCID *aClassIDNoAlloc)
{
return NS_ERROR_NOT_AVAILABLE;
}