Fix endless revalidation in when validation makes identical changes.

As seen in popular NFFBAR.pdf form.

-- Short-circuit update where possible.
-- Remove bNotify parameter from notifiers and instead do not
   call them if notification is not intended.

Change-Id: Ic3e41a7f4f49e0dff94b81e5a475132e985f2eb6
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/78330
Reviewed-by: Daniel Hosseinian <dhoss@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
diff --git a/fxjs/xfa/cjx_object.cpp b/fxjs/xfa/cjx_object.cpp
index dec8d87..7e7dfd8 100644
--- a/fxjs/xfa/cjx_object.cpp
+++ b/fxjs/xfa/cjx_object.cpp
@@ -371,10 +371,14 @@
 void CJX_Object::SetMeasure(XFA_Attribute eAttr,
                             const CXFA_Measurement& mValue,
                             bool bNotify) {
+  // Can't short-circuit update here when the value is the same since it
+  // might have come from further up the chain from where we are setting it.
   uint32_t key = GetMapKey_Element(GetXFAObject()->GetElementType(), eAttr);
-  OnChanging(eAttr, bNotify);
+  if (bNotify)
+    OnChanging(eAttr);
   SetMapModuleMeasurement(key, mValue);
-  OnChanged(eAttr, bNotify, false);
+  if (bNotify)
+    OnChanged(eAttr, false);
 }
 
 Optional<CXFA_Measurement> CJX_Object::TryMeasure(XFA_Attribute eAttr,
@@ -418,11 +422,16 @@
                               bool bScriptModify) {
   CXFA_Node* xfaObj = GetXFANode();
   uint32_t key = GetMapKey_Element(xfaObj->GetElementType(), eAttr);
-  OnChanging(eAttr, bNotify);
-  SetMapModuleString(key, wsValue);
-  if (eAttr == XFA_Attribute::Name)
-    xfaObj->UpdateNameHash();
-  OnChanged(eAttr, bNotify, bScriptModify);
+  Optional<WideString> old_value = GetMapModuleString(key);
+  if (!old_value.has_value() || old_value.value() != wsValue) {
+    if (bNotify)
+      OnChanging(eAttr);
+    SetMapModuleString(key, wsValue);
+    if (eAttr == XFA_Attribute::Name)
+      xfaObj->UpdateNameHash();
+    if (bNotify)
+      OnChanged(eAttr, bScriptModify);
+  }
 
   if (!xfaObj->IsNeedSavingXMLNode() || eAttr == XFA_Attribute::QualifiedName ||
       eAttr == XFA_Attribute::BindingNode) {
@@ -461,12 +470,16 @@
   auto* xfaObj = GetXFANode();
   uint32_t key =
       GetMapKey_Element(xfaObj->GetElementType(), XFA_Attribute::Value);
-
-  OnChanging(XFA_Attribute::Value, bNotify);
-  SetMapModuleString(key, wsValue);
-  OnChanged(XFA_Attribute::Value, bNotify, bScriptModify);
-  if (xfaObj->IsNeedSavingXMLNode())
-    xfaObj->SetToXML(wsXMLValue);
+  Optional<WideString> old_value = GetMapModuleString(key);
+  if (!old_value.has_value() || old_value.value() != wsValue) {
+    if (bNotify)
+      OnChanging(XFA_Attribute::Value);
+    SetMapModuleString(key, wsValue);
+    if (bNotify)
+      OnChanged(XFA_Attribute::Value, bScriptModify);
+    if (xfaObj->IsNeedSavingXMLNode())
+      xfaObj->SetToXML(wsXMLValue);
+  }
 }
 
 Optional<WideString> CJX_Object::TryCData(XFA_Attribute eAttr,
@@ -478,6 +491,7 @@
 
   if (!bUseDefault)
     return pdfium::nullopt;
+
   return GetXFANode()->GetDefaultCData(eAttr);
 }
 
@@ -485,10 +499,14 @@
                                      int32_t value,
                                      bool bNotify) {
   uint32_t key = GetMapKey_Element(GetXFAObject()->GetElementType(), eAttr);
-  OnChanging(eAttr, bNotify);
-  SetMapModuleValue(key, value);
-  OnChanged(eAttr, bNotify, false);
-
+  Optional<int32_t> old_value = GetMapModuleValue(key);
+  if (!old_value.has_value() || old_value.value() != value) {
+    if (bNotify)
+      OnChanging(eAttr);
+    SetMapModuleValue(key, value);
+    if (bNotify)
+      OnChanged(eAttr, false);
+  }
   CXFA_Node* pNode = GetXFANode();
   return pNode->IsNeedSavingXMLNode() ? ToXMLElement(pNode->GetXMLMappingNode())
                                       : nullptr;
@@ -915,20 +933,23 @@
   ToNode(pSrcObj)->JSObject()->MoveBufferMapData(pDstObj);
 }
 
-void CJX_Object::OnChanging(XFA_Attribute eAttr, bool bNotify) {
-  if (!bNotify || !GetXFANode()->IsInitialized())
+void CJX_Object::OnChanging(XFA_Attribute eAttr) {
+  if (!GetXFANode()->IsInitialized())
     return;
 
   CXFA_FFNotify* pNotify = GetDocument()->GetNotify();
-  if (pNotify)
-    pNotify->OnValueChanging(GetXFANode(), eAttr);
+  if (!pNotify)
+    return;
+
+  pNotify->OnValueChanging(GetXFANode(), eAttr);
 }
 
 void CJX_Object::OnChanged(XFA_Attribute eAttr,
-                           bool bNotify,
                            bool bScriptModify) {
-  if (bNotify && GetXFANode()->IsInitialized())
-    GetXFANode()->SendAttributeChangeMessage(eAttr, bScriptModify);
+  if (!GetXFANode()->IsInitialized())
+    return;
+
+  GetXFANode()->SendAttributeChangeMessage(eAttr, bScriptModify);
 }
 
 CJX_Object::CalcData* CJX_Object::GetOrCreateCalcData(cppgc::Heap* heap) {
diff --git a/fxjs/xfa/cjx_object.h b/fxjs/xfa/cjx_object.h
index d6e1872..b2a7ca9 100644
--- a/fxjs/xfa/cjx_object.h
+++ b/fxjs/xfa/cjx_object.h
@@ -248,8 +248,8 @@
                                                      XFA_Element eType) const;
   CXFA_Node* GetOrCreatePropertyInternal(int32_t index, XFA_Element eType);
 
-  void OnChanged(XFA_Attribute eAttr, bool bNotify, bool bScriptModify);
-  void OnChanging(XFA_Attribute eAttr, bool bNotify);
+  void OnChanging(XFA_Attribute eAttr);
+  void OnChanged(XFA_Attribute eAttr, bool bScriptModify);
 
   // Returns a pointer to the XML node that needs to be updated with the new
   // attribute value. |nullptr| if no update is needed.