Add scoped objects and methods for saved doc / page in EmbedderTest

Add ScopedSavedDoc and ScopedSavedPage, along with corresponding
EmbedderTest methods to hand them out. Use these new scopers in
fpdf_annot_embeddertest.cpp to avoid having to manually call
CloseSavedPage() and CloseSavedDocument().

Bug: 407147676
Change-Id: Ic77873d9521c26bd6b294a236c7b0563504e3cfa
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/130111
Commit-Queue: Lei Zhang <thestig@chromium.org>
Reviewed-by: Tom Sepez <tsepez@chromium.org>
diff --git a/fpdfsdk/fpdf_annot_embeddertest.cpp b/fpdfsdk/fpdf_annot_embeddertest.cpp
index fb33ce1..1c8d9f7 100644
--- a/fpdfsdk/fpdf_annot_embeddertest.cpp
+++ b/fpdfsdk/fpdf_annot_embeddertest.cpp
@@ -716,23 +716,23 @@
 
   // Reopen the document and make sure it still renders the same. Since the link
   // does not have a border, it does not affect the rendering.
-  ASSERT_TRUE(OpenSavedDocument());
-  FPDF_PAGE saved_page = LoadSavedPage(0);
-  ASSERT_TRUE(saved_page);
-  VerifySavedRendering(saved_page, 200, 200, pdfium::HelloWorldChecksum());
-  EXPECT_EQ(1, FPDFPage_GetAnnotCount(saved_page));
-
   {
-    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(saved_page, 0));
+    ScopedSavedDoc saved_doc = OpenScopedSavedDocument();
+    ASSERT_TRUE(saved_doc);
+    ScopedSavedPage saved_page = LoadScopedSavedPage(0);
+    ASSERT_TRUE(saved_page);
+    VerifySavedRendering(saved_page.get(), 200, 200,
+                         pdfium::HelloWorldChecksum());
+    EXPECT_EQ(1, FPDFPage_GetAnnotCount(saved_page.get()));
+
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(saved_page.get(), 0));
     ASSERT_TRUE(annot);
     EXPECT_EQ(FPDF_ANNOT_LINK, FPDFAnnot_GetSubtype(annot.get()));
     VerifyUriActionInLink(document(), FPDFAnnot_GetLink(annot.get()), kUri);
-    VerifyUriActionInLink(
-        document(), FPDFLink_GetLinkAtPoint(saved_page, 40.0, 50.0), kUri);
+    VerifyUriActionInLink(document(),
+                          FPDFLink_GetLinkAtPoint(saved_page.get(), 40.0, 50.0),
+                          kUri);
   }
-
-  CloseSavedPage(saved_page);
-  CloseSavedDocument();
 }
 
 TEST_F(FPDFAnnotEmbedderTest, AddAndSaveUnderlineAnnotation) {
@@ -781,18 +781,19 @@
     return "dba153419f67b7c0c0e3d22d3e8910d5";
   }();
 
-  ASSERT_TRUE(OpenSavedDocument());
-  FPDF_PAGE saved_page = LoadSavedPage(0);
-  ASSERT_TRUE(saved_page);
-  VerifySavedRendering(saved_page, 612, 792, checksum);
-
-  // Check that the saved document has 2 annotations on the first page
-  EXPECT_EQ(2, FPDFPage_GetAnnotCount(saved_page));
-
   {
+    ScopedSavedDoc saved_doc = OpenScopedSavedDocument();
+    ASSERT_TRUE(saved_doc);
+    ScopedSavedPage saved_page = LoadScopedSavedPage(0);
+    ASSERT_TRUE(saved_page);
+    VerifySavedRendering(saved_page.get(), 612, 792, checksum);
+
+    // Check that the saved document has 2 annotations on the first page
+    EXPECT_EQ(2, FPDFPage_GetAnnotCount(saved_page.get()));
+
     // Check that the second annotation is an underline annotation and verify
     // its quadpoints.
-    ScopedFPDFAnnotation new_annot(FPDFPage_GetAnnot(saved_page, 1));
+    ScopedFPDFAnnotation new_annot(FPDFPage_GetAnnot(saved_page.get(), 1));
     ASSERT_TRUE(new_annot);
     EXPECT_EQ(FPDF_ANNOT_UNDERLINE, FPDFAnnot_GetSubtype(new_annot.get()));
     FS_QUADPOINTSF new_quadpoints;
@@ -803,9 +804,6 @@
     EXPECT_NEAR(quadpoints.x4, new_quadpoints.x4, 0.001f);
     EXPECT_NEAR(quadpoints.y4, new_quadpoints.y4, 0.001f);
   }
-
-  CloseSavedPage(saved_page);
-  CloseSavedDocument();
 }
 
 TEST_F(FPDFAnnotEmbedderTest, GetAndSetQuadPoints) {
@@ -1282,17 +1280,17 @@
 
   EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
 
-  // Open the saved document.
-  ASSERT_TRUE(OpenSavedDocument());
-  FPDF_PAGE saved_page = LoadSavedPage(0);
-  ASSERT_TRUE(saved_page);
-  VerifySavedRendering(saved_page, 595, 842, md5_new_annot);
-
-  // Check that the document has a correct count of annotations and objects.
-  EXPECT_EQ(3, FPDFPage_GetAnnotCount(saved_page));
-
   {
-    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(saved_page, 2));
+    ScopedSavedDoc saved_doc = OpenScopedSavedDocument();
+    ASSERT_TRUE(saved_doc);
+    ScopedSavedPage saved_page = LoadScopedSavedPage(0);
+    ASSERT_TRUE(saved_page);
+    VerifySavedRendering(saved_page.get(), 595, 842, md5_new_annot);
+
+    // Check that the document has a correct count of annotations and objects.
+    EXPECT_EQ(3, FPDFPage_GetAnnotCount(saved_page.get()));
+
+    ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(saved_page.get(), 2));
     ASSERT_TRUE(annot);
     EXPECT_EQ(1, FPDFAnnot_GetObjectCount(annot.get()));
 
@@ -1304,9 +1302,6 @@
     EXPECT_EQ(rect.right, new_rect.right);
     EXPECT_EQ(rect.top, new_rect.top);
   }
-
-  CloseSavedPage(saved_page);
-  CloseSavedDocument();
 }
 
 TEST_F(FPDFAnnotEmbedderTest, ModifyAnnotationFlags) {
@@ -1640,13 +1635,15 @@
   // Save the document and close the page.
   EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
 
-  // Open the saved annotation.
-  ASSERT_TRUE(OpenSavedDocument());
-  FPDF_PAGE saved_page = LoadSavedPage(0);
-  ASSERT_TRUE(saved_page);
-  VerifySavedRendering(saved_page, 595, 842, AnnotationStampWithApChecksum());
   {
-    ScopedFPDFAnnotation new_annot(FPDFPage_GetAnnot(saved_page, 0));
+    ScopedSavedDoc saved_doc = OpenScopedSavedDocument();
+    ASSERT_TRUE(saved_doc);
+    ScopedSavedPage saved_page = LoadScopedSavedPage(0);
+    ASSERT_TRUE(saved_page);
+    VerifySavedRendering(saved_page.get(), 595, 842,
+                         AnnotationStampWithApChecksum());
+
+    ScopedFPDFAnnotation new_annot(FPDFPage_GetAnnot(saved_page.get(), 0));
 
     // Check that the string value of the modified date is the newly-set
     // value.
@@ -1661,9 +1658,6 @@
                                        buf.data(), length_bytes));
     EXPECT_EQ(kNewDate, GetPlatformWString(buf.data()));
   }
-
-  CloseSavedPage(saved_page);
-  CloseSavedDocument();
 }
 
 TEST_F(FPDFAnnotEmbedderTest, GetNumberValue) {
@@ -1796,11 +1790,13 @@
   // Save the modified document, then reopen it.
   EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
 
-  ASSERT_TRUE(OpenSavedDocument());
-  FPDF_PAGE saved_page = LoadSavedPage(0);
-  ASSERT_TRUE(page);
   {
-    ScopedFPDFAnnotation new_annot(FPDFPage_GetAnnot(saved_page, 0));
+    ScopedSavedDoc saved_doc = OpenScopedSavedDocument();
+    ASSERT_TRUE(saved_doc);
+    ScopedSavedPage saved_page = LoadScopedSavedPage(0);
+    ASSERT_TRUE(saved_page);
+
+    ScopedFPDFAnnotation new_annot(FPDFPage_GetAnnot(saved_page.get(), 0));
 
     // Check that the new annotation value is equal to the value we set before
     // saving.
@@ -1814,10 +1810,6 @@
                                    buf.data(), rollover_length_bytes));
     EXPECT_EQ(L"new test ap", GetPlatformWString(buf.data()));
   }
-
-  // Close saved document.
-  CloseSavedPage(saved_page);
-  CloseSavedDocument();
 }
 
 TEST_F(FPDFAnnotEmbedderTest, RemoveOptionalAP) {
@@ -2240,17 +2232,19 @@
     EXPECT_EQ(kData, GetPlatformWString(buf.data()));
   }
 
+  // Save a copy, open the copy, and check the annotation again.
+  // Note that it renders the rotation.
+  EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
+
   {
-    // Save a copy, open the copy, and check the annotation again.
-    // Note that it renders the rotation.
-    EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
-    ASSERT_TRUE(OpenSavedDocument());
-    FPDF_PAGE saved_page = LoadSavedPage(0);
+    ScopedSavedDoc saved_doc = OpenScopedSavedDocument();
+    ASSERT_TRUE(saved_doc);
+    ScopedSavedPage saved_page = LoadScopedSavedPage(0);
     ASSERT_TRUE(saved_page);
 
-    EXPECT_EQ(2, FPDFPage_GetAnnotCount(saved_page));
+    EXPECT_EQ(2, FPDFPage_GetAnnotCount(saved_page.get()));
     {
-      ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(saved_page, 0));
+      ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(saved_page.get(), 0));
       ASSERT_TRUE(annot);
       EXPECT_EQ(FPDF_ANNOT_TEXT, FPDFAnnot_GetSubtype(annot.get()));
 
@@ -2261,7 +2255,7 @@
     }
 
     {
-      ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(saved_page, 0));
+      ScopedFPDFAnnotation annot(FPDFPage_GetAnnot(saved_page.get(), 0));
       ASSERT_TRUE(annot);
       // TODO(thestig): This return FPDF_ANNOT_UNKNOWN for some reason.
       // EXPECT_EQ(FPDF_ANNOT_TEXT, FPDFAnnot_GetSubtype(annot.get()));
@@ -2271,9 +2265,6 @@
                                              kBufSize));
       EXPECT_EQ(kData, GetPlatformWString(buf.data()));
     }
-
-    CloseSavedPage(saved_page);
-    CloseSavedDocument();
   }
 }
 
@@ -3858,13 +3849,13 @@
   // Save the document and close the page.
   EXPECT_TRUE(FPDF_SaveAsCopy(document(), this, 0));
 
-  ASSERT_TRUE(OpenSavedDocument());
-  FPDF_PAGE saved_page = LoadSavedPage(1);
-  ASSERT_TRUE(saved_page);
-  VerifySavedRendering(saved_page, 612, 792, modified_checksum);
-
-  CloseSavedPage(saved_page);
-  CloseSavedDocument();
+  {
+    ScopedSavedDoc saved_doc = OpenScopedSavedDocument();
+    ASSERT_TRUE(saved_doc);
+    ScopedSavedPage saved_page = LoadScopedSavedPage(1);
+    ASSERT_TRUE(saved_page);
+    VerifySavedRendering(saved_page.get(), 612, 792, modified_checksum);
+  }
 }
 
 TEST_F(FPDFAnnotEmbedderTest, GetAndAddFileAttachmentAnnotation) {
diff --git a/testing/embedder_test.cpp b/testing/embedder_test.cpp
index 125c5c7..fec7059 100644
--- a/testing/embedder_test.cpp
+++ b/testing/embedder_test.cpp
@@ -685,6 +685,10 @@
 }
 #endif  // BUILDFLAG(IS_WIN)
 
+EmbedderTest::ScopedSavedDoc EmbedderTest::OpenScopedSavedDocument() {
+  return ScopedSavedDoc(this);
+}
+
 FPDF_DOCUMENT EmbedderTest::OpenSavedDocument() {
   return OpenSavedDocumentWithPassword(nullptr);
 }
@@ -723,6 +727,11 @@
   saved_avail_.reset();
 }
 
+EmbedderTest::ScopedSavedPage EmbedderTest::LoadScopedSavedPage(
+    int page_index) {
+  return ScopedSavedPage(this, page_index);
+}
+
 FPDF_PAGE EmbedderTest::LoadSavedPage(int page_index) {
   CHECK(saved_form_handle());
   CHECK_GE(page_index, 0);
@@ -906,13 +915,41 @@
 }
 #endif
 
-EmbedderTest::ScopedPage::ScopedPage() : test_(nullptr), page_(nullptr) {}
+EmbedderTest::ScopedSavedDoc::ScopedSavedDoc()
+    : test_(nullptr), doc_(nullptr) {}
+
+EmbedderTest::ScopedSavedDoc::ScopedSavedDoc(EmbedderTest* test)
+    : test_(test), doc_(test->OpenSavedDocument()) {}
+
+EmbedderTest::ScopedSavedDoc::ScopedSavedDoc(ScopedSavedDoc&& that) noexcept
+    : test_(std::move(that.test_)), doc_(std::exchange(that.doc_, nullptr)) {}
+
+EmbedderTest::ScopedSavedDoc& EmbedderTest::ScopedSavedDoc::operator=(
+    ScopedSavedDoc&& that) noexcept {
+  test_ = std::move(that.test_);
+  doc_ = std::exchange(that.doc_, nullptr);
+  return *this;
+}
+
+EmbedderTest::ScopedSavedDoc::~ScopedSavedDoc() {
+  if (doc_) {
+    test_->CloseSavedDocument();
+  }
+}
+
+EmbedderTest::ScopedPageBase::ScopedPageBase(EmbedderTest* test, FPDF_PAGE page)
+    : test_(test), page_(page) {}
+
+EmbedderTest::ScopedPageBase::~ScopedPageBase() = default;
+
+EmbedderTest::ScopedPage::ScopedPage() : ScopedPageBase(nullptr, nullptr) {}
 
 EmbedderTest::ScopedPage::ScopedPage(EmbedderTest* test, int page_index)
-    : test_(test), page_(test->LoadPage(page_index)) {}
+    : ScopedPageBase(test, test->LoadPage(page_index)) {}
 
 EmbedderTest::ScopedPage::ScopedPage(EmbedderTest::ScopedPage&& that) noexcept
-    : test_(std::move(that.test_)), page_(std::exchange(that.page_, nullptr)) {}
+    : ScopedPageBase(std::move(that.test_),
+                     std::exchange(that.page_, nullptr)) {}
 
 EmbedderTest::ScopedPage& EmbedderTest::ScopedPage::operator=(
     EmbedderTest::ScopedPage&& that) noexcept {
@@ -926,3 +963,28 @@
     test_->UnloadPage(page_);
   }
 }
+
+EmbedderTest::ScopedSavedPage::ScopedSavedPage()
+    : ScopedPageBase(nullptr, nullptr) {}
+
+EmbedderTest::ScopedSavedPage::ScopedSavedPage(EmbedderTest* test,
+                                               int page_index)
+    : ScopedPageBase(test, test->LoadSavedPage(page_index)) {}
+
+EmbedderTest::ScopedSavedPage::ScopedSavedPage(
+    EmbedderTest::ScopedSavedPage&& that) noexcept
+    : ScopedPageBase(std::move(that.test_),
+                     std::exchange(that.page_, nullptr)) {}
+
+EmbedderTest::ScopedSavedPage& EmbedderTest::ScopedSavedPage::operator=(
+    EmbedderTest::ScopedSavedPage&& that) noexcept {
+  test_ = std::move(that.test_);
+  page_ = std::exchange(that.page_, nullptr);
+  return *this;
+}
+
+EmbedderTest::ScopedSavedPage::~ScopedSavedPage() {
+  if (page_) {
+    test_->CloseSavedPage(page_);
+  }
+}
diff --git a/testing/embedder_test.h b/testing/embedder_test.h
index 58fb0b0..87688bb 100644
--- a/testing/embedder_test.h
+++ b/testing/embedder_test.h
@@ -89,7 +89,40 @@
                                                  int modifiers) {}
   };
 
-  class ScopedPage {
+  class ScopedSavedDoc {
+   public:
+    ScopedSavedDoc();
+    explicit ScopedSavedDoc(EmbedderTest* test);
+    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);
@@ -98,14 +131,17 @@
     ScopedPage(ScopedPage&&) noexcept;
     ScopedPage& operator=(ScopedPage&&) noexcept;
     ~ScopedPage();
+  };
 
-    FPDF_PAGE get() { return page_; }
-
-    explicit operator bool() const { return !!page_; }
-
-   private:
-    UnownedPtr<EmbedderTest> test_;
-    FPDF_PAGE page_;
+  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();
@@ -283,9 +319,11 @@
                                 unsigned long size);
 
   // See comments in the respective non-Saved versions of these methods.
+  ScopedSavedDoc OpenScopedSavedDocument();
   FPDF_DOCUMENT OpenSavedDocument();
   FPDF_DOCUMENT OpenSavedDocumentWithPassword(const char* password);
   void CloseSavedDocument();
+  ScopedSavedPage LoadScopedSavedPage(int page_index);
   FPDF_PAGE LoadSavedPage(int page_index);
   void CloseSavedPage(FPDF_PAGE page);