Avoid underflow before calling CFDE_TextEditEngine::AdjustGap().

The text by itself may already exceed the limit if it was previously
granted an exemption. Increasing the limit maintains the invariant
that |text_length_| <= |character_limit_|.

-- Add the link to the explainer dsinclair suggested

Bug: chromium:1029437
Change-Id: I7962522dc8253f6070524c3342b65bd3aefca5bf
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/65190
Reviewed-by: Lei Zhang <thestig@chromium.org>
Reviewed-by: dsinclair <dsinclair@chromium.org>
Commit-Queue: Tom Sepez <tsepez@chromium.org>
diff --git a/xfa/fde/cfde_texteditengine.cpp b/xfa/fde/cfde_texteditengine.cpp
index 21b365b..0df28b4 100644
--- a/xfa/fde/cfde_texteditengine.cpp
+++ b/xfa/fde/cfde_texteditengine.cpp
@@ -308,12 +308,17 @@
   // engine. Otherwise, if you enter 123456789 for an SSN into a field
   // with a 9 character limit and we reformat to 123-45-6789 we'll truncate
   // the 89 when inserting into the text edit. See https://crbug.com/pdfium/1089
-  if (has_character_limit_ && add_operation != RecordOperation::kSkipNotify &&
-      text_length_ + length > character_limit_) {
-    exceeded_limit = true;
-    length = character_limit_ - text_length_;
+  if (has_character_limit_ && text_length_ + length > character_limit_) {
+    if (add_operation == RecordOperation::kSkipNotify) {
+      // Raise the limit to allow subsequent changes to expanded text.
+      character_limit_ = text_length_ + length;
+    } else {
+      // Trucate the text to comply with the limit.
+      CHECK(text_length_ <= character_limit_);
+      length = character_limit_ - text_length_;
+      exceeded_limit = true;
+    }
   }
-
   AdjustGap(idx, length);
 
   if (validation_enabled_ || limit_horizontal_area_ || limit_vertical_area_) {
diff --git a/xfa/fde/cfde_texteditengine.h b/xfa/fde/cfde_texteditengine.h
index 7c78c6c..d95994d 100644
--- a/xfa/fde/cfde_texteditengine.h
+++ b/xfa/fde/cfde_texteditengine.h
@@ -214,8 +214,11 @@
   float line_spacing_;
   std::vector<WideString::CharType> content_;
   size_t text_length_;
+
+  // See e.g. https://en.wikipedia.org/wiki/Gap_buffer
   size_t gap_position_;
   size_t gap_size_;
+
   size_t available_width_;
   size_t character_limit_;
   size_t visible_line_count_;
diff --git a/xfa/fde/cfde_texteditengine_unittest.cpp b/xfa/fde/cfde_texteditengine_unittest.cpp
index 38c0532..f1fc182 100644
--- a/xfa/fde/cfde_texteditengine_unittest.cpp
+++ b/xfa/fde/cfde_texteditengine_unittest.cpp
@@ -161,6 +161,24 @@
   engine()->SetDelegate(nullptr);
 }
 
+TEST_F(CFDE_TextEditEngineTest, InsertSkipNotify) {
+  engine()->SetHasCharacterLimit(true);
+  engine()->SetCharacterLimit(8);
+  engine()->Insert(0, L"Hello");
+  engine()->Insert(5, L" World",
+                   CFDE_TextEditEngine::RecordOperation::kSkipNotify);
+  EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
+
+  engine()->Insert(0, L"Not inserted");
+  EXPECT_STREQ(L"Hello World", engine()->GetText().c_str());
+
+  engine()->Delete(5, 1);
+  EXPECT_STREQ(L"HelloWorld", engine()->GetText().c_str());
+
+  engine()->Insert(0, L"****");
+  EXPECT_STREQ(L"*HelloWorld", engine()->GetText().c_str());
+}
+
 TEST_F(CFDE_TextEditEngineTest, Delete) {
   EXPECT_STREQ(L"", engine()->Delete(0, 50).c_str());
   EXPECT_STREQ(L"", engine()->GetText().c_str());