diff --git a/core/includes/form.inc b/core/includes/form.inc
index 87f8edf..deda05c 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -1281,6 +1281,16 @@ function form_process_button($element, $form_state) {
   if (isset($element['#limit_validation_errors']) && $element['#limit_validation_errors'] !== FALSE) {
     $element['#attributes']['formnovalidate'] = 'formnovalidate';
   }
+  // Ensure to attach JS library to prevent duplicate form submissions, unless
+  // the form is submitted via GET or this form button explicitly opted-out.
+  if ($form_state['method'] != 'get') {
+    if (empty($element['#allow_multi_submit'])) {
+      $element['#attached']['library'][] = array('system', 'drupal.form');
+    }
+    else {
+      $element['#attributes']['data-multi-submit'] = 1;
+    }
+  }
   return $element;
 }
 
diff --git a/core/misc/form.js b/core/misc/form.js
index ff134e2..10f73dd 100644
--- a/core/misc/form.js
+++ b/core/misc/form.js
@@ -60,6 +60,28 @@ Drupal.behaviors.formUpdated = {
 };
 
 /**
+ * Prevents consecutive form submissions of identical form values.
+ *
+ * @todo Support the [data-multi-submit] attribute or ditch that idea?
+ */
+Drupal.behaviors.formPreventMultipleSubmit = {
+  attach: function (context) {
+    $(context).find('form:not([method="GET"])').on('submit', this.onFormSubmit);
+  },
+
+  onFormSubmit: function (event) {
+    var $form = $(event.target);
+    var previousValues = $form.data('form-submit-last'), formValues = $form.serialize();
+    if (previousValues == formValues) {
+      event.preventDefault();
+    }
+    else {
+      $form.data('form-submit-last', formValues);
+    }
+  }
+};
+
+/**
  * Prepopulate form fields with information from the visitor cookie.
  */
 Drupal.behaviors.fillUserInfoFromCookie = {
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 9500adf..0e79c66 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -288,6 +288,7 @@ function system_element_info() {
     '#name' => 'op',
     '#is_button' => TRUE,
     '#executes_submit_callback' => TRUE,
+    '#allow_multi_submit' => FALSE,
     '#limit_validation_errors' => FALSE,
     '#process' => array('form_process_button', 'ajax_process_form'),
     '#pre_render' => array('form_pre_render_button'),
@@ -298,6 +299,7 @@ function system_element_info() {
     '#name' => 'op',
     '#is_button' => TRUE,
     '#executes_submit_callback' => FALSE,
+    '#allow_multi_submit' => TRUE,
     '#limit_validation_errors' => FALSE,
     '#process' => array('form_process_button', 'ajax_process_form'),
     '#pre_render' => array('form_pre_render_button'),
@@ -307,6 +309,7 @@ function system_element_info() {
     '#input' => TRUE,
     '#is_button' => TRUE,
     '#executes_submit_callback' => TRUE,
+    '#allow_multi_submit' => FALSE,
     '#limit_validation_errors' => FALSE,
     '#process' => array('form_process_button', 'ajax_process_form'),
     '#return_value' => TRUE,
