Cleanup formcalc method generation

This CL simplifies the FormCalc method call generation when converted to
JavaScript. Currently we output the same chunk of code to run the
given method on an array or object per method call. This CL pulls out
the common execution code to a pfm_method_runner function which is used
instead.

An embedder test has been added to verify that method invocation from
formcalc works correctly.

Bug: chromium:814848
Change-Id: I1ec052eab051053fedcb464d57e0e15228b8c5a2
Reviewed-on: https://pdfium-review.googlesource.com/32372
Reviewed-by: Henrique Nakashima <hnakashima@chromium.org>
Commit-Queue: dsinclair <dsinclair@chromium.org>
diff --git a/fxjs/cfxjse_formcalc_context_embeddertest.cpp b/fxjs/cfxjse_formcalc_context_embeddertest.cpp
index d48d5cc..a7efc14 100644
--- a/fxjs/cfxjse_formcalc_context_embeddertest.cpp
+++ b/fxjs/cfxjse_formcalc_context_embeddertest.cpp
@@ -1444,3 +1444,14 @@
     EXPECT_FALSE(ExecuteSilenceFailure(tests[i]));
   }
 }
+
+TEST_F(CFXJSE_FormCalcContextEmbedderTest, MethodCall) {
+  ASSERT_TRUE(OpenDocument("simple_xfa.pdf"));
+
+  const char test[] = {"$form.form1.TextField11.getAttribute(\"h\")"};
+  EXPECT_TRUE(Execute(test));
+
+  CFXJSE_Value* value = GetValue();
+  EXPECT_TRUE(value->IsString());
+  EXPECT_STREQ("12.7mm", value->ToString().c_str());
+}
diff --git a/testing/xfa_js_embedder_test.cpp b/testing/xfa_js_embedder_test.cpp
index fe54621..97009f3 100644
--- a/testing/xfa_js_embedder_test.cpp
+++ b/testing/xfa_js_embedder_test.cpp
@@ -76,7 +76,7 @@
 
   CFXJSE_Value msg(GetIsolate());
   value_->GetObjectPropertyByIdx(1, &msg);
-  fprintf(stderr, "JS: %.*s\n", static_cast<int>(input.GetLength()),
+  fprintf(stderr, "FormCalc: %.*s\n", static_cast<int>(input.GetLength()),
           input.unterminated_c_str());
   // If the parsing of the input fails, then v8 will not run, so there will be
   // no value here to print.
diff --git a/xfa/fxfa/fm2js/cxfa_fmexpression.cpp b/xfa/fxfa/fm2js/cxfa_fmexpression.cpp
index a3c65a2..ef2153d 100644
--- a/xfa/fxfa/fm2js/cxfa_fmexpression.cpp
+++ b/xfa/fxfa/fm2js/cxfa_fmexpression.cpp
@@ -89,6 +89,16 @@
   }
 
   js << L"(function() {\n";
+  js << L"let pfm_method_runner = function(obj, cb) {\n";
+  js << L"  if (pfm_rt.is_ary(obj)) {\n";
+  js << L"    let pfm_method_return = null;\n";
+  js << L"    for (var idx = obj.length -1; idx > 1; idx--) {\n";
+  js << L"      pfm_method_return = cb(obj[idx]);\n";
+  js << L"    }\n";
+  js << L"    return pfm_method_return;\n";
+  js << L"  }\n";
+  js << L"  return cb(obj);\n";
+  js << L"};\n";
   js << L"var pfm_ret = null;\n";
 
   for (const auto& expr : expressions_) {
@@ -100,7 +110,6 @@
 
   js << L"return pfm_rt.get_val(pfm_ret);\n";
   js << L"}).call(this);";
-
   return !CXFA_IsTooBig(js);
 }
 
diff --git a/xfa/fxfa/fm2js/cxfa_fmparser_unittest.cpp b/xfa/fxfa/fm2js/cxfa_fmparser_unittest.cpp
index 0dba59c..81fae08 100644
--- a/xfa/fxfa/fm2js/cxfa_fmparser_unittest.cpp
+++ b/xfa/fxfa/fm2js/cxfa_fmparser_unittest.cpp
@@ -42,6 +42,16 @@
 TEST(CXFA_FMParserTest, CommentThenValue) {
   const wchar_t ret[] =
       L"(function() {\n"
+      L"let pfm_method_runner = function(obj, cb) {\n"
+      L"  if (pfm_rt.is_ary(obj)) {\n"
+      L"    let pfm_method_return = null;\n"
+      L"    for (var idx = obj.length -1; idx > 1; idx--) {\n"
+      L"      pfm_method_return = cb(obj[idx]);\n"
+      L"    }\n"
+      L"    return pfm_method_return;\n"
+      L"  }\n"
+      L"  return cb(obj);\n"
+      L"};\n"
       L"var pfm_ret = null;\n"
       L"pfm_ret = 12;\n"
       L"return pfm_rt.get_val(pfm_ret);\n"
@@ -75,6 +85,16 @@
 
   const wchar_t ret[] =
       L"(function() {\n"
+      L"let pfm_method_runner = function(obj, cb) {\n"
+      L"  if (pfm_rt.is_ary(obj)) {\n"
+      L"    let pfm_method_return = null;\n"
+      L"    for (var idx = obj.length -1; idx > 1; idx--) {\n"
+      L"      pfm_method_return = cb(obj[idx]);\n"
+      L"    }\n"
+      L"    return pfm_method_return;\n"
+      L"  }\n"
+      L"  return cb(obj);\n"
+      L"};\n"
       L"var pfm_ret = null;\n"
       L"if (pfm_rt.is_obj(this))\n{\n"
       L"pfm_rt.asgn_val_op(this, pfm_rt.Avg(pfm_rt.neg_op(3), 5, "
@@ -159,6 +179,16 @@
 
   const wchar_t ret[] = {
       L"(function() {\n"
+      L"let pfm_method_runner = function(obj, cb) {\n"
+      L"  if (pfm_rt.is_ary(obj)) {\n"
+      L"    let pfm_method_return = null;\n"
+      L"    for (var idx = obj.length -1; idx > 1; idx--) {\n"
+      L"      pfm_method_return = cb(obj[idx]);\n"
+      L"    }\n"
+      L"    return pfm_method_return;\n"
+      L"  }\n"
+      L"  return cb(obj);\n"
+      L"};\n"
       L"var pfm_ret = null;\n"
       L"function MyFunction(param1, param2) {\n"
       L"var pfm_ret = null;\n"
@@ -187,6 +217,16 @@
 
   const wchar_t ret[] = {
       L"(function() {\n"
+      L"let pfm_method_runner = function(obj, cb) {\n"
+      L"  if (pfm_rt.is_ary(obj)) {\n"
+      L"    let pfm_method_return = null;\n"
+      L"    for (var idx = obj.length -1; idx > 1; idx--) {\n"
+      L"      pfm_method_return = cb(obj[idx]);\n"
+      L"    }\n"
+      L"    return pfm_method_return;\n"
+      L"  }\n"
+      L"  return cb(obj);\n"
+      L"};\n"
       L"var pfm_ret = null;\n"
       L"function MyFunction() {\n"
       L"var pfm_ret = null;\n"
@@ -262,20 +302,22 @@
   const wchar_t input[] = {L"i.f(O)"};
   const wchar_t ret[] = {
       L"(function() {\n"
-      L"var pfm_ret = null;\n"
-      L"pfm_ret = pfm_rt.get_val((function () {\n"
-      L"let pfm_cb = function(obj) {\n"
-      L"return obj.f(pfm_rt.get_val(O));\n"
+      L"let pfm_method_runner = function(obj, cb) {\n"
+      L"  if (pfm_rt.is_ary(obj)) {\n"
+      L"    let pfm_method_return = null;\n"
+      L"    for (var idx = obj.length -1; idx > 1; idx--) {\n"
+      L"      pfm_method_return = cb(obj[idx]);\n"
+      L"    }\n"
+      L"    return pfm_method_return;\n"
+      L"  }\n"
+      L"  return cb(obj);\n"
       L"};\n"
-      L"if (pfm_rt.is_ary(i)) {\n"
-      L"let method_return_value = null;\n"
-      L"for (var index = i.length - 1; index > 1; index--) {\n"
-      L"method_return_value = pfm_cb(i[index]);\n"
-      L"}\n"
-      L"return method_return_value;\n"
-      L"} else {\n"
-      L"return pfm_cb(i);\n"
-      L"}}).call(this));\n"
+      L"var pfm_ret = null;\n"
+      L"pfm_ret = pfm_rt.get_val((function() {\n"
+      L"  return pfm_method_runner(i, function(obj) {\n"
+      L"    return obj.f(pfm_rt.get_val(O));\n"
+      L"  });\n"
+      L"}).call(this));\n"
       L"return pfm_rt.get_val(pfm_ret);\n"
       L"}).call(this);"};
 
@@ -293,44 +335,30 @@
   const wchar_t input[] = {L"i.f(O.e(O.e(O)))"};
   const wchar_t ret[] = {
       L"(function() {\n"
+      L"let pfm_method_runner = function(obj, cb) {\n"
+      L"  if (pfm_rt.is_ary(obj)) {\n"
+      L"    let pfm_method_return = null;\n"
+      L"    for (var idx = obj.length -1; idx > 1; idx--) {\n"
+      L"      pfm_method_return = cb(obj[idx]);\n"
+      L"    }\n"
+      L"    return pfm_method_return;\n"
+      L"  }\n"
+      L"  return cb(obj);\n"
+      L"};\n"
       L"var pfm_ret = null;\n"
-      L"pfm_ret = pfm_rt.get_val((function () {\n"
-      L"let pfm_cb = function(obj) {\n"
-      L"return obj.f(pfm_rt.get_val((function () {\n"
-      L"let pfm_cb = function(obj) {\n"
-      L"return obj.e(pfm_rt.get_val((function () {\n"
-      L"let pfm_cb = function(obj) {\n"
-      L"return obj.e(pfm_rt.get_val(O));\n"
-      L"};\n"
-      L"if (pfm_rt.is_ary(O)) {\n"
-      L"let method_return_value = null;\n"
-      L"for (var index = O.length - 1; index > 1; index--) {\n"
-      L"method_return_value = pfm_cb(O[index]);\n"
-      L"}\n"
-      L"return method_return_value;\n"
-      L"} else {\n"
-      L"return pfm_cb(O);\n"
-      L"}}).call(this)));\n"
-      L"};\n"
-      L"if (pfm_rt.is_ary(O)) {\n"
-      L"let method_return_value = null;\n"
-      L"for (var index = O.length - 1; index > 1; index--) {\n"
-      L"method_return_value = pfm_cb(O[index]);\n"
-      L"}\n"
-      L"return method_return_value;\n"
-      L"} else {\n"
-      L"return pfm_cb(O);\n"
-      L"}}).call(this)));\n"
-      L"};\n"
-      L"if (pfm_rt.is_ary(i)) {\n"
-      L"let method_return_value = null;\n"
-      L"for (var index = i.length - 1; index > 1; index--) {\n"
-      L"method_return_value = pfm_cb(i[index]);\n"
-      L"}\n"
-      L"return method_return_value;\n"
-      L"} else {\n"
-      L"return pfm_cb(i);\n"
-      L"}}).call(this));\n"
+      L"pfm_ret = pfm_rt.get_val((function() {\n"
+      L"  return pfm_method_runner(i, function(obj) {\n"
+      L"    return obj.f(pfm_rt.get_val((function() {\n"
+      L"  return pfm_method_runner(O, function(obj) {\n"
+      L"    return obj.e(pfm_rt.get_val((function() {\n"
+      L"  return pfm_method_runner(O, function(obj) {\n"
+      L"    return obj.e(pfm_rt.get_val(O));\n"
+      L"  });\n"
+      L"}).call(this)));\n"
+      L"  });\n"
+      L"}).call(this)));\n"
+      L"  });\n"
+      L"}).call(this));\n"
       L"return pfm_rt.get_val(pfm_ret);\n"
       L"}).call(this);"};
 
diff --git a/xfa/fxfa/fm2js/cxfa_fmsimpleexpression.cpp b/xfa/fxfa/fm2js/cxfa_fmsimpleexpression.cpp
index 0698993..e34d4ba 100644
--- a/xfa/fxfa/fm2js/cxfa_fmsimpleexpression.cpp
+++ b/xfa/fxfa/fm2js/cxfa_fmsimpleexpression.cpp
@@ -676,22 +676,14 @@
   if (!m_pExp1->ToJavaScript(buf, ReturnType::kInfered))
     return false;
 
-  js << L"(function () {\n";
-  js << L"let pfm_cb = function(obj) {\n";
-  js << L"return obj.";
+  js << L"(function() {\n";
+  js << L"  return pfm_method_runner(" << buf << L", function(obj) {\n";
+  js << L"    return obj.";
   if (!m_pExp2->ToJavaScript(js, ReturnType::kInfered))
     return false;
   js << L";\n";
-  js << L"};\n";
-  js << L"if (pfm_rt.is_ary(" << buf << L")) {\n";
-  js << L"let method_return_value = null;\n";
-  js << L"for (var index = " << buf << L".length - 1; index > 1; index--) {\n";
-  js << L"method_return_value = pfm_cb(" << buf << L"[index]);\n";
-  js << L"}\n";
-  js << L"return method_return_value;\n";
-  js << L"} else {\n";
-  js << L"return pfm_cb(" << buf << L");\n";
-  js << L"}}).call(this)";
+  js << L"  });\n";
+  js << L"}).call(this)";
   return !CXFA_IsTooBig(js);
 }