Acrobatでは合計が再計算されるのに、自作のDelphiビューアではフリーズしたままであるフォームは、レンダリングのバグであることはほとんどありません。PDFiumは、ホストがJSプラットフォームをアタッチしない限り、すべてのドキュメントJavaScriptを無効化します。PDFium Componentはバージョン2.13.2以降、そのプラットフォームを自動的に接続するため、V8が有効化されたDLLがロードされていれば、AcroForm JavaScript、ドキュメントレベルのスクリプト、およびフィールドの計算が実行されます。ホストは一連のイベントを介して、スクリプトが試みるすべての操作を表示、リダイレクト、または拒否(ベト)する制御権を持ち続けます
この一言は、これまでいくつかの形で見られたサポートの質問を解決します。「私の注文フォームはAcrobatでは各行の合計を計算するが、アプリ内では計算しない」、「app.alertがまったく呼び出されない」、「日付フィールドが自動的にフォーマットされない」などです。これらはすべて同じ仕組み(コントラクト)に起因しており、この仕組みを理解することは10分ほどの価値があります。なぜなら、この仕組みは悪意のあるドキュメントに対するセキュリティの境界線でもあるからです
なぜPDFのフォーム計算はPDFiumで動作しないのか?
PDFiumはドキュメントのJavaScriptを厳密にオプトインとして扱います。フォーム入力環境のm_pJsPlatformメンバーがNULLである場合、ドキュメント内のすべてのスクリプトは何もせずにスキップされます。エラーも、ログ行も、部分的な実行もありません。計算フィールドは最後に保存された値を保持し、app.alertは消え、フォーマットスクリプトは実行されません。常に独自のJavaScriptエンジンを搭載しているAcrobatは同じファイルを問題なく実行するため、この不一致はご自身のコードのバグのように見えますが、実際にはその下にあるライブラリの意図的なデフォルト設定です
PDFium Componentには、歴史的にもう1つのレイヤーがありました。バージョン2.13.1までは、バインディングはXFA初期化ブランチ内でのみm_pJsPlatformをアタッチしていたため、スクリプト付きPDFの大部分を占める通常のAcroFormドキュメントは、V8対応のDLLが存在する場合であってもJavaScriptプラットフォームを一切取得できませんでした。バージョン2.13.2では、このアタッチ処理がXFAの判定ロジックから切り離されました。InitializeFormFillは、ドキュメントがXFAであるかどうかにかかわらず、V8FeaturesAvailableがTrueを返す場合は常にIPDF_JsPlatformテーブルを構築するようになりました。実用上の結果として、通常のAcroFormドキュメントでも、ドキュメントレベルのスクリプト、フィールドの計算チェーン、およびapp.alertが実行されるようになりました
DelphiでAcroForm JavaScriptが必要とするV8 DLLはどれですか?
AcroForm JavaScriptは、V8エンジンを組み込んでコンパイルされたPDFiumバイナリを必要とします。PDFium Componentは、ライブラリが最初にロードされる前にグローバルのEnableV8EngineフラグがTrueである場合、これをpdfium.v8.dllとしてロードします。ここで明確にしておくべき罠が1つあります。コンポーネントは/XFAマーカーを事前スキャンしてXFAドキュメントを自動検出し、代わりにEnableV8Engineを切り替えますが、JavaScriptを含む通常のAcroFormドキュメントにはXFAマーカーがないため、自動検出は行われません。作成するビューアでフォームスクリプトを実行する場合は、最初のドキュメントがロードされる前に、ご自身でこのフラグを1回設定してください。通常のpdfium.dllがロードされた後では、プロセスはエンジンを切り替えることができません
uses PDFium;
procedure TViewerForm.FormCreate(Sender: TObject);
begin
// Must run before the singleton PDFium library first loads;
// once plain pdfium.dll is in the process it cannot be swapped
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は、信頼性の高い実行時チェック手段です。ロードされたバイナリが実際にV8依存のエクスポート関数を解決しているかどうかを報告するため、インストーラーが最終的にどのDLLを配置したかに関係なくチェックが機能します。同じエンジンと同じフラグは、動的なXFAレンダリングにも使用されます。XFA検出とV8自動選択の相互作用に関する背景は、PDFiumによるXFAフォームの検出とXFAパケットの抽出で説明されています
アラート、印刷、メール、フォーム送信のホストイベント
スクリプトが行う視覚的な処理はすべて、TPdfイベントを通じてコードにルーティングされます。これらはすべて、割り当てられていない場合はデフォルトで安全な何もしない動作(no-op)になります。OnJavaScriptAlertは、app.alertからメッセージ、タイトル、ボタンタイプ、およびアイコンを受け取り、押されたボタンの値を返せるようにします。OnJavaScriptResponseは、パスワードフラグを含むapp.responseの入力プロンプトに対応します。OnJavaScriptBeep、OnJavaScriptPrint、OnJavaScriptMail、およびOnJavaScriptSubmitFormは、ビープ音、印刷、メール送信、および送信の要求を完全なパラメータセットとともに提供します。これらのいずれも割り当てていないビューアでも、正しくレンダリングされ、計算されます。ドキュメントは単にダイアログを開いたり、印刷したり、何かを送信したりできないだけです
procedure TViewerForm.PdfJavaScriptAlert(Sender: TObject;
const Msg, Title: WString; ButtonType, Icon: Integer;
var Result_: Integer; var Handled: Boolean);
begin
// Route app.alert through the VCL so it is owned by our window
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
// Nothing is transmitted unless this handler chooses to send it
LogAudit(Format('Document requested form submit to %s (%d bytes)',
[Url, Length(Data)]));
end;
デフォルトの動作が分離されていることは、脅威モデリングにおいて重要です。OnJavaScriptMailおよびOnJavaScriptSubmitFormは通知スタイルです。PDFium Componentは自身でネットワークやMAPIのアクションを実行しないため、悪意のあるドキュメントが準備されていないホストに対してthis.submitFormまたはthis.mailDocを呼び出しても、まったく何も起こりません。ホストは、ハンドラーを割り当てて独自のトランスポート処理を実装することでオプトインする必要があります。つまり、データをマシンから外部に移動させる決定は、常に開発者がコード内で行うものであり、URLとペイロードを手元に置いた上で行われます
悪意のあるPDFによるURLの起動を防ぐにはどうすればよいですか?
URIアクションは、歴史的なデフォルトがパッシブではなくアクティブであった唯一の場所であり、そのため専用の拒否イベントが用意されました。バージョン2.13.1より前は、FFI_DoURIActionWithKeyboardModifierコールバックはXFAフィールドが提供したURIを条件なしでシェル実行していたため、悪意のあるドキュメントにクリック時で任意のURLを起動させる能力を与えていました。OnXfaUriActionはこれを防ぎます。コンポーネントは実行前にこのイベントを参照し、ハンドラーがHandledをTrueに設定すると、デフォルト of ShellExecuteWの呼び出しを完全に抑制します。イベントを割り当てないままにすると、互換性のために古い動作が維持されるため、セキュリティを意識したビューアでは常にこのイベントを割り当てる必要があります
procedure TViewerForm.PdfXfaUriAction(Sender: TObject;
const Uri: WString; Modifiers: Integer; var Handled: Boolean);
begin
// Veto anything that is not plain https; the default would
// otherwise ShellExecuteW the URI as-is
if not SameText(Copy(Uri, 1, 8), 'https://') then
begin
LogAudit('Blocked URI action: ' + Uri);
Handled := True;
end;
end;
スキーマの許可リスト(ホワイトリスト)は最小限の対策です。より厳格なビューアは、ユーザーに確認するか、あるいはすべてを独自のサンドボックス化されたブラウザコンポーネント経由でルーティングします。同じ拒否(ベト)哲学は、v2.13.1のイベントセット(OnXfaEmail、OnXfaHttpRequest、OnXfaOpenFile)全体に広がっており、コンポーネント自体がネットワークやファイルの転送を実行しない一方で、要求されたアクションのテキストパラメータを公開します。これらの保護機能が、レンダリングの分離を含む、より広範な「信頼できないドキュメント」対策にどのように適合するかについては、Delphiでの安全なPDFプレビューの構築で説明されています
SetFocusedFormFieldTextによるフォーカスされたフィールドへの書き込み
バージョン2.13.2で追加されたTPdf.SetFocusedFormFieldTextは、FocusedFormFieldTextの書き込み用の機能です。FORM_SelectAllTextを介してフォーカスされたフィールドの現在の内容を選択し、ユーザーが代替テキストを入力したのとまったく同じようにFORM_ReplaceSelectionを通じて上書きします。テキストはインタラクティブな編集パスを通じて入力されるため、フィールドに割り当てられているキー入力、フォーマット、および計算スクリプトは手動入力と同じように実行されます。これにより、JavaScriptを有効にしているビューアでプログラムから入力を行うための適切なプリミティブになります。フィールドのトラバースとフォーカス管理については、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: value commits to /V when focus leaves the field.
// XFA: the write stays in the in-memory edit buffer only and
// will not survive a save
end;
そのコメントにある永続性の非対称性は、形式的な警告ではなく実際の制限です。AcroFormのテキストフィールドやコンボボックスの場合、編集された値はフォーカスを失ったときにフィールドの/Vエントリにコミットされ、保存しても保持されます。XFAテキストフィールドの場合、PDFiumはウィジェットの値をXFA datasetsパケットに書き戻して整合させる公開APIを提供していないため、書き込みはインメモリの編集バッファにのみ留まります。ドキュメントを保存しても変更は引き継がれません。永続的なXFAデータの編集が必要な場合は、ウィジェットではなくdatasets XML自体を編集してください
出荷前に知っておく価値のある制限事項
さらに2つの実質的な境界線が残っています。第一に、PDFiumが実装するJavaScript APIは、フォームスクリプトのコア(フィールドアクセス、計算およびフォーマットイベント、app.alertとapp.response、印刷、メール、および送信要求)を中心としたAcrobatオブジェクトモデルの動作可能なサブセットです。Acrobat専用の特異なオブジェクトに依存するドキュメントは、通常は何の通知もなく機能が低下するため、動作の同等性を想定するのではなく、ユーザーが扱う実際のフォームでテストしてください。第二に、V8を有効にすることは、通常のビルドよりも大幅に大きなバイナリであるpdfium.v8.dllを出荷することを意味します。スクリプトやXFAを必要としないビューアは、より小さなDLLを維持し、m_pJsPlatformをアタッチしないままにしておくことができ、それ自体が有効なセキュリティ姿勢となります
これらすべてから得られるパターンは、心地よいほど退屈なものです。起動時にEnableV8Engineを設定し、ダイアログがネイティブに見えるようにalertとresponseイベントを割り当て、OnXfaUriActionを割り当ててmailとsubmitイベントを監査ログに記録し、他のすべては要件で別途指定されるまで何もしないデフォルトのままにしておきます。完全なイベントリファレンスとフォーム入力APIサーフェスは、PDFium Componentの製品ページに文書化されています