mirror of
https://pdfium.googlesource.com/pdfium
synced 2026-08-01 13:48:10 +08:00
Embedder tests often install stack-local delegates, but EmbedderTest can invoke delegate callbacks while closing the document in TearDown(). By then, a stack delegate may already have been destroyed. Add SetOwnedDelegate() so the fixture owns the custom delegate through document teardown, then restores the default delegate before releasing it. Migrate all existing call sites, remove the raw SetDelegate() API, and store the active delegate as an UnownedPtr. Bug: 535697671 Change-Id: I8e8da028e9c563e82d40099637c63978122077eb Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/152110 Commit-Queue: Lei Zhang <thestig@chromium.org> Reviewed-by: Lei Zhang <thestig@chromium.org> Reviewed-by: Tom Sepez <tsepez@chromium.org> Commit-Queue: Tom Sepez <tsepez@chromium.org>
472 lines
18 KiB
C++
472 lines
18 KiB
C++
// Copyright 2015 The PDFium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#ifndef TESTING_EMBEDDER_TEST_H_
|
|
#define TESTING_EMBEDDER_TEST_H_
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <concepts>
|
|
#include <fstream>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "build/build_config.h"
|
|
#include "core/fxcrt/bytestring.h"
|
|
#include "core/fxcrt/check.h"
|
|
#include "core/fxcrt/span.h"
|
|
#include "core/fxcrt/unowned_ptr.h"
|
|
#include "public/cpp/fpdf_scopers.h"
|
|
#include "public/fpdf_dataavail.h"
|
|
#include "public/fpdf_ext.h"
|
|
#include "public/fpdf_formfill.h"
|
|
#include "public/fpdf_save.h"
|
|
#include "public/fpdfview.h"
|
|
#include "testing/fake_file_access.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
class TestLoader;
|
|
|
|
// The loading time of the CFGAS_FontMgr is linear in the number of times it is
|
|
// loaded. So, if a test suite has a lot of tests that need a font manager they
|
|
// can end up executing very, very slowly.
|
|
|
|
// Helper macro for common equality assertions with a fixed tolerance of 0.001.
|
|
#define EXPECT_NEAR_THREE_PLACES(a, b) EXPECT_NEAR((a), (b), 0.001)
|
|
|
|
// This class is used to load a PDF document, and then run programatic
|
|
// API tests against it.
|
|
class EmbedderTest : public ::testing::Test,
|
|
public UNSUPPORT_INFO,
|
|
public IPDF_JSPLATFORM,
|
|
public FPDF_FORMFILLINFO,
|
|
public FPDF_FILEWRITE {
|
|
public:
|
|
enum class LinearizeOption { kDefaultLinearize, kMustLinearize };
|
|
enum class JavaScriptOption { kDisableJavaScript, kEnableJavaScript };
|
|
|
|
class Delegate {
|
|
public:
|
|
virtual ~Delegate() = default;
|
|
|
|
// Equivalent to UNSUPPORT_INFO::FSDK_UnSupport_Handler().
|
|
virtual void UnsupportedHandler(int type) {}
|
|
|
|
// Equivalent to IPDF_JSPLATFORM::app_alert().
|
|
virtual int Alert(FPDF_WIDESTRING message,
|
|
FPDF_WIDESTRING title,
|
|
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) {}
|
|
|
|
// Equivalent to FPDF_FORMFILLINFO::FFI_GetPage().
|
|
virtual FPDF_PAGE GetPage(FPDF_FORMFILLINFO* info,
|
|
FPDF_DOCUMENT document,
|
|
int page_index);
|
|
|
|
// Equivalent to FPDF_FORMFILLINFO::FFI_DoURIAction().
|
|
virtual void DoURIAction(FPDF_BYTESTRING uri) {}
|
|
|
|
// Equivalent to FPDF_FORMFILLINFO::FFI_DoGoToAction().
|
|
virtual void DoGoToAction(FPDF_FORMFILLINFO* info,
|
|
int page_index,
|
|
int zoom_mode,
|
|
float* pos_arry,
|
|
int array_size) {}
|
|
|
|
// Equivalent to FPDF_FORMFILLINFO::FFI_OnFocusChange().
|
|
virtual void OnFocusChange(FPDF_FORMFILLINFO* info,
|
|
FPDF_ANNOTATION annot,
|
|
int page_index) {}
|
|
|
|
// Equivalent to FPDF_FORMFILLINFO::FFI_DoURIActionWithKeyboardModifier().
|
|
virtual void DoURIActionWithKeyboardModifier(FPDF_FORMFILLINFO* info,
|
|
FPDF_BYTESTRING uri,
|
|
int modifiers) {}
|
|
};
|
|
|
|
class ScopedSavedDoc {
|
|
public:
|
|
ScopedSavedDoc();
|
|
explicit ScopedSavedDoc(EmbedderTest* test);
|
|
ScopedSavedDoc(EmbedderTest* test, const ByteString& password);
|
|
ScopedSavedDoc(const ScopedSavedDoc&) = delete;
|
|
ScopedSavedDoc& operator=(const ScopedSavedDoc&) = delete;
|
|
ScopedSavedDoc(ScopedSavedDoc&&) noexcept;
|
|
ScopedSavedDoc& operator=(ScopedSavedDoc&&) noexcept;
|
|
~ScopedSavedDoc();
|
|
|
|
FPDF_DOCUMENT get() { return doc_; }
|
|
|
|
explicit operator bool() const { return !!doc_; }
|
|
|
|
private:
|
|
UnownedPtr<EmbedderTest> test_;
|
|
FPDF_DOCUMENT doc_;
|
|
};
|
|
|
|
class ScopedPageBase {
|
|
public:
|
|
FPDF_PAGE get() { return page_; }
|
|
|
|
explicit operator bool() const { return !!page_; }
|
|
|
|
protected:
|
|
ScopedPageBase(EmbedderTest* test, FPDF_PAGE page);
|
|
~ScopedPageBase();
|
|
|
|
UnownedPtr<EmbedderTest> test_;
|
|
FPDF_PAGE page_;
|
|
};
|
|
|
|
class ScopedPage : public ScopedPageBase {
|
|
public:
|
|
ScopedPage();
|
|
ScopedPage(EmbedderTest* test, int page_index);
|
|
ScopedPage(const ScopedPage&) = delete;
|
|
ScopedPage& operator=(const ScopedPage&) = delete;
|
|
ScopedPage(ScopedPage&&) noexcept;
|
|
ScopedPage& operator=(ScopedPage&&) noexcept;
|
|
~ScopedPage();
|
|
};
|
|
|
|
class ScopedSavedPage : public ScopedPageBase {
|
|
public:
|
|
ScopedSavedPage();
|
|
ScopedSavedPage(EmbedderTest* test, int page_index);
|
|
ScopedSavedPage(const ScopedSavedPage&) = delete;
|
|
ScopedSavedPage& operator=(const ScopedSavedPage&) = delete;
|
|
ScopedSavedPage(ScopedSavedPage&&) noexcept;
|
|
ScopedSavedPage& operator=(ScopedSavedPage&&) noexcept;
|
|
~ScopedSavedPage();
|
|
};
|
|
|
|
EmbedderTest();
|
|
~EmbedderTest() override;
|
|
|
|
void SetUp() override;
|
|
void TearDown() override;
|
|
|
|
Delegate* GetDelegate() { return delegate_; }
|
|
|
|
// Creates and installs a delegate owned by this fixture. The delegate stays
|
|
// alive until TearDown() closes the document and restores the default
|
|
// delegate. This method may only be called once per fixture.
|
|
template <std::derived_from<Delegate> T, typename... Args>
|
|
T& SetOwnedDelegate(Args&&... args) {
|
|
CHECK(!owned_delegate_);
|
|
auto delegate = std::make_unique<T>(std::forward<Args>(args)...);
|
|
T& result = *delegate;
|
|
owned_delegate_ = std::move(delegate);
|
|
delegate_ = owned_delegate_.get();
|
|
return result;
|
|
}
|
|
|
|
void SetFormFillInfoVersion(int form_fill_info_version) {
|
|
form_fill_info_version_ = form_fill_info_version;
|
|
}
|
|
|
|
void SetDocumentFromAvail();
|
|
FPDF_DOCUMENT document() const { return document_.get(); }
|
|
FPDF_DOCUMENT saved_document() const { return saved_document_.get(); }
|
|
FPDF_FORMHANDLE form_handle() const { return form_handle_.get(); }
|
|
FPDF_FORMHANDLE saved_form_handle() const { return saved_form_handle_.get(); }
|
|
|
|
// Wrapper for FPDFAvail_Create() to set `avail_`.
|
|
void CreateAvail(FX_FILEAVAIL* file_avail, FPDF_FILEACCESS* file);
|
|
FPDF_AVAIL avail() { return avail_.get(); }
|
|
|
|
// Create an empty document, and its form fill environment.
|
|
void CreateEmptyDocument();
|
|
|
|
// Create an empty document without a form fill environment.
|
|
void CreateEmptyDocumentWithoutFormFillEnvironment();
|
|
|
|
// Open the document specified by `filename`, and create its form fill
|
|
// environment, or return false on failure. The `filename` is relative to
|
|
// the test data directory where we store all the test files. `password` can
|
|
// be an empty string if the file is not password protected. If
|
|
// `javascript_opts` is kDisableJavascript, then the document will be given
|
|
// stubs in place of the real JS engine.
|
|
virtual bool OpenDocumentWithOptions(const std::string& filename,
|
|
const ByteString& password,
|
|
LinearizeOption linearize_option,
|
|
JavaScriptOption javascript_option);
|
|
|
|
// Variants provided for convenience.
|
|
bool OpenDocument(const std::string& filename);
|
|
bool OpenDocumentLinearized(const std::string& filename);
|
|
bool OpenDocumentWithPassword(const std::string& filename,
|
|
const ByteString& password);
|
|
bool OpenDocumentWithoutJavaScript(const std::string& filename);
|
|
|
|
// Close the document from a previous OpenDocument() call. This happens
|
|
// automatically at tear-down, and is usually not explicitly required,
|
|
// unless testing multiple documents or duplicate destruction.
|
|
void CloseDocument();
|
|
|
|
// Perform JavaScript actions that are to run at document open time.
|
|
void DoOpenActions();
|
|
|
|
// Determine the page numbers present in the document.
|
|
int GetFirstPageNum();
|
|
int GetPageCount();
|
|
|
|
// Load a specific page of the open document with a given non-negative
|
|
// `page_index`. On success, fire form events for the page and return a
|
|
// ScopedPage with the page handle. On failure, return an empty ScopedPage.
|
|
// The caller needs to let the ScopedPage go out of scope to properly unload
|
|
// the page, and must do so before the page's document and `this` get
|
|
// destroyed.
|
|
// The caller cannot call this for a `page_index` if it already obtained and
|
|
// holds the page handle for that page.
|
|
ScopedPage LoadScopedPage(int page_index);
|
|
|
|
// Prefer LoadScopedPage() above.
|
|
//
|
|
// Load a specific page of the open document with a given non-negative
|
|
// `page_index`. On success, fire form events for the page and return a page
|
|
// handle. On failure, return nullptr.
|
|
// The caller does not own the returned page handle, but must call
|
|
// UnloadPage() on it when done.
|
|
// The caller cannot call this for a `page_index` if it already obtained and
|
|
// holds the page handle for that page.
|
|
FPDF_PAGE LoadPage(int page_index);
|
|
|
|
// Same as LoadPage(), but does not fire form events.
|
|
FPDF_PAGE LoadPageNoEvents(int page_index);
|
|
|
|
// Fire form unload events and release the resources for a `page` obtained
|
|
// from LoadPage(). Further use of `page` is prohibited after calling this.
|
|
void UnloadPage(FPDF_PAGE page);
|
|
|
|
// Same as UnloadPage(), but does not fire form events.
|
|
void UnloadPageNoEvents(FPDF_PAGE page);
|
|
|
|
// Apply standard highlighting color/alpha to forms.
|
|
void SetInitialFormFieldHighlight(FPDF_FORMHANDLE form);
|
|
|
|
// RenderLoadedPageWithFlags() with no flags.
|
|
ScopedFPDFBitmap RenderLoadedPage(FPDF_PAGE page);
|
|
|
|
// Convert `page` loaded via LoadPage() into a bitmap with the specified page
|
|
// rendering `flags`.
|
|
//
|
|
// See public/fpdfview.h for a list of page rendering flags.
|
|
ScopedFPDFBitmap RenderLoadedPageWithFlags(FPDF_PAGE page, int flags);
|
|
|
|
// RenderSavedPageWithFlags() with no flags.
|
|
ScopedFPDFBitmap RenderSavedPage(FPDF_PAGE page);
|
|
|
|
// Convert `page` loaded via LoadSavedPage() into a bitmap with the specified
|
|
// page rendering `flags`.
|
|
//
|
|
// See public/fpdfview.h for a list of page rendering flags.
|
|
ScopedFPDFBitmap RenderSavedPageWithFlags(FPDF_PAGE page, int flags);
|
|
|
|
// Convert `page` into a bitmap with the specified page rendering `flags`.
|
|
// The form handle associated with `page` should be passed in via `handle`.
|
|
// If `handle` is nullptr, then forms on the page will not be rendered.
|
|
//
|
|
// See public/fpdfview.h for a list of page rendering flags.
|
|
// If none of the above Render methods are appropriate, then use this one.
|
|
static ScopedFPDFBitmap RenderPageWithFlags(FPDF_PAGE page,
|
|
FPDF_FORMHANDLE handle,
|
|
int flags);
|
|
|
|
// Simplified form of RenderPageWithFlags() with no handle and no flags.
|
|
static ScopedFPDFBitmap RenderPage(FPDF_PAGE page);
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
// Convert `page` into EMF with the specified page rendering `flags`.
|
|
static std::vector<uint8_t> RenderPageWithFlagsToEmf(FPDF_PAGE page,
|
|
int flags);
|
|
|
|
// Get the PostScript data from `emf_data`.
|
|
static std::string GetPostScriptFromEmf(pdfium::span<const uint8_t> emf_data);
|
|
#endif // BUILDFLAG(IS_WIN)
|
|
|
|
// Return bytes for each of the FPDFBitmap_* format types.
|
|
static int BytesPerPixelForFormat(int format);
|
|
|
|
protected:
|
|
using PageNumberToHandleMap = std::map<int, FPDF_PAGE>;
|
|
|
|
bool OpenDocumentHelper(const ByteString& password,
|
|
LinearizeOption linearize_option,
|
|
JavaScriptOption javascript_option,
|
|
FakeFileAccess* network_simulator,
|
|
ScopedFPDFDocument* document,
|
|
ScopedFPDFAvail* avail,
|
|
ScopedFPDFFormHandle* form_handle);
|
|
|
|
FPDF_FORMHANDLE SetupFormFillEnvironment(FPDF_DOCUMENT doc,
|
|
JavaScriptOption javascript_option);
|
|
|
|
// Return the hash of only the pixels in `bitmap`. i.e. Excluding the gap, if
|
|
// any, at the end of a row where the stride is larger than width * bpp.
|
|
static std::string HashBitmap(FPDF_BITMAP bitmap);
|
|
|
|
// For debugging purposes.
|
|
// Write `bitmap` as a PNG to `filename`.
|
|
static void WriteBitmapToPng(FPDF_BITMAP bitmap, const std::string& filename);
|
|
|
|
// Check `bitmap` matches `expectation_png_name`, where `expectation_png_name`
|
|
// is the name of testing/resources/embedder_tests/expectation_png_name.png.
|
|
static void CompareBitmap(FPDF_BITMAP bitmap,
|
|
std::string_view expectation_png_name);
|
|
|
|
// Like CompareBitmap(), except instead of just adding ".png" to
|
|
// `expectation_png_name`, this method will look for the expectation PNG using
|
|
// several suffixes in order: "_$renderer_$os_$cpu.png", "_$renderer_$os.png",
|
|
// "_$renderer.png", "_$os_$cpu.png", "_$os.png".
|
|
//
|
|
// $cpu is for macOS x86 only and is used to differentiate from macOS ARM.
|
|
//
|
|
// For example, with "hello_world", using Skia on macOS, the list of
|
|
// expectation PNGs are:
|
|
// - hello_world_skia_mac_x86.png
|
|
// - hello_world_skia_mac.png
|
|
// - hello_world_skia.png
|
|
// - hello_world_mac_x86.png
|
|
// - hello_world_mac.png
|
|
// - hello_world.png
|
|
//
|
|
// This is similar to the behavior in testing/tools/pngdiffer.py.
|
|
//
|
|
// `max_pixel_per_channel_delta` can optionally be set to tolerate minor pixel
|
|
// discrepancies. The default is exact matching.
|
|
static void CompareBitmapWithExpectationSuffix(
|
|
FPDF_BITMAP bitmap,
|
|
std::string_view expectation_png_name,
|
|
int max_pixel_per_channel_delta = 0);
|
|
|
|
// Same as `CompareBitmapWithExpectationSuffix()`, but automatically
|
|
// applies platform-specific tolerance.
|
|
static void CompareBitmapWithFuzzyExpectationSuffix(
|
|
FPDF_BITMAP bitmap,
|
|
std::string_view expectation_png_name);
|
|
|
|
void ClearString() { data_string_.clear(); }
|
|
const std::string& GetString() const { return data_string_; }
|
|
|
|
static int GetBlockFromString(void* param,
|
|
unsigned long pos,
|
|
unsigned char* buf,
|
|
unsigned long size);
|
|
|
|
// See comments in the respective non-Saved versions of these methods.
|
|
ScopedSavedDoc OpenScopedSavedDocument();
|
|
ScopedSavedDoc OpenScopedSavedDocumentWithPassword(
|
|
const ByteString& password);
|
|
ScopedSavedPage LoadScopedSavedPage(int page_index);
|
|
FPDF_PAGE LoadSavedPage(int page_index);
|
|
void CloseSavedPage(FPDF_PAGE page);
|
|
|
|
// See comments for CompareBitmap() and CompareBitmapWithExpectationSuffix()
|
|
// above.
|
|
void VerifySavedRendering(FPDF_PAGE page,
|
|
std::string_view expectation_png_name);
|
|
void VerifySavedRenderingWithExpectationSuffix(
|
|
FPDF_PAGE page,
|
|
std::string_view expectation_png_name);
|
|
void VerifySavedRenderingWithFuzzyExpectationSuffix(
|
|
FPDF_PAGE page,
|
|
std::string_view expectation_png_name);
|
|
void VerifySavedDocument(std::string_view expectation_png_name);
|
|
void VerifySavedDocumentWithExpectationSuffix(
|
|
std::string_view expectation_png_name);
|
|
void VerifySavedDocumentWithFuzzyExpectationSuffix(
|
|
std::string_view expectation_png_name);
|
|
|
|
void SetWholeFileAvailable();
|
|
|
|
#ifndef NDEBUG
|
|
// For debugging purposes.
|
|
// While open, write any data that gets passed to WriteBlockCallback() to
|
|
// `filename`. This is typically used to capture data from FPDF_SaveAsCopy()
|
|
// calls.
|
|
void OpenPDFFileForWrite(const std::string& filename);
|
|
void ClosePDFFileForWrite();
|
|
#endif
|
|
|
|
private:
|
|
static int WriteBlockCallback(FPDF_FILEWRITE* pFileWrite,
|
|
const void* data,
|
|
unsigned long size);
|
|
|
|
// Helper method for the methods below.
|
|
static int GetPageNumberForPage(const PageNumberToHandleMap& page_map,
|
|
FPDF_PAGE page);
|
|
// Find `page` inside `page_map_` and return the associated page number, or -1
|
|
// if `page` cannot be found.
|
|
int GetPageNumberForLoadedPage(FPDF_PAGE page) const;
|
|
|
|
// Same as GetPageNumberForLoadedPage(), but with `saved_page_map_`.
|
|
int GetPageNumberForSavedPage(FPDF_PAGE page) const;
|
|
|
|
// Helpers for opening saved documents. These are intended for internal use
|
|
// only. Callers should use the Scoped methods that manage lifetime
|
|
// automatically.
|
|
FPDF_DOCUMENT OpenSavedDocument();
|
|
FPDF_DOCUMENT OpenSavedDocumentWithPassword(const ByteString& password);
|
|
|
|
// Closes a document opened via OpenSavedDocument(). This must only be invoked
|
|
// in documents opened by the helpers above. This is intended for internal use
|
|
// in the destructor of the scoped methods.
|
|
void CloseSavedDocument();
|
|
|
|
void UnloadPageCommon(FPDF_PAGE page, bool do_events);
|
|
FPDF_PAGE LoadPageCommon(int page_index, bool do_events);
|
|
|
|
ScopedFPDFBitmap VerifySavedRenderingCommon(FPDF_PAGE page);
|
|
ScopedFPDFBitmap VerifySavedDocumentCommon();
|
|
|
|
// Keep `delegate_` after both possible pointees so it is destroyed first.
|
|
std::unique_ptr<Delegate> default_delegate_;
|
|
std::unique_ptr<Delegate> owned_delegate_;
|
|
UnownedPtr<Delegate> delegate_;
|
|
|
|
#ifdef PDF_ENABLE_XFA
|
|
int form_fill_info_version_ = 2;
|
|
#else // PDF_ENABLE_XFA
|
|
int form_fill_info_version_ = 1;
|
|
#endif // PDF_ENABLE_XFA
|
|
|
|
// must outlive `loader_`.
|
|
std::vector<uint8_t> file_contents_;
|
|
std::unique_ptr<TestLoader> loader_;
|
|
FPDF_FILEACCESS file_access_; // must outlive `avail_`.
|
|
std::unique_ptr<FakeFileAccess> fake_file_access_; // must outlive `avail_`.
|
|
ScopedFPDFAvail avail_;
|
|
ScopedFPDFDocument document_;
|
|
ScopedFPDFFormHandle form_handle_;
|
|
PageNumberToHandleMap page_map_;
|
|
|
|
FPDF_FILEACCESS saved_file_access_; // must outlive `saved_avail_`.
|
|
// must outlive `saved_avail_`.
|
|
std::unique_ptr<FakeFileAccess> saved_fake_file_access_;
|
|
ScopedFPDFAvail saved_avail_;
|
|
ScopedFPDFDocument saved_document_;
|
|
ScopedFPDFFormHandle saved_form_handle_;
|
|
PageNumberToHandleMap saved_page_map_;
|
|
|
|
std::string data_string_;
|
|
std::string saved_document_file_data_;
|
|
std::ofstream filestream_;
|
|
};
|
|
|
|
#endif // TESTING_EMBEDDER_TEST_H_
|