[PDF Ink Signatures] Clean up page unload preventers
Currently, entries in `PDFiumEngine::stroked_pages_unload_preventers_` never get removed. This is because `ink_stroke_objects_map_` entries originally never got discarded, as stated in the pdfium_engine.h comments. With https://crrev.com/1382585, this assumption is no longer true. Fix this discrepancy by: 1) Change `ink_stroke_objects_map_` to store the page index in addition to the page objects in the map's key. As a result, the key is now a new struct InkStrokeData, and `ink_stroke_objects_map_` gets renamed to `ink_stroke_data_`. Update the comments to reflect these changes. 2) Change PDFiumEngine::DiscardStroke() to check if a given page still has strokes after discards. If not, remove the associated page unload preventer. The change in (1) will be needed in the near future to fix https://crbug.com/395766076 as well. Since undo discards and thumbnail redrawing are relatively infrequent operations, there is no need to make `ink_stroke_data_` a map of maps. Bug: 378427083, 395766076 Change-Id: I05dad30b3413eeb7569d90b23aca9bc0218460dc Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6270681 Reviewed-by: Alan Screen <awscreen@chromium.org> Reviewed-by: Andy Phan <andyphan@chromium.org> Commit-Queue: Lei Zhang <thestig@chromium.org> Cr-Commit-Position: refs/heads/main@{#1420813}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
46ba987cc7
commit
6063291522
@ -602,7 +602,7 @@ PDFiumEngine::~PDFiumEngine() {
|
||||
find_results_.clear();
|
||||
selection_.clear();
|
||||
#if BUILDFLAG(ENABLE_PDF_INK2)
|
||||
ink_stroke_objects_map_.clear();
|
||||
ink_stroke_data_.clear();
|
||||
stroked_pages_unload_preventers_.clear();
|
||||
#endif
|
||||
|
||||
@ -4531,12 +4531,14 @@ void PDFiumEngine::ApplyStroke(int page_index,
|
||||
ink_stroked_pages_needing_regeneration_.insert(page_index);
|
||||
|
||||
bool inserted =
|
||||
ink_stroke_objects_map_.insert({id, std::move(page_objects)}).second;
|
||||
ink_stroke_data_
|
||||
.insert({id, InkStrokeData(page_index, std::move(page_objects))})
|
||||
.second;
|
||||
CHECK(inserted); // Stroke IDs should be unique when added.
|
||||
|
||||
// Since there is now a page reference in `ink_stroke_objects_map_`, ensure
|
||||
// that this page has a ScopedUnloadPreventer so that the reference doesn't
|
||||
// becomes stale if PDFiumPage::Unload() gets called.
|
||||
// Since there are now page references in `ink_stroke_data_`, ensure that this
|
||||
// page has a ScopedUnloadPreventer so that the references do not become stale
|
||||
// if PDFiumPage::Unload() gets called.
|
||||
if (!stroked_pages_unload_preventers_.contains(page_index)) {
|
||||
stroked_pages_unload_preventers_.insert(
|
||||
{page_index, PDFiumPage::ScopedUnloadPreventer(pdfium_page)});
|
||||
@ -4547,9 +4549,9 @@ void PDFiumEngine::UpdateStrokeActive(int page_index,
|
||||
InkStrokeId id,
|
||||
bool active) {
|
||||
CHECK(PageIndexInBounds(page_index));
|
||||
auto it = ink_stroke_objects_map_.find(id);
|
||||
CHECK(it != ink_stroke_objects_map_.end());
|
||||
for (FPDF_PAGEOBJECT page_object : it->second) {
|
||||
auto it = ink_stroke_data_.find(id);
|
||||
CHECK(it != ink_stroke_data_.end());
|
||||
for (FPDF_PAGEOBJECT page_object : it->second.page_objects) {
|
||||
bool result = FPDFPageObj_SetIsActive(page_object, active);
|
||||
CHECK(result);
|
||||
}
|
||||
@ -4558,18 +4560,26 @@ void PDFiumEngine::UpdateStrokeActive(int page_index,
|
||||
|
||||
void PDFiumEngine::DiscardStroke(int page_index, InkStrokeId id) {
|
||||
CHECK(PageIndexInBounds(page_index));
|
||||
auto it = ink_stroke_objects_map_.find(id);
|
||||
CHECK(it != ink_stroke_objects_map_.end());
|
||||
for (FPDF_PAGEOBJECT page_object : it->second) {
|
||||
auto it = ink_stroke_data_.find(id);
|
||||
CHECK(it != ink_stroke_data_.end());
|
||||
for (FPDF_PAGEOBJECT page_object : it->second.page_objects) {
|
||||
bool result =
|
||||
FPDFPage_RemoveObject(pages_[page_index]->GetPage(), page_object);
|
||||
CHECK(result);
|
||||
|
||||
// After FPDFPage_RemoveObject(), ownership of `page_object` is transferred
|
||||
// from the page to `this`, so free it.
|
||||
// FPDFPage_RemoveObject() transferred ownership of `page_object` to the
|
||||
// caller. Free it since `page_object` is being discarded.
|
||||
FPDFPageObj_Destroy(page_object);
|
||||
}
|
||||
ink_stroke_objects_map_.erase(it);
|
||||
ink_stroke_data_.erase(it);
|
||||
|
||||
bool page_still_has_strokes =
|
||||
std::ranges::any_of(ink_stroke_data_, [page_index](const auto& it) {
|
||||
return it.second.page_index == page_index;
|
||||
});
|
||||
if (!page_still_has_strokes) {
|
||||
stroked_pages_unload_preventers_.erase(page_index);
|
||||
}
|
||||
}
|
||||
|
||||
PDFLoadedWithV2InkAnnotations PDFiumEngine::ContainsV2InkPath(
|
||||
@ -4635,6 +4645,18 @@ void PDFiumEngine::RegenerateContents() {
|
||||
}
|
||||
ink_stroked_pages_needing_regeneration_.clear();
|
||||
}
|
||||
|
||||
PDFiumEngine::InkStrokeData::InkStrokeData(
|
||||
int page_index,
|
||||
std::vector<FPDF_PAGEOBJECT> page_objects)
|
||||
: page_index(page_index), page_objects(std::move(page_objects)) {}
|
||||
|
||||
PDFiumEngine::InkStrokeData::InkStrokeData(InkStrokeData&&) noexcept = default;
|
||||
|
||||
PDFiumEngine::InkStrokeData& PDFiumEngine::InkStrokeData::operator=(
|
||||
InkStrokeData&&) noexcept = default;
|
||||
|
||||
PDFiumEngine::InkStrokeData::~InkStrokeData() = default;
|
||||
#endif // BUILDFLAG(ENABLE_PDF_INK2)
|
||||
|
||||
PDFiumEngine::ProgressivePaint::ProgressivePaint(int index,
|
||||
|
@ -435,13 +435,18 @@ class PDFiumEngine : public DocumentLoader::Client, public IFSDK_PAUSE {
|
||||
InkModeledShapeId id,
|
||||
bool active);
|
||||
|
||||
// Regenerate contents for all pages that need it due to Ink strokes.
|
||||
void RegenerateContents();
|
||||
|
||||
const std::map<InkModeledShapeId, FPDF_PAGEOBJECT>&
|
||||
ink_modeled_shape_map_for_testing() const {
|
||||
return ink_modeled_shape_map_;
|
||||
}
|
||||
|
||||
// Regenerate contents for all pages that need it due to Ink strokes.
|
||||
void RegenerateContents();
|
||||
const std::map<int, PDFiumPage::ScopedUnloadPreventer>&
|
||||
stroked_pages_unload_preventers_for_testing() const {
|
||||
return stroked_pages_unload_preventers_;
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_PDF_INK2)
|
||||
|
||||
// DocumentLoader::Client:
|
||||
@ -1235,19 +1240,28 @@ class PDFiumEngine : public DocumentLoader::Client, public IFSDK_PAUSE {
|
||||
|
||||
#if BUILDFLAG(ENABLE_PDF_INK2)
|
||||
// Map of zero-based page indices with Ink strokes to page unload preventers.
|
||||
// Pages with Ink strokes have page references in `ink_stroke_objects_map_`,
|
||||
// so these unload preventers ensure those page pointers stay valid by
|
||||
// keeping the pages in memory. Entries don't get deleted from
|
||||
// `ink_stroke_objects_map_`, so just need one preventer per page instead of
|
||||
// per stroke.
|
||||
// Pages with Ink strokes have page references in `ink_stroke_data_`, so these
|
||||
// unload preventers ensure those page handles stay valid by keeping the page
|
||||
// in memory. Use one unload preventer per page for simplicity.
|
||||
std::map<int, PDFiumPage::ScopedUnloadPreventer>
|
||||
stroked_pages_unload_preventers_;
|
||||
|
||||
// The handles for stroke path page objects within the PDF document, mapped
|
||||
// using the `InkStrokeId` provided during stroke creation. The handles are
|
||||
// protected against becoming stale from page unloads by
|
||||
// `stroked_pages_unload_preventers_`.
|
||||
std::map<InkStrokeId, std::vector<FPDF_PAGEOBJECT>> ink_stroke_objects_map_;
|
||||
struct InkStrokeData {
|
||||
InkStrokeData(int page_index, std::vector<FPDF_PAGEOBJECT> page_objects);
|
||||
InkStrokeData(InkStrokeData&&) noexcept;
|
||||
InkStrokeData& operator=(InkStrokeData&&) noexcept;
|
||||
~InkStrokeData();
|
||||
|
||||
int page_index;
|
||||
|
||||
// The handles for stroke path page objects within the PDF document.
|
||||
// `stroked_pages_unload_preventers_` protects these handles from going
|
||||
// stale.
|
||||
std::vector<FPDF_PAGEOBJECT> page_objects;
|
||||
};
|
||||
|
||||
// Data associated for Ink strokes, keyed by stroke IDs.
|
||||
std::map<InkStrokeId, InkStrokeData> ink_stroke_data_;
|
||||
|
||||
// Tracks the pages which need to be regenerated before saving due to Ink
|
||||
// stroke changes.
|
||||
|
@ -2229,6 +2229,8 @@ TEST_P(PDFiumEngineInkDrawTest, StrokeData) {
|
||||
const base::FilePath kAppliedStroke2FilePath(
|
||||
GetInkTestDataFilePath("applied_stroke2.png"));
|
||||
CheckPdfRendering(page.GetPage(), kPageSizeInPoints, kAppliedStroke2FilePath);
|
||||
EXPECT_TRUE(engine->stroked_pages_unload_preventers_for_testing().contains(
|
||||
kPageIndex));
|
||||
|
||||
// Getting the save data should now have the new strokes.
|
||||
// Verify visibility of strokes in that copy. Must call GetSaveData()
|
||||
@ -2256,6 +2258,8 @@ TEST_P(PDFiumEngineInkDrawTest, StrokeData) {
|
||||
EXPECT_EQ(GetPdfMarkObjCountForTesting(engine->doc(),
|
||||
kInkAnnotationIdentifierKeyV2),
|
||||
1);
|
||||
EXPECT_TRUE(engine->stroked_pages_unload_preventers_for_testing().contains(
|
||||
kPageIndex));
|
||||
|
||||
// Set the highlighter stroke as active again, to perform the equivalent of an
|
||||
// "redo" action. The affected stroke should be included in the saved PDF data
|
||||
@ -2269,6 +2273,8 @@ TEST_P(PDFiumEngineInkDrawTest, StrokeData) {
|
||||
EXPECT_EQ(GetPdfMarkObjCountForTesting(engine->doc(),
|
||||
kInkAnnotationIdentifierKeyV2),
|
||||
2);
|
||||
EXPECT_TRUE(engine->stroked_pages_unload_preventers_for_testing().contains(
|
||||
kPageIndex));
|
||||
}
|
||||
|
||||
TEST_P(PDFiumEngineInkDrawTest, StrokeDiscardStroke) {
|
||||
@ -2311,6 +2317,8 @@ TEST_P(PDFiumEngineInkDrawTest, StrokeDiscardStroke) {
|
||||
const base::FilePath kAppliedStroke1FilePath(
|
||||
GetInkTestDataFilePath("applied_stroke1.png"));
|
||||
CheckPdfRendering(page.GetPage(), kPageSizeInPoints, kAppliedStroke1FilePath);
|
||||
EXPECT_TRUE(engine->stroked_pages_unload_preventers_for_testing().contains(
|
||||
kPageIndex));
|
||||
|
||||
// Set the stroke as inactive, to perform the equivalent of an "undo" action.
|
||||
engine->UpdateStrokeActive(kPageIndex, kStrokeId, /*active=*/false);
|
||||
@ -2324,11 +2332,15 @@ TEST_P(PDFiumEngineInkDrawTest, StrokeDiscardStroke) {
|
||||
kInkAnnotationIdentifierKeyV2),
|
||||
0);
|
||||
EXPECT_EQ(FPDFPage_CountObjects(page.GetPage()), 1);
|
||||
EXPECT_TRUE(engine->stroked_pages_unload_preventers_for_testing().contains(
|
||||
kPageIndex));
|
||||
|
||||
// Discard the stroke.
|
||||
engine->DiscardStroke(kPageIndex, kStrokeId);
|
||||
|
||||
EXPECT_EQ(FPDFPage_CountObjects(page.GetPage()), 0);
|
||||
EXPECT_FALSE(engine->stroked_pages_unload_preventers_for_testing().contains(
|
||||
kPageIndex));
|
||||
|
||||
// Draw a new stroke, reusing the same InkStrokeId. This can occur after an
|
||||
// undo action.
|
||||
@ -2346,6 +2358,8 @@ TEST_P(PDFiumEngineInkDrawTest, StrokeDiscardStroke) {
|
||||
GetInkTestDataFilePath("applied_stroke3.png"));
|
||||
CheckPdfRendering(page.GetPage(), kPageSizeInPoints, kAppliedStroke3FilePath);
|
||||
EXPECT_EQ(FPDFPage_CountObjects(page.GetPage()), 1);
|
||||
EXPECT_TRUE(engine->stroked_pages_unload_preventers_for_testing().contains(
|
||||
kPageIndex));
|
||||
}
|
||||
|
||||
TEST_P(PDFiumEngineInkDrawTest, LoadedV2InkPathsAndUpdateShapeActive) {
|
||||
|
Reference in New Issue
Block a user