diff --git a/core/misc/form.autosave.js b/core/misc/form.autosave.js
new file mode 100644
index 0000000..52b56de
--- /dev/null
+++ b/core/misc/form.autosave.js
@@ -0,0 +1,234 @@
+(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.
+    $('form').each(function() {
+
+      var formId = $(this).attr('id');
+
+      // 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($(this).find("input[name='changed']").val(), 10);
+
+      // @TODO Make this work generically, not just node form.
+      if (/-node-form/.test(formId)) {
+        var changedForm = Drupal.form.localUpdate(formId, changed);
+
+        if (changedForm === true) {
+          $(Drupal.theme('formLocalEditsWarning')).insertBefore('#' + formId).hide().fadeIn('slow');
+        }
+
+        // Make an initial backup if the form requires it,
+        // forexample on preview or on form validation error.
+        if ($(this).hasClass('form-save-to-localstorage-on-load')) {
+          Drupal.form.localSave(formId, changed);
+        }
+
+        // @TODO Deal with save situation where user has same form open in two or more tabs.
+        // On a formUpdated event, save the forms state.
+        $(this).on('formUpdated', function(e) {
+          Drupal.form.localSave(formId, changed);
+        });
+
+        // Clear the saved form when the user deliberatley
+        // clicks the overlay close link.
+        $(document).bind('drupalOverlayCloseByLink', function (event) {
+          Drupal.form.localClear(formId);
+        });
+
+        // Clear form on submission except if the submitting
+        // element has registered not to.
+        $(this).on('submit', function() {
+          Drupal.form.localClear(formId);
+        });
+      }
+    });
+  }
+};
+
+/**
+ * Namespace form functions in the form object.
+ */
+Drupal.form = {};
+
+/**
+ * Save form values into localStorage.
+ *
+ * @param string formId
+ *   The form id 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 (formId, changed) {
+
+  var serializedForm = {
+    // Store the last time this form was saved on the server.
+    _changed: changed
+  };
+
+  // Serialize all form text elements.
+  $("#" + formId + " .form-text").each(function() {
+    serializedForm[this.id] = this.value;
+  });
+
+  // Serialize all form text area elements.
+  $("#" + formId + " .form-textarea").each(function() {
+    serializedForm[this.id] = this.value;
+  });
+
+  // Serialize all form checkbox elements.
+  $("#" + formId + " .form-checkbox").each(function() {
+    serializedForm[this.id] = this.checked;
+  });
+
+  var saveString = JSON.stringify(serializedForm);
+
+  // @TODO: Add radios and select elements.
+  // 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;
+  }
+
+  console.log(changed);
+  console.log(formValues._changed);
+
+  // 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);
+
+    // Get the type of element, its either sum subtype
+    // of INPUT or a TEXTAREA.
+    var type = el.tagName === 'INPUT' ? $(el).attr('type') : el.tagName;
+
+    // @TODO Add support for selects / radios.
+    // @TODO Replace nasty nested ifs with something more elegant.
+    if ( type === 'TEXTAREA' || type === 'text') {
+      el.value = formValues[key];
+    }
+    else if ( type === 'checkbox' ) {
+      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();
+      }
+    }
+  }
+
+  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.
+ * @param interval interval
+ *   (optional) A interval timer reference that should
+ *   be stopped when a form is cleared.
+ *
+ * @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 edited this form before so it has local 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 b9bf7eb..aea7fff 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -59,6 +59,9 @@ class NodeFormController extends EntityFormController {
     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['#attributes']['class'][] = 'form-save-to-localstorage-on-load';
     }
     else {
       unset($node->in_preview);
diff --git a/core/modules/overlay/overlay-parent.js b/core/modules/overlay/overlay-parent.js
index 678d68a..b53534e 100644
--- a/core/modules/overlay/overlay-parent.js
+++ b/core/modules/overlay/overlay-parent.js
@@ -132,7 +132,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)
@@ -564,6 +565,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 6baa387..8b03e0f 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1198,6 +1198,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'),
