diff --git a/core/misc/form.js b/core/misc/form.js
index ff134e2..68ea947 100644
--- a/core/misc/form.js
+++ b/core/misc/form.js
@@ -60,6 +60,62 @@ Drupal.behaviors.formUpdated = {
 };
 
 /**
+ * Prevents consecutive form submissions of identical form values.
+ *
+ * Repetitive form submissions that would submit the identical form values are
+ * prevented, unless the form values are different to the previously submitted
+ * values.
+ *
+ * A vastly simplified re-implementation of user-agent UX behavior that should
+ * be natively supported by major web browsers, but at this point in time, only
+ * Mozilla Firefox has a built-in protection.
+ *
+ * A form value-based approach ensures that the constraint is triggered for
+ * consecutive, identical form submissions only. A form button-based approach
+ * (1) would rely on [visible] buttons to exist where technically not required
+ * and (2) would require more complex state management if there are multiple
+ * buttons in a form.
+ *
+ * This implementation is based on form-level submit events only and relies on
+ * jQuery's (limited) serialize() method to determine submitted form values.
+ * As such, the following limitations exist:
+ *
+ * - Event handlers on form buttons that preventDefault() do not receive a
+ *   double-submit protection. That is deemed to be fine, since such button
+ *   events typically trigger reversible client-side or server-side operations
+ *   that are local to the context of a form only.
+ * - Changed values in advanced form controls, such as file or image elements,
+ *   are not part of the form values being compared between consecutive form
+ *   submits (due to limitations of jQuery.serialize()). That is deemed to be
+ *   acceptable, because if the user forgot to attach a file, then the size of
+ *   HTTP payload will most likely be small enough to be fully passed to the
+ *   server-side endpoint within (milli)seconds. If the user mistakenly attached
+ *   a wrong file and is technically versed enough to manage to cancel the form
+ *   submission (and HTTP payload) in order to attach a different file, then,
+ *   bad luck, that 0.1% edge-case is not supported here.
+ *
+ * Lastly, all forms using method="GET" are idempotent by definition of HTTP
+ * standards, so excluded in this implementation.
+ */
+Drupal.behaviors.formSingleSubmit = {
+  attach: function (context) {
+    function onFormSubmit(e) {
+      var $form = $(e.currentTarget);
+      var formValues = $form.serialize();
+      var previousValues = $form.data('form-submit-last');
+      if (previousValues == formValues) {
+        e.preventDefault();
+      }
+      else {
+        $form.data('form-submit-last', formValues);
+      }
+    }
+
+    $('body').once('form-single-submit').on('submit', 'form:not([method="GET"])', onFormSubmit);
+  }
+};
+
+/**
  * Prepopulate form fields with information from the visitor cookie.
  */
 Drupal.behaviors.fillUserInfoFromCookie = {
