 core/modules/ckeditor/js/ckeditor.js               |   73 +++++--
 .../ckeditor/Plugin/editor/editor/CKEditor.php     |    3 +-
 core/modules/edit/edit.module                      |    1 -
 core/modules/edit/edit.routing.yml                 |    8 -
 core/modules/edit/js/backbone.drupalform.js        |   22 ++-
 .../editingWidgets/drupalcontenteditablewidget.js  |   36 +---
 .../edit/js/createjs/editingWidgets/formwidget.js  |    4 +-
 core/modules/edit/js/util.js                       |   35 ----
 .../edit/js/views/propertyeditordecoration-view.js |   30 ++-
 core/modules/edit/js/views/toolbar-view.js         |    6 +
 ...RenderedWithoutTransformationFiltersCommand.php |   28 ---
 .../edit/lib/Drupal/edit/EditController.php        |   28 ---
 .../edit/Plugin/edit/editor/DirectEditor.php       |    2 +-
 .../Drupal/edit/Plugin/edit/editor/FormEditor.php  |    2 +-
 .../edit/lib/Drupal/edit/Tests/EditTestBase.php    |    2 +-
 .../Drupal/edit/Tests/MetadataGeneratorTest.php    |    2 -
 core/modules/editor/editor.module                  |   37 ++++
 core/modules/editor/editor.routing.yml             |    7 +
 core/modules/editor/js/editor.createjs.js          |  157 +++++++++++++++
 .../editor/Ajax/GetUntransformedTextCommand.php    |   29 +++
 .../editor/lib/Drupal/editor/EditorController.php  |   48 +++++
 .../Drupal/editor/Plugin/edit/editor/Editor.php    |  100 ++++++++++
 .../Drupal/editor/Tests/EditIntegrationTest.php    |  200 ++++++++++++++++++++
 .../Plugin/editor/editor/UnicornEditor.php         |    3 +-
 24 files changed, 708 insertions(+), 155 deletions(-)

diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js
index b8318be..3568452 100644
--- a/core/modules/ckeditor/js/ckeditor.js
+++ b/core/modules/ckeditor/js/ckeditor.js
@@ -1,20 +1,11 @@
-(function (Drupal, CKEDITOR) {
+(function (Drupal, drupalSettings, CKEDITOR, $) {
 
 "use strict";
 
 Drupal.editors.ckeditor = {
 
   attach: function (element, format) {
-    var externalPlugins = format.editorSettings.externalPlugins;
-    // Register and load additional CKEditor plugins as necessary.
-    if (externalPlugins) {
-      for (var pluginName in externalPlugins) {
-        if (externalPlugins.hasOwnProperty(pluginName)) {
-          CKEDITOR.plugins.addExternal(pluginName, externalPlugins[pluginName], '');
-        }
-      }
-      delete format.editorSettings.drupalExternalPlugins;
-    }
+    this._loadExternalPlugins(format);
     return !!CKEDITOR.replace(element, format.editorSettings);
   },
 
@@ -26,11 +17,69 @@ Drupal.editors.ckeditor = {
       }
       else {
         editor.destroy();
+        element.removeAttribute('contentEditable');
       }
     }
     return !!editor;
+  },
+
+  onChange: function (element, callback) {
+    var editor = CKEDITOR.dom.element.get(element).getEditor();
+    if (editor) {
+      var changed = function () {
+        callback(editor.getData());
+      };
+      // @todo Make this more elegant once http://dev.ckeditor.com/ticket/9794
+      // is fixed.
+      editor.on('key', changed);
+      editor.on('paste', changed);
+      editor.on('afterCommandExec', changed);
+    }
+    return !!editor;
+  },
+
+  attachInlineEditor: function (element, format, mainToolbarId, floatedToolbarId) {
+    this._loadExternalPlugins(format);
+
+    var settings = $.extend(true, {}, format.editorSettings);
+
+    // If a toolbar is already provided for "true WYSIWYG" (in-place editing),
+    // then use that toolbar instead: override the default settings to render
+    // CKEditor UI's top toolbar into mainToolbar, and don't render the bottom
+    // toolbar at all. (CKEditor doesn't need a floated toolbar.)
+    if (mainToolbarId) {
+      var settingsOverride = {
+        extraPlugins: 'sharedspace',
+        removePlugins: 'floatingspace,elementspath',
+        sharedSpaces: {
+          top: mainToolbarId
+        }
+      };
+      settings.extraPlugins += ',' + settingsOverride.extraPlugins;
+      settings.removePlugins += ',' + settingsOverride.removePlugins;
+      settings.sharedSpaces = settingsOverride.sharedSpaces;
+    }
+
+    // CKEditor requires an element to already have the contentEditable
+    // attribute set to "true", otherwise it won't attach an inline editor.
+    element.setAttribute('contentEditable', 'true');
+
+    return !!CKEDITOR.inline(element, settings);
+  },
+
+  _loadExternalPlugins: function(format) {
+    var externalPlugins = format.editorSettings.drupalExternalPlugins;
+    // Register and load additional CKEditor plugins as necessary.
+    if (externalPlugins) {
+      for (var pluginName in externalPlugins) {
+        if (externalPlugins.hasOwnProperty(pluginName)) {
+          CKEDITOR.plugins.addExternal(pluginName, externalPlugins[pluginName], '');
+        }
+      }
+      delete format.editorSettings.drupalExternalPlugins;
+    }
   }
 
 };
 
-})(Drupal, CKEDITOR);
+})(Drupal, drupalSettings, CKEDITOR, jQuery);
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php
index e4c0948..960b3f8 100644
--- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php
@@ -18,7 +18,8 @@
  * @Plugin(
  *   id = "ckeditor",
  *   label = @Translation("CKEditor"),
- *   module = "ckeditor"
+ *   module = "ckeditor",
+ *   supports_inline_editing = TRUE
  * )
  */
 class CKEditor extends EditorBase {
diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index e4a3f82..ad6bce9 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -97,7 +97,6 @@ function edit_library_info() {
         'data' => array('edit' => array(
           'metadataURL' => url('edit/metadata'),
           'fieldFormURL' => url('edit/form/!entity_type/!id/!field_name/!langcode/!view_mode'),
-          'rerenderProcessedTextURL' => url('edit/text/!entity_type/!id/!field_name/!langcode/!view_mode'),
           'context' => 'body',
         )),
         'type' => 'setting',
diff --git a/core/modules/edit/edit.routing.yml b/core/modules/edit/edit.routing.yml
index f63dc82..d66881d 100644
--- a/core/modules/edit/edit.routing.yml
+++ b/core/modules/edit/edit.routing.yml
@@ -12,11 +12,3 @@ edit_field_form:
   requirements:
     _permission: 'access in-place editing'
     _access_edit_entity_field: 'TRUE'
-
-edit_text:
-  pattern: '/edit/text/{entity_type}/{entity}/{field_name}/{langcode}/{view_mode}'
-  defaults:
-    _controller: '\Drupal\edit\EditController::getUntransformedText'
-  requirements:
-    _permission: 'access in-place editing'
-    _access_edit_entity_field: 'TRUE'
diff --git a/core/modules/edit/js/backbone.drupalform.js b/core/modules/edit/js/backbone.drupalform.js
index ba79e76..791a231 100644
--- a/core/modules/edit/js/backbone.drupalform.js
+++ b/core/modules/edit/js/backbone.drupalform.js
@@ -123,8 +123,7 @@ Backbone.syncDirect = function(method, model, options) {
 
         // Successfully saved.
         Drupal.ajax[base].commands.editFieldFormSaved = function (ajax, response, status) {
-          Drupal.edit.util.form.unajaxifySaving(jQuery(ajax.element));
-          jQuery('#edit_backstage form').remove();
+          Backbone.syncDirectCleanUp();
 
           // Call Backbone.sync's success callback with the rerendered field.
           var changedAttributes = {};
@@ -161,4 +160,23 @@ Backbone.syncDirect = function(method, model, options) {
   }
 };
 
+/**
+ * Cleans up the hidden form that Backbone.syncDirect uses for syncing.
+ *
+ * This is called automatically by Backbone.syncDirect when saving is successful
+ * (i.e. when there are no validation errors). Only when editing is canceled
+ * while a PropertyEditor widget is in the invalid state, this must be called
+ * "manually" (in practice, ToolbarView does this). This is necessary because
+ * Backbone.syncDirect is not aware of the application state, it only does the
+ * syncing.
+ * An alternative could be to also remove the hidden form when validation errors
+ * occur, but then the form must be retrieved again, thus resulting in another
+ * roundtrip, which is bad for front-end performance.
+ */
+Backbone.syncDirectCleanUp = function() {
+  var $submit = jQuery('#edit_backstage form .edit-form-submit');
+  Drupal.edit.util.form.unajaxifySaving($submit);
+  jQuery('#edit_backstage form').remove();
+};
+
 })(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js b/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
index caac604..bc86a04 100644
--- a/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
+++ b/core/modules/edit/js/createjs/editingWidgets/drupalcontenteditablewidget.js
@@ -6,7 +6,9 @@
 
 "use strict";
 
-  jQuery.widget('Drupal.drupalContentEditableWidget', jQuery.Create.editWidget, {
+  // @todo D8: use jQuery UI Widget bridging.
+  // @see http://drupal.org/node/1874934#comment-7124904
+  jQuery.widget('DrupalEditEditor.direct', jQuery.Create.editWidget, {
 
     /**
      * Implements getEditUISettings() method.
@@ -54,8 +56,6 @@
           if (from !== 'inactive') {
             // Removes the "contenteditable" attribute.
             this.disable();
-            this._removeValidationErrors();
-            this._cleanUp();
           }
           break;
         case 'highlighted':
@@ -70,42 +70,14 @@
         case 'changed':
           break;
         case 'saving':
-          this._removeValidationErrors();
           break;
         case 'saved':
           break;
         case 'invalid':
           break;
       }
-    },
-
-    /**
-     * Removes validation errors' markup changes, if any.
-     *
-     * Note: this only needs to happen for type=direct, because for type=direct,
-     * the property DOM element itself is modified; this is not the case for
-     * type=form.
-     */
-    _removeValidationErrors: function() {
-      this.element
-        .removeClass('edit-validation-error')
-        .next('.edit-validation-errors').remove();
-    },
-
-    /**
-     * Cleans up after the widget has been saved.
-     *
-     * Note: this is where the Create.Storage and accompanying Backbone.sync
-     * abstractions "leak" implementation details. That is only the case because
-     * we have to use Drupal's Form API as a transport mechanism. It is
-     * unfortunately a stateful transport mechanism, and that's why we have to
-     * clean it up here. This clean-up is only necessary when canceling the
-     * editing of a property after having attempted to save at least once.
-     */
-    _cleanUp: function() {
-      Drupal.edit.util.form.unajaxifySaving(jQuery('#edit_backstage form .edit-form-submit'));
-      jQuery('#edit_backstage form').remove();
     }
+
   });
 
 })(jQuery, Drupal);
diff --git a/core/modules/edit/js/createjs/editingWidgets/formwidget.js b/core/modules/edit/js/createjs/editingWidgets/formwidget.js
index 83dad4f..e7e8b69 100644
--- a/core/modules/edit/js/createjs/editingWidgets/formwidget.js
+++ b/core/modules/edit/js/createjs/editingWidgets/formwidget.js
@@ -6,7 +6,9 @@
 
 "use strict";
 
-  $.widget('Drupal.drupalFormWidget', $.Create.editWidget, {
+  // @todo D8: change the name to "form" + use jQuery UI Widget bridging.
+  // @see http://drupal.org/node/1874934#comment-7124904
+  $.widget('DrupalEditEditor.formEditEditor', $.Create.editWidget, {
 
     id: null,
     $formContainer: null,
diff --git a/core/modules/edit/js/util.js b/core/modules/edit/js/util.js
index 6633859..e0aa490 100644
--- a/core/modules/edit/js/util.js
+++ b/core/modules/edit/js/util.js
@@ -54,41 +54,6 @@ Drupal.edit.util.buildUrl = function(id, urlFormat) {
   });
 };
 
-/**
- * Loads rerendered processed text for a given property.
- *
- * Leverages Drupal.ajax' ability to have scoped (per-instance) command
- * implementations to be able to call a callback.
- *
- * @param options
- *   An object with the following keys:
- *    - $editorElement (required): the PredicateEditor DOM element.
- *    - propertyID (required): the property ID that uniquely identifies the
- *      property for which this form will be loaded.
- *    - callback (required: A callback function that will receive the rerendered
- *      processed text.
- */
-Drupal.edit.util.loadRerenderedProcessedText = function(options) {
-  // Create a Drupal.ajax instance to load the form.
-  Drupal.ajax[options.propertyID] = new Drupal.ajax(options.propertyID, options.$editorElement, {
-    url: Drupal.edit.util.buildUrl(options.propertyID, drupalSettings.edit.rerenderProcessedTextURL),
-    event: 'edit-internal.edit',
-    submit: { nocssjs : true },
-    progress: { type : null } // No progress indicator.
-  });
-  // Implement a scoped editFieldRenderedWithoutTransformationFilters AJAX
-  // command: calls the callback.
-  Drupal.ajax[options.propertyID].commands.editFieldRenderedWithoutTransformationFilters = function(ajax, response, status) {
-    options.callback(response.data);
-    // Delete the Drupal.ajax instance that called this very function.
-    delete Drupal.ajax[options.propertyID];
-    options.$editorElement.off('edit-internal.edit');
-  };
-  // This will ensure our scoped editFieldRenderedWithoutTransformationFilters
-  // AJAX command gets called.
-  options.$editorElement.trigger('edit-internal.edit');
-};
-
 Drupal.edit.util.form = {
   /**
    * Loads a form, calls a callback to inserts.
diff --git a/core/modules/edit/js/views/propertyeditordecoration-view.js b/core/modules/edit/js/views/propertyeditordecoration-view.js
index aad9832..7c3b94c 100644
--- a/core/modules/edit/js/views/propertyeditordecoration-view.js
+++ b/core/modules/edit/js/views/propertyeditordecoration-view.js
@@ -32,7 +32,8 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
    *   - editor: the editor object with an 'options' object that has these keys:
    *      * entity: the VIE entity for the property.
    *      * property: the predicate of the property.
-   *      * widget: the parent EditableeEntity widget.
+   *      * widget: the parent EditableEntity widget.
+   *      * editorName: the name of the PropertyEditor widget
    *   - toolbarId: the ID attribute of the toolbar as rendered in the DOM.
    */
   initialize: function(options) {
@@ -40,6 +41,7 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
     this.toolbarId = options.toolbarId;
 
     this.predicate = this.editor.options.property;
+    this.editorName = this.editor.options.editorName;
 
     this.$el.css('background-color', this._getBgColor(this.$el));
 
@@ -55,6 +57,9 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
       case 'inactive':
         if (from !== null) {
           this.undecorate();
+          if (from === 'invalid') {
+            this._removeValidationErrors();
+          }
         }
         break;
       case 'candidate':
@@ -63,6 +68,9 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
           this.stopHighlight();
           if (from !== 'highlighted') {
             this.stopEdit();
+            if (from === 'invalid') {
+              this._removeValidationErrors();
+            }
           }
         }
         break;
@@ -83,6 +91,9 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
       case 'changed':
         break;
       case 'saving':
+        if (from === 'invalid') {
+          this._removeValidationErrors();
+        }
         break;
       case 'saved':
         break;
@@ -329,7 +340,24 @@ Drupal.edit.views.PropertyEditorDecorationView = Backbone.View.extend({
     else {
       callback();
     }
+  },
+
+  /**
+   * Removes validation errors' markup changes, if any.
+   *
+   * Note: this only needs to happen for type=direct, because for type=direct,
+   * the property DOM element itself is modified; this is not the case for
+   * type=form.
+   */
+  _removeValidationErrors: function() {
+    if (this.editorName !== 'form') {
+      this.$el
+        .removeClass('edit-validation-error')
+        .next('.edit-validation-errors')
+        .remove();
+    }
   }
+
 });
 
 })(jQuery, Backbone, Drupal);
diff --git a/core/modules/edit/js/views/toolbar-view.js b/core/modules/edit/js/views/toolbar-view.js
index b052362..bca1351 100644
--- a/core/modules/edit/js/views/toolbar-view.js
+++ b/core/modules/edit/js/views/toolbar-view.js
@@ -68,6 +68,9 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
       case 'inactive':
         if (from) {
           this.remove();
+          if (this.editorName !== 'form') {
+            Backbone.syncDirectCleanUp();
+          }
         }
         break;
       case 'candidate':
@@ -75,6 +78,9 @@ Drupal.edit.views.ToolbarView = Backbone.View.extend({
           this.render();
         }
         else {
+          if (this.editorName !== 'form') {
+            Backbone.syncDirectCleanUp();
+          }
           // Remove all toolgroups; they're no longer necessary.
           this.$el
             .removeClass('edit-highlighted edit-editing')
diff --git a/core/modules/edit/lib/Drupal/edit/Ajax/FieldRenderedWithoutTransformationFiltersCommand.php b/core/modules/edit/lib/Drupal/edit/Ajax/FieldRenderedWithoutTransformationFiltersCommand.php
deleted file mode 100644
index 53a8826..0000000
--- a/core/modules/edit/lib/Drupal/edit/Ajax/FieldRenderedWithoutTransformationFiltersCommand.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\edit\Ajax\FieldRenderedWithoutTransformationFiltersCommand.
- */
-
-namespace Drupal\edit\Ajax;
-
-use Drupal\Core\Ajax\CommandInterface;
-
-/**
- * AJAX command to rerender a processed text field without any transformation
- * filters.
- */
-class FieldRenderedWithoutTransformationFiltersCommand extends BaseCommand {
-
-  /**
-   * Constructs a FieldRenderedWithoutTransformationFiltersCommand object.
-   *
-   * @param string $data
-   *   The data to pass on to the client side.
-   */
-  public function __construct($data) {
-    parent::__construct('editFieldRenderedWithoutTransformationFilters', $data);
-  }
-
-}
diff --git a/core/modules/edit/lib/Drupal/edit/EditController.php b/core/modules/edit/lib/Drupal/edit/EditController.php
index e20d48f..a9b051b 100644
--- a/core/modules/edit/lib/Drupal/edit/EditController.php
+++ b/core/modules/edit/lib/Drupal/edit/EditController.php
@@ -16,7 +16,6 @@
 use Drupal\edit\Ajax\FieldFormCommand;
 use Drupal\edit\Ajax\FieldFormSavedCommand;
 use Drupal\edit\Ajax\FieldFormValidationErrorsCommand;
-use Drupal\edit\Ajax\FieldRenderedWithoutTransformationFiltersCommand;
 
 /**
  * Returns responses for Edit module routes.
@@ -117,31 +116,4 @@ public function fieldForm(EntityInterface $entity, $field_name, $langcode, $view
     return $response;
   }
 
-  /**
-   * Returns an Ajax response to render a text field without transformation filters.
-   *
-   * @param int $entity
-   *   The entity of which a processed text field is being rerendered.
-   * @param string $field_name
-   *   The name of the (processed text) field that that is being rerendered
-   * @param string $langcode
-   *   The name of the language for which the processed text field is being
-   *   rererendered.
-   * @param string $view_mode
-   *   The view mode the processed text field should be rerendered in.
-   * @return \Drupal\Core\Ajax\AjaxResponse
-   *   The Ajax response.
-   */
-  public function getUntransformedText(EntityInterface $entity, $field_name, $langcode, $view_mode) {
-    $response = new AjaxResponse();
-
-    $output = field_view_field($entity, $field_name, $view_mode, $langcode);
-    $langcode = $output['#language'];
-    // Direct text editing is only supported for single-valued fields.
-    $editable_text = check_markup($output['#items'][0]['value'], $output['#items'][0]['format'], $langcode, FALSE, array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE));
-    $response->addCommand(new FieldRenderedWithoutTransformationFiltersCommand($editable_text));
-
-    return $response;
-  }
-
 }
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php
index 0a386c5..b61b372 100644
--- a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php
+++ b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/DirectEditor.php
@@ -16,7 +16,7 @@
  *
  * @Plugin(
  *   id = "direct",
- *   jsClassName = "drupalContentEditableWidget",
+ *   jsClassName = "direct",
  *   module = "edit"
  * )
  */
diff --git a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php
index 59e8d67..f0ca366 100644
--- a/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php
+++ b/core/modules/edit/lib/Drupal/edit/Plugin/edit/editor/FormEditor.php
@@ -16,7 +16,7 @@
  *
  * @Plugin(
  *   id = "form",
- *   jsClassName = "drupalFormWidget",
+ *   jsClassName = "formEditEditor",
  *   module = "edit"
  * )
  */
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php b/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php
index c3a91fb..4587ba8 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/EditTestBase.php
@@ -30,7 +30,7 @@ function setUp() {
 
     $this->installSchema('system', 'variable');
     $this->installSchema('field', array('field_config', 'field_config_instance'));
-    $this->installSchema('field_test', 'test_entity');
+    $this->installSchema('field_test', array('test_entity', 'test_entity_revision'));
 
     // Set default storage backend.
     variable_set('field_storage_default', $this->default_storage);
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/MetadataGeneratorTest.php b/core/modules/edit/lib/Drupal/edit/Tests/MetadataGeneratorTest.php
index 529d535..61cf5ed 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/MetadataGeneratorTest.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/MetadataGeneratorTest.php
@@ -56,8 +56,6 @@ public static function getInfo() {
   function setUp() {
     parent::setUp();
 
-    $this->installSchema('field_test', 'test_entity_revision');
-
     $this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
     $this->accessChecker = new MockEditEntityFieldAccessCheck();
     $this->editorSelector = new EditorSelector($this->editorManager);
diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module
index 6db6210..f0ad7fd 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -78,11 +78,48 @@ function editor_library_info() {
       array('system', 'jquery.once'),
     ),
   );
+  // Create.js PropertyEditor widget library names begin with "edit.editor".
+  $libraries['edit.editor.editor'] = array(
+    'title' => '"Editor" Create.js PropertyEditor widget',
+    'version' => VERSION,
+    'js' => array(
+      $path . '/js/editor.createjs.js' => array(
+        'scope' => 'footer',
+        'attributes' => array('defer' => TRUE),
+      ),
+      array(
+        'type' => 'setting',
+        'data' => array(
+          'editor' => array(
+            'getUntransformedTextURL' => url('editor/!entity_type/!id/!field_name/!langcode/!view_mode'),
+          )
+        )
+      ),
+    ),
+    'dependencies' => array(
+      array('edit', 'edit'),
+      array('editor', 'drupal.editor'),
+      array('system', 'drupal.ajax'),
+      array('system', 'drupalSettings'),
+    ),
+  );
 
   return $libraries;
 }
 
 /**
+ * Implements hook_custom_theme().
+ *
+ * @todo Add an event subscriber to the Ajax system to automatically set the
+ *   base page theme for all Ajax requests, and then remove this one off.
+ */
+function editor_custom_theme() {
+  if (substr(current_path(), 0, 7) === 'editor/') {
+    return ajax_base_page_theme();
+  }
+}
+
+/**
  * Implements hook_form_FORM_ID_alter().
  */
 function editor_form_filter_admin_overview_alter(&$form, $form_state) {
diff --git a/core/modules/editor/editor.routing.yml b/core/modules/editor/editor.routing.yml
new file mode 100644
index 0000000..0bb56cf
--- /dev/null
+++ b/core/modules/editor/editor.routing.yml
@@ -0,0 +1,7 @@
+editor_field_untransformed_text:
+  pattern: '/editor/{entity_type}/{entity}/{field_name}/{langcode}/{view_mode}'
+  defaults:
+    _controller: '\Drupal\editor\EditorController::getUntransformedText'
+  requirements:
+    _permission: 'access in-place editing'
+    _access_edit_entity_field: 'TRUE'
diff --git a/core/modules/editor/js/editor.createjs.js b/core/modules/editor/js/editor.createjs.js
new file mode 100644
index 0000000..c4bfae6
--- /dev/null
+++ b/core/modules/editor/js/editor.createjs.js
@@ -0,0 +1,157 @@
+/**
+ * @file
+ * Text editor-based Create.js widget for processed text content in Drupal.
+ *
+ * Depends on editor.module. Works with any (WYSIWYG) editor that implements the
+ * editor.js API, including the optional attachInlineEditor() and onChange()
+ * methods.
+ * For example, assuming that a hypothetical editor's name was "Magical Editor"
+ * and its editor.js API implementation lived at Drupal.editors.magical, this
+ * JavaScript would use:
+ *  - Drupal.editors.magical.attachInlineEditor()
+ *  - Drupal.editors.magical.onChange()
+ *  - Drupal.editors.magical.detach()
+ */
+(function (jQuery, Drupal, drupalSettings) {
+
+"use strict";
+
+// @todo D8: use jQuery UI Widget bridging.
+// @see http://drupal.org/node/1874934#comment-7124904
+jQuery.widget('DrupalEditEditor.editor', jQuery.DrupalEditEditor.direct, {
+
+  textFormat: null,
+  textFormatHasTransformations: null,
+  textEditor: null,
+
+  /**
+   * Implements Create.editWidget.getEditUISettings.
+   */
+  getEditUISettings: function () {
+    return { padding: true, unifiedToolbar: true, fullWidthToolbar: true };
+  },
+
+  /**
+   * Implements jQuery.widget._init.
+   *
+   * @todo D8: Remove this.
+   * @see http://drupal.org/node/1874934
+   */
+  _init: function () {},
+
+  /**
+   * Implements Create.editWidget._initialize.
+   */
+  _initialize: function () {
+    var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
+    var metadata = Drupal.edit.metadataCache[propertyID].custom;
+
+    this.textFormat = drupalSettings.editor.formats[metadata.format];
+    this.textFormatHasTransformations = metadata.formatHasTransformations;
+    this.textEditor = Drupal.editors[this.textFormat.editor];
+  },
+
+  /**
+   * Implements Create.editWidget.stateChange.
+   */
+  stateChange: function (from, to) {
+    var that = this;
+    switch (to) {
+      case 'inactive':
+        break;
+
+      case 'candidate':
+        // Detach the text editor when entering the 'candidate' state from one
+        // of the states where it could have been attached.
+        if (from !== 'inactive' && from !== 'highlighted') {
+            this.textEditor.detach(this.element.get(0), this.textFormat);
+        }
+        break;
+
+      case 'highlighted':
+        break;
+
+      case 'activating':
+        // When transformation filters have been been applied to the processed
+        // text of this field, then we'll need to load a re-processed version of
+        // it without the transformation filters.
+        if (this.textFormatHasTransformations) {
+          var propertyID = Drupal.edit.util.calcPropertyID(this.options.entity, this.options.property);
+          this._getUntransformedText(propertyID, this.element, function (untransformedText) {
+            that.element.html(untransformedText);
+            that.options.activated();
+          });
+        }
+        // When no transformation filters have been applied: start WYSIWYG
+        // editing immediately!
+        else {
+          this.options.activated();
+        }
+        break;
+
+      case 'active':
+        this.textEditor.attachInlineEditor(
+          this.element.get(0),
+          this.textFormat,
+          this.toolbarView.getMainWysiwygToolgroupId(),
+          this.toolbarView.getFloatedWysiwygToolgroupId()
+        );
+        // Set the state to 'changed' whenever the content has changed.
+        this.textEditor.onChange(this.element.get(0), function (html) {
+          that.options.changed(html);
+        });
+        break;
+
+      case 'changed':
+        break;
+
+      case 'saving':
+        break;
+
+      case 'saved':
+        break;
+
+      case 'invalid':
+        break;
+    }
+  },
+
+  /**
+   * Loads untransformed text for a given property.
+   *
+   * More accurately: it re-processes processed text to exclude transformation
+   * filters used by the text format.
+   *
+   * @param String propertyID
+   *   A property ID that uniquely identifies the given property.
+   * @param jQuery $editorElement
+   *   The property's PropertyEditor DOM element.
+   * @param Function callback
+   *   A callback function that will receive the untransformed text.
+   *
+   * @see \Drupal\editor\Ajax\GetUntransformedTextCommand
+   */
+  _getUntransformedText: function (propertyID, $editorElement, callback) {
+    // Create a Drupal.ajax instance to load the form.
+    Drupal.ajax[propertyID] = new Drupal.ajax(propertyID, $editorElement, {
+      url: Drupal.edit.util.buildUrl(propertyID, drupalSettings.editor.getUntransformedTextURL),
+      event: 'editor-internal.editor',
+      submit: { nocssjs : true },
+      progress: { type : null } // No progress indicator.
+    });
+    // Implement a scoped editorGetUntransformedText AJAX command: calls the
+    // callback.
+    Drupal.ajax[propertyID].commands.editorGetUntransformedText = function(ajax, response, status) {
+      callback(response.data);
+      // Delete the Drupal.ajax instance that called this very function.
+      delete Drupal.ajax[propertyID];
+      $editorElement.off('editor-internal.editor');
+    };
+    // This will ensure our scoped editorGetUntransformedText AJAX command
+    // gets called.
+    $editorElement.trigger('editor-internal.editor');
+  }
+
+});
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/editor/lib/Drupal/editor/Ajax/GetUntransformedTextCommand.php b/core/modules/editor/lib/Drupal/editor/Ajax/GetUntransformedTextCommand.php
new file mode 100644
index 0000000..c1971d1
--- /dev/null
+++ b/core/modules/editor/lib/Drupal/editor/Ajax/GetUntransformedTextCommand.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\editor\Ajax\GetUntransformedTextCommand.
+ */
+
+namespace Drupal\editor\Ajax;
+
+use Drupal\Core\Ajax\CommandInterface;
+use Drupal\edit\Ajax\BaseCommand;
+
+/**
+ * AJAX command to rerender a processed text field without any transformation
+ * filters.
+ */
+class GetUntransformedTextCommand extends BaseCommand {
+
+  /**
+   * Constructs a GetUntransformedTextCommand object.
+   *
+   * @param string $data
+   *   The data to pass on to the client side.
+   */
+  public function __construct($data) {
+    parent::__construct('editorGetUntransformedText', $data);
+  }
+
+}
diff --git a/core/modules/editor/lib/Drupal/editor/EditorController.php b/core/modules/editor/lib/Drupal/editor/EditorController.php
new file mode 100644
index 0000000..6e2ef76
--- /dev/null
+++ b/core/modules/editor/lib/Drupal/editor/EditorController.php
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\editor\EditorController.
+ */
+
+namespace Drupal\editor;
+
+use Symfony\Component\DependencyInjection\ContainerAware;
+use Drupal\Core\Ajax\AjaxResponse;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\editor\Ajax\GetUntransformedTextCommand;
+
+/**
+ * Returns responses for Editor module routes.
+ */
+class EditorController extends ContainerAware {
+
+  /**
+   * Returns an Ajax response to render a text field without transformation filters.
+   *
+   * @param int $entity
+   *   The entity of which a processed text field is being rerendered.
+   * @param string $field_name
+   *   The name of the (processed text) field that that is being rerendered
+   * @param string $langcode
+   *   The name of the language for which the processed text field is being
+   *   rererendered.
+   * @param string $view_mode
+   *   The view mode the processed text field should be rerendered in.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   The Ajax response.
+   */
+  public function getUntransformedText(EntityInterface $entity, $field_name, $langcode, $view_mode) {
+    $response = new AjaxResponse();
+
+    $output = field_view_field($entity, $field_name, $view_mode, $langcode);
+    $langcode = $output['#language'];
+    // Direct text editing is only supported for single-valued fields.
+    $editable_text = check_markup($output['#items'][0]['value'], $output['#items'][0]['format'], $langcode, FALSE, array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE));
+    $response->addCommand(new GetUntransformedTextCommand($editable_text));
+
+    return $response;
+  }
+
+}
diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php b/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php
new file mode 100644
index 0000000..31e12cc
--- /dev/null
+++ b/core/modules/editor/lib/Drupal/editor/Plugin/edit/editor/Editor.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\editor\Plugin\edit\editor\Editor.
+ */
+
+namespace Drupal\editor\Plugin\edit\editor;
+
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\edit\EditorInterface;
+use Drupal\field\FieldInstance;
+
+
+/**
+ * Defines the "editor" Create.js PropertyEditor widget.
+ *
+ * @Plugin(
+ *   id = "editor",
+ *   jsClassName = "editor",
+ *   alternativeTo = {"direct"},
+ *   module = "editor"
+ * )
+ */
+class Editor extends PluginBase implements EditorInterface {
+
+  /**
+   * Implements \Drupal\edit\Plugin\EditorInterface::isCompatible().
+   */
+  function isCompatible(FieldInstance $instance, array $items) {
+    $field = field_info_field($instance['field_name']);
+
+    // This editor is incompatible with multivalued fields.
+    if ($field['cardinality'] != 1) {
+      return FALSE;
+    }
+    // This editor is compatible with processed ("rich") text fields; but only
+    // if there is a currently active text format, that text format has an
+    // associated editor and that editor supports inline editing.
+    elseif (!empty($instance['settings']['text_processing'])) {
+      $format_id = $items[0]['format'];
+      if (isset($format_id) && $editor = editor_load($format_id)) {
+        $definition = drupal_container()->get('plugin.manager.editor')->getDefinition($editor->editor);
+        if ($definition['supports_inline_editing'] === TRUE) {
+          return TRUE;
+        }
+      }
+
+      return FALSE;
+    }
+  }
+
+  /**
+   * Implements \Drupal\edit\Plugin\EditorInterface::getMetadata().
+   */
+  function getMetadata(FieldInstance $instance, array $items) {
+    $format_id = $items[0]['format'];
+    $metadata['format'] = $format_id;
+    $metadata['formatHasTransformations'] = $this->textFormatHasTransformationFilters($format_id);
+    return $metadata;
+  }
+
+  /**
+   * Returns whether the text format has transformation filters.
+   */
+  protected function textFormatHasTransformationFilters($format_id) {
+    return (bool) count(array_intersect(array(FILTER_TYPE_TRANSFORM_REVERSIBLE, FILTER_TYPE_TRANSFORM_IRREVERSIBLE), filter_get_filter_types_by_format($format_id)));
+  }
+
+  /**
+   * Implements \Drupal\edit\EditorInterface::getAttachments().
+   */
+  public function getAttachments() {
+    global $user;
+
+    $user_format_ids = array_keys(filter_formats($user));
+    $manager = drupal_container()->get('plugin.manager.editor');
+    $definitions = $manager->getDefinitions();
+
+    // Filter the current user's formats to those that support inline editing.
+    $formats = array();
+    foreach ($user_format_ids as $format_id) {
+      $editor = editor_load($format_id);
+      if ($editor && isset($definitions[$editor->editor]) && isset($definitions[$editor->editor]['supports_inline_editing']) && $definitions[$editor->editor]['supports_inline_editing'] === TRUE) {
+        $formats[] = $format_id;
+      }
+    }
+
+    // Get the attachments for all text editors that the user might use.
+    $attachments = $manager->getAttachments($formats);
+
+    // Also include editor.module's Create.js PropertyEditor widget.
+    $attachments['library'][] = array('editor', 'edit.editor.editor');
+
+    return $attachments;
+  }
+
+}
diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditIntegrationTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditIntegrationTest.php
new file mode 100644
index 0000000..6417bd3
--- /dev/null
+++ b/core/modules/editor/lib/Drupal/editor/Tests/EditIntegrationTest.php
@@ -0,0 +1,200 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\editor\Tests\EditorIntegrationTest.
+ */
+
+namespace Drupal\editor\Tests;
+
+use Drupal\edit\EditorSelector;
+use Drupal\edit\MetadataGenerator;
+use Drupal\edit\Plugin\EditorManager;
+use Drupal\edit\Tests\EditTestBase;
+use Drupal\edit_test\MockEditEntityFieldAccessCheck;
+use Drupal\editor\EditorController;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Tests Edit module integration (Editor module's inline editing support).
+ */
+class EditIntegrationTest extends EditTestBase {
+
+  /**
+   * The manager for editor (Create.js PropertyEditor widget) plug-ins.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $editorManager;
+
+  /**
+   * The metadata generator object to be tested.
+   *
+   * @var \Drupal\edit\MetadataGeneratorInterface.php
+   */
+  protected $metadataGenerator;
+
+  /**
+   * The editor selector object to be used by the metadata generator object.
+   *
+   * @var \Drupal\edit\EditorSelectorInterface
+   */
+  protected $editorSelector;
+
+  /**
+   * The access checker object to be used by the metadata generator object.
+   *
+   * @var \Drupal\edit\Access\EditEntityFieldAccessCheckInterface
+   */
+  protected $accessChecker;
+
+  /**
+   * The name of the field ued for tests.
+   *
+   * @var string
+   */
+  protected $field_name;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'In-place text editors (Edit module integration)',
+      'description' => 'Tests Edit module integration (Editor module\'s inline editing support).',
+      'group' => 'Text Editor',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    // Install the Filter module.
+    $this->installSchema('system', 'url_alias');
+    $this->enableModules(array('user', 'filter'));
+
+    // Enable the Text Editor and Text Editor Test module.
+    $this->enableModules(array('editor', 'editor_test'));
+
+    // Create a field.
+    $this->field_name = 'field_textarea';
+    $this->createFieldWithInstance(
+      $this->field_name, 'text', 1, 'Long text field',
+      // Instance settings.
+      array('text_processing' => 1),
+      // Widget type & settings.
+      'text_textarea',
+      array('size' => 42),
+      // 'default' formatter type & settings.
+      'text_default',
+      array()
+    );
+
+    // Create text format.
+    $full_html_format = entity_create('filter_format', array(
+      'format' => 'full_html',
+      'name' => 'Full HTML',
+      'weight' => 1,
+      'filters' => array(),
+    ));
+    $full_html_format->save();
+
+    // Associate text editor with text format.
+    $editor = entity_create('editor', array(
+      'format' => $full_html_format->format,
+      'editor' => 'unicorn',
+    ));
+    $editor->save();
+  }
+
+  /**
+   * Retrieves the FieldInstance object for the given field and returns the
+   * editor that Edit selects.
+   */
+  protected function getSelectedEditor($items, $field_name, $view_mode = 'default') {
+    $options = entity_get_display('test_entity', 'test_bundle', $view_mode)->getComponent($field_name);
+    $field_instance = field_info_instance('test_entity', $field_name, 'test_bundle');
+    return $this->editorSelector->getEditor($options['type'], $field_instance, $items);
+  }
+
+  /**
+   * Tests editor selection when the Editor module is present.
+   *
+   * Tests a textual field, with text processing, with cardinality 1 and >1,
+   * always with a ProcessedTextEditor plug-in present, but with varying text
+   * format compatibility.
+   */
+  function testEditorSelection() {
+    $this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
+    $this->editorSelector = new EditorSelector($this->editorManager);
+
+    // Pretend there is an entity with these items for the field.
+    $items = array(array('value' => 'Hello, world!', 'format' => 'filtered_html'));
+
+    // Editor selection w/ cardinality 1, text format w/o associated text editor.
+    $this->assertEqual('form', $this->getSelectedEditor($items, $this->field_name), "With cardinality 1, and the filtered_html text format, the 'form' editor is selected.");
+
+    // Editor selection w/ cardinality 1, text format w/ associated text editor.
+    $items[0]['format'] = 'full_html';
+    $this->assertEqual('editor', $this->getSelectedEditor($items, $this->field_name), "With cardinality 1, and the full_html text format, the 'editor' editor is selected.");
+
+    // Editor selection with text processing, cardinality >1
+    $this->field_textarea_field['cardinality'] = 2;
+    field_update_field($this->field_textarea_field);
+    $items[] = array('value' => 'Hallo, wereld!', 'format' => 'full_html');
+    $this->assertEqual('form', $this->getSelectedEditor($items, $this->field_name), "With cardinality >1, and both items using the full_html text format, the 'form' editor is selected.");
+  }
+
+  /**
+   * Tests (custom) metadata when the "Editor" Create.js editor is used.
+   */
+  function testMetadata() {
+    $this->editorManager = new EditorManager($this->container->getParameter('container.namespaces'));
+    $this->accessChecker = new MockEditEntityFieldAccessCheck();
+    $this->editorSelector = new EditorSelector($this->editorManager);
+    $this->metadataGenerator = new MetadataGenerator($this->accessChecker, $this->editorSelector, $this->editorManager);
+
+    // Create an entity with values for the field.
+    $this->entity = field_test_create_entity();
+    $this->is_new = TRUE;
+    $this->entity->{$this->field_name}[LANGUAGE_NOT_SPECIFIED] = array(array('value' => 'Test', 'format' => 'full_html'));
+    field_test_entity_save($this->entity);
+    $entity = entity_load('test_entity', $this->entity->ftid);
+
+    // Verify metadata.
+    $instance = field_info_instance($entity->entityType(), $this->field_name, $entity->bundle());
+    $metadata = $this->metadataGenerator->generate($entity, $instance, LANGUAGE_NOT_SPECIFIED, 'default');
+    $expected = array(
+      'access' => TRUE,
+      'label' => 'Long text field',
+      'editor' => 'editor',
+      'aria' => 'Entity test_entity 1, field Long text field',
+      'custom' => array(
+        'format' => 'full_html',
+        'formatHasTransformations' => FALSE,
+      ),
+    );
+    $this->assertEqual($expected, $metadata, 'The correct metadata (including custom metadata) is generated.');
+  }
+
+  /**
+   * Tests GetUntransformedTextCommand AJAX command.
+   */
+  function testGetUntransformedTextCommand() {
+    // Create an entity with values for the field.
+    $this->entity = field_test_create_entity();
+    $this->is_new = TRUE;
+    $this->entity->{$this->field_name}[LANGUAGE_NOT_SPECIFIED] = array(array('value' => 'Test', 'format' => 'full_html'));
+    field_test_entity_save($this->entity);
+    $entity = entity_load('test_entity', $this->entity->ftid);
+
+    // Verify AJAX response.
+    $controller = new EditorController();
+    $request = new Request();
+    $response = $controller->getUntransformedText($entity, $this->field_name, LANGUAGE_NOT_SPECIFIED, 'default');
+    $expected = array(
+      array(
+        'command' => 'editorGetUntransformedText',
+        'data' => 'Test',
+      )
+    );
+    $this->assertEqual(drupal_json_encode($expected), $response->prepare($request)->getContent(), 'The GetUntransformedTextCommand AJAX command works correctly.');
+  }
+}
diff --git a/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/editor/editor/UnicornEditor.php b/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/editor/editor/UnicornEditor.php
index 6a0fbaa..4ba8fac 100644
--- a/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/editor/editor/UnicornEditor.php
+++ b/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/editor/editor/UnicornEditor.php
@@ -18,7 +18,8 @@
  * @Plugin(
  *   id = "unicorn",
  *   label = @Translation("Unicorn Editor"),
- *   module = "editor_test"
+ *   module = "editor_test",
+ *   supports_inline_editing = TRUE
  * )
  */
 class UnicornEditor extends EditorBase {
