From a9b9c9b86e32f5322a013e041d3b0cf77f31b4ea Mon Sep 17 00:00:00 2001 From: "allan%beaufour.dk" Date: Mon, 22 May 2006 09:06:14 +0000 Subject: [PATCH] [XForms] Optimize repeat refreshing. Bug 331452, r=smaug+aaronr --- extensions/xforms/nsIXFormsControl.idl | 15 +- extensions/xforms/nsIXFormsControlBase.idl | 6 +- .../xforms/nsXFormsContextContainer.cpp | 34 ++- extensions/xforms/nsXFormsControlStub.cpp | 34 ++- extensions/xforms/nsXFormsControlStub.h | 24 +- extensions/xforms/nsXFormsDelegateStub.cpp | 4 +- extensions/xforms/nsXFormsItemSetElement.cpp | 18 +- extensions/xforms/nsXFormsModelElement.cpp | 120 ++++---- extensions/xforms/nsXFormsModelElement.h | 13 +- extensions/xforms/nsXFormsOutputElement.cpp | 6 +- extensions/xforms/nsXFormsRepeatElement.cpp | 287 +++++++++++------- 11 files changed, 330 insertions(+), 231 deletions(-) diff --git a/extensions/xforms/nsIXFormsControl.idl b/extensions/xforms/nsIXFormsControl.idl index 08181e5a514d..22015768f2e2 100644 --- a/extensions/xforms/nsIXFormsControl.idl +++ b/extensions/xforms/nsIXFormsControl.idl @@ -49,7 +49,7 @@ interface nsIDOMElement; /** * Interface implemented by all XForms form control classes. */ -[uuid(1bea8750-2133-4a00-986a-04e5cbd129b2)] +[uuid(8c84afe1-e071-4d45-b3da-c5aa93154343)] interface nsIXFormsControl : nsIXFormsContextControl { /** @@ -62,13 +62,24 @@ interface nsIXFormsControl : nsIXFormsContextControl */ readonly attribute nsIDOMNode boundNode; + /** + * Binds the control to the model. Only handles attaching to the model + * (including reattaching from any old model). + * + * @note It can also set the boundNode, but does not do a proper node + * binding, as in setting up dependencies, attaching index() listeners, etc. + * + * @param setBoundNode Set boundNode too? + */ + void bindToModel(in boolean setBoundNode); + /** * The instance nodes that the control depend on. * * In other words, all the instance nodes that could influence which node * the control is bound to (mBoundNode). For example: * If a node has @ref="/share[@owner = /me]", it depends on all /share - * nodes, all @owned attributes on /share nodes, and all /me nodes. + * nodes, all @owner attributes on /share nodes, and all /me nodes. */ readonly attribute nsCOMArrayPtr dependencies; diff --git a/extensions/xforms/nsIXFormsControlBase.idl b/extensions/xforms/nsIXFormsControlBase.idl index bd9da8b04693..f4a6b8cf362c 100644 --- a/extensions/xforms/nsIXFormsControlBase.idl +++ b/extensions/xforms/nsIXFormsControlBase.idl @@ -36,14 +36,16 @@ * ***** END LICENSE BLOCK ***** */ #include "nsISupports.idl" -[uuid(b8540fca-a671-4a1d-8471-6e89820f0848)] +[uuid(92b8a6aa-3692-4132-8df6-a61a936d5e9d)] interface nsIXFormsControlBase : nsISupports { /** * This tells the form control to update its node binding based on the * current instance data. + * + * @return Did the binding change the context? */ - void bind(); + boolean bind(); /** * This tells the form control to update its state based on the current diff --git a/extensions/xforms/nsXFormsContextContainer.cpp b/extensions/xforms/nsXFormsContextContainer.cpp index 66dda69802f3..5cd3ef297757 100644 --- a/extensions/xforms/nsXFormsContextContainer.cpp +++ b/extensions/xforms/nsXFormsContextContainer.cpp @@ -89,11 +89,15 @@ protected: PRInt32 mContextSize; /** Does this element have the repeat-index? */ - PRBool mHasIndex; + PRPackedBool mHasIndex; + + /** Has context changed since last bind? */ + PRPackedBool mContextIsDirty; public: nsXFormsContextContainer() - : mContextPosition(1), mContextSize(1), mHasIndex(PR_FALSE) {} + : mContextPosition(1), mContextSize(1), mHasIndex(PR_FALSE), + mContextIsDirty(PR_FALSE) {} NS_DECL_ISUPPORTS_INHERITED @@ -102,7 +106,7 @@ public: NS_IMETHOD DocumentChanged(nsIDOMDocument *aNewDocument); // nsIXFormsControl - NS_IMETHOD Bind(); + NS_IMETHOD Bind(PRBool *aContextChanged); NS_IMETHOD SetContext(nsIDOMNode *aContextNode, PRInt32 aContextPosition, PRInt32 aContextSize); @@ -271,11 +275,18 @@ nsXFormsContextContainer::SetContext(nsIDOMNode *aContextNode, PRInt32 aContextPosition, PRInt32 aContextSize) { - mBoundNode = aContextNode; - mContextPosition = aContextPosition; - mContextSize = aContextSize; + mContextIsDirty = (mContextIsDirty || + mBoundNode != aContextNode || + mContextPosition != aContextPosition || + mContextSize != aContextSize); - return Bind(); + if (mContextIsDirty) { + mBoundNode = aContextNode; + mContextPosition = aContextPosition; + mContextSize = aContextSize; + } + + return BindToModel(); } NS_IMETHODIMP @@ -299,12 +310,11 @@ nsXFormsContextContainer::GetContext(nsAString &aModelID, // nsIXFormsControl NS_IMETHODIMP -nsXFormsContextContainer::Bind() +nsXFormsContextContainer::Bind(PRBool *aContextChanged) { - - nsresult rv = BindToModel(); - NS_ENSURE_SUCCESS(rv, rv); - + NS_ENSURE_ARG(aContextChanged); + *aContextChanged = mContextIsDirty; + mContextIsDirty = PR_FALSE; return NS_OK; } diff --git a/extensions/xforms/nsXFormsControlStub.cpp b/extensions/xforms/nsXFormsControlStub.cpp index e4746e556223..d9fcba485386 100644 --- a/extensions/xforms/nsXFormsControlStub.cpp +++ b/extensions/xforms/nsXFormsControlStub.cpp @@ -141,12 +141,16 @@ nsXFormsControlStubBase::RemoveIndexListeners() } NS_IMETHODIMP -nsXFormsControlStubBase::ResetBoundNode(const nsString &aBindAttribute, - PRUint16 aResultType, - nsIDOMXPathResult **aResult) +nsXFormsControlStubBase::ResetBoundNode(const nsString &aBindAttribute, + PRUint16 aResultType, + PRBool *aContextChanged) { + NS_ENSURE_ARG(aContextChanged); + // Clear existing bound node, etc. - mBoundNode = nsnull; + *aContextChanged = mBoundNode ? PR_TRUE : PR_FALSE; + nsCOMPtr oldBoundNode; + oldBoundNode.swap(mBoundNode); mUsesModelBinding = PR_FALSE; mAppearDisabled = PR_FALSE; mDependencies.Clear(); @@ -166,7 +170,7 @@ nsXFormsControlStubBase::ResetBoundNode(const nsString &aBindAttribute, if (rv == NS_OK_XFORMS_DEFERRED || !result) { // Binding was deferred, or not bound - return NS_OK; + return rv; } // Get context node, if any @@ -177,6 +181,8 @@ nsXFormsControlStubBase::ResetBoundNode(const nsString &aBindAttribute, result->GetSingleNodeValue(getter_AddRefs(mBoundNode)); } + *aContextChanged = (oldBoundNode != mBoundNode); + if (!mBoundNode) { // If there's no result (ie, no instance node) returned by the above, it // means that the binding is not pointing to an instance data node, so we @@ -189,19 +195,15 @@ nsXFormsControlStubBase::ResetBoundNode(const nsString &aBindAttribute, return wrapper->SetIntrinsicState(kDisabledIntrinsicState); } - if (aResult) { - *aResult = nsnull; - result.swap(*aResult); // transfers ref - } - return NS_OK; } NS_IMETHODIMP -nsXFormsControlStubBase::Bind() +nsXFormsControlStubBase::Bind(PRBool* aContextChanged) { return ResetBoundNode(NS_LITERAL_STRING("ref"), - nsIDOMXPathResult::FIRST_ORDERED_NODE_TYPE); + nsIDOMXPathResult::FIRST_ORDERED_NODE_TYPE, + aContextChanged); } NS_IMETHODIMP @@ -364,7 +366,7 @@ nsXFormsControlStubBase::ProcessNodeBinding(const nsString &aBindingAtt return rv; } -nsresult +NS_IMETHODIMP nsXFormsControlStubBase::BindToModel(PRBool aSetBoundNode) { nsCOMPtr oldModel(mModel); @@ -604,7 +606,8 @@ nsXFormsControlStubBase::ForceModelDetach(PRBool aRebind) return NS_OK; } - nsresult rv = Bind(); + PRBool dummy; + nsresult rv = Bind(&dummy); NS_ENSURE_SUCCESS(rv, rv); return rv == NS_OK_XFORMS_DEFERRED ? NS_OK : Refresh(); } @@ -747,7 +750,8 @@ void nsXFormsControlStubBase::AfterSetAttribute(nsIAtom *aName) { if (IsBindingAttribute(aName)) { - nsresult rv = Bind(); + PRBool dummy; + nsresult rv = Bind(&dummy); if (NS_SUCCEEDED(rv) && rv != NS_OK_XFORMS_DEFERRED) Refresh(); } diff --git a/extensions/xforms/nsXFormsControlStub.h b/extensions/xforms/nsXFormsControlStub.h index f9a62840a334..6838c506536c 100644 --- a/extensions/xforms/nsXFormsControlStub.h +++ b/extensions/xforms/nsXFormsControlStub.h @@ -81,12 +81,13 @@ public: // nsIXFormsControl NS_IMETHOD GetBoundNode(nsIDOMNode **aBoundNode); + NS_IMETHOD BindToModel(PRBool aSetBoundNode = PR_FALSE); NS_IMETHOD GetDependencies(nsCOMArray **aDependencies); NS_IMETHOD GetElement(nsIDOMElement **aElement); - NS_IMETHOD ResetBoundNode(const nsString &aBindAttribute, - PRUint16 aResultType, - nsIDOMXPathResult **aResult = nsnull); - NS_IMETHOD Bind(); + NS_IMETHOD ResetBoundNode(const nsString &aBindAttribute, + PRUint16 aResultType, + PRBool *aContextChanged); + NS_IMETHOD Bind(PRBool *aContextChanged); NS_IMETHOD Refresh(); NS_IMETHOD GetOnDeferredBindList(PRBool *onList); NS_IMETHOD SetOnDeferredBindList(PRBool putOnList); @@ -263,17 +264,6 @@ protected: /** Removes the index change event listeners */ void RemoveIndexListeners(); - /** - * Binds the control to the model. Just sets mModel, and handle attaching to - * the model (including reattaching from any old model). - * - * @note It can also set the mBoundNode, but does not do a proper node - * binding, as in setting up dependencies, attaching index() listeners, etc. - * - * @param aSetBoundNode Set mBoundNode too? - */ - nsresult BindToModel(PRBool aSetBoundNode = PR_FALSE); - /** * Forces detaching from the model. * @@ -350,9 +340,9 @@ public: return nsXFormsControlStubBase::AttributeRemoved(aName); } - NS_IMETHOD Bind() + NS_IMETHOD Bind(PRBool *aContextChanged) { - return nsXFormsControlStubBase::Bind(); + return nsXFormsControlStubBase::Bind(aContextChanged); } /** Constructor */ diff --git a/extensions/xforms/nsXFormsDelegateStub.cpp b/extensions/xforms/nsXFormsDelegateStub.cpp index 3d093887a39c..8c68dbffa53d 100644 --- a/extensions/xforms/nsXFormsDelegateStub.cpp +++ b/extensions/xforms/nsXFormsDelegateStub.cpp @@ -116,10 +116,8 @@ nsXFormsDelegateStub::Refresh() SetMozTypeAttribute(); nsCOMPtr widget = do_QueryInterface(mElement); - if (!widget) - return NS_ERROR_FAILURE; - return widget->Refresh(); + return widget ? widget->Refresh() : NS_OK; } NS_IMETHODIMP diff --git a/extensions/xforms/nsXFormsItemSetElement.cpp b/extensions/xforms/nsXFormsItemSetElement.cpp index c603a55ce618..9638cfb683b4 100644 --- a/extensions/xforms/nsXFormsItemSetElement.cpp +++ b/extensions/xforms/nsXFormsItemSetElement.cpp @@ -70,7 +70,7 @@ public: NS_IMETHOD DoneAddingChildren(); // nsIXFormsControlBase overrides - NS_IMETHOD Bind(); + NS_IMETHOD Bind(PRBool *aContextChanged); NS_IMETHOD Refresh(); // nsIXFormsSelectChild @@ -222,8 +222,10 @@ nsXFormsItemSetElement::SelectItemByNode(nsIDOMNode *aNode, } NS_IMETHODIMP -nsXFormsItemSetElement::Bind() +nsXFormsItemSetElement::Bind(PRBool *aContextChanged) { + NS_ENSURE_ARG(aContextChanged); + *aContextChanged = PR_FALSE; return BindToModel(); } @@ -303,23 +305,21 @@ nsXFormsItemSetElement::Refresh() getter_AddRefs(itemNode)); NS_ENSURE_SUCCESS(rv, rv); + anonContent->AppendChild(itemNode, getter_AddRefs(tmpNode)); + // XXX Could we get rid of the ? rv = domDoc->CreateElementNS(NS_LITERAL_STRING(NS_NAMESPACE_XFORMS), NS_LITERAL_STRING("contextcontainer"), getter_AddRefs(contextContainer)); NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr modelElement = do_QueryInterface(model); - nsAutoString modelID; - modelElement->GetAttribute(NS_LITERAL_STRING("id"), modelID); - - contextContainer->SetAttribute(NS_LITERAL_STRING("model"), modelID); + itemNode->AppendChild(contextContainer, getter_AddRefs(tmpNode)); nsCOMPtr ctx(do_QueryInterface(contextContainer)); if (ctx) { ctx->SetContext(node, i + 1, nodeCount); } + // Clone the template content under the item for (PRUint32 j = 0; j < templateNodeCount; ++j) { templateNodes->Item(j, getter_AddRefs(templateNode)); @@ -327,8 +327,6 @@ nsXFormsItemSetElement::Refresh() contextContainer->AppendChild(cloneNode, getter_AddRefs(templateNode)); } - itemNode->AppendChild(contextContainer, getter_AddRefs(tmpNode)); - anonContent->AppendChild(itemNode, getter_AddRefs(tmpNode)); } // refresh parent we diff --git a/extensions/xforms/nsXFormsModelElement.cpp b/extensions/xforms/nsXFormsModelElement.cpp index db8ab6578fdc..091459d73a01 100644 --- a/extensions/xforms/nsXFormsModelElement.cpp +++ b/extensions/xforms/nsXFormsModelElement.cpp @@ -639,7 +639,7 @@ nsXFormsModelElement::nsXFormsModelElement() mSchemaTotal(0), mPendingInstanceCount(0), mDocumentLoaded(PR_FALSE), - mNeedsRefresh(PR_FALSE), + mRebindAllControls(PR_FALSE), mInstancesInitialized(PR_FALSE), mReadyHandled(PR_FALSE), mLazyModel(PR_FALSE), @@ -1062,18 +1062,8 @@ nsXFormsModelElement::Rebuild() NS_ENSURE_SUCCESS(rv, rv); // 3. Re-attach all elements - if (mReadyHandled) { // if it's not during initializing phase - nsXFormsControlListItem::iterator it; - for (it = mFormControls.begin(); it != mFormControls.end(); ++it) { - nsCOMPtr 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; + if (mDocumentLoaded) { + mRebindAllControls = PR_TRUE; } // 4. Rebuild graph @@ -1199,16 +1189,16 @@ nsXFormsModelElement::RefreshSubTree(nsXFormsControlListItem *aCurrent, #ifdef DEBUG_MODEL nsCOMPtr controlElement; control->GetElement(getter_AddRefs(controlElement)); - printf("rebind: %d, mNeedsRefresh: %d, rebindChildren: %d\n", - rebind, mNeedsRefresh, rebindChildren); + printf("rebind: %d, mRebindAllControls: %d, aForceRebind: %d\n", + rebind, mRebindAllControls, aForceRebind); if (controlElement) { printf("Checking control: "); //DBG_TAGINFO(controlElement); } #endif - if (mNeedsRefresh || rebind) { - refresh = PR_TRUE; + if (mRebindAllControls || rebind) { + refresh = rebind = PR_TRUE; } else { PRBool usesModelBinding = PR_FALSE; control->GetUsesModelBinding(&usesModelBinding); @@ -1299,22 +1289,18 @@ nsXFormsModelElement::RefreshSubTree(nsXFormsControlListItem *aCurrent, // Handle rebinding if (rebind) { - nsCOMPtr oldBoundNode; - control->GetBoundNode(getter_AddRefs(oldBoundNode)); - rv = control->Bind(); + rv = control->Bind(&rebindChildren); NS_ENSURE_SUCCESS(rv, rv); - control->GetBoundNode(getter_AddRefs(boundNode)); - rebindChildren = (oldBoundNode != boundNode); } // Handle refreshing if (rebind || refresh) { 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. + // XXX: bug 336608: 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); } @@ -1341,13 +1327,17 @@ nsXFormsModelElement::Refresh() return NS_OK; } + // XXXbeaufour: Can we somehow suspend redraw / "screen update" while doing + // the refresh? That should save a lot of time, and avoid flickering of + // controls. + // 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; + mRebindAllControls = PR_FALSE; mMDG.ClearDispatchFlags(); return NS_OK; @@ -1407,6 +1397,11 @@ NS_IMETHODIMP nsXFormsModelElement::AddFormControl(nsIXFormsControl *aControl, nsIXFormsControl *aParent) { +#ifdef DEBUG_MODEL + printf("nsXFormsModelElement::AddFormControl(con: %p, parent: %p)\n", + (void*) aControl, (void*) aParent); +#endif + NS_ENSURE_ARG(aControl); return mFormControls.AddControl(aControl, aParent); } @@ -1414,6 +1409,11 @@ nsXFormsModelElement::AddFormControl(nsIXFormsControl *aControl, NS_IMETHODIMP nsXFormsModelElement::RemoveFormControl(nsIXFormsControl *aControl) { +#ifdef DEBUG_MODEL + printf("nsXFormsModelElement::RemoveFormControl(con: %p)\n", + (void*) aControl); +#endif + NS_ENSURE_ARG(aControl); PRBool removed; nsresult rv = mFormControls.RemoveControl(aControl, removed); @@ -2109,6 +2109,7 @@ nsXFormsModelElement::InitializeControls() nsXFormsControlListItem::iterator it; nsresult rv; + PRBool dummy; for (it = mFormControls.begin(); it != mFormControls.end(); ++it) { // Get control nsCOMPtr control = (*it)->Control(); @@ -2121,17 +2122,14 @@ nsXFormsModelElement::InitializeControls() // DBG_TAGINFO(controlElement); #endif // Rebind - rv = control->Bind(); - NS_ENSURE_SUCCESS(rv, rv); - - // Get bound node - nsCOMPtr boundNode; - rv = control->GetBoundNode(getter_AddRefs(boundNode)); + rv = control->Bind(&dummy); NS_ENSURE_SUCCESS(rv, rv); // Refresh controls rv = control->Refresh(); - NS_ENSURE_SUCCESS(rv, rv); + // XXX: Bug 336608, refresh still fails for some controls, for some + // reason. + // NS_ENSURE_SUCCESS(rv, rv); } mChangedNodes.Clear(); @@ -2176,13 +2174,6 @@ nsXFormsModelElement::MaybeNotifyCompletion() } } - // 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. @@ -2214,8 +2205,17 @@ nsXFormsModelElement::MaybeNotifyCompletion() } } + // Register deferred binds with the model. It does not bind the controls, + // only bind them to the model they belong to. nsXFormsModelElement::ProcessDeferredBinds(domDoc); + // Okay, dispatch xforms-model-construct-done + for (i = 0; i < models->Count(); ++i) { + nsXFormsModelElement *model = + NS_STATIC_CAST(nsXFormsModelElement *, models->ElementAt(i)); + nsXFormsUtils::DispatchEvent(model->mElement, eEvent_ModelConstructDone); + } + nsCOMPtr doc = do_QueryInterface(domDoc); if (doc) { PRUint32 loadingMessages = NS_PTR_TO_UINT32( @@ -2229,6 +2229,7 @@ nsXFormsModelElement::MaybeNotifyCompletion() } } + // Backup instances and fire xforms-ready for (i = 0; i < models->Count(); ++i) { nsXFormsModelElement *model = NS_STATIC_CAST(nsXFormsModelElement *, models->ElementAt(i)); @@ -2488,8 +2489,8 @@ nsXFormsModelElement::MessageLoadFinished() mElement->GetOwnerDocument(getter_AddRefs(domDoc)); const nsVoidArray *models = GetModelList(domDoc); nsCOMPtrdoc = do_QueryInterface(domDoc); - nsCOMArray *deferredBindList = - NS_STATIC_CAST(nsCOMArray *, + nsCOMArray *deferredBindList = + NS_STATIC_CAST(nsCOMArray *, doc->GetProperty(nsXFormsAtoms::deferredBindListProperty)); // if we've already gotten the xforms-model-construct-done event and not @@ -2554,8 +2555,8 @@ DeleteBindList(void *aObject, } /* static */ nsresult -nsXFormsModelElement::DeferElementBind(nsIDOMDocument *aDoc, - nsIXFormsControlBase *aControl) +nsXFormsModelElement::DeferElementBind(nsIDOMDocument *aDoc, + nsIXFormsControl *aControl) { nsCOMPtr doc = do_QueryInterface(aDoc); @@ -2570,7 +2571,7 @@ nsXFormsModelElement::DeferElementBind(nsIDOMDocument *aDoc, // We need to keep the document order of the controls AND don't want // to walk the deferredBindList every time we want to check about adding a // control. - nsCOMPtr controlBase(do_QueryInterface(aControl)); + nsCOMPtr controlBase(do_QueryInterface(aControl)); NS_ENSURE_STATE(controlBase); PRBool onList = PR_FALSE; @@ -2579,12 +2580,12 @@ nsXFormsModelElement::DeferElementBind(nsIDOMDocument *aDoc, return NS_OK; } - nsCOMArray *deferredBindList = - NS_STATIC_CAST(nsCOMArray *, + nsCOMArray *deferredBindList = + NS_STATIC_CAST(nsCOMArray *, doc->GetProperty(nsXFormsAtoms::deferredBindListProperty)); if (!deferredBindList) { - deferredBindList = new nsCOMArray(16); + deferredBindList = new nsCOMArray(16); NS_ENSURE_TRUE(deferredBindList, NS_ERROR_OUT_OF_MEMORY); doc->SetProperty(nsXFormsAtoms::deferredBindListProperty, deferredBindList, @@ -2604,6 +2605,10 @@ nsXFormsModelElement::DeferElementBind(nsIDOMDocument *aDoc, /* static */ void nsXFormsModelElement::ProcessDeferredBinds(nsIDOMDocument *aDoc) { +#ifdef DEBUG_MODEL + printf("nsXFormsModelElement::ProcessDeferredBinds()\n"); +#endif + nsCOMPtr doc = do_QueryInterface(aDoc); if (!doc) { @@ -2614,17 +2619,16 @@ nsXFormsModelElement::ProcessDeferredBinds(nsIDOMDocument *aDoc) doc->SetProperty(nsXFormsAtoms::readyForBindProperty, doc); - nsCOMArray *deferredBindList = - NS_STATIC_CAST(nsCOMArray *, + nsCOMArray *deferredBindList = + NS_STATIC_CAST(nsCOMArray *, 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(); - base->SetOnDeferredBindList(PR_FALSE); + for (PRInt32 i = 0; i < deferredBindList->Count(); ++i) { + nsIXFormsControl *control = deferredBindList->ObjectAt(i); + if (control) { + control->BindToModel(PR_FALSE); + control->SetOnDeferredBindList(PR_FALSE); } } diff --git a/extensions/xforms/nsXFormsModelElement.h b/extensions/xforms/nsXFormsModelElement.h index 959b35a5f13e..4dab1721f67f 100644 --- a/extensions/xforms/nsXFormsModelElement.h +++ b/extensions/xforms/nsXFormsModelElement.h @@ -248,7 +248,7 @@ public: NS_IMETHOD OnCreated(nsIXTFGenericElementWrapper *aWrapper); // nsIXFormsControlBase overrides - NS_IMETHOD Bind() { + NS_IMETHOD Bind(PRBool *aContextChanged) { // dummy method, so does nothing return NS_OK; }; @@ -275,7 +275,7 @@ public: * @param aControl XForms control waiting to be bound */ static NS_HIDDEN_(nsresult) DeferElementBind(nsIDOMDocument *aDoc, - nsIXFormsControlBase *aControl); + nsIXFormsControl *aControl); static nsresult NeedsPostRefresh(nsIXFormsControl* aControl); @@ -333,8 +333,11 @@ private: nsXFormsEvent aOnEvent); /** - * Call the Bind() and Refresh() on controls which was deferred because - * the model was not ready. + * Handle controls bindings which was deferred because the model was not + * ready. + * + * @note Only registers the controls with the model. Does not setup + * bindings, etc. * * @param aDoc Document that contains the XForms control */ @@ -397,7 +400,7 @@ private: * Indicates whether all controls should be refreshed on the next Refresh() * run. */ - PRPackedBool mNeedsRefresh; + PRPackedBool mRebindAllControls; /** * Indicates whether instance elements have been initialized diff --git a/extensions/xforms/nsXFormsOutputElement.cpp b/extensions/xforms/nsXFormsOutputElement.cpp index acab4e82a606..cdb3d80de979 100755 --- a/extensions/xforms/nsXFormsOutputElement.cpp +++ b/extensions/xforms/nsXFormsOutputElement.cpp @@ -77,7 +77,7 @@ class nsXFormsOutputElement : public nsXFormsDelegateStub { public: // nsIXFormsControl - NS_IMETHOD Bind(); + NS_IMETHOD Bind(PRBool* aContextChanged); NS_IMETHOD Refresh(); NS_IMETHOD GetBoundNode(nsIDOMNode **aBoundNode); @@ -105,12 +105,12 @@ nsXFormsOutputElement::nsXFormsOutputElement() // nsIXFormsControl nsresult -nsXFormsOutputElement::Bind() +nsXFormsOutputElement::Bind(PRBool *aContextChanged) { SetDOMStringToNull(mValue); mUseValueAttribute = PR_FALSE; - nsresult rv = nsXFormsDelegateStub::Bind(); + nsresult rv = nsXFormsDelegateStub::Bind(aContextChanged); NS_ENSURE_SUCCESS(rv, rv); if (!mHasParent || !mElement || rv == NS_OK_XFORMS_DEFERRED) diff --git a/extensions/xforms/nsXFormsRepeatElement.cpp b/extensions/xforms/nsXFormsRepeatElement.cpp index 159e4e1d5972..6ed0faa53d0e 100644 --- a/extensions/xforms/nsXFormsRepeatElement.cpp +++ b/extensions/xforms/nsXFormsRepeatElement.cpp @@ -192,9 +192,6 @@ class nsXFormsRepeatElement : public nsXFormsDelegateStub, public nsIXFormsRepeatElement { protected: - /** True while children are being added */ - PRBool mAddingChildren; - /** * The current repeat-index, 0 if no row is selected (can happen for nested * repeats) or there are no rows at all. @@ -218,11 +215,21 @@ protected: * ResetInnerRepeats(). */ PRUint32 mLevel; + + /** + * The number of "repeat rows" the repeat currently have unrolled. + */ + PRUint32 mCurrentRowCount; + /** + * True while children are being added + */ + PRPackedBool mAddingChildren; + /** * Are we a parent for nested repeats */ - PRBool mIsParent; + PRPackedBool mIsParent; /** * The currently selected repeat (nested repeats) @@ -288,11 +295,24 @@ protected: */ void SanitizeIndex(PRUint32 *aIndex, PRBool aIsScroll = PR_FALSE); + /** + * Unroll the template content into the visual rows by cloning template + * content and inserting into contextcontainers. + */ + nsresult UnrollRows(nsIDOMXPathResult *aNodeset); + /** * Returns either the anonymous content of the repeat or null; */ already_AddRefed GetAnonymousContent(); + /** + * Insert the template content for a repeat node into the given node. + * + * @param aNode The node to insert content into. + */ + nsresult InsertTemplateContent(nsIDOMNode *aNode); + public: NS_DECL_ISUPPORTS_INHERITED @@ -305,7 +325,7 @@ public: NS_IMETHOD DoneAddingChildren(); // nsIXFormsControl - NS_IMETHOD Bind(); + NS_IMETHOD Bind(PRBool *aContextChanged); NS_IMETHOD Refresh(); NS_IMETHOD TryFocus(PRBool* aOK); NS_IMETHOD IsEventTarget(PRBool *aOK); @@ -315,10 +335,11 @@ public: // nsXFormsRepeatElement nsXFormsRepeatElement() : - mAddingChildren(PR_FALSE), mCurrentIndex(0), mMaxIndex(0), mLevel(1), + mCurrentRowCount(0), + mAddingChildren(PR_FALSE), mIsParent(PR_FALSE) {} @@ -559,10 +580,17 @@ nsXFormsRepeatElement::IndexHasChanged() // they are rebound and refreshed(). nsCOMArray indexes(mIndexUsers); + PRBool contextChange; + nsresult rv; for (PRInt32 i = 0; i < indexes.Count(); ++i) { nsCOMPtr control = indexes[i]; - control->Bind(); - control->Refresh(); + rv = control->Bind(&contextChange); + NS_ENSURE_SUCCESS(rv, rv); + rv = control->Refresh(); + if (contextChange) { + // XXX: bug 335525 + NS_WARNING("Need to refresh children of index() user!\n"); + } } return NS_OK; @@ -677,52 +705,164 @@ nsXFormsRepeatElement::HandleNodeInsert(nsIDOMNode *aNode) // nsXFormsControl NS_IMETHODIMP -nsXFormsRepeatElement::Bind() +nsXFormsRepeatElement::Bind(PRBool *aContextChanged) { + NS_ENSURE_ARG(aContextChanged); + nsCOMPtr domDoc; mElement->GetOwnerDocument(getter_AddRefs(domDoc)); if (!nsXFormsUtils::IsDocumentReadyForBind(domDoc)) { nsXFormsModelElement::DeferElementBind(domDoc, this); + *aContextChanged = PR_FALSE; return NS_OK_XFORMS_DEFERRED; } - return BindToModel(PR_TRUE); + + nsresult rv = BindToModel(); + NS_ENSURE_SUCCESS(rv, rv); + + *aContextChanged = PR_TRUE; + return NS_OK; } -NS_IMETHODIMP -nsXFormsRepeatElement::Refresh() +nsresult +nsXFormsRepeatElement::InsertTemplateContent(nsIDOMNode *aNode) { - if (!mElement || mAddingChildren || mIsParent) { - return NS_OK; + NS_ENSURE_ARG(aNode); + + nsCOMPtr child; + nsresult rv = mElement->GetFirstChild(getter_AddRefs(child)); + NS_ENSURE_SUCCESS(rv, rv); + while (child) { + nsCOMPtr childClone; + rv = CloneNode(child, getter_AddRefs(childClone)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr newNode; + rv = aNode->AppendChild(childClone, getter_AddRefs(newNode)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = child->GetNextSibling(getter_AddRefs(newNode)); + NS_ENSURE_SUCCESS(rv, rv); + child = newNode; } + return NS_OK; +} + +nsresult +nsXFormsRepeatElement::UnrollRows(nsIDOMXPathResult *aNodeset) +{ + NS_ENSURE_STATE(mElement); + nsCOMPtr anon = GetAnonymousContent(); if (!anon) { return NS_OK; } - nsPostRefresh postRefresh = nsPostRefresh(); - nsresult rv; - - // Clear any existing children - nsCOMPtr cNode; - anon->GetFirstChild(getter_AddRefs(cNode)); - while (cNode) { - nsCOMPtr retNode; - anon->RemoveChild(cNode, getter_AddRefs(retNode)); - anon->GetFirstChild(getter_AddRefs(cNode)); + if (!aNodeset || !mModel) { + mMaxIndex = 0; + } else { + PRUint32 contextSize; + rv = aNodeset->GetSnapshotLength(&contextSize); + NS_ENSURE_SUCCESS(rv, rv); + mMaxIndex = contextSize; } - // Get the nodeset we are bound to - nsCOMPtr result; - nsCOMPtr model; - rv = ProcessNodeBinding(NS_LITERAL_STRING("nodeset"), - nsIDOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE, - getter_AddRefs(result), - getter_AddRefs(model)); + // STEP 1: Remove rows + nsCOMPtr tmp; + if (mMaxIndex < mCurrentRowCount) { + for (PRUint32 i = mMaxIndex; i < mCurrentRowCount; ++i) { + nsCOMPtr lastChild; + rv = anon->GetLastChild(getter_AddRefs(lastChild)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = anon->RemoveChild(lastChild, getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(rv, rv); + } + } else if (mMaxIndex > mCurrentRowCount) { + // STEP 2: Add rows + nsCOMPtr domDoc; + rv = anon->GetOwnerDocument(getter_AddRefs(domDoc)); + NS_ENSURE_SUCCESS(rv, rv); + for (PRUint32 i = mCurrentRowCount; i < mMaxIndex; ++i) { + // Create + nsCOMPtr container; + rv = domDoc->CreateElementNS(NS_LITERAL_STRING(NS_NAMESPACE_XFORMS), + NS_LITERAL_STRING("contextcontainer"), + getter_AddRefs(container)); + NS_ENSURE_SUCCESS(rv, rv); + + container->SetAttribute(NS_LITERAL_STRING("class"), + NS_LITERAL_STRING("xf-repeat-item")); - if (NS_FAILED(rv) | !result | !model) - return rv; + rv = anon->AppendChild(container, getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // Repeat has no content + if (!mMaxIndex) { + mCurrentRowCount = 0; + return NS_OK; + } + + // STEP 3: Update context on rows + nsCOMPtr child; + rv = anon->GetFirstChild(getter_AddRefs(child)); + NS_ENSURE_SUCCESS(rv, rv); + + for (PRUint32 i = 0; i < mMaxIndex; ++i) { + NS_ASSERTION(child, "Unrolled content does not match index size?!"); + // Get context node + nsCOMPtr contextNode; + rv = aNodeset->SnapshotItem(i, getter_AddRefs(contextNode)); + NS_ENSURE_SUCCESS(rv, rv); + + // Set context node, position, and size + nsCOMPtr childContext = do_QueryInterface(child); + NS_ASSERTION(childContext, + "content child not implementing nsIXFormsContextControl?!"); + + rv = childContext->SetContext(contextNode, i + 1, mMaxIndex); + NS_ENSURE_SUCCESS(rv, rv); + + // Next child + rv = child->GetNextSibling(getter_AddRefs(tmp)); + NS_ENSURE_SUCCESS(rv, rv); + tmp.swap(child); + } + + // Rows deleted, nothing more to do + if (mCurrentRowCount >= mMaxIndex) { + mCurrentRowCount = mMaxIndex; + return NS_OK; + } + + // STEP 4: Insert template content into newly created rows + nsCOMPtr containerList; + rv = anon->GetChildNodes(getter_AddRefs(containerList)); + NS_ENSURE_SUCCESS(rv, rv); + for (PRUint32 i = mCurrentRowCount; i < mMaxIndex; ++i) { + nsCOMPtr container; + rv = containerList->Item(i, getter_AddRefs(container)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = InsertTemplateContent(container); + NS_ENSURE_SUCCESS(rv, rv); + } + + mCurrentRowCount = mMaxIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsXFormsRepeatElement::Refresh() +{ + if (mAddingChildren || mIsParent) + return NS_OK; + + nsPostRefresh postRefresh = nsPostRefresh(); /// @todo The spec says: "This node-set must consist of contiguous child /// element nodes, with the same local name and namespace name of a common @@ -732,81 +872,20 @@ nsXFormsRepeatElement::Refresh() /// /// Can/should we check this somehow? (XXX) - PRUint32 contextSize; - rv = result->GetSnapshotLength(&contextSize); + + // Get the nodeset we are bound to + nsresult rv; + nsCOMPtr result; + rv = ProcessNodeBinding(NS_LITERAL_STRING("nodeset"), + nsIDOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE, + getter_AddRefs(result)); NS_ENSURE_SUCCESS(rv, rv); - if (!contextSize) - return NS_OK; - - // Get model ID - nsCOMPtr modelElement = do_QueryInterface(model); - NS_ENSURE_TRUE(modelElement, NS_ERROR_FAILURE); - nsAutoString modelID; - modelElement->GetAttribute(NS_LITERAL_STRING("id"), modelID); - - // Get DOM document - nsCOMPtr domDoc; - rv = mElement->GetOwnerDocument(getter_AddRefs(domDoc)); + // Unroll the repeat rows + rv = UnrollRows(result); NS_ENSURE_SUCCESS(rv, rv); - mMaxIndex = contextSize; - for (PRUint32 i = 1; i < mMaxIndex + 1; ++i) { - // Create - nsCOMPtr riElement; - rv = domDoc->CreateElementNS(NS_LITERAL_STRING(NS_NAMESPACE_XFORMS), - NS_LITERAL_STRING("contextcontainer"), - getter_AddRefs(riElement)); - NS_ENSURE_SUCCESS(rv, rv); - - riElement->SetAttribute(NS_LITERAL_STRING("class"), - NS_LITERAL_STRING("xf-repeat-item")); - - // Set model as attribute - if (!modelID.IsEmpty()) { - riElement->SetAttribute(NS_LITERAL_STRING("model"), modelID); - } - - // Get context node - nsCOMPtr riContext = do_QueryInterface(riElement); - NS_ENSURE_TRUE(riContext, NS_ERROR_FAILURE); - - nsCOMPtr contextNode; - rv = result->SnapshotItem(i - 1, getter_AddRefs(contextNode)); - NS_ENSURE_SUCCESS(rv, rv); - - // Set context node, position, and size - rv = riContext->SetContext(contextNode, i, contextSize); - NS_ENSURE_SUCCESS(rv, rv); - - // We need to insert the context node before adding the children, or the - // children will fail to set up their proper XForms context. - nsCOMPtr domNode; - rv = anon->AppendChild(riElement, getter_AddRefs(domNode)); - NS_ENSURE_SUCCESS(rv, rv); - - // Iterate over template children, clone them, and append them to - // \ - nsCOMPtr child; - rv = mElement->GetFirstChild(getter_AddRefs(child)); - NS_ENSURE_SUCCESS(rv, rv); - while (child) { - /// XXX the node probably refreshes itself twice here, once on cloning - /// and once when it's inserted ... that's not necessary. - nsCOMPtr childClone; - rv = CloneNode(child, getter_AddRefs(childClone)); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr newNode; - rv = riElement->AppendChild(childClone, getter_AddRefs(newNode)); - NS_ENSURE_SUCCESS(rv, rv); - - rv = child->GetNextSibling(getter_AddRefs(newNode)); - NS_ENSURE_SUCCESS(rv, rv); - child = newNode; - } - } - + // Maintain the index if (mCurrentIndex) { // somebody might have been fooling around with our children since last // refresh (either using delete or through script, so check the index