 core/modules/edit/js/editors/formEditor.js         |   11 ++-
 core/modules/edit/js/models/FieldModel.js          |   57 +++++++++++++-
 core/modules/edit/js/util.js                       |    7 +-
 core/modules/edit/js/views/AppView.js              |   80 ++++++++++++++++++--
 core/modules/edit/js/views/EditorView.js           |    7 +-
 .../lib/Drupal/edit/Ajax/FieldFormSavedCommand.php |   27 ++++++-
 .../edit/lib/Drupal/edit/EditController.php        |   75 +++++++++++++-----
 .../edit/lib/Drupal/edit/Tests/EditLoadingTest.php |    6 ++
 8 files changed, 235 insertions(+), 35 deletions(-)

diff --git a/core/modules/edit/js/editors/formEditor.js b/core/modules/edit/js/editors/formEditor.js
index 10f380b..c4d1a50 100644
--- a/core/modules/edit/js/editors/formEditor.js
+++ b/core/modules/edit/js/editors/formEditor.js
@@ -167,7 +167,8 @@ Drupal.edit.editors.form = Drupal.edit.EditorView.extend({
 
     // Create an AJAX object for the form associated with the field.
     var formSaveAjax = Drupal.edit.util.form.ajaxifySaving({
-      nocssjs: false
+      nocssjs: false,
+      other_view_modes: fieldModel.findOtherViewModes()
     }, $submit);
 
     // Successfully saved.
@@ -175,8 +176,12 @@ Drupal.edit.editors.form = Drupal.edit.EditorView.extend({
       cleanUpAjax();
       // First, transition the state to 'saved'.
       fieldModel.set('state', 'saved');
-      // Then, set the 'html' attribute on the field model. This will cause the
-      // field to be rerendered.
+      // Second, set the 'htmlForOtherViewModes' attribute, so that when this
+      // field is rerendered, the change can be propagated to other instances of
+      // this field, which may be displayed in different view modes.
+      fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
+      // Finally, set the 'html' attribute on the field model. This will cause
+      // the field to be rerendered.
       fieldModel.set('html', response.data);
     };
 
diff --git a/core/modules/edit/js/models/FieldModel.js b/core/modules/edit/js/models/FieldModel.js
index ba5b1be..5726628 100644
--- a/core/modules/edit/js/models/FieldModel.js
+++ b/core/modules/edit/js/models/FieldModel.js
@@ -28,6 +28,11 @@ Drupal.edit.FieldModel = Backbone.Model.extend({
     // Callback function for validating changes between states. Receives the
     // previous state, new state, context, and a callback
     acceptStateChange: null,
+    // A logical field ID, of the form
+    // "<entity type>/<id>/<field name>/<language>", i.e. the fieldID without
+    // the view mode, to be able to identify other instances of the same field
+    // on the page but rendered in a different view mode. e.g. "node/1/field_tags/und".
+    logicalFieldID: null,
 
     // The attributes below are stateful. The ones above will never change
     // during the life of a FieldModel instance.
@@ -44,7 +49,11 @@ Drupal.edit.FieldModel = Backbone.Model.extend({
     // The full HTML representation of this field (with the element that has
     // the data-edit-field-id as the outer element). Used to propagate changes
     // from this field instance to other instances of the same field.
-    html: null
+    html: null,
+    // An object containing the full HTML representations (values) of other view
+    // modes (keys) of this field, for other instances of this field displayed
+    // in a different view mode.
+    htmlForOtherViewModes: null
   },
 
   /**
@@ -56,6 +65,9 @@ Drupal.edit.FieldModel = Backbone.Model.extend({
 
     // Enlist field automatically in the associated entity's field collection.
     this.get('entity').get('fields').add(this);
+
+    //
+    this.set('logicalFieldID', this.get('fieldID').split('/').slice(0, 4).join('/'))
   },
 
   /**
@@ -107,6 +119,49 @@ Drupal.edit.FieldModel = Backbone.Model.extend({
    */
   getEntityID: function () {
     return this.get('fieldID').split('/').slice(0, 2).join('/');
+  },
+
+  /**
+   * Extracts the view mode ID from this field's ID.
+   *
+   * @return String
+   *   A view mode ID.
+   */
+  getViewMode: function () {
+    return this.get('fieldID').split('/').pop();
+  },
+
+  /**
+   * Find other instances of this field with different view modes.
+   *
+   * @return Array
+   *   An array containing view mode IDs.
+   */
+  findOtherViewModes: function () {
+    var currentField = this;
+    // Second, figure out if there are .
+    // They must be notified of state changes, hence this must happen while
+    // the associated fields are still in the 'inactive' state.
+    var otherViewModes = [];
+    Drupal.edit.collections.fields
+      // Find all instances of fields that display the same logical field (same
+      // entity, same field, just a different instance and maybe a different
+      // view mode).
+      .where({ logicalFieldID: currentField.get('logicalFieldID') })
+      .forEach(function (field) {
+        // Ignore the current field.
+        if (field === currentField) {
+          return;
+        }
+        // Also ignore other fields with the same view mode.
+        else if (field.get('fieldID') === currentField.get('fieldID')) {
+          return;
+        }
+        else {
+          otherViewModes.push(field.getViewMode());
+        }
+      });
+    return otherViewModes;
   }
 
 }, {
diff --git a/core/modules/edit/js/util.js b/core/modules/edit/js/util.js
index 2663077..ce5cbb7 100644
--- a/core/modules/edit/js/util.js
+++ b/core/modules/edit/js/util.js
@@ -84,6 +84,8 @@ Drupal.edit.util.form = {
    *   An object with the following keys:
    *    - nocssjs: (required) boolean indicating whether no CSS and JS should be
    *      returned (necessary when the form is invisible to the user).
+   *    - other_view_modes: (required) array containing view mode IDs (of other
+   *      instances of this field on the page).
    * @return Drupal.ajax
    *   A Drupal.ajax instance.
    */
@@ -94,7 +96,10 @@ Drupal.edit.util.form = {
       setClick: true,
       event: 'click.edit',
       progress: { type: null },
-      submit: { nocssjs : options.nocssjs },
+      submit: {
+        nocssjs : options.nocssjs,
+        other_view_modes : options.other_view_modes
+      },
       // Reimplement the success handler to ensure Drupal.attachBehaviors() does
       // not get called on the form.
       success: function (response, status) {
diff --git a/core/modules/edit/js/views/AppView.js b/core/modules/edit/js/views/AppView.js
index 3bce3ed..0bcfd25 100644
--- a/core/modules/edit/js/views/AppView.js
+++ b/core/modules/edit/js/views/AppView.js
@@ -41,6 +41,7 @@ Drupal.edit.AppView = Backbone.View.extend({
       // Track app state.
       .on('change:state', this.editorStateChange, this)
       // Respond to field model HTML representation change events.
+      .on('change:html', this.propagateUpdatedField, this)
       .on('change:html', this.renderUpdatedField, this)
       // Respond to addition.
       .on('add', this.rerenderedFieldToCandidate, this)
@@ -417,20 +418,33 @@ Drupal.edit.AppView = Backbone.View.extend({
    *
    * @param Drupal.edit.FieldModel fieldModel
    *   The FieldModel whose 'html' attribute changed.
+   * @param String html
+   *   The updated 'html' attribute.
+   * @param Object options
+   *   An object with the following keys:
+   *   - Boolean propagation: whether this change to the 'html' attribute
+   *     occurred because of the propagation of changes to another instance of
+   *     this field.
    */
-  renderUpdatedField: function (fieldModel) {
+  renderUpdatedField: function (fieldModel, html, options) {
     // Get data necessary to rerender property before it is unavailable.
-    var html = fieldModel.get('html');
     var $fieldWrapper = $(fieldModel.get('el'));
     var $context = $fieldWrapper.parent();
 
-    // First set the state to 'candidate', to allow all attached views to
-    // clean up all their "active state"-related changes.
-    fieldModel.set('state', 'candidate');
+    // When propagating the changes of another instance of this field, this
+    // field is not being actively edited and hence no state changes are
+    // necessary. So: only update the state of this field when the rerendering
+    // of this field happens not because of propagation, but because it is being
+    // edited itself.
+    if (!options.propagation) {
+      // First set the state to 'candidate', to allow all attached views to
+      // clean up all their "active state"-related changes.
+      fieldModel.set('state', 'candidate');
 
-    // Set the field's state to 'inactive', to enable the updating of its DOM
-    // value.
-    fieldModel.set('state', 'inactive', { reason: 'rerender' });
+      // Set the field's state to 'inactive', to enable the updating of its DOM
+      // value.
+      fieldModel.set('state', 'inactive', { reason: 'rerender' });
+    }
 
     // Destroy the field model; this will cause all attached views to be
     // destroyed too, and removal from all collections in which it exists.
@@ -445,6 +459,56 @@ Drupal.edit.AppView = Backbone.View.extend({
   },
 
   /**
+   * Propagates the changes to an updated field to all instances of that field.
+   *
+   * @param Drupal.edit.FieldModel updatedField
+   *   The FieldModel whose 'html' attribute changed.
+   * @param String html
+   *   The updated 'html' attribute.
+   * @param Object options
+   *   An object with the following keys:
+   *   - Boolean propagation: whether this change to the 'html' attribute
+   *     occurred because of the propagation of changes to another instance of
+   *     this field.
+   *
+   * @see Drupal.edit.AppView.renderUpdatedField()
+   */
+  propagateUpdatedField: function (updatedField, html, options) {
+    // Don't propagate field updates that themselves were caused by propagation.
+    if (options.propagation) {
+      return;
+    }
+
+    var htmlForOtherViewModes = updatedField.get('htmlForOtherViewModes');
+    Drupal.edit.collections.fields
+      // Find all instances of fields that display the same logical field (same
+      // entity, same field, just a different instance and maybe a different
+      // view mode).
+      .where({ logicalFieldID: updatedField.get('logicalFieldID') })
+      .forEach(function (field) {
+        // Ignore the field that was already updated.
+        if (field === updatedField) {
+          return;
+        }
+        // If this other instance of the field has the same view mode, we can
+        // update it easily.
+        else if (field.getViewMode() === updatedField.getViewMode()) {
+          field.set('html', updatedField.get('html'));
+        }
+        // If this other instance of the field has a different view mode, and
+        // that is one of the view modes for which a re-rendered version is
+        // available (and that should be the case unless this field was only
+        // added to the page after editing of the updated field began), then use
+        // that view mode's re-rendered version.
+        else {
+          if (field.getViewMode() in htmlForOtherViewModes) {
+            field.set('html', htmlForOtherViewModes[field.getViewMode()], { propagation: true });
+          }
+        }
+      });
+  },
+
+  /**
    * If the new in-place editable field is for the entity that's currently
    * being edited, then transition it to the 'candidate' state.
    *
diff --git a/core/modules/edit/js/views/EditorView.js b/core/modules/edit/js/views/EditorView.js
index def53ab..c67996c 100644
--- a/core/modules/edit/js/views/EditorView.js
+++ b/core/modules/edit/js/views/EditorView.js
@@ -189,6 +189,7 @@ Drupal.edit.EditorView = Backbone.View.extend({
       fieldID: this.fieldModel.get('fieldID'),
       $el: this.$el,
       nocssjs: true,
+      other_view_modes: fieldModel.findOtherViewModes(),
       // Reset an existing entry for this entity in the TempStore (if any) when
       // saving the field. Logically speaking, this should happen in a separate
       // request because this is an entity-level operation, not a field-level
@@ -225,7 +226,11 @@ Drupal.edit.EditorView = Backbone.View.extend({
         removeHiddenForm();
         // First, transition the state to 'saved'.
         fieldModel.set('state', 'saved');
-        // Then, set the 'html' attribute on the field model. This will cause
+        // Second, set the 'htmlForOtherViewModes' attribute, so that when this
+        // field is rerendered, the change can be propagated to other instances of
+        // this field, which may be displayed in different view modes.
+        fieldModel.set('htmlForOtherViewModes', response.other_view_modes);
+        // Finally, set the 'html' attribute on the field model. This will cause
         // the field to be rerendered.
         fieldModel.set('html', response.data);
       };
diff --git a/core/modules/edit/lib/Drupal/edit/Ajax/FieldFormSavedCommand.php b/core/modules/edit/lib/Drupal/edit/Ajax/FieldFormSavedCommand.php
index 2e89c19..7e2b57f 100644
--- a/core/modules/edit/lib/Drupal/edit/Ajax/FieldFormSavedCommand.php
+++ b/core/modules/edit/lib/Drupal/edit/Ajax/FieldFormSavedCommand.php
@@ -16,13 +16,36 @@
 class FieldFormSavedCommand extends BaseCommand {
 
   /**
+   * The same re-rendered edited field, but in different view modes.
+   *
+   * @var array
+   */
+  protected $other_view_modes;
+
+  /**
    * Constructs a FieldFormSavedCommand object.
    *
    * @param string $data
-   *   The data to pass on to the client side.
+   *   The re-rendered edited field to pass on to the client side.
+   * @param array $other_view_modes
+   *   The same re-rendered edited field, but in different view modes, for other
+   *   instances of the same field on the user's page. Keyed by view mode.
    */
-  public function __construct($data) {
+  public function __construct($data, $other_view_modes = array()) {
     parent::__construct('editFieldFormSaved', $data);
+
+    $this->other_view_modes = $other_view_modes;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render() {
+    return array(
+      'command' => $this->command,
+      'data' => $this->data,
+      'other_view_modes' => $this->other_view_modes,
+    );
   }
 
 }
diff --git a/core/modules/edit/lib/Drupal/edit/EditController.php b/core/modules/edit/lib/Drupal/edit/EditController.php
index c63b956..418945d 100644
--- a/core/modules/edit/lib/Drupal/edit/EditController.php
+++ b/core/modules/edit/lib/Drupal/edit/EditController.php
@@ -12,6 +12,7 @@
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Drupal\Component\Utility\MapArray;
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityInterface;
@@ -232,26 +233,20 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view
       // updated view of the field from the tempstore copy.
       $entity = $this->tempStoreFactory->get('edit')->get($entity->uuid());
 
-      // Render the field. If the view mode ID is not an Entity Display view
-      // mode ID, then the field was rendered using a custom render pipeline,
-      // that is: not the Entity/Field API render pipeline.
-      // An example could be Views' render pipeline. In the example of Views,
-      // the view mode ID would probably contain the View's ID, display and the
-      // row index.
-      $entity_view_mode_ids = array_keys(entity_get_view_modes($entity->entityType()));
-      if (in_array($view_mode_id, $entity_view_mode_ids)) {
-        $output = field_view_field($entity, $field_name, $view_mode_id, $langcode);
-      }
-      else {
-        // Each part of a custom (non-Entity Display) view mode ID is separated
-        // by a dash; the first part must be the module name.
-        $mode_id_parts = explode('-', $view_mode_id, 2);
-        $module = reset($mode_id_parts);
-        $args = array($entity, $field_name, $view_mode_id, $langcode);
-        $output = $this->moduleHandler->invoke($module, 'edit_render_field', $args);
-      }
+      // Closure to render the field given a view mode.
+      $render_field_in_view_mode = function ($view_mode_id) use ($entity, $field_name, $langcode) {
+        return $this->renderField($entity, $field_name, $langcode, $view_mode_id);
+      };
+
+      // Re-render the updated field.
+      $output = $render_field_in_view_mode($view_mode_id);
 
-      $response->addCommand(new FieldFormSavedCommand(drupal_render($output)));
+      // Re-render the updated field for other view modes (i.e. for other
+      // instances of the same logical field on the user's page).
+      $other_view_mode_ids = $request->request->get('other_view_modes') ?: array();
+      $other_view_modes = MapArray::copyValuesToKeys($other_view_mode_ids, $render_field_in_view_mode);
+
+      $response->addCommand(new FieldFormSavedCommand($output, $other_view_modes));
     }
     else {
       $response->addCommand(new FieldFormCommand(drupal_render($form)));
@@ -275,6 +270,48 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view
   }
 
   /**
+   * Renders a field.
+   *
+   * If the view mode ID is not an Entity Display view mode ID, then the field
+   * was rendered using a custom render pipeline (not the Entity/Field API
+   * render pipeline).
+   *
+   * An example could be Views' render pipeline. In that case, the view mode ID
+   * would probably contain the View's ID, display and the row index.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being edited.
+   * @param string $field_name
+   *   The name of the field that is being edited.
+   * @param string $langcode
+   *   The name of the language for which the field is being edited.
+   * @param string $view_mode_id
+   *   The view mode the field should be rerendered in. Either an Entity Display
+   *   view mode ID, or a custom one. See hook_edit_render_field().
+   *
+   * @return string
+   *   Rendered HTML.
+   *
+   * @see hook_edit_render_field()
+   */
+  protected function renderField(EntityInterface $entity, $field_name, $langcode, $view_mode_id) {
+    $entity_view_mode_ids = array_keys(entity_get_view_modes($entity->entityType()));
+    if (in_array($view_mode_id, $entity_view_mode_ids)) {
+      $output = field_view_field($entity, $field_name, $view_mode_id, $langcode);
+    }
+    else {
+      // Each part of a custom (non-Entity Display) view mode ID is separated
+      // by a dash; the first part must be the module name.
+      $mode_id_parts = explode('-', $view_mode_id, 2);
+      $module = reset($mode_id_parts);
+      $args = array($entity, $field_name, $view_mode_id, $langcode);
+      $output = $this->moduleHandler->invoke($module, 'edit_render_field', $args);
+    }
+
+    return drupal_render($output);
+  }
+
+  /**
    * Saves an entity into the database, from TempStore.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
index 4c815e1..df931e9 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
@@ -216,6 +216,7 @@ public function testUserWithPermission() {
       $this->assertIdentical(1, count($ajax_commands), 'The field form HTTP request results in one AJAX command.');
       $this->assertIdentical('editFieldFormSaved', $ajax_commands[0]['command'], 'The first AJAX command is an editFieldFormSaved command.');
       $this->assertTrue(strpos($ajax_commands[0]['data'], 'Fine thanks.'), 'Form value saved and printed back.');
+      $this->assertIdentical($ajax_commands[0]['other_view_modes'], array(), 'Field was not rendered in any other view mode.');
 
       // Ensure the text on the original node did not change yet.
       $this->drupalGet('node/1');
@@ -331,6 +332,9 @@ public function testCustomPipeline() {
         'body[0][format]' => 'filtered_html',
         'op' => t('Save'),
       );
+      // Assume there is another field on this page, which doesn't use a custom
+      // render pipeline, but the default one, and it uses the "full" view mode.
+      $post += array('other_view_modes[]' => 'full');
 
       // Submit field form and check response. Should render with the custom
       // render pipeline.
@@ -341,6 +345,8 @@ public function testCustomPipeline() {
       $this->assertIdentical('editFieldFormSaved', $ajax_commands[0]['command'], 'The first AJAX command is an editFieldFormSaved command.');
       $this->assertTrue(strpos($ajax_commands[0]['data'], 'Fine thanks.'), 'Form value saved and printed back.');
       $this->assertTrue(strpos($ajax_commands[0]['data'], '<div class="edit-test-wrapper">') !== FALSE, 'Custom render pipeline used to render the value.');
+      $this->assertIdentical(array_keys($ajax_commands[0]['other_view_modes']), array('full'), 'Field was also rendered in the "full" view mode.');
+      $this->assertTrue(strpos($ajax_commands[0]['other_view_modes']['full'], 'Fine thanks.'), '"full" version of field contains the form value.');
     }
   }
 
