Index: misc/ajax.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/ajax.js,v
retrieving revision 1.3
diff -u -p -r1.3 ajax.js
--- misc/ajax.js	2 Oct 2009 14:55:40 -0000	1.3
+++ misc/ajax.js	27 Oct 2009 20:42:10 -0000
@@ -120,6 +120,9 @@ Drupal.ajax = function (base, element, e
   var options = {
     url: ajax.url,
     data: ajax.button,
+    beforeSerialize: function (element_settings, options) {
+      return ajax.beforeSerialize(element_settings, options);
+    },
     beforeSubmit: function (form_values, element_settings, options) {
       return ajax.beforeSubmit(form_values, element_settings, options);
     },
@@ -177,6 +180,18 @@ Drupal.ajax = function (base, element, e
 };
 
 /**
+ * Handler for the form serialization.
+ *
+ * Runs before the beforeSubmit() handler (see below), and unlike that one, runs
+ * before field data is collected.
+ */
+Drupal.ajax.prototype.beforeSerialize = function (element, options) {
+  // Allow detaching behaviors to update field values before collecting them.
+  var settings = this.settings || Drupal.settings;
+  Drupal.detachBehaviors(this.form, settings, 'serialize');
+};
+
+/**
  * Handler for the form redirection submission.
  */
 Drupal.ajax.prototype.beforeSubmit = function (form_values, element, options) {
@@ -230,6 +245,13 @@ Drupal.ajax.prototype.success = function
     }
   }
 
+  // Reattach behaviors that were detached in beforeSerialize(). The
+  // attachBehaviors() called on the new content from processing the response
+  // commands is not sufficient, because behaviors from the entire form need
+  // to be reattached.
+  var settings = this.settings || Drupal.settings;
+  Drupal.attachBehaviors(this.form, settings);
+
   Drupal.unfreezeHeight();
 
   // Remove any response-specific settings so they don't get used on the next
@@ -280,6 +302,9 @@ Drupal.ajax.prototype.error = function (
   $(this.wrapper).show();
   // Re-enable the element.
   $(this.element).removeClass('progress-disabled').attr('disabled', false);
+  // Reattach behaviors that were detached in beforeSerialize().
+  var settings = response.settings || this.settings || Drupal.settings;
+  Drupal.attachBehaviors(this.form, settings);
 };
 
 /**
@@ -300,6 +325,17 @@ Drupal.ajax.prototype.commands = {
     // Safari with long string lengths. http://dev.jquery.com/ticket/3178
     var new_content = $('<div></div>').html(response.data);
 
+    // If removing content from the wrapper, detach behaviors first.
+    switch (method) {
+      case 'html':
+      case 'replaceWith':
+      case 'replaceAll':
+      case 'empty':
+      case 'remove':
+        var settings = response.settings || ajax.settings || Drupal.settings;
+        Drupal.detachBehaviors(wrapper, settings);
+    }
+
     // Add the new content to the page.
     wrapper[method](new_content);
 
@@ -333,6 +369,8 @@ Drupal.ajax.prototype.commands = {
    * Command to remove a chunk from the page.
    */
   remove: function (ajax, response, status) {
+    var settings = response.settings || ajax.settings || Drupal.settings;
+    Drupal.detachBehaviors($(response.selector), settings);
     $(response.selector).remove();
   },
 
Index: misc/drupal.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/drupal.js,v
retrieving revision 1.59
diff -u -p -r1.59 drupal.js
--- misc/drupal.js	27 Oct 2009 04:12:39 -0000	1.59
+++ misc/drupal.js	27 Oct 2009 20:43:38 -0000
@@ -22,10 +22,10 @@ if ($ === undefined) {
  * object using the method 'attach' and optionally also 'detach' as follows:
  * @code
  *    Drupal.behaviors.behaviorName = {
- *      attach: function (context) {
+ *      attach: function (context, settings) {
  *        ...
  *      },
- *      detach: function (context) {
+ *      detach: function (context, settings, trigger) {
  *        ...
  *      }
  *    };
@@ -81,16 +81,38 @@ Drupal.attachBehaviors = function (conte
  * @param context
  *   An element to detach behaviors from. If none is given, the document element
  *   is used.
+ * @param settings
+ *   An object containing settings for the current context. If none given, the
+ *   global Drupal.settings object is used.
+ * @param trigger
+ *   A string containing what's causing the behaviors to be detached. The
+ *   possible triggers are:
+ *   - unload: (default) The context element is being removed from the DOM.
+ *   - move: The element is about to be moved within the DOM (for example,
+ *     during a tabledrag row swap). After the move is completed,
+ *     Drupal.attachBehaviors() is called, so that the behavior can undo
+ *     whatever it did in response to the move. Many behaviors won't need to
+ *     do anything simply in response to the element being moved, but because
+ *     IFRAME elements reload their "src" when being moved within the DOM,
+ *     behaviors bound to IFRAME elements (like WYSIWYG editors) may need to
+ *     take some action.
+ *   - serialize: When an AJAX form is submitted, this is called with the
+ *     form as the context. This provides every behavior within the form an
+ *     opportunity to ensure that the field elements have correct content
+ *     in them before the form is serialized. The canonical use-case is so
+ *     that WYSIWYG editors can update the hidden textarea to which they are
+ *     bound.
  *
  * @see Drupal.attachBehaviors
  */
-Drupal.detachBehaviors = function (context, settings) {
+Drupal.detachBehaviors = function (context, settings, trigger) {
   context = context || document;
   settings = settings || Drupal.settings;
+  trigger = trigger || 'unload';
   // Execute all of them.
   $.each(Drupal.behaviors, function () {
     if ($.isFunction(this.detach)) {
-      this.detach(context, settings);
+      this.detach(context, settings, trigger);
     }
   });
 };
Index: misc/tabledrag.js
===================================================================
RCS file: /cvs/drupal/drupal/misc/tabledrag.js,v
retrieving revision 1.31
diff -u -p -r1.31 tabledrag.js
--- misc/tabledrag.js	20 Sep 2009 19:14:40 -0000	1.31
+++ misc/tabledrag.js	27 Oct 2009 19:21:21 -0000
@@ -903,7 +903,9 @@ Drupal.tableDrag.prototype.row.prototype
  *   DOM element what will be swapped with the row group.
  */
 Drupal.tableDrag.prototype.row.prototype.swap = function (position, row) {
+  Drupal.detachBehaviors(this.group, Drupal.settings, 'move');
   $(row)[position](this.group);
+  Drupal.attachBehaviors(this.group, Drupal.settings);
   this.changed = true;
   this.onSwap(row);
 };
