0

[unseasoned-pdf] Add unit tests for DocumentLoadComplete().

Add unit tests for PdfViewPluginBase::DocumentLoadComplete(), which
exercises multiple private methods: DidStopLoading(), SendAttachments(),
SendBookmarks(), SendMetadata(), SendLoadingProgress() and
LoadAccessibility().

Also make `AccessibilityState` and `kMaximumSavedFileSize` public so
that they can be used in unit tests.

Change-Id: I8d548dd1f2eb5f9e6d860b0d8f1e1fe4fb329389
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2986560
Commit-Queue: Hui Yingst <nigi@chromium.org>
Reviewed-by: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#898413}
This commit is contained in:
Hui Yingst
2021-07-03 22:07:30 +00:00
committed by Chromium LUCI CQ
parent 12259abdda
commit 13d2d165f5
4 changed files with 393 additions and 10 deletions

@ -6,6 +6,16 @@
namespace chrome_pdf {
bool AccessibilityDocInfo::operator==(const AccessibilityDocInfo& other) const {
return page_count == other.page_count &&
text_accessible == other.text_accessible &&
text_copyable == other.text_copyable;
}
bool AccessibilityDocInfo::operator!=(const AccessibilityDocInfo& other) const {
return !(*this == other);
}
AccessibilityTextStyleInfo::AccessibilityTextStyleInfo() = default;
AccessibilityTextStyleInfo::AccessibilityTextStyleInfo(

@ -17,6 +17,9 @@
namespace chrome_pdf {
struct AccessibilityDocInfo {
bool operator==(const AccessibilityDocInfo& other) const;
bool operator!=(const AccessibilityDocInfo& other) const;
uint32_t page_count = 0;
bool text_accessible = false;
bool text_copyable = false;

@ -61,6 +61,16 @@ class PdfViewPluginBase : public PDFEngine::Client,
public:
using PDFEngine::Client::ScheduleTaskOnMainThread;
// Do not save files with over 100 MB. This cap should be kept in sync with
// and is also enforced in chrome/browser/resources/pdf/pdf_viewer.js.
static constexpr size_t kMaximumSavedFileSize = 100 * 1000 * 1000;
enum class AccessibilityState {
kOff = 0, // Off.
kPending, // Enabled but waiting for doc to load.
kLoaded, // Fully loaded.
};
enum class DocumentLoadState {
kLoading = 0,
kComplete,
@ -148,16 +158,6 @@ class PdfViewPluginBase : public PDFEngine::Client,
}
protected:
// Do not save files with over 100 MB. This cap should be kept in sync with
// and is also enforced in chrome/browser/resources/pdf/pdf_viewer.js.
static constexpr size_t kMaximumSavedFileSize = 100 * 1000 * 1000;
enum class AccessibilityState {
kOff = 0, // Off.
kPending, // Enabled but waiting for doc to load.
kLoaded, // Fully loaded.
};
struct BackgroundPart {
gfx::Rect location;
uint32_t color;

@ -12,11 +12,14 @@
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/memory/weak_ptr.h"
#include "base/test/icu_test_util.h"
#include "base/time/time.h"
#include "base/values.h"
#include "pdf/accessibility_structs.h"
#include "pdf/buildflags.h"
#include "pdf/content_restriction.h"
#include "pdf/document_attachment_info.h"
#include "pdf/document_metadata.h"
#include "pdf/pdf_engine.h"
#include "pdf/pdfium/pdfium_engine.h"
#include "pdf/ppapi_migration/callback.h"
@ -24,6 +27,7 @@
#include "pdf/ppapi_migration/url_loader.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/geometry/size.h"
@ -56,10 +60,24 @@ class TestPDFiumEngine : public PDFiumEngine {
return base::Contains(permissions_, permission);
}
const std::vector<DocumentAttachmentInfo>& GetDocumentAttachmentInfoList()
const override {
return doc_attachment_info_list_;
}
const DocumentMetadata& GetDocumentMetadata() const override {
return metadata_;
}
int GetNumberOfPages() const override {
return static_cast<int>(kPageNumber);
}
base::Value GetBookmarks() override {
// Return an empty bookmark list.
return base::Value(base::Value::Type::LIST);
}
uint32_t GetLoadedByteSize() override { return sizeof(kSaveData); }
bool ReadLoadedBytes(uint32_t length, void* buffer) override {
@ -80,16 +98,112 @@ class TestPDFiumEngine : public PDFiumEngine {
permissions_.insert(permission);
}
protected:
std::vector<DocumentAttachmentInfo>& doc_attachment_info_list() {
return doc_attachment_info_list_;
}
DocumentMetadata& metadata() { return metadata_; }
private:
std::vector<DocumentAttachmentInfo> doc_attachment_info_list_;
DocumentMetadata metadata_;
base::flat_set<PDFEngine::DocumentPermission> permissions_;
};
class TestPDFiumEngineWithDocInfo : public TestPDFiumEngine {
public:
explicit TestPDFiumEngineWithDocInfo(PDFEngine::Client* client)
: TestPDFiumEngine(client) {}
base::Value GetBookmarks() override {
// Create `bookmark1` which navigates to an in-doc position. This bookmark
// will be in the top-level bookmark list.
base::Value bookmark1(base::Value::Type::DICTIONARY);
bookmark1.SetStringKey("title", "Bookmark 1");
bookmark1.SetIntKey("page", 2);
bookmark1.SetIntKey("x", 10);
bookmark1.SetIntKey("y", 20);
bookmark1.SetDoubleKey("zoom", 2.0);
// Create `bookmark2` which navigates to a web page. This bookmark will be a
// child of `bookmark1`.
base::Value bookmark2(base::Value::Type::DICTIONARY);
bookmark2.SetStringKey("title", "Bookmark 2");
bookmark2.SetStringKey("uri", "test.com");
base::Value children_of_bookmark1(base::Value::Type::LIST);
children_of_bookmark1.Append(std::move(bookmark2));
bookmark1.SetKey("children", std::move(children_of_bookmark1));
// Create the top-level bookmark list.
base::Value bookmarks(base::Value::Type::LIST);
bookmarks.Append(std::move(bookmark1));
return bookmarks;
}
absl::optional<gfx::Size> GetUniformPageSizePoints() override {
return gfx::Size(1000, 1200);
}
// Initialize attachments, metadata for testing.
void InitializeDocument() {
InitializeDocumentAttachments();
InitializeDocumentMetadata();
}
private:
void InitializeDocumentAttachments() {
doc_attachment_info_list().resize(3);
// A regular attachment.
doc_attachment_info_list()[0].name = u"attachment1.txt";
doc_attachment_info_list()[0].creation_date = u"D:20170712214438-07'00'";
doc_attachment_info_list()[0].modified_date = u"D:20160115091400";
doc_attachment_info_list()[0].is_readable = true;
doc_attachment_info_list()[0].size_bytes = 13u;
// An unreadable attachment.
doc_attachment_info_list()[1].name = u"attachment2.pdf";
doc_attachment_info_list()[1].is_readable = false;
// A readable attachment that exceeds download size limit.
doc_attachment_info_list()[2].name = u"attachment3.mov";
doc_attachment_info_list()[2].is_readable = true;
doc_attachment_info_list()[2].size_bytes =
PdfViewPluginBase::kMaximumSavedFileSize + 1;
}
void InitializeDocumentMetadata() {
metadata().version = PdfVersion::k1_7;
metadata().size_bytes = 13u;
metadata().page_count = 13u;
metadata().linearized = true;
metadata().has_attachments = true;
metadata().tagged = true;
metadata().form_type = FormType::kAcroForm;
metadata().title = "Title";
metadata().author = "Author";
metadata().subject = "Subject";
metadata().keywords = "Keywords";
metadata().creator = "Creator";
metadata().producer = "Producer";
ASSERT_TRUE(base::Time::FromUTCString("2021-05-04 11:12:13",
&metadata().creation_date));
ASSERT_TRUE(
base::Time::FromUTCString("2021-06-04 15:16:17", &metadata().mod_date));
}
};
// This test approach relies on PdfViewPluginBase continuing to exist.
// PdfViewPluginBase and PdfViewWebPlugin are going to merge once
// OutOfProcessInstance is deprecated.
class FakePdfViewPluginBase : public PdfViewPluginBase {
public:
// Public for testing.
using PdfViewPluginBase::accessibility_state;
using PdfViewPluginBase::document_load_state;
using PdfViewPluginBase::edit_mode;
using PdfViewPluginBase::engine;
@ -195,6 +309,93 @@ class FakePdfViewPluginBase : public PdfViewPluginBase {
std::vector<base::Value> sent_messages_;
};
base::Value CreateExpectedFormTextFieldFocusChangeResponse() {
base::Value message(base::Value::Type::DICTIONARY);
message.SetStringKey("type", "formFocusChange");
message.SetBoolKey("focused", false);
return message;
}
base::Value CreateExpectedAttachmentsResponse() {
base::Value attachments(base::Value::Type::LIST);
{
base::Value attachment(base::Value::Type::DICTIONARY);
attachment.SetStringKey("name", "attachment1.txt");
attachment.SetIntKey("size", 13);
attachment.SetBoolKey("readable", true);
attachments.Append(std::move(attachment));
}
{
base::Value attachment(base::Value::Type::DICTIONARY);
attachment.SetStringKey("name", "attachment2.pdf");
attachment.SetIntKey("size", 0);
attachment.SetBoolKey("readable", false);
attachments.Append(std::move(attachment));
}
{
base::Value attachment(base::Value::Type::DICTIONARY);
attachment.SetStringKey("name", "attachment3.mov");
attachment.SetIntKey("size", -1);
attachment.SetBoolKey("readable", true);
attachments.Append(std::move(attachment));
}
base::Value message(base::Value::Type::DICTIONARY);
message.SetStringKey("type", "attachments");
message.SetKey("attachmentsData", std::move(attachments));
return message;
}
base::Value CreateExpectedBookmarksResponse(base::Value bookmarks) {
base::Value message(base::Value::Type::DICTIONARY);
message.SetStringKey("type", "bookmarks");
message.SetKey("bookmarksData", std::move(bookmarks));
return message;
}
base::Value CreateExpectedMetadataResponse() {
base::Value metadata(base::Value::Type::DICTIONARY);
metadata.SetStringKey("version", "1.7");
metadata.SetStringKey("fileSize", "13 B");
metadata.SetBoolKey("linearized", true);
metadata.SetStringKey("title", "Title");
metadata.SetStringKey("author", "Author");
metadata.SetStringKey("subject", "Subject");
metadata.SetStringKey("keywords", "Keywords");
metadata.SetStringKey("creator", "Creator");
metadata.SetStringKey("producer", "Producer");
metadata.SetStringKey("creationDate", "5/4/21, 4:12:13 AM");
metadata.SetStringKey("modDate", "6/4/21, 8:16:17 AM");
metadata.SetStringKey("pageSize", "13.89 × 16.67 in (portrait)");
metadata.SetBoolKey("canSerializeDocument", true);
base::Value message(base::Value::Type::DICTIONARY);
message.SetStringKey("type", "metadata");
message.SetKey("metadataData", std::move(metadata));
return message;
}
base::Value CreateExpectedNoMetadataResponse() {
base::Value metadata(base::Value::Type::DICTIONARY);
metadata.SetStringKey("fileSize", "0 B");
metadata.SetBoolKey("linearized", false);
metadata.SetStringKey("pageSize", "Varies");
metadata.SetBoolKey("canSerializeDocument", true);
base::Value message(base::Value::Type::DICTIONARY);
message.SetStringKey("type", "metadata");
message.SetKey("metadataData", std::move(metadata));
return message;
}
base::Value CreateExpectedLoadingProgressResponse() {
base::Value message(base::Value::Type::DICTIONARY);
message.SetStringKey("type", "loadProgress");
message.SetDoubleKey("progress", 100);
return message;
}
base::Value CreateSaveRequestMessage(PdfViewPluginBase::SaveRequestType type,
const std::string& token) {
base::Value message(base::Value::Type::DICTIONARY);
@ -237,6 +438,30 @@ class PdfViewPluginBaseWithEngineTest : public PdfViewPluginBaseTest {
}
};
class PdfViewPluginBaseWithScopedLocaleTest
: public PdfViewPluginBaseWithEngineTest {
protected:
base::test::ScopedRestoreICUDefaultLocale scoped_locale_{"en_US"};
base::test::ScopedRestoreDefaultTimezone la_time_{"America/Los_Angeles"};
};
class PdfViewPluginBaseWithDocInfoTest
: public PdfViewPluginBaseWithScopedLocaleTest {
public:
void SetUp() override {
std::unique_ptr<TestPDFiumEngineWithDocInfo> engine =
std::make_unique<TestPDFiumEngineWithDocInfo>(&fake_plugin_);
fake_plugin_.InitializeEngine(std::move(engine));
// Initialize some arbitrary document information for the engine.
static_cast<TestPDFiumEngineWithDocInfo*>(fake_plugin_.engine())
->InitializeDocument();
}
};
using PdfViewPluginBaseWithoutDocInfoTest =
PdfViewPluginBaseWithScopedLocaleTest;
TEST_F(PdfViewPluginBaseTest, CreateUrlLoaderInFullFrame) {
fake_plugin_.set_full_frame(true);
ASSERT_TRUE(fake_plugin_.full_frame());
@ -263,6 +488,151 @@ TEST_F(PdfViewPluginBaseTest, CreateUrlLoaderWithoutFullFrame) {
EXPECT_FALSE(fake_plugin_.GetDidCallStartLoadingForTesting());
}
TEST_F(PdfViewPluginBaseWithDocInfoTest,
DocumentLoadCompleteInFullFramePdfViewerWithAccessibilityEnabled) {
// Notify the render frame about document loading.
fake_plugin_.set_full_frame(true);
ASSERT_TRUE(fake_plugin_.full_frame());
fake_plugin_.CreateUrlLoader();
ASSERT_FALSE(fake_plugin_.IsPrintPreview());
ASSERT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading,
fake_plugin_.document_load_state());
// Change the accessibility state to pending so that accessibility can be
// loaded later.
fake_plugin_.EnableAccessibility();
EXPECT_EQ(PdfViewPluginBase::AccessibilityState::kPending,
fake_plugin_.accessibility_state());
EXPECT_CALL(fake_plugin_, UserMetricsRecordAction("PDF.LoadSuccess"));
EXPECT_CALL(fake_plugin_, SetFormFieldInFocus(false));
EXPECT_CALL(fake_plugin_, PluginDidStopLoading());
EXPECT_CALL(fake_plugin_,
SetContentRestrictions(fake_plugin_.GetContentRestrictions()));
EXPECT_CALL(fake_plugin_,
SetAccessibilityDocInfo(fake_plugin_.GetAccessibilityDocInfo()));
fake_plugin_.DocumentLoadComplete();
EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete,
fake_plugin_.document_load_state());
EXPECT_EQ(PdfViewPluginBase::AccessibilityState::kLoaded,
fake_plugin_.accessibility_state());
// Check all the sent messages.
ASSERT_EQ(5u, fake_plugin_.sent_messages().size());
EXPECT_EQ(CreateExpectedFormTextFieldFocusChangeResponse(),
fake_plugin_.sent_messages()[0]);
EXPECT_EQ(CreateExpectedAttachmentsResponse(),
fake_plugin_.sent_messages()[1]);
EXPECT_EQ(
CreateExpectedBookmarksResponse(fake_plugin_.engine()->GetBookmarks()),
fake_plugin_.sent_messages()[2]);
EXPECT_EQ(CreateExpectedMetadataResponse(), fake_plugin_.sent_messages()[3]);
EXPECT_EQ(CreateExpectedLoadingProgressResponse(),
fake_plugin_.sent_messages()[4]);
}
TEST_F(PdfViewPluginBaseWithDocInfoTest,
DocumentLoadCompleteInFullFramePdfViewerWithAccessibilityDisabled) {
// Notify the render frame about document loading.
fake_plugin_.set_full_frame(true);
ASSERT_TRUE(fake_plugin_.full_frame());
fake_plugin_.CreateUrlLoader();
ASSERT_FALSE(fake_plugin_.IsPrintPreview());
ASSERT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading,
fake_plugin_.document_load_state());
ASSERT_EQ(PdfViewPluginBase::AccessibilityState::kOff,
fake_plugin_.accessibility_state());
EXPECT_CALL(fake_plugin_, UserMetricsRecordAction("PDF.LoadSuccess"));
EXPECT_CALL(fake_plugin_, SetFormFieldInFocus(false));
EXPECT_CALL(fake_plugin_, PluginDidStopLoading());
EXPECT_CALL(fake_plugin_,
SetContentRestrictions(fake_plugin_.GetContentRestrictions()));
EXPECT_CALL(fake_plugin_,
SetAccessibilityDocInfo(fake_plugin_.GetAccessibilityDocInfo()))
.Times(0);
fake_plugin_.DocumentLoadComplete();
EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete,
fake_plugin_.document_load_state());
EXPECT_EQ(PdfViewPluginBase::AccessibilityState::kOff,
fake_plugin_.accessibility_state());
// Check all the sent messages.
ASSERT_EQ(5u, fake_plugin_.sent_messages().size());
EXPECT_EQ(CreateExpectedFormTextFieldFocusChangeResponse(),
fake_plugin_.sent_messages()[0]);
EXPECT_EQ(CreateExpectedAttachmentsResponse(),
fake_plugin_.sent_messages()[1]);
EXPECT_EQ(
CreateExpectedBookmarksResponse(fake_plugin_.engine()->GetBookmarks()),
fake_plugin_.sent_messages()[2]);
EXPECT_EQ(CreateExpectedMetadataResponse(), fake_plugin_.sent_messages()[3]);
EXPECT_EQ(CreateExpectedLoadingProgressResponse(),
fake_plugin_.sent_messages()[4]);
}
TEST_F(PdfViewPluginBaseWithDocInfoTest,
DocumentLoadCompleteInNonFullFramePdfViewer) {
ASSERT_FALSE(fake_plugin_.full_frame());
fake_plugin_.CreateUrlLoader();
ASSERT_FALSE(fake_plugin_.IsPrintPreview());
ASSERT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading,
fake_plugin_.document_load_state());
EXPECT_CALL(fake_plugin_, UserMetricsRecordAction("PDF.LoadSuccess"));
EXPECT_CALL(fake_plugin_, SetFormFieldInFocus(false));
EXPECT_CALL(fake_plugin_, PluginDidStopLoading()).Times(0);
EXPECT_CALL(fake_plugin_,
SetContentRestrictions(fake_plugin_.GetContentRestrictions()))
.Times(0);
fake_plugin_.DocumentLoadComplete();
EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete,
fake_plugin_.document_load_state());
// Check all the sent messages.
ASSERT_EQ(5u, fake_plugin_.sent_messages().size());
EXPECT_EQ(CreateExpectedFormTextFieldFocusChangeResponse(),
fake_plugin_.sent_messages()[0]);
EXPECT_EQ(CreateExpectedAttachmentsResponse(),
fake_plugin_.sent_messages()[1]);
EXPECT_EQ(
CreateExpectedBookmarksResponse(fake_plugin_.engine()->GetBookmarks()),
fake_plugin_.sent_messages()[2]);
EXPECT_EQ(CreateExpectedMetadataResponse(), fake_plugin_.sent_messages()[3]);
EXPECT_EQ(CreateExpectedLoadingProgressResponse(),
fake_plugin_.sent_messages()[4]);
}
TEST_F(PdfViewPluginBaseWithoutDocInfoTest, DocumentLoadCompletePostMessages) {
fake_plugin_.CreateUrlLoader();
ASSERT_FALSE(fake_plugin_.IsPrintPreview());
ASSERT_EQ(PdfViewPluginBase::DocumentLoadState::kLoading,
fake_plugin_.document_load_state());
EXPECT_CALL(fake_plugin_, UserMetricsRecordAction("PDF.LoadSuccess"));
EXPECT_CALL(fake_plugin_, SetFormFieldInFocus(false));
fake_plugin_.DocumentLoadComplete();
EXPECT_EQ(PdfViewPluginBase::DocumentLoadState::kComplete,
fake_plugin_.document_load_state());
// Check the sent messages when the document doesn't have any metadata,
// attachments or bookmarks.
ASSERT_EQ(3u, fake_plugin_.sent_messages().size());
EXPECT_EQ(CreateExpectedFormTextFieldFocusChangeResponse(),
fake_plugin_.sent_messages()[0]);
EXPECT_EQ(CreateExpectedNoMetadataResponse(),
fake_plugin_.sent_messages()[1]);
EXPECT_EQ(CreateExpectedLoadingProgressResponse(),
fake_plugin_.sent_messages()[2]);
}
TEST_F(PdfViewPluginBaseTest, DocumentLoadFailedWithNotifiedRenderFrame) {
// Notify the render frame about document loading.
fake_plugin_.set_full_frame(true);