Artikel Teknis

Menjalankan AcroForm JavaScript di Delphi dengan PDFium Component

Formulir yang totalnya dihitung ulang di Acrobat tetapi macet di viewer Delphi Anda sendiri hampir tidak pernah merupakan bug rendering. PDFium menonaktifkan semua JavaScript dokumen kecuali jika host memasang platform JS, dan PDFium Component menghubungkan platform tersebut secara otomatis sejak versi 2.13.2 ke atas, sehingga JavaScript AcroForm, skrip tingkat dokumen (doc-level), dan perhitungan bidang dieksekusi setiap kali DLL berkemampuan V8 dimuat. Host tetap memegang kendali melalui serangkaian kejadian (events) yang dapat menampilkan, mengalihkan, atau memveto apa pun yang coba dilakukan oleh skrip

Satu kalimat tersebut menyelesaikan pertanyaan dukungan yang telah kami lihat dalam beberapa bentuk: "formulir pesanan saya menghitung total baris di Acrobat tetapi tidak di aplikasi saya", "app.alert tidak pernah dipicu", "bidang tanggal tidak memformat dirinya sendiri". Semuanya merujuk kembali pada kontrak yang sama, dan memahaminya selama sepuluh menit sangat berharga karena kontrak yang sama tersebut juga merupakan batas keamanan Anda terhadap dokumen yang berbahaya

Mengapa perhitungan formulir PDF tidak berfungsi di PDFium?

PDFium memperlakukan JavaScript dokumen secara ketat sebagai pilihan (opt-in): jika anggota m_pJsPlatform dari lingkungan pengisian formulir bernilai NULL, setiap skrip dalam dokumen dilewati secara diam-diam. Tidak ada kesalahan, tidak ada baris log, tidak ada eksekusi parsial. Bidang kalkulasi mempertahankan nilai terakhir yang disimpan, app.alert lenyap, skrip format tidak pernah berjalan. Acrobat, yang selalu menyertakan mesin JavaScript-nya sendiri, menjalankan berkas yang sama dengan lancar, itulah sebabnya perbedaan tersebut terlihat sangat mirip dengan bug pada kode Anda padahal sebenarnya itu adalah default yang disengaja dalam pustaka di bawahnya

PDFium Component memiliki lapisan kedua yang historis untuk hal ini. Hingga versi 2.13.1 pengikatan hanya memasang m_pJsPlatform di dalam cabang inisialisasi XFA, sehingga dokumen AcroForm biasa, yang merupakan mayoritas mutlak dari PDF berskrip, tidak pernah mendapatkan platform JavaScript sama sekali bahkan ketika DLL berkemampuan V8 ada. Versi 2.13.2 menarik pemasangan tersebut keluar dari keputusan XFA: InitializeFormFill sekarang membangun tabel IPDF_JsPlatform setiap kali V8FeaturesAvailable mengembalikan nilai True, terlepas dari apakah dokumen tersebut adalah XFA. Hasil praktisnya adalah skrip tingkat dokumen, rantai perhitungan bidang, dan app.alert sekarang dieksekusi di dokumen AcroForm biasa juga

DLL V8 mana yang dibutuhkan AcroForm JavaScript di Delphi?

AcroForm JavaScript memerlukan biner PDFium yang dikompilasi dengan mesin V8, yang dimuat oleh PDFium Component sebagai pdfium.v8.dll ketika bendera global EnableV8Engine bernilai True sebelum pustaka dimuat pertama kali. Ada satu jebakan di sini yang patut dinyatakan dengan jelas: komponen secara otomatis mendeteksi dokumen XFA dengan memindai terlebih dahulu penanda /XFA dan mengaktifkan EnableV8Engine untuk Anda, tetapi dokumen AcroForm biasa dengan JavaScript tidak membawa penanda XFA, sehingga tidak ada deteksi otomatis yang terjadi. Jika viewer Anda harus menjalankan skrip formulir, atur sendiri benderanya, sekali, sebelum dokumen pertama dimuat; setelah pdfium.dll biasa dimuat, proses tidak dapat mengganti mesin

uses PDFium;

procedure TViewerForm.FormCreate(Sender: TObject);
begin
  // Harus dijalankan sebelum pustaka PDFium singleton dimuat pertama kali;
  // begitu pdfium.dll biasa berada di proses, ia tidak dapat ditukar
  EnableV8Engine := True;
end;

procedure TViewerForm.OpenDocument(const FileName: string);
begin
  FPdf.LoadDocument(FileName);
  if not V8FeaturesAvailable then
    StatusBar.SimpleText :=
      'JavaScript disabled: pdfium.v8.dll not deployed';
end;

V8FeaturesAvailable adalah penyelidikan runtime yang jujur: ia melaporkan apakah biner yang dimuat benar-benar menyelesaikan ekspor yang bergantung pada V8, sehingga pemeriksaan berfungsi tidak peduli DLL mana yang akhirnya disebarkan oleh penginstal. Mesin yang sama dan bendera yang sama juga mendukung rendering XFA dinamis; latar belakang tentang bagaimana interaksi deteksi XFA dan pemilihan otomatis V8 dibahas dalam mendeteksi formulir XFA dan mengekstrak paket XFA dengan PDFium

Kejadian (events) host untuk peringatan, cetak, surat, dan pengiriman formulir

Setiap hal terlihat yang dilakukan skrip dialihkan kembali ke kode Anda melalui kejadian TPdf, dan masing-masing default-nya adalah no-op (tanpa operasi) yang aman saat tidak ditetapkan. OnJavaScriptAlert menerima pesan, judul, jenis tombol, dan ikon dari app.alert dan memungkinkan Anda mengembalikan hasil tombol yang ditekan; OnJavaScriptResponse melayani permintaan input app.response termasuk bendera kata sandi; OnJavaScriptBeep, OnJavaScriptPrint, OnJavaScriptMail, dan OnJavaScriptSubmitForm memunculkan permintaan bip (beep), cetak, surat, dan kirim formulir dengan set parameter lengkapnya. Viewer yang tidak menetapkan satu pun dari ini tetap merender dan menghitung dengan benar; dokumen tersebut hanya tidak dapat membuka dialog, mencetak, atau mengirim apa pun

procedure TViewerForm.PdfJavaScriptAlert(Sender: TObject;
  const Msg, Title: WString; ButtonType, Icon: Integer;
  var Result_: Integer; var Handled: Boolean);
begin
  // Alihkan app.alert melalui VCL sehingga dimiliki oleh jendela kita
  Result_ := MessageBox(Handle, PWideChar(Msg), PWideChar(Title),
    MB_OK or MB_ICONINFORMATION);
  Handled := True;
end;

procedure TViewerForm.PdfJavaScriptSubmitForm(Sender: TObject;
  const Url: WString; const Data: TBytes);
begin
  // Tidak ada yang ditransmisikan kecuali handler ini memilih untuk mengirimkannya
  LogAudit(Format('Document requested form submit to %s (%d bytes)',
    [Url, Length(Data)]));
end;

Pemisahan perilaku default ini penting untuk pemodelan ancaman. OnJavaScriptMail dan OnJavaScriptSubmitForm bergaya pemberitahuan (notification-style): PDFium Component tidak melakukan tindakan jaringan atau MAPI sendiri, sehingga dokumen berbahaya yang memanggil this.submitForm atau this.mailDoc terhadap host yang tidak siap tidak menghasilkan apa-apa. Host harus memilih untuk ikut serta dengan menetapkan handler dan mengimplementasikan transportasi itu sendiri, yang berarti keputusan untuk memindahkan data keluar dari mesin selalu ada di tangan Anda, dibuat dalam kode Anda, dengan URL dan payload di tangan

Bagaimana cara menghentikan PDF berbahaya agar tidak meluncurkan URL?

Tindakan URI adalah satu-satunya tempat di mana default historis aktif alih-alih pasif, dan itulah sebabnya mereka mendapatkan hak veto khusus. Sebelum versi 2.13.1, callback FFI_DoURIActionWithKeyboardModifier langsung mengeksekusi shell (shell-executed) URI apa pun yang disediakan oleh bidang XFA, tanpa syarat, yang memberikan dokumen berbahaya kemampuan untuk meluncurkan URL sembarangan saat diklik. OnXfaUriAction menutup celah tersebut: komponen berkonsultasi dengan kejadian sebelum mengeksekusi, dan handler yang menyetel Handled ke True menekan default ShellExecuteW sepenuhnya. Membiarkan kejadian tidak ditetapkan mempertahankan perilaku lama untuk kompatibilitas, sehingga viewer yang sadar keamanan harus selalu menetapkannya

procedure TViewerForm.PdfXfaUriAction(Sender: TObject;
  const Uri: WString; Modifiers: Integer; var Handled: Boolean);
begin
  // Veto apa pun yang bukan https biasa; default-nya
  // sebaliknya akan menjalankan ShellExecuteW pada URI apa adanya
  if not SameText(Copy(Uri, 1, 8), 'https://') then
  begin
    LogAudit('Blocked URI action: ' + Uri);
    Handled := True;
  end;
end;

Daftar izin (allow-list) pada skema adalah hal minimum; viewer yang lebih ketat akan meminta konfirmasi dengan pengguna atau mengalihkan semuanya melalui komponen browser sandbox-nya sendiri. Filosofi veto yang sama meluas ke seluruh set kejadian v2.13.1: OnXfaEmail, OnXfaHttpRequest, dan OnXfaOpenFile masing-masing mengekspos parameter tekstual dari tindakan yang diminta sementara komponen itu sendiri tidak melakukan transportasi jaringan atau berkas. Bagaimana pengawal ini cocok dengan strategi dokumen tidak terpercaya yang lebih luas, termasuk isolasi rendering, adalah subjek dari membangun pratinjau PDF yang aman di Delphi

Menulis ke bidang terfokus dengan SetFocusedFormFieldText

TPdf.SetFocusedFormFieldText, yang ditambahkan di versi 2.13.2, adalah pendamping sisi tulis untuk FocusedFormFieldText: ia memilih konten saat ini dari bidang terfokus melalui FORM_SelectAllText and menimpanya melalui FORM_ReplaceSelection, persis seperti jika pengguna telah mengetik penggantinya. Karena teks masuk melalui jalur pengeditan interaktif, skrip penekanan tombol (keystroke), format, dan kalkulasi yang terpasang pada bidang dipicu dengan cara yang sama seperti untuk masukan manual, menjadikannya primitif yang tepat untuk pengisian terprogram dalam viewer yang menjaga JavaScript tetap aktif. Penelusuran bidang dan manajemen fokus di sekitarnya dibahas dalam navigasi bidang formulir dengan PDFium Component

if FPdf.FocusedFormFieldIndex >= 0 then
begin
  if not FPdf.SetFocusedFormFieldText('42.50') then
    ShowMessage('No focused field accepts text on this page');
  // AcroForm: nilai berkomitmen ke /V saat fokus meninggalkan bidang.
  // XFA: penulisan hanya berada di buffer edit dalam memori saja dan
  // tidak akan bertahan saat disimpan
end;

Asimetri persistensi dalam komentar tersebut adalah batasan nyata, bukan sekadar peringatan untuk formulir. Untuk bidang teks dan kombo AcroForm, nilai yang diedit berkomitmen ke entri /V bidang tersebut saat kehilangan fokus dan bertahan setelah disimpan. Untuk bidang teks XFA, penulisan hanya mendarat di buffer edit dalam memori, karena PDFium tidak mengekspos API publik untuk merekonsiliasi nilai widget kembali ke paket dataset XFA; menyimpan dokumen tidak akan membawa perubahan tersebut. Jika pengeditan data XFA yang tahan lama adalah persyaratannya, edit XML dataset itu sendiri alih-alih widget-nya

Batasan yang perlu diketahui sebelum Anda mengirimkan

Dua batasan jujur tetap ada. Pertama, API JavaScript yang diimplementasikan PDFium adalah subset kerja dari model objek Acrobat, yang berpusat pada inti pembuatan skrip formulir: akses bidang, kejadian perhitungan dan format, app.alert dan app.response, permintaan cetak, surat, dan kirim. Dokumen yang bersandar pada objek eksotis khusus Acrobat akan turun kinerjanya, biasanya secara diam-diam, jadi ujilah formulir sebenarnya yang ditangani pengguna Anda alih-alih menganggap ada kesamaan penuh. Kedua, mengaktifkan V8 berarti mengirimkan pdfium.v8.dll, biner yang jauh lebih besar daripada build biasa; viewer yang tidak pernah membutuhkan skrip atau XFA dapat secara sah menyimpan DLL yang lebih kecil dan membiarkan m_pJsPlatform tidak terpasang, yang merupakan sikap keamanan yang valid dalam dirinya sendiri

Pola yang dihasilkan dari semua ini cukup sederhana: atur EnableV8Engine saat startup, tetapkan kejadian peringatan (alert) dan respons sehingga dialog terlihat native, tetapkan OnXfaUriAction dan catat log audit kejadian surat dan pengiriman, dan biarkan hal lainnya pada default no-op sampai ada persyaratan lain yang menyatakannya. Referensi kejadian lengkap dan permukaan API pengisian formulir didokumentasikan di halaman produk PDFium Component