diff --git a/core/includes/form.inc b/core/includes/form.inc
index 5634be8..d280d59 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -871,6 +871,9 @@ function drupal_process_form($form_id, &$form, &$form_state) {
       // In case of errors, do not break HTML IDs of other forms.
       drupal_static_reset('drupal_html_id');
     }
+    elseif (!empty($form['#autosave'])) {
+      $form['#autosave']['#presave'] = TRUE;
+    }
 
     if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild']) {
       // Execute form submit handlers.
@@ -949,6 +952,20 @@ function drupal_process_form($form_id, &$form, &$form_state) {
     }
   }
 
+  if (!empty($form['#autosave']['#enabled'])) {
+    $form_autosave_settings = array(
+      'formAutosave' => array(
+        $form['#id'] => array(
+          'form' => $form['#id'],
+          'enabled' => TRUE,
+          'presave' => !empty($form['#autosave']['#presave']),
+          'changed' => !empty($form['#autosave']['#changed']) ? $form['#autosave']['#changed'] : 0,
+        ),
+      ),
+    );
+    drupal_add_js($form_autosave_settings, 'setting');
+  }
+
   // After processing the form, the form builder or a #process callback may
   // have set $form_state['cache'] to indicate that the form and form state
   // shall be cached. But the form may only be cached if the 'no_cache' property
diff --git a/core/misc/form.autosave.js b/core/misc/form.autosave.js
new file mode 100644
index 0000000..bfe11cc
--- /dev/null
+++ b/core/misc/form.autosave.js
@@ -0,0 +1,254 @@
+(function ($) {
+
+"use strict";
+
+/**
+ * Automatically save the contents of a form
+ * using localStorage when it is modified.
+ */
+Drupal.behaviors.formAutoSave = {
+  attach: function(context, settings) {
+
+    // Handle each form on the page.
+    for (var formId in settings.formAutosave) {
+      var $form = $('#' + formId);
+
+      // Get the last time this form was saved to the server and
+      // make sure it is a number. In the case where changed is
+      // not set on the form, this will be missing (NaN).
+      var changed = parseInt(settings.formAutosave[formId].changed, 10);
+
+      // Update the form with values from localStorage if available.
+      var changedForm = Drupal.form.localUpdate(formId, changed);
+
+      // If the form was updated from localStorage, alert the user.
+      if (changedForm === true) {
+        var localEditWarning = $(Drupal.theme('formLocalEditsWarning'));
+        var clearFormLink = $('<a class="button">' + Drupal.t('Discard previous changes') + '</a>');
+        localEditWarning.append(clearFormLink);
+        clearFormLink.on('click', function() {
+          $form[0].reset();
+          Drupal.form.localClear(formId);
+          localEditWarning.fadeOut('slow');
+        });
+        localEditWarning.insertBefore('#' + formId).hide().fadeIn('slow');
+      }
+
+      // Make an initial backup if the form requires it,
+      // for example on preview or on form validation error.
+      if (settings.formAutosave[formId].presave) {
+        Drupal.form.localSave($form[0], changed);
+      }
+
+      // On a formUpdated event, save the form's values to localStorage.
+      $form.on('change keyup', function() {
+        Drupal.form.localSave($form[0], changed);
+      });
+
+      // Clear the saved form when the user deliberatley
+      // clicks the overlay close link.
+      $(document).on('drupalOverlayCloseByLink', function() {
+        Drupal.form.localClear(formId);
+      });
+
+      // Clear form on any submission of data to the server.
+      $form.on('submit', function() {
+        Drupal.form.localClear(formId);
+      });
+    };
+  }
+};
+
+/**
+ * Namespace form functions in the form object.
+ */
+Drupal.form = {};
+
+/**
+ * Save form values into localStorage.
+ *
+ * @param element form
+ *   The form on the page whose values to save.
+ * @param int|NaN changed
+ *   The last time the form was saved on the server
+ *   or NaN if not specified.
+ */
+Drupal.form.localSave = function (form, changed) {
+
+  var formId = form.id;
+  var $form = $(form);
+
+  var serializedForm = {
+    // Store the last time this form was saved on the server.
+    _changed: changed
+  };
+
+  // Serialize all form text elements.
+  $form.find(".form-text").each(function() {
+    serializedForm[this.id] = this.value;
+  });
+
+  // Serialize all form textarea elements.
+  $form.find(".form-textarea").each(function() {
+    serializedForm[this.id] = this.value;
+  });
+
+  // Serialize all form checkbox elements.
+  $form.find(".form-checkbox").each(function() {
+    serializedForm[this.id] = this.checked;
+  });
+
+  // Serialize all form select elements.
+  $form.find(".form-select").each(function() {
+    var selectId = this.id;
+    var $select = $(this);
+    serializedForm[selectId] = [];
+    $select.find(":selected").each(function() {
+      serializedForm[selectId].push($select.val());
+    });
+  });
+
+  // Serialize all form radio elements.
+  $form.find(".form-radio").each(function() {
+    serializedForm[this.id] = this.checked;
+  });
+
+  var saveString = JSON.stringify(serializedForm);
+
+  // Put all serialized items into localStorage.
+  localStorage.setItem(Drupal.form.getLocalFormId(formId), saveString);
+};
+
+/**
+ * Updates the DOM representation of a form with a serialised
+ * version.
+ *
+ * @param string formId
+ *   The form id to update from local
+ * @param int|NaN changed
+ *   The form last updated timestamp from the server.
+ *   If the local copy is behind the server then it will
+ *   not be loaded.
+ *   If changed is NaN then the form does not provide this
+ *   so we can only assume local copy is latest.
+ * @param array[object] formValues
+ *   (optional) A loaded array of form elements
+ *   If not provided, the values will be loaded
+ *   from localStorage.
+ *
+ * @return boolean
+ *   TRUE if something was updated otherwise FALSE.
+ */
+Drupal.form.localUpdate = function(formId, changed, formValues) {
+
+  // If changed is not provided, make it NaN.
+  changed = typeof changed !== 'undefined' ? changed : Number.NaN;
+
+  // If formValues was not given, try to load from localStorage.
+  formValues = typeof formValues !== 'undefined' ? formValues : Drupal.form.localLoad(formId);
+
+  if (formValues.length === 0) {
+    // If there is nothing to update leave here.
+    return false;
+  }
+
+  // Check if the local data values for the form are out of date
+  // compared with the latest from the server.
+  if ( !isNaN(changed) && (isNaN(formValues._changed) || formValues._changed !== changed) ) {
+    // The local copy is out of date so do not load it.
+    return false;
+  }
+
+  // Remove the _changed data item before loading formValues.
+  delete formValues._changed;
+
+  // Loop over all form values and apply.
+  for ( var key in formValues ) {
+
+    // Get the DOM element to set a saved value for.
+    var el = document.getElementById(key);
+
+    if (el) {
+      // Get the type of element, its either sum subtype
+      // of INPUT or a TEXTAREA.
+      var type = el.tagName === 'INPUT' ? $(el).attr('type') : el.tagName;
+
+      if ( type === 'TEXTAREA' || type === 'text') {
+        el.value = formValues[key];
+      }
+      else if ( type === 'checkbox' || type === 'radio' ) {
+        if (el.checked != formValues[key]) {
+          // We want to physically click this as it might
+          // invoke other JS events such as displaying
+          // previously hidden page furniture.
+          el.click();
+        }
+      }
+      else if ( type === 'SELECT' ) {
+        for ( var optionsIndex = 0; optionsIndex < el.options.length; optionsIndex++ ) {
+          el.options[optionsIndex].selected = $.inArray(el.options[optionsIndex].value, formValues[key]) > -1;
+        }
+      }
+    }
+  }
+
+  return true;
+};
+
+/**
+ *  Loads form values from the localStorage
+ *
+ * @param string formId
+ *   The id of the form
+ *
+ * @return array[object]
+ *   An array of form value objects
+ */
+Drupal.form.localLoad = function (formId) {
+
+  var serializedForm = localStorage.getItem(Drupal.form.getLocalFormId(formId));
+
+  if (typeof(serializedForm) !== 'string') {
+    return [];
+  }
+
+  return JSON.parse( serializedForm );
+};
+
+/**
+ * Clear a form from localStorage.
+ *
+ * @param string formId
+ *   The form id to remove from localStorage.
+ */
+Drupal.form.localClear = function (formId) {
+  localStorage.removeItem(Drupal.form.getLocalFormId(formId));
+};
+
+
+/**
+ * Get Local Storage ID from Form ID
+ *
+ * @param string formId
+ *   The form id to get LocalStorage ID for.
+ *
+ * @return string
+ *   A local storage id of the form:
+ *   "Drupal.Form.node.add.article.article-node-form"
+ */
+Drupal.form.getLocalFormId = function (formId) {
+  return 'Drupal.Form' + window.location.pathname.replace(/\//g, '.') + formId;
+};
+
+/**
+ * Themed warning message to display when a forms
+ * values from Drupal are overriden with localStorage.
+ *
+ * @return string
+ *   The warning message.
+ */
+Drupal.theme.formLocalEditsWarning = function () {
+  return '<div class="messages warning">' + Drupal.t("You have previous changes which have not been saved yet. ") + '</div>';
+};
+
+})(jQuery);
diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index 215ad81..23910a6 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -56,10 +56,20 @@ protected function prepareEntity(EntityInterface $node) {
    */
   public function form(array $form, array &$form_state, EntityInterface $node) {
     $user_config = config('user.settings');
+
+    // Let the node form use localstorage javascript mechanism.
+    $form['#autosave'] = array(
+      '#enabled' => TRUE,
+      '#changed' => isset($node->changed) ? $node->changed : NULL,
+    );
+
     // Some special stuff when previewing a node.
     if (isset($form_state['node_preview'])) {
       $form['#prefix'] = $form_state['node_preview'];
       $node->in_preview = TRUE;
+
+      // In preview state, backup the unsaved state of the node on load.
+      $form['#autosave']['#presave'] = TRUE;
     }
     else {
       unset($node->in_preview);
diff --git a/core/modules/overlay/overlay-parent.js b/core/modules/overlay/overlay-parent.js
index caf9336..af868b4 100644
--- a/core/modules/overlay/overlay-parent.js
+++ b/core/modules/overlay/overlay-parent.js
@@ -137,7 +137,8 @@ Drupal.overlay.create = function () {
     .bind('drupalOverlayClose' + eventClass, $.proxy(this, 'eventhandlerRefreshPage'))
     .bind('drupalOverlayBeforeClose' + eventClass +
           ' drupalOverlayBeforeLoad' + eventClass +
-          ' drupalOverlayResize' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent'));
+          ' drupalOverlayResize' + eventClass +
+          ' drupalOverlayCloseByLink' + eventClass, $.proxy(this, 'eventhandlerDispatchEvent'));
 
   if ($('.overlay-displace-top, .overlay-displace-bottom').length) {
     $(document)
@@ -569,6 +570,11 @@ Drupal.overlay.eventhandlerOverrideLink = function (event) {
 
   // Close the overlay when the link contains the overlay-close class.
   if ($target.hasClass('overlay-close')) {
+    // Trigger event informing scripts the overlay was
+    // deliberatley closed rather than just navigating away.
+    var linkCloseOverlayEvent = $.Event('drupalOverlayCloseByLink');
+    $(document).trigger(linkCloseOverlayEvent);
+
     // Clearing the overlay URL fragment will close the overlay.
     $.bbq.removeState('overlay');
     return;
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 950fee6..9cf1d75 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1162,6 +1162,7 @@ function system_library_info() {
     'version' => VERSION,
     'js' => array(
       'core/misc/form.js' => array('group' => JS_LIBRARY, 'weight' => 1),
+      'core/misc/form.autosave.js' => array('group' => JS_LIBRARY, 'weight' => 2),
     ),
     'dependencies' => array(
       array('system', 'jquery'),
