Add FPDF_GetArrayBufferAllocatorSharedInstance()

This is also an enabling step to call PumpMessageLoop()
from pdfium_test without changing other APIs since it
will own the isolate required for pumping.

-- use it under pdfium_test.cc

Bug: chromium:1091380
Change-Id: I3368987ed89f9ba8250caec2c40f759691aaf896
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/70391
Commit-Queue: Tom Sepez <tsepez@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
diff --git a/fpdfsdk/fpdf_view.cpp b/fpdfsdk/fpdf_view.cpp
index 0eddaf8..7d74625 100644
--- a/fpdfsdk/fpdf_view.cpp
+++ b/fpdfsdk/fpdf_view.cpp
@@ -47,6 +47,11 @@
 #include "third_party/base/ptr_util.h"
 #include "third_party/base/span.h"
 
+#ifdef PDF_ENABLE_V8
+#include "fxjs/cfx_v8.h"
+#include "third_party/base/no_destructor.h"
+#endif
+
 #ifdef PDF_ENABLE_XFA
 #include "fpdfsdk/fpdfxfa/cpdfxfa_context.h"
 #include "fpdfsdk/fpdfxfa/cpdfxfa_page.h"
@@ -989,6 +994,11 @@
   // Use interpreted JS only to avoid RWX pages in our address space.
   return "--no-expose-wasm --jitless";
 }
+
+FPDF_EXPORT void* FPDF_CALLCONV FPDF_GetArrayBufferAllocatorSharedInstance() {
+  static pdfium::base::NoDestructor<CFX_V8ArrayBufferAllocator> allocator;
+  return allocator.get();
+}
 #endif  // PDF_ENABLE_V8
 
 #ifdef PDF_ENABLE_XFA
diff --git a/fpdfsdk/fpdf_view_c_api_test.c b/fpdfsdk/fpdf_view_c_api_test.c
index 9ee6778..68189fc 100644
--- a/fpdfsdk/fpdf_view_c_api_test.c
+++ b/fpdfsdk/fpdf_view_c_api_test.c
@@ -413,6 +413,9 @@
     CHK(FPDF_DestroyLibrary);
     CHK(FPDF_DeviceToPage);
     CHK(FPDF_DocumentHasValidCrossReferenceTable);
+#ifdef PDF_ENABLE_V8
+    CHK(FPDF_GetArrayBufferAllocatorSharedInstance);
+#endif
     CHK(FPDF_GetDocPermissions);
     CHK(FPDF_GetFileVersion);
     CHK(FPDF_GetLastError);
diff --git a/public/fpdfview.h b/public/fpdfview.h
index bd8cbbe..079a856 100644
--- a/public/fpdfview.h
+++ b/public/fpdfview.h
@@ -1295,6 +1295,21 @@
 //          NUL-terminated string of the form "--flag1 --flag2".
 //          The caller must not attempt to modify or free the result.
 FPDF_EXPORT const char* FPDF_CALLCONV FPDF_GetRecommendedV8Flags();
+
+// Experimental API.
+// Function: FPDF_GetArrayBufferAllocatorSharedInstance()
+//          Helper function for initializing V8 isolates that will
+//          use PDFium's internal memory management.
+// Parameters:
+//          None.
+// Return Value:
+//          Pointer to a suitable v8::ArrayBuffer::Allocator, returned
+//          as void for C compatibility.
+// Notes:
+//          Use is optional, but allows external creation of isolates
+//          matching the ones PDFium will make when none is provided
+//          via |FPDF_LIBRARY_CONFIG::m_pIsolate|.
+FPDF_EXPORT void* FPDF_CALLCONV FPDF_GetArrayBufferAllocatorSharedInstance();
 #endif  // PDF_ENABLE_V8
 
 #ifdef PDF_ENABLE_XFA
diff --git a/samples/pdfium_test.cc b/samples/pdfium_test.cc
index d7f211c..d983526 100644
--- a/samples/pdfium_test.cc
+++ b/samples/pdfium_test.cc
@@ -229,6 +229,11 @@
 }
 
 #ifdef PDF_ENABLE_V8
+
+struct V8IsolateDeleter {
+  inline void operator()(v8::Isolate* ptr) { ptr->Dispose(); }
+};
+
 // 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.
@@ -1125,26 +1130,33 @@
     return 1;
   }
 
-#ifdef PDF_ENABLE_V8
-  std::unique_ptr<v8::Platform> platform;
-#ifdef V8_USE_EXTERNAL_STARTUP_DATA
-  v8::StartupData snapshot;
-  if (!options.disable_javascript) {
-    platform = InitializeV8ForPDFiumWithStartupData(
-        options.exe_path, options.bin_directory, &snapshot);
-  }
-#else   // V8_USE_EXTERNAL_STARTUP_DATA
-  if (!options.disable_javascript)
-    platform = InitializeV8ForPDFium(options.exe_path);
-#endif  // V8_USE_EXTERNAL_STARTUP_DATA
-#endif  // PDF_ENABLE_V8
-
   FPDF_LIBRARY_CONFIG config;
   config.version = 2;
   config.m_pUserFontPaths = nullptr;
   config.m_pIsolate = nullptr;
   config.m_v8EmbedderSlot = 0;
 
+#ifdef PDF_ENABLE_V8
+#ifdef V8_USE_EXTERNAL_STARTUP_DATA
+  v8::StartupData snapshot;
+#endif  // V8_USE_EXTERNAL_STARTUP_DATA
+  std::unique_ptr<v8::Platform> platform;
+  std::unique_ptr<v8::Isolate, V8IsolateDeleter> isolate;
+  if (!options.disable_javascript) {
+#ifdef V8_USE_EXTERNAL_STARTUP_DATA
+    platform = InitializeV8ForPDFiumWithStartupData(
+        options.exe_path, options.bin_directory, &snapshot);
+#else   // V8_USE_EXTERNAL_STARTUP_DATA
+    platform = InitializeV8ForPDFium(options.exe_path);
+#endif  // V8_USE_EXTERNAL_STARTUP_DATA
+    v8::Isolate::CreateParams params;
+    params.array_buffer_allocator = static_cast<v8::ArrayBuffer::Allocator*>(
+        FPDF_GetArrayBufferAllocatorSharedInstance());
+    isolate.reset(v8::Isolate::New(params));
+    config.m_pIsolate = isolate.get();
+  }
+#endif  // PDF_ENABLE_V8
+
   const char* path_array[2] = {nullptr, nullptr};
   Optional<const char*> custom_font_path = GetCustomFontPath(options);
   if (custom_font_path.has_value()) {
@@ -1212,6 +1224,7 @@
 
 #ifdef PDF_ENABLE_V8
   if (!options.disable_javascript) {
+    isolate.reset();
     v8::V8::ShutdownPlatform();
 #ifdef V8_USE_EXTERNAL_STARTUP_DATA
     free(const_cast<char*>(snapshot.data));