 core/includes/form.inc                             |   11 +
 core/misc/form.autosave.js                         |   25 ++
 core/misc/garlic/garlic.js                         |  421 ++++++++++++++++++++
 core/modules/ckeditor/js/ckeditor.js               |    1 -
 .../node/lib/Drupal/node/NodeFormController.php    |    6 +
 core/modules/overlay/overlay-parent.js             |    8 +-
 core/modules/system/system.module                  |   15 +
 7 files changed, 485 insertions(+), 2 deletions(-)

diff --git a/core/includes/form.inc b/core/includes/form.inc
index 375f178..675aefd 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -951,6 +951,17 @@ function drupal_process_form($form_id, &$form, &$form_state) {
     }
   }
 
+  if (!empty($form['#autosave'])) {
+    $form['#attached']['js'][] = array(
+      'data' => array(
+        'formAutoSave' => array(
+          $form['#id'] => !empty($form['#autosave']['#changed']) ? $form['#autosave']['#changed'] : 0
+        ),
+      ),
+      'type' => '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..ffb4db5
--- /dev/null
+++ b/core/misc/form.autosave.js
@@ -0,0 +1,25 @@
+(function ($, Drupal) {
+
+"use strict";
+
+/**
+ * Automatically saves the contents of a form using localStorage when modified.
+ */
+Drupal.behaviors.formAutoSave = {
+  attach: function (context, settings) {
+    for (var formID in settings.formAutoSave) {
+      if (settings.formAutoSave.hasOwnProperty(formID)) {
+        $(context).find('#' + formID).garlic({
+          expires: 86400 * 30,
+          conflictManager: { enabled: false },
+          // Not yet supported by Garlic, but we need something like this to
+          // ensure Garlic doesn't overwrite values when the form contains
+          // different values by default.
+          lastModifiedIdentifier: settings.formAutoSave[formID]
+        });
+      }
+    }
+  }
+};
+
+})(jQuery, Drupal);
diff --git a/core/misc/garlic/garlic.js b/core/misc/garlic/garlic.js
new file mode 100644
index 0000000..9a3db32
--- /dev/null
+++ b/core/misc/garlic/garlic.js
@@ -0,0 +1,421 @@
+/*
+  Garlic.js allows you to automatically persist your forms' text field values locally,
+  until the form is submitted. This way, your users don't lose any precious data if they
+  accidentally close their tab or browser.
+
+  author: Guillaume Potier - @guillaumepotier
+*/
+
+!function ($) {
+
+  "use strict";
+  /*global localStorage */
+  /*global document */
+
+  /* STORAGE PUBLIC CLASS DEFINITION
+   * =============================== */
+  var Storage = function ( options ) {
+    this.defined = 'undefined' !== typeof localStorage;
+  }
+
+  Storage.prototype = {
+
+    constructor: Storage
+
+    , get: function ( key, placeholder ) {
+      return localStorage.getItem( key ) ? localStorage.getItem( key ) : 'undefined' !== typeof placeholder ? placeholder : null;
+    }
+
+    , has: function ( key ) {
+      return localStorage.getItem( key ) ? true : false;
+    }
+
+    , set: function ( key, value, fn ) {
+      if ( 'string' === typeof value ) {
+
+        // if value is null, remove storage if exists
+        if ( '' === value ) {
+          this.destroy( key );
+        } else {
+          localStorage.setItem( key , value );
+        }
+      }
+
+      return 'function' === typeof fn ? fn() : true;
+    }
+
+    , destroy: function ( key, fn ) {
+      localStorage.removeItem( key );
+      return 'function' === typeof fn ? fn() : true;
+    }
+
+    , clean: function ( fn ) {
+      for ( var i = localStorage.length - 1; i >= 0; i-- ) {
+        if ( 'undefined' === typeof Array.indexOf && -1 !== localStorage.key(i).indexOf( 'garlic:' ) ) {
+          localStorage.removeItem( localStorage.key(i) );
+        }
+      }
+
+      return 'function' === typeof fn ? fn() : true;
+    }
+
+    , clear: function ( fn ) {
+      localStorage.clear();
+      return 'function' === typeof fn ? fn() : true;
+    }
+  }
+
+ /* GARLIC PUBLIC CLASS DEFINITION
+  * =============================== */
+
+  var Garlic = function ( element, storage, options ) {
+    this.init( 'garlic', element, storage, options );
+  }
+
+  Garlic.prototype = {
+
+    constructor: Garlic
+
+    /* init data, bind jQuery on() actions */
+    , init: function ( type, element, storage, options ) {
+      this.type = type;
+      this.$element = $( element );
+      this.options = this.getOptions( options );
+      this.storage = storage;
+      this.path = this.getPath();
+      this.parentForm = this.$element.closest( 'form' );
+      this.$element.addClass('garlic-auto-save');
+      this.expiresFlag = !this.options.expires ? false : ( this.$element.data( 'expires' ) ? this.path : this.getPath( this.parentForm ) ) + '_flag' ;
+
+      // bind garlic events
+      this.$element.on( this.options.events.join( '.' + this.type + ' ') , false, $.proxy( this.persist, this ) );
+
+      if ( this.options.destroy ) {
+        $( this.parentForm ).on( 'submit reset' , false, $.proxy( this.destroy, this ) );
+      }
+
+      // retrieve garlic persisted data
+      this.retrieve();
+    }
+
+    , getOptions: function ( options ) {
+      return $.extend( {}, $.fn[this.type].defaults, options, this.$element.data() );
+    }
+
+    /* temporary store data / state in localStorage */
+    , persist: function () {
+
+      // some binded events are redundant (change & paste for example), persist only once by field val
+      if ( this.val === this.$element.val() ) {
+        return;
+      }
+
+      this.val = this.$element.val();
+
+      // if auto-expires is enabled, set the expiration date for future auto-deletion
+      if ( this.options.expires ) {
+        this.storage.set( this.expiresFlag , ( new Date().getTime() + this.options.expires * 1000 ).toString() );
+      }
+
+      // for checkboxes, we need to implement an unchecked / checked behavior
+      if ( this.$element.is( 'input[type=checkbox]' ) ) {
+        return this.storage.set( this.path , this.$element.attr( 'checked' ) ? 'checked' : 'unchecked' );
+      }
+
+      this.storage.set( this.path , this.$element.val() );
+    }
+
+    /* retrieve localStorage data / state and update elem accordingly */
+    , retrieve: function () {
+      if ( this.storage.has( this.path ) ) {
+
+        // if data expired, destroy it!
+        if ( this.options.expires ) {
+          var date = new Date().getTime();
+          if ( this.storage.get( this.expiresFlag ) < date.toString() ) {
+            this.storage.destroy( this.path );
+            return;
+          } else {
+            this.$element.attr( 'expires-in',  Math.floor( ( parseInt( this.storage.get( this.expiresFlag ) ) - date ) / 1000 ) );
+          }
+        }
+
+        var storedValue = this.storage.get( this.path );
+
+        // if conflictManager enabled, manage fields with already provided data, different from the one stored
+        if ( this.options.conflictManager.enabled && this.detectConflict() ) {
+          return this.conflictManager();
+        }
+
+        // input[type=checkbox] and input[type=radio] have a special checked / unchecked behavior
+        if ( this.$element.is( 'input[type=radio], input[type=checkbox]' ) ) {
+
+          // for checkboxes and radios
+          if ( 'checked' === storedValue || this.$element.val() === storedValue ) {
+            return this.$element.attr( 'checked', true );
+
+          // only needed for checkboxes
+          } else if ( 'unchecked' === storedValue ) {
+            this.$element.attr( 'checked', false );
+          }
+
+          return;
+        }
+
+        // for input[type=text], select and textarea, just set val()
+        this.$element.val( storedValue );
+
+        // trigger custom user function when data is retrieved
+        this.options.onRetrieve( this.$element, storedValue );
+
+        return;
+      }
+    }
+
+    /* there is a conflict when initial data / state differs from persisted data / state */
+    , detectConflict: function() {
+      var self = this;
+
+      // radio buttons and checkboxes are yet not supported
+      if ( this.$element.is( 'input[type=checkbox], input[type=radio]' ) ) {
+        return false;
+      }
+
+      // there is a default not null value and we have a different one stored
+      if ( this.$element.val() && this.storage.get( this.path ) !== this.$element.val() ) {
+
+        // for select elements, we need to check if there is a default checked value
+        if ( this.$element.is( 'select' ) ) {
+          var selectConflictDetected = false;
+
+          // foreach each options except first one, always considered as selected, seeking for a default selected one
+          this.$element.find( 'option' ).each( function () {
+            if ( $( this ).index() !== 0 && $( this ).attr( 'selected' ) && $( this ).val() !== self.storage.get( this.path ) ) {
+              selectConflictDetected = true;
+              return;
+            }
+          });
+
+          return selectConflictDetected;
+        }
+
+        return true;
+      }
+
+      return false;
+    }
+
+    /* manage here the conflict, show default value depending on options.garlicPriority value */
+    , conflictManager: function () {
+
+      // user can define here a custom function that could stop Garlic default behavior, if returns false
+      if ( 'function' === typeof this.options.conflictManager.onConflictDetected
+        && !this.options.conflictManager.onConflictDetected( this.$element, this.storage.get( this.path ) ) ) {
+        return false;
+      }
+
+      if ( this.options.conflictManager.garlicPriority ) {
+        this.$element.data( 'swap-data', this.$element.val() );
+        this.$element.data( 'swap-state', 'garlic' );
+        this.$element.val( this.storage.get( this.path ) );
+      } else {
+        this.$element.data( 'swap-data', this.storage.get( this.path ) );
+        this.$element.data( 'swap-state', 'default' );
+      }
+
+      this.swapHandler();
+      this.$element.addClass( 'garlic-conflict-detected' );
+      this.$element.closest( 'input[type=submit]' ).attr( 'disabled', true );
+    }
+
+    /* manage swap user interface */
+    , swapHandler: function () {
+      var swapChoiceElem = $( this.options.conflictManager.template );
+      this.$element.after( swapChoiceElem.text( this.options.conflictManager.message ) );
+      swapChoiceElem.on( 'click', false, $.proxy( this.swap, this ) );
+    }
+
+    /* swap data / states for conflicted elements */
+    , swap: function () {
+      var val = this.$element.data( 'swap-data' );
+      this.$element.data( 'swap-state', 'garlic' === this.$element.data( 'swap-state' ) ? 'default' : 'garlic' );
+      this.$element.data( 'swap-data', this.$element.val());
+      $( this.$element ).val( val );
+    }
+
+    /* delete localStorage persistance only */
+    , destroy: function () {
+      this.storage.destroy( this.path );
+    }
+
+    /* remove data / reset state AND delete localStorage */
+    , remove: function () {
+      this.remove();
+
+      if ( this.$element.is( 'input[type=radio], input[type=checkbox]' ) ) {
+        $( this.$element ).attr( 'checked', false );
+        return;
+      }
+
+      this.$element.val( '' );
+    }
+
+    /* retuns an unique identifier for form elements, depending on their behaviors:
+       * radio buttons: domain > pathname > form.<attr.name>[:eq(x)] > input.<attr.name>
+          no eq(); must be all stored under the same field name inside the same form
+
+       * checkbokes: domain > pathname > form.<attr.name>[:eq(x)] > [fieldset, div, span..] > input.<attr.name>[:eq(y)]
+          cuz' they have the same name, must detect their exact position in the form. detect the exact hierarchy in DOM elements
+
+       * other inputs: domain > pathname > form.<attr.name>[:eq(x)] > input.<attr.name>[:eq(y)]
+          we just need the element name / eq() inside a given form
+    */
+    , getPath: function ( elem ) {
+
+      if ( 'undefined' === typeof elem ) {
+        elem = this.$element;
+      }
+
+      // Requires one element.
+      if ( elem.length != 1 ) {
+        return false;
+      }
+
+      var path = ''
+        , fullPath = elem.is( 'input[type=checkbox]' )
+        , node = elem;
+
+      while ( node.length ) {
+        var realNode = node[0]
+          , name = realNode.nodeName;
+
+        if ( !name ) {
+          break;
+        }
+
+        name = name.toLowerCase();
+
+        var parent = node.parent()
+          , siblings = parent.children( name );
+
+        // don't need to pollute path with select, fieldsets, divs and other noisy elements,
+        // exept for checkboxes that need exact path, cuz have same name and sometimes same eq()!
+        if ( !$( realNode ).is( 'form, input, select, textarea' ) && !fullPath ) {
+          node = parent;
+          continue;
+        }
+
+        // set input type as name + name attr if exists
+        name += $( realNode ).attr( 'name' ) ? '.' + $( realNode ).attr( 'name' ) : '';
+
+        // if has sibilings, get eq(), exept for radio buttons
+        if ( siblings.length > 1 && !$( realNode ).is( 'input[type=radio]' ) ) {
+          name += ':eq(' + siblings.index( realNode ) + ')';
+        }
+
+        path = name + ( path ? '>' + path : '' );
+
+        // break once we came up to form:eq(x), no need to go further
+        if ( 'form' == realNode.nodeName.toLowerCase() ) {
+          break;
+        }
+
+        node = parent;
+      }
+
+      return 'garlic:' + document.domain + ( this.options.domain ? '*' : window.location.pathname ) + '>' + path;
+    }
+
+    , getStorage: function () {
+      return this.storage;
+    }
+  }
+
+  /* GARLIC PLUGIN DEFINITION
+  * ========================= */
+
+  $.fn.garlic = function ( option, fn ) {
+    var options = $.extend(true, {}, $.fn.garlic.defaults, option, this.data() )
+      , storage = new Storage()
+      , returnValue = false;
+
+    // this plugin heavily rely on local Storage. If there is no localStorage or data-storage=false, no need to go further
+    if ( !storage.defined ) {
+      return false;
+    }
+
+    function bind ( self ) {
+      var $this = $( self )
+        , data = $this.data( 'garlic' )
+        , fieldOptions = $.extend( {}, options, $this.data() );
+
+      // don't bind an elem with data-storage=false
+      if ( 'undefined' !== typeof fieldOptions.storage && !fieldOptions.storage ) {
+        return;
+      }
+
+      // don't bind a password type field
+      if ( 'password' === $( self ).attr( 'type' ) ) {
+        return;
+      }
+
+      // if data never binded, bind it right now!
+      if ( !data ) {
+        $this.data( 'garlic', ( data = new Garlic( self, storage, fieldOptions ) ) );
+      }
+
+      // here is our garlic public function accessor, currently does not support args
+      if ( 'string' === typeof option && 'function' === typeof data[option] ) {
+        return data[option]();
+      }
+    }
+
+    // loop through every elemt we want to garlic
+    this.each(function () {
+
+      // if a form elem is given, bind all its input children
+      if ( $( this ).is( 'form' ) ) {
+        $( this ).find( options.inputs ).each( function () {
+          returnValue = bind( $( this ) );
+        });
+
+      // if it is a Garlic supported single element, bind it too
+      // add here a return instance, cuz' we could call public methods on single elems with data[option]() above
+      } else if ( $( this ).is( options.inputs ) ) {
+        returnValue = bind( $( this ) );
+      }
+    });
+
+    return 'function' === typeof fn ? fn() : returnValue;
+  }
+
+  /* GARLIC CONFIGS & OPTIONS
+  * ========================= */
+  $.fn.garlic.Constructor = Garlic;
+
+  $.fn.garlic.defaults = {
+      destroy: true                                                                               // Remove or not localstorage on submit & clear
+    , inputs: 'input, textarea, select'                                                           // Default supported inputs.
+    , events: [ 'DOMAttrModified', 'textInput', 'input', 'change', 'keypress', 'paste', 'focus' ] // Events list that trigger a localStorage
+    , domain: false                                                                               // Store et retrieve forms data accross all domain, not just on
+    , expires: false                                                                              // false for no expiration, otherwise (int) in seconds for auto-expiration
+    , conflictManager: {
+        enabled: true                                                                             // Manage default data and persisted data. If false, persisted data will always replace default ones
+      , garlicPriority: true                                                                      // If form have default data, garlic persisted data will be shown first
+      , template: '<span class="garlic-swap"></span>'                                             // Template used to swap between values if conflict detected
+      , message: 'This is your saved data. Click here to see default one'                         // Default message for swapping data / state
+      , onConflictDetected: function ( item, storedVal ) { return true; }                         // This function will be triggered if a conflict is detected on an item. Return true if you want Garlic behavior, return false if you want to override it
+    }
+   , onRetrieve: function ( item, storedVal ) {}                                                  // This function will be triggered each time Garlic find an retrieve a local stored data for a field
+  }
+
+  /* GARLIC DATA-API
+  * =============== */
+  $( window ).on( 'load', function () {
+    $( '[data-persist="garlic"]' ).each( function () {
+      $(this).garlic();
+    })
+  });
+
+// This plugin works with jQuery or Zepto (with data extension builded for Zepto. See changelog 0.0.6)
+}(window.jQuery || window.Zepto);
diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js
index 42aff34..519149e 100644
--- a/core/modules/ckeditor/js/ckeditor.js
+++ b/core/modules/ckeditor/js/ckeditor.js
@@ -58,7 +58,6 @@ Drupal.editors.ckeditor = {
     return !!CKEDITOR.inline(element, settings);
   },
   _loadExternalPlugins: function(format) {
-    debugger;
     // Register additional Drupal plugins as necessary.
     if (format.editorSettings.externalPlugins) {
       for (var pluginName in format.editorSettings.externalPlugins) {
diff --git a/core/modules/node/lib/Drupal/node/NodeFormController.php b/core/modules/node/lib/Drupal/node/NodeFormController.php
index 08ecf08..c12bd5c 100644
--- a/core/modules/node/lib/Drupal/node/NodeFormController.php
+++ b/core/modules/node/lib/Drupal/node/NodeFormController.php
@@ -57,6 +57,12 @@ 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 to prevent data loss.
+    $form['#autosave'] = array(
+      '#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'];
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 cdf7b90..4ef5f22 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -1282,12 +1282,14 @@ 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'),
       array('system', 'drupal'),
       array('system', 'jquery.cookie'),
       array('system', 'jquery.once'),
+      array('system', 'garlic'),
     ),
   );
 
@@ -2014,6 +2016,19 @@ function system_library_info() {
     ),
   );
 
+  // Garlic.
+  $libraries['garlic'] = array(
+    'title' => 'garlic.js',
+    'website' => 'http://garlicjs.org/',
+    'version' => '1.1.2',
+    'js' => array(
+      'core/misc/garlic/garlic.js' => array('group' => JS_LIBRARY, 'weight' => 0),
+    ),
+    'dependencies' => array(
+      array('system', 'jquery'),
+    ),
+  );
+
   // VIE.
   $libraries['vie.core'] = array(
     'title' => 'VIE.js core (excluding services, views and xdr)',
