Merge to XFA: Use JS_ExpandKeywordParams() in app.response()

Original Review URL: https://codereview.chromium.org/1654523002 .
(cherry picked from commit 3859258ebe9e81e1f766b57e7f78c786ae4ed495)

TBR=thestig@chromium.org

Review URL: https://codereview.chromium.org/1658753002 .
diff --git a/fpdfsdk/src/javascript/app.cpp b/fpdfsdk/src/javascript/app.cpp
index 057bf7e..3a971cf 100644
--- a/fpdfsdk/src/javascript/app.cpp
+++ b/fpdfsdk/src/javascript/app.cpp
@@ -690,77 +690,50 @@
                       const std::vector<CJS_Value>& params,
                       CJS_Value& vRet,
                       CFX_WideString& sError) {
-  CFX_WideString swQuestion = L"";
-  CFX_WideString swLabel = L"";
-  CFX_WideString swTitle = L"PDF";
-  CFX_WideString swDefault = L"";
-  bool bPassWord = false;
-
+  CJS_Context* pContext = static_cast<CJS_Context*>(cc);
   CJS_Runtime* pRuntime = CJS_Runtime::FromContext(cc);
-  v8::Isolate* isolate = pRuntime->GetIsolate();
+  std::vector<CJS_Value> newParams =
+      JS_ExpandKeywordParams(pRuntime, params, 5, L"cQuestion", L"cTitle",
+                             L"cDefault", L"bPassword", L"cLabel");
 
-  int iLength = params.size();
-  if (iLength > 0 && params[0].GetType() == CJS_Value::VT_object) {
-    v8::Local<v8::Object> pObj = params[0].ToV8Object();
-    v8::Local<v8::Value> pValue =
-        FXJS_GetObjectElement(isolate, pObj, L"cQuestion");
-    swQuestion =
-        CJS_Value(pRuntime, pValue, GET_VALUE_TYPE(pValue)).ToCFXWideString();
-
-    pValue = FXJS_GetObjectElement(isolate, pObj, L"cTitle");
-    swTitle =
-        CJS_Value(pRuntime, pValue, GET_VALUE_TYPE(pValue)).ToCFXWideString();
-
-    pValue = FXJS_GetObjectElement(isolate, pObj, L"cDefault");
-    swDefault =
-        CJS_Value(pRuntime, pValue, GET_VALUE_TYPE(pValue)).ToCFXWideString();
-
-    pValue = FXJS_GetObjectElement(isolate, pObj, L"cLabel");
-    swLabel =
-        CJS_Value(pRuntime, pValue, GET_VALUE_TYPE(pValue)).ToCFXWideString();
-
-    pValue = FXJS_GetObjectElement(isolate, pObj, L"bPassword");
-    bPassWord = CJS_Value(pRuntime, pValue, GET_VALUE_TYPE(pValue)).ToBool();
-  } else {
-    switch (iLength) {
-      case 5:
-        swLabel = params[4].ToCFXWideString();
-      // FALLTHROUGH
-      case 4:
-        bPassWord = params[3].ToBool();
-      // FALLTHROUGH
-      case 3:
-        swDefault = params[2].ToCFXWideString();
-      // FALLTHROUGH
-      case 2:
-        swTitle = params[1].ToCFXWideString();
-      // FALLTHROUGH
-      case 1:
-        swQuestion = params[0].ToCFXWideString();
-      // FALLTHROUGH
-      default:
-        break;
-    }
+  if (newParams[0].GetType() == CJS_Value::VT_unknown) {
+    sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAMERROR);
+    return FALSE;
   }
+  CFX_WideString swQuestion = newParams[0].ToCFXWideString();
 
-  CJS_Context* pContext = (CJS_Context*)cc;
-  CPDFDoc_Environment* pApp = pContext->GetReaderApp();
+  CFX_WideString swTitle = L"PDF";
+  if (newParams[1].GetType() != CJS_Value::VT_unknown)
+    swTitle = newParams[1].ToCFXWideString();
+
+  CFX_WideString swDefault;
+  if (newParams[2].GetType() != CJS_Value::VT_unknown)
+    swDefault = newParams[2].ToCFXWideString();
+
+  bool bPassword = false;
+  if (newParams[3].GetType() != CJS_Value::VT_unknown)
+    bPassword = newParams[3].ToBool();
+
+  CFX_WideString swLabel;
+  if (newParams[4].GetType() != CJS_Value::VT_unknown)
+    swLabel = newParams[4].ToCFXWideString();
 
   const int MAX_INPUT_BYTES = 2048;
   std::unique_ptr<char[]> pBuff(new char[MAX_INPUT_BYTES + 2]);
   memset(pBuff.get(), 0, MAX_INPUT_BYTES + 2);
-  int nLengthBytes = pApp->JS_appResponse(
+
+  int nLengthBytes = pContext->GetReaderApp()->JS_appResponse(
       swQuestion.c_str(), swTitle.c_str(), swDefault.c_str(), swLabel.c_str(),
-      bPassWord, pBuff.get(), MAX_INPUT_BYTES);
-  if (nLengthBytes <= 0) {
-    vRet.SetNull();
+      bPassword, pBuff.get(), MAX_INPUT_BYTES);
+
+  if (nLengthBytes < 0 || nLengthBytes > MAX_INPUT_BYTES) {
+    sError = JSGetStringFromID(pContext, IDS_STRING_JSPARAM_TOOLONG);
     return FALSE;
   }
-  nLengthBytes = std::min(nLengthBytes, MAX_INPUT_BYTES);
 
-  CFX_WideString ret_string = CFX_WideString::FromUTF16LE(
-      (unsigned short*)pBuff.get(), nLengthBytes / sizeof(unsigned short));
-  vRet = ret_string.c_str();
+  vRet = CFX_WideString::FromUTF16LE(reinterpret_cast<uint16_t*>(pBuff.get()),
+                                     nLengthBytes / sizeof(uint16_t))
+             .c_str();
   return TRUE;
 }
 
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index e3e28a5..11afc2f 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -195,6 +195,9 @@
 }
 #endif
 
+// These example JS platform callback handlers are entirely optional,
+// and exist here to show the flow of information from a document back
+// to the embedder.
 int ExampleAppAlert(IPDF_JSPLATFORM*,
                     FPDF_WIDESTRING msg,
                     FPDF_WIDESTRING title,
@@ -207,6 +210,29 @@
   return 0;
 }
 
+int ExampleAppResponse(IPDF_JSPLATFORM*,
+                       FPDF_WIDESTRING question,
+                       FPDF_WIDESTRING title,
+                       FPDF_WIDESTRING defaultValue,
+                       FPDF_WIDESTRING label,
+                       FPDF_BOOL isPassword,
+                       void* response,
+                       int length) {
+  printf("%ls: %ls, defaultValue=%ls, label=%ls, isPassword=%d, length=%d\n",
+         GetPlatformWString(title).c_str(),
+         GetPlatformWString(question).c_str(),
+         GetPlatformWString(defaultValue).c_str(),
+         GetPlatformWString(label).c_str(), isPassword, length);
+
+  // UTF-16, always LE regardless of platform.
+  uint8_t* ptr = static_cast<uint8_t*>(response);
+  ptr[0] = 'N';
+  ptr[1] = 0;
+  ptr[2] = 'o';
+  ptr[3] = 0;
+  return 4;
+}
+
 void ExampleDocGotoPage(IPDF_JSPLATFORM*, int pageNumber) {
   printf("Goto Page: %d\n", pageNumber);
 }
@@ -425,6 +451,7 @@
   memset(&platform_callbacks, '\0', sizeof(platform_callbacks));
   platform_callbacks.version = 3;
   platform_callbacks.app_alert = ExampleAppAlert;
+  platform_callbacks.app_response = ExampleAppResponse;
   platform_callbacks.Doc_gotoPage = ExampleDocGotoPage;
   platform_callbacks.Doc_mail = ExampleDocMail;
 
diff --git a/testing/resources/javascript/app_repsonse.in b/testing/resources/javascript/app_repsonse.in
new file mode 100644
index 0000000..bd78da3
--- /dev/null
+++ b/testing/resources/javascript/app_repsonse.in
@@ -0,0 +1,75 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /OpenAction 10 0 R
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [
+    3 0 R
+  ]
+>>
+endobj
+% Page number 0.
+{{object 3 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /Resources <<
+    /Font <</F1 15 0 R>>
+  >>
+  /Contents [21 0 R]
+  /MediaBox [0 0 612 792]
+>>
+% OpenAction action
+{{object 10 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS 11 0 R
+>>
+endobj
+% JS program to exexute
+{{object 11 0}} <<
+>>
+stream
+var result;
+try {
+  result = app.response("question");
+  app.alert("result: " + result);
+  result = app.response("question", "title", "default", true, "label");
+  app.alert("result: " + result);
+  result = app.response({"cQuestion": "question"});
+  app.alert("result: " + result);
+  result = app.response({
+    "cQuestion": "question",
+    "cTitle": "title",
+    "cDefault": "default",
+    "bPassword": true,
+    "cLabel": "label"
+  });
+  app.alert("result: " + result);
+} catch (e) {
+  app.alert("unexpected error " + e);
+}
+try {
+  app.response();
+  app.alert("unexpected success");
+} catch (e) {
+  app.alert("Caught expected error " + e);
+}
+try {
+  app.response({});
+  app.alert("unexpected success");
+} catch (e) {
+  app.alert("Caught expected error " + e);
+}
+endstream
+endobj
+{{xref}}
+trailer <<
+  /Root 1 0 R
+>>
+{{startxref}}
+%%EOF
diff --git a/testing/resources/javascript/app_repsonse_expected.txt b/testing/resources/javascript/app_repsonse_expected.txt
new file mode 100644
index 0000000..8a29959
--- /dev/null
+++ b/testing/resources/javascript/app_repsonse_expected.txt
@@ -0,0 +1,10 @@
+PDF: question, defaultValue=, label=, isPassword=0, length=2048
+Alert: result: No
+title: question, defaultValue=default, label=label, isPassword=1, length=2048
+Alert: result: No
+PDF: question, defaultValue=, label=, isPassword=0, length=2048
+Alert: result: No
+title: question, defaultValue=default, label=label, isPassword=1, length=2048
+Alert: result: No
+Alert: Caught expected error app.response: Incorrect number of parameters passed to function.
+Alert: Caught expected error app.response: Incorrect number of parameters passed to function.