Automated test case for 487928.

Reproducing this bug requires the embedder to fire timers, something the
single-pass pdfium-test binary doesn't do properly at the present. So
we modify the embedder test delegate to allow the immediate triggering
of the same.

Perform some cleanup along the way by removing EmbedderTestDefaultDelegate
-- it buys us nothing over the the no-op one.

And, of course, v8 initialization is busted again, and we need v8 here.

R=thestig@chromium.org

Review URL: https://codereview.chromium.org/1153213004
diff --git a/BUILD.gn b/BUILD.gn
index 4074c75..bab57b9 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -886,15 +886,21 @@
     "testing/embedder_test.cpp",
     "testing/embedder_test.h",
     "testing/embedder_test_mock_delegate.h",
+    "testing/embedder_test_timer_handling_delegate.h",
     "testing/fx_string_testhelpers.cpp",
     "testing/fx_string_testhelpers.h",
   ]
   deps = [
     "//testing/gmock",
     "//testing/gtest",
+    "//v8:v8_libplatform",
     ":pdfium",
   ]
-  include_dirs = [ "." ]
+  include_dirs = [
+    "//v8",
+    "//v8/include",
+    "."
+  ]
   configs -= [ "//build/config/compiler:chromium_code" ]
   configs += [
     ":pdfium_config",
diff --git a/fpdfsdk/src/fpdfformfill_embeddertest.cpp b/fpdfsdk/src/fpdfformfill_embeddertest.cpp
index b4cc111..615ada9 100644
--- a/fpdfsdk/src/fpdfformfill_embeddertest.cpp
+++ b/fpdfsdk/src/fpdfformfill_embeddertest.cpp
@@ -6,6 +6,7 @@
 #include "../../public/fpdf_formfill.h"
 #include "../../testing/embedder_test.h"
 #include "../../testing/embedder_test_mock_delegate.h"
+#include "../../testing/embedder_test_timer_handling_delegate.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
@@ -19,6 +20,8 @@
   EmbedderTestMockDelegate mock;
   EXPECT_CALL(mock, Alert(_, _, _, _)).Times(0);
   EXPECT_CALL(mock, UnsupportedHandler(_)).Times(0);
+  EXPECT_CALL(mock, SetTimer(_, _)).Times(0);
+  EXPECT_CALL(mock, KillTimer(_)).Times(0);
   SetDelegate(&mock);
 
   EXPECT_TRUE(OpenDocument("testing/resources/hello_world.pdf"));
@@ -26,3 +29,15 @@
   EXPECT_NE(nullptr, page);
   UnloadPage(page);
 }
+
+TEST_F(FPDFFormFillEmbeddertest, BUG_487928) {
+  EmbedderTestTimerHandlingDelegate delegate;
+  SetDelegate(&delegate);
+
+  EXPECT_TRUE(OpenDocument("testing/resources/bug_487928.pdf"));
+  FPDF_PAGE page = LoadPage(0);
+  EXPECT_NE(nullptr, page);
+  DoOpenActions();
+  delegate.AdvanceTime(5000);
+  UnloadPage(page);
+}
diff --git a/pdfium.gyp b/pdfium.gyp
index 7f1d908..c8215e7 100644
--- a/pdfium.gyp
+++ b/pdfium.gyp
@@ -852,10 +852,12 @@
       'dependencies': [
         '<(DEPTH)/testing/gmock.gyp:gmock',
         '<(DEPTH)/testing/gtest.gyp:gtest',
+        '<(DEPTH)/v8/tools/gyp/v8.gyp:v8_libplatform',
         'pdfium',
       ],
       'include_dirs': [
-        '<(DEPTH)'
+        '<(DEPTH)',
+        '<(DEPTH)/v8',
       ],
       'sources': [
         'core/src/fpdfapi/fpdf_parser/fpdf_parser_decode_embeddertest.cpp',
@@ -870,6 +872,7 @@
         'testing/embedder_test.cpp',
         'testing/embedder_test.h',
         'testing/embedder_test_mock_delegate.h',
+        'testing/embedder_test_timer_handling_delegate.h',
         'testing/fx_string_testhelpers.cpp',
         'testing/fx_string_testhelpers.h',
       ],
diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp
index 70c32af..8457a79 100644
--- a/testing/embedder_test.cpp
+++ b/testing/embedder_test.cpp
@@ -18,6 +18,7 @@
 #include "../public/fpdf_text.h"
 #include "../public/fpdfview.h"
 #include "testing/gmock/include/gmock/gmock.h"
+#include "v8/include/libplatform/libplatform.h"
 #include "v8/include/v8.h"
 
 #ifdef _WIN32
@@ -94,58 +95,6 @@
 
 }  // namespace
 
-class EmbedderTestDefaultDelegate : public EmbedderTest::Delegate {
- public:
-  int Alert(FPDF_WIDESTRING, FPDF_WIDESTRING, int, int) override {
-    printf("Form_Alert called.\n");
-    return 0;
-  }
-
-  void UnsupportedHandler(int type) {
-    std::string feature = "Unknown";
-    switch (type) {
-      case FPDF_UNSP_DOC_XFAFORM:
-        feature = "XFA";
-        break;
-      case FPDF_UNSP_DOC_PORTABLECOLLECTION:
-        feature = "Portfolios_Packages";
-        break;
-      case FPDF_UNSP_DOC_ATTACHMENT:
-      case FPDF_UNSP_ANNOT_ATTACHMENT:
-        feature = "Attachment";
-        break;
-      case FPDF_UNSP_DOC_SECURITY:
-        feature = "Rights_Management";
-        break;
-      case FPDF_UNSP_DOC_SHAREDREVIEW:
-        feature = "Shared_Review";
-        break;
-      case FPDF_UNSP_DOC_SHAREDFORM_ACROBAT:
-      case FPDF_UNSP_DOC_SHAREDFORM_FILESYSTEM:
-      case FPDF_UNSP_DOC_SHAREDFORM_EMAIL:
-        feature = "Shared_Form";
-        break;
-      case FPDF_UNSP_ANNOT_3DANNOT:
-        feature = "3D";
-        break;
-      case FPDF_UNSP_ANNOT_MOVIE:
-        feature = "Movie";
-        break;
-      case FPDF_UNSP_ANNOT_SOUND:
-        feature = "Sound";
-        break;
-      case FPDF_UNSP_ANNOT_SCREEN_MEDIA:
-      case FPDF_UNSP_ANNOT_SCREEN_RICHMEDIA:
-        feature = "Screen";
-        break;
-      case FPDF_UNSP_ANNOT_SIG:
-        feature = "Digital_Signature";
-        break;
-    }
-    printf("Unsupported feature: %s.\n", feature.c_str());
-  }
-};
-
 class TestLoader {
  public:
   TestLoader(const char* pBuf, size_t len);
@@ -183,7 +132,7 @@
   memset(&hints_, 0, sizeof(hints_));
   memset(&file_access_, 0, sizeof(file_access_));
   memset(&file_avail_, 0, sizeof(file_avail_));
-  default_delegate_ = new EmbedderTestDefaultDelegate();
+  default_delegate_ = new EmbedderTest::Delegate();
   delegate_ = default_delegate_;
 }
 
@@ -193,9 +142,15 @@
 
 void EmbedderTest::SetUp() {
     v8::V8::InitializeICU();
-    // By enabling predicatble mode, V8 won't post any background tasks.
+
+    platform_ = v8::platform::CreateDefaultPlatform();
+    v8::V8::InitializePlatform(platform_);
+    v8::V8::Initialize();
+
+    // By enabling predictable mode, V8 won't post any background tasks.
     const char predictable_flag[] = "--predictable";
-    v8::V8::SetFlagsFromString(predictable_flag, strlen(predictable_flag));
+    v8::V8::SetFlagsFromString(predictable_flag,
+                               static_cast<int>(strlen(predictable_flag)));
 
 #ifdef V8_USE_EXTERNAL_STARTUP_DATA
     ASSERT_TRUE(GetExternalData(g_exe_path_, "natives_blob.bin", &natives_));
@@ -221,6 +176,8 @@
   }
   FPDFAvail_Destroy(avail_);
   FPDF_DestroyLibrary();
+  v8::V8::ShutdownPlatform();
+  delete platform_;
   delete loader_;
   free(file_contents_);
 }
@@ -262,6 +219,8 @@
   FPDF_FORMFILLINFO* formfillinfo = static_cast<FPDF_FORMFILLINFO*>(this);
   memset(formfillinfo, 0, sizeof(FPDF_FORMFILLINFO));
   formfillinfo->version = 1;
+  formfillinfo->FFI_SetTimer = SetTimerTrampoline;
+  formfillinfo->FFI_KillTimer = KillTimerTrampoline;
   formfillinfo->m_pJsPlatform = platform;
 
   form_handle_ = FPDFDOC_InitFormFillEnvironment(document_, formfillinfo);
@@ -333,6 +292,19 @@
   return test->delegate_->Alert(message, title, type, icon);
 }
 
+// static
+int EmbedderTest::SetTimerTrampoline(FPDF_FORMFILLINFO* info,
+                                     int msecs, TimerCallback fn) {
+  EmbedderTest* test = static_cast<EmbedderTest*>(info);
+  return test->delegate_->SetTimer(msecs, fn);
+}
+
+// static
+void EmbedderTest::KillTimerTrampoline(FPDF_FORMFILLINFO* info, int id) {
+  EmbedderTest* test = static_cast<EmbedderTest*>(info);
+  return test->delegate_->KillTimer(id);
+}
+
 // Can't use gtest-provided main since we need to stash the path to the
 // executable in order to find the external V8 binary data files.
 int main(int argc, char** argv) {
diff --git a/testing/embedder_test.h b/testing/embedder_test.h
index 5cede6c..b0834dd 100644
--- a/testing/embedder_test.h
+++ b/testing/embedder_test.h
@@ -36,6 +36,12 @@
                       int type, int icon) {
       return 0;
     }
+
+    // Equivalent to FPDF_FORMFILLINFO::FFI_SetTimer().
+    virtual int SetTimer(int msecs, TimerCallback fn) { return 0; }
+
+    // Equivalent to FPDF_FORMFILLINFO::FFI_KillTimer().
+    virtual void KillTimer(int id) { }
   };
 
   EmbedderTest();
@@ -81,6 +87,7 @@
   FX_DOWNLOADHINTS hints_;
   FPDF_FILEACCESS file_access_;
   FX_FILEAVAIL file_avail_;
+  v8::Platform* platform_;
   v8::StartupData natives_;
   v8::StartupData snapshot_;
   TestLoader* loader_;
@@ -91,7 +98,9 @@
   static void UnsupportedHandlerTrampoline(UNSUPPORT_INFO*, int type);
   static int AlertTrampoline(IPDF_JSPLATFORM* plaform, FPDF_WIDESTRING message,
                              FPDF_WIDESTRING title, int type, int icon);
+  static int SetTimerTrampoline(FPDF_FORMFILLINFO* info, int msecs,
+                                TimerCallback fn);
+  static void KillTimerTrampoline(FPDF_FORMFILLINFO* info, int id);
 };
 
 #endif  // TESTING_EMBEDDER_TEST_H_
-
diff --git a/testing/embedder_test_mock_delegate.h b/testing/embedder_test_mock_delegate.h
index 526e117..1e4e995 100644
--- a/testing/embedder_test_mock_delegate.h
+++ b/testing/embedder_test_mock_delegate.h
@@ -13,6 +13,8 @@
   MOCK_METHOD1(UnsupportedHandler, void(int type));
   MOCK_METHOD4(Alert, int(FPDF_WIDESTRING message, FPDF_WIDESTRING title,
                           int type, int icon));
+  MOCK_METHOD2(SetTimer, int(int msecs, TimerCallback fn));
+  MOCK_METHOD1(KillTimer, void(int msecs));
 };
 
 #endif  // TESTING_EMBEDDER_TEST_MOCK_DELEGATE_H_
diff --git a/testing/embedder_test_timer_handling_delegate.h b/testing/embedder_test_timer_handling_delegate.h
new file mode 100644
index 0000000..d05a134
--- /dev/null
+++ b/testing/embedder_test_timer_handling_delegate.h
@@ -0,0 +1,54 @@
+// Copyright 2015 PDFium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef TESTING_EMBEDDER_TEST_TIMER_HANDLING_DELEGATE_H_
+#define TESTING_EMBEDDER_TEST_TIMER_HANDLING_DELEGATE_H_
+
+#include <map>
+#include <utility>
+
+#include "embedder_test.h"
+
+class EmbedderTestTimerHandlingDelegate : public EmbedderTest::Delegate {
+public:
+  int SetTimer(int msecs, TimerCallback fn) override {
+    expiry_to_timer_map_.insert(std::pair<int, Timer>(
+        msecs + imaginary_elapsed_msecs_, Timer(++next_timer_id_, fn)));
+    return next_timer_id_;
+  }
+
+  void KillTimer(int id) override {
+    for (auto iter = expiry_to_timer_map_.begin();
+         iter != expiry_to_timer_map_.end(); ++iter) {
+      if (iter->second.first == id) {
+        expiry_to_timer_map_.erase(iter);
+        break;
+      }
+    }
+  }
+
+  void AdvanceTime(int increment_msecs) {
+    imaginary_elapsed_msecs_ += increment_msecs;
+    while (1) {
+      auto iter = expiry_to_timer_map_.begin();
+      if (iter == expiry_to_timer_map_.end()) {
+        break;
+      }
+      Timer t = iter->second;
+      if (t.first > imaginary_elapsed_msecs_) {
+        break;
+      }
+      expiry_to_timer_map_.erase(iter);
+      t.second(t.first);  // Fire timer.
+    }
+  }
+
+protected:
+  using Timer = std::pair<int, TimerCallback>;  // ID, callback pair.
+  std::multimap<int, Timer> expiry_to_timer_map_;  // Keyed by timeout.
+  int next_timer_id_ = 0;
+  int imaginary_elapsed_msecs_ = 0;
+};
+
+#endif // TESTING_EMBEDDER_TEST_TIMER_HANDLING_DELEGATE_H_
diff --git a/testing/resources/bug_487928.in b/testing/resources/bug_487928.in
new file mode 100644
index 0000000..9f6d7f2
--- /dev/null
+++ b/testing/resources/bug_487928.in
@@ -0,0 +1,122 @@
+{{header}}
+{{object 1 0}} <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm 6 0 R
+  /Names <</JavaScript 13 0 R>>
+>>
+endobj
+{{object 2 0}} <<
+  /Type /Pages
+  /Count 1
+  /Kids [4 0 R]
+>>
+endobj
+{{object 4 0}} <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /CropBox [0 0 612 792]
+  /Resources  <<>>
+  /Annots [5 0 R]
+>>
+endobj
+{{object 5 0}} <<
+  /FT /Tx
+  /Ff 29360128
+  /T (txtName)
+  /Type /Annot
+  /Subtype /Widget
+  /F 4
+  /M (D:20150514070426+05'30')
+  /Rect [180.279 715.6 256.186 744.072]
+  /BS  <<
+    /W 1
+    /S /S
+  >>
+  /DA (/Helv 0 Tf 0 0 0 rg)
+  /AP <</N 8 0 R>>
+  /V ()
+  /AA 19 0 R
+>>
+endobj
+{{object 6 0}} <<
+  /DR <<
+    /Font <</Helv 7 0 R>>
+  >>
+  /DA (/Helv 0 Tf 0 g)
+  /Fields [5 0 R]
+>>
+endobj
+{{object 7 0}} <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+  /Encoding /WinAnsiEncoding
+>>
+endobj
+{{object 8 0}} <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Matrix [1 0 0 1 0 0]
+  /BBox [0 0 75.907 28.472]
+  /Resources  <<
+    /Font <</FXF0 7 0 R>>
+  >>
+>>
+stream
+q
+Q
+
+
+endstream
+endobj
+{{object 11 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS 50 0 R
+>>
+endobj
+{{object 13 0}} <<
+  /Names [(startDelay) 11 0 R]
+>>
+endobj
+{{object 19 0}} <<
+  /V 53 0 R
+>>
+endobj
+{{object 50 0}} <<
+>>
+stream
+function startDelay()
+{
+  f = this.getField("txtName");
+  f.delay = true;
+  f.value = 'test';
+  f.delay = false;
+}
+app.setTimeOut("startDelay()", 3000);
+endstream
+endobj
+{{object 53 0}} <<
+  /Type /Action
+  /S /JavaScript
+  /JS 54 0 R
+>>
+endobj
+{{object 54 0}} <<
+>>
+stream
+f1 = this.getField("txtName");
+f1.delay = true;
+f1.value = 'test new';
+f1.delay = false;
+endstream
+endobj
+{{xref}}
+trailer <<
+  /Root 1 0 R
+>>
+{{startxref}}
+%%EOF
diff --git a/testing/resources/bug_487928.pdf b/testing/resources/bug_487928.pdf
new file mode 100644
index 0000000..dcfdebc
--- /dev/null
+++ b/testing/resources/bug_487928.pdf
@@ -0,0 +1,180 @@
+%PDF-1.7
+% ò¤ô
+1 0 obj <<
+  /Type /Catalog
+  /Pages 2 0 R
+  /AcroForm 6 0 R
+  /Names <</JavaScript 13 0 R>>
+>>
+endobj
+2 0 obj <<
+  /Type /Pages
+  /Count 1
+  /Kids [4 0 R]
+>>
+endobj
+4 0 obj <<
+  /Type /Page
+  /Parent 2 0 R
+  /MediaBox [0 0 612 792]
+  /CropBox [0 0 612 792]
+  /Resources  <<>>
+  /Annots [5 0 R]
+>>
+endobj
+5 0 obj <<
+  /FT /Tx
+  /Ff 29360128
+  /T (txtName)
+  /Type /Annot
+  /Subtype /Widget
+  /F 4
+  /M (D:20150514070426+05'30')
+  /Rect [180.279 715.6 256.186 744.072]
+  /BS  <<
+    /W 1
+    /S /S
+  >>
+  /DA (/Helv 0 Tf 0 0 0 rg)
+  /AP <</N 8 0 R>>
+  /V ()
+  /AA 19 0 R
+>>
+endobj
+6 0 obj <<
+  /DR <<
+    /Font <</Helv 7 0 R>>
+  >>
+  /DA (/Helv 0 Tf 0 g)
+  /Fields [5 0 R]
+>>
+endobj
+7 0 obj <<
+  /Type /Font
+  /Subtype /Type1
+  /BaseFont /Helvetica
+  /Encoding /WinAnsiEncoding
+>>
+endobj
+8 0 obj <<
+  /Type /XObject
+  /Subtype /Form
+  /FormType 1
+  /Matrix [1 0 0 1 0 0]
+  /BBox [0 0 75.907 28.472]
+  /Resources  <<
+    /Font <</FXF0 7 0 R>>
+  >>
+>>
+stream
+q
+Q
+
+
+endstream
+endobj
+11 0 obj <<
+  /Type /Action
+  /S /JavaScript
+  /JS 50 0 R
+>>
+endobj
+13 0 obj <<
+  /Names [(startDelay) 11 0 R]
+>>
+endobj
+19 0 obj <<
+  /V 53 0 R
+>>
+endobj
+50 0 obj <<
+>>
+stream
+function startDelay()
+{
+  f = this.getField("txtName");
+  f.delay = true;
+  f.value = 'test';
+  f.delay = false;
+}
+app.setTimeOut("startDelay()", 3000);
+endstream
+endobj
+53 0 obj <<
+  /Type /Action
+  /S /JavaScript
+  /JS 54 0 R
+>>
+endobj
+54 0 obj <<
+>>
+stream
+f1 = this.getField("txtName");
+f1.delay = true;
+f1.value = 'test new';
+f1.delay = false;
+endstream
+endobj
+xref
+0 55
+0000000000 65535 f 
+0000000015 00000 n 
+0000000118 00000 n 
+0000000000 65535 f 
+0000000181 00000 n 
+0000000320 00000 n 
+0000000595 00000 n 
+0000000697 00000 n 
+0000000802 00000 n 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000994 00000 n 
+0000000000 65535 f 
+0000001062 00000 n 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000001115 00000 n 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000000000 65535 f 
+0000001149 00000 n 
+0000000000 65535 f 
+0000000000 65535 f 
+0000001341 00000 n 
+0000001409 00000 n 
+trailer <<
+  /Root 1 0 R
+>>
+startxref
+1537
+%%EOF