diff --git a/src/display/annotation_storage.js b/src/display/annotation_storage.js
index d9c7bc21a..96e730223 100644
--- a/src/display/annotation_storage.js
+++ b/src/display/annotation_storage.js
@@ -19,6 +19,14 @@
 class AnnotationStorage {
   constructor() {
     this._storage = new Map();
+    this._modified = false;
+
+    // Callbacks to signal when the modification state is set or reset.
+    // This is used by the viewer to only bind on `beforeunload` if forms
+    // are actually edited to prevent doing so unconditionally since that
+    // can have undesirable efffects.
+    this.onSetModified = null;
+    this.onResetModified = null;
   }
 
   /**
@@ -49,6 +57,9 @@ class AnnotationStorage {
    * @param {Object} value
    */
   setValue(key, value) {
+    if (this._storage.get(key) !== value) {
+      this.setModified();
+    }
     this._storage.set(key, value);
   }
 
@@ -62,6 +73,24 @@ class AnnotationStorage {
   get size() {
     return this._storage.size;
   }
+
+  setModified() {
+    if (!this._modified) {
+      this._modified = true;
+      if (typeof this.onSetModified === "function") {
+        this.onSetModified();
+      }
+    }
+  }
+
+  resetModified() {
+    if (this._modified) {
+      this._modified = false;
+      if (typeof this.onResetModified === "function") {
+        this.onResetModified();
+      }
+    }
+  }
 }
 
 export { AnnotationStorage };
diff --git a/src/display/api.js b/src/display/api.js
index ff17ffec8..2c4d4c6d2 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -2536,12 +2536,16 @@ class WorkerTransport {
   }
 
   saveDocument(annotationStorage) {
-    return this.messageHandler.sendWithPromise("SaveDocument", {
-      numPages: this._numPages,
-      annotationStorage:
-        (annotationStorage && annotationStorage.getAll()) || null,
-      filename: this._fullReader.filename,
-    });
+    return this.messageHandler
+      .sendWithPromise("SaveDocument", {
+        numPages: this._numPages,
+        annotationStorage:
+          (annotationStorage && annotationStorage.getAll()) || null,
+        filename: this._fullReader.filename,
+      })
+      .finally(() => {
+        annotationStorage.resetModified();
+      });
   }
 
   getDestinations() {
diff --git a/test/unit/annotation_storage_spec.js b/test/unit/annotation_storage_spec.js
index 15585548a..1f55da3f9 100644
--- a/test/unit/annotation_storage_spec.js
+++ b/test/unit/annotation_storage_spec.js
@@ -38,5 +38,48 @@ describe("AnnotationStorage", function () {
       expect(value).toEqual("an other string");
       done();
     });
+
+    it("should call onSetModified() if value is changed", function (done) {
+      const annotationStorage = new AnnotationStorage();
+      let called = false;
+      const callback = function () {
+        called = true;
+      };
+      annotationStorage.onSetModified = callback;
+      annotationStorage.getOrCreateValue("asdf", "original");
+      expect(called).toBe(false);
+
+      // not changing value
+      annotationStorage.setValue("asdf", "original");
+      expect(called).toBe(false);
+
+      // changing value
+      annotationStorage.setValue("asdf", "modified");
+      expect(called).toBe(true);
+      done();
+    });
+  });
+
+  describe("ResetModified", function () {
+    it("should call onResetModified() if set", function (done) {
+      const annotationStorage = new AnnotationStorage();
+      let called = false;
+      const callback = function () {
+        called = true;
+      };
+      annotationStorage.onResetModified = callback;
+      annotationStorage.getOrCreateValue("asdf", "original");
+
+      // not changing value
+      annotationStorage.setValue("asdf", "original");
+      annotationStorage.resetModified();
+      expect(called).toBe(false);
+
+      // changing value
+      annotationStorage.setValue("asdf", "modified");
+      annotationStorage.resetModified();
+      expect(called).toBe(true);
+      done();
+    });
   });
 });
diff --git a/web/base_viewer.js b/web/base_viewer.js
index e4dce389c..385a2ffe0 100644
--- a/web/base_viewer.js
+++ b/web/base_viewer.js
@@ -127,6 +127,12 @@ function isSameScale(oldScale, newScale) {
   return false;
 }
 
+function beforeUnload(evt) {
+  evt.preventDefault();
+  evt.returnValue = "";
+  return false;
+}
+
 /**
  * Simple viewer control to display PDF content/pages.
  * @implements {IRenderableView}
@@ -436,6 +442,12 @@ class BaseViewer {
     const firstPagePromise = pdfDocument.getPage(1);
 
     const annotationStorage = pdfDocument.annotationStorage;
+    annotationStorage.onSetModified = function () {
+      window.addEventListener("beforeunload", beforeUnload);
+    };
+    annotationStorage.onResetModified = function () {
+      window.removeEventListener("beforeunload", beforeUnload);
+    };
 
     this._pagesCapability.promise.then(() => {
       this.eventBus.dispatch("pagesloaded", {