diff --git a/core/modules/edit/edit.module b/core/modules/edit/edit.module
index 5b115607aa704de29b3967e7699520d097ffe3a5..c29672e83024d5b218c4fb7fd714f2e939c6449f 100644
--- a/core/modules/edit/edit.module
+++ b/core/modules/edit/edit.module
@@ -52,7 +52,6 @@ function edit_page_build(&$page) {
   $page['#attached']['js'][] = array(
     'type' => 'setting',
     'data' => array('edit' => array(
-      'metadataURL' => url('edit/metadata'),
       'fieldFormURL' => url('edit/form/!entity_type/!id/!field_name/!langcode/!view_mode'),
       'context' => 'body',
     )),
diff --git a/core/modules/edit/edit.routing.yml b/core/modules/edit/edit.routing.yml
index b2c0b438f8b787366181d1531f1a5e045752f09d..50aa2c99390db4a47730edd06090a10bb4dd99ce 100644
--- a/core/modules/edit/edit.routing.yml
+++ b/core/modules/edit/edit.routing.yml
@@ -5,6 +5,13 @@ edit_metadata:
   requirements:
     _permission: 'access in-place editing'
 
+edit_attachments:
+  pattern: '/edit/attachments'
+  defaults:
+    _controller: '\Drupal\edit\EditController::attachments'
+  requirements:
+    _permission: 'access in-place editing'
+
 edit_field_form:
   pattern: '/edit/form/{entity_type}/{entity}/{field_name}/{langcode}/{view_mode_id}'
   defaults:
diff --git a/core/modules/edit/js/edit.js b/core/modules/edit/js/edit.js
index 36c884ffcc9bcf3c542a5deb80fa559bb5decf98..102fef7f8bc982c9e5f161c79c1a673fbc112848 100644
--- a/core/modules/edit/js/edit.js
+++ b/core/modules/edit/js/edit.js
@@ -234,36 +234,72 @@ function fetchMissingMetadata (callback) {
     var fieldElementsWithoutMetadata = _.pluck(fieldsMetadataQueue, 'el');
     fieldsMetadataQueue = [];
 
-    $(window).ready(function () {
-      var id = 'edit-load-metadata';
-      // Create a temporary element to be able to use Drupal.ajax.
-      var $el = $('<div id="' + id + '" class="element-hidden"></div>').appendTo('body');
-      // Create a Drupal.ajax instance to load the form.
-      Drupal.ajax[id] = new Drupal.ajax(id, $el, {
-        url: drupalSettings.edit.metadataURL,
-        event: 'edit-internal.edit',
-        submit: { 'fields[]': fieldIDs },
-        // No progress indicator.
-        progress: { type: null }
-      });
-      // Implement a scoped editMetaData AJAX command: calls the callback.
-      Drupal.ajax[id].commands.editMetadata = function (ajax, response, status) {
+    $.ajax({
+      url: Drupal.url('edit/metadata'),
+      type: 'POST',
+      data: { 'fields[]' : fieldIDs },
+      dataType: 'json',
+      success: function(results) {
         // Store the metadata.
-        _.each(response.data, function (fieldMetadata, fieldID) {
+        _.each(results, function (fieldMetadata, fieldID) {
           Drupal.edit.metadata.add(fieldID, fieldMetadata);
         });
-        // Clean-up.
-        delete Drupal.ajax[id];
-        $el.remove();
 
         callback(fieldElementsWithoutMetadata);
-      };
-      // This will ensure our scoped editMetadata AJAX command gets called.
-      $el.trigger('edit-internal.edit');
+      }
     });
   }
 }
 
+/**
+ * Loads missing in-place editor's attachments (JavaScript and CSS files).
+ *
+ * Missing in-place editors are those whose fields are actively being used on
+ * the page but don't have
+ *
+ * @param Function callback
+ *   Callback function to be called when the missing in-place editors (if any)
+ *   have been inserted into the DOM. i.e. they may still be loading.
+ */
+function loadMissingEditors (callback) {
+  var loadedEditors = _.keys(Drupal.edit.editors);
+  var missingEditors = [];
+  Drupal.edit.collections.fields.each(function (fieldModel) {
+    var id = fieldModel.id;
+    var metadata = Drupal.edit.metadata.get(id);
+    if (metadata.access && _.indexOf(loadedEditors, metadata.editor) === -1) {
+      missingEditors.push(metadata.editor);
+    }
+  });
+  missingEditors = _.uniq(missingEditors);
+  if (missingEditors.length === 0) {
+    callback();
+  }
+
+  // @todo Simplify this once https://drupal.org/node/1533366 lands.
+  var id = 'edit-load-editors';
+  // Create a temporary element to be able to use Drupal.ajax.
+  var $el = $('<div id="' + id + '" class="element-hidden"></div>').appendTo('body');
+  // Create a Drupal.ajax instance to load the form.
+  Drupal.ajax[id] = new Drupal.ajax(id, $el, {
+    url: Drupal.url('edit/attachments'),
+    event: 'edit-internal.edit',
+    submit: { 'editors[]': missingEditors },
+    // No progress indicator.
+    progress: { type: null }
+  });
+  // Implement a scoped insert AJAX command: calls the callback after all AJAX
+  // command functions have been executed (hence the deferred calling).
+  var realInsert = Drupal.ajax.prototype.commands.insert;
+  Drupal.ajax[id].commands.insert = function (ajax, response, status) {
+    _.defer(function() { callback(); });
+    realInsert(ajax, response, status);
+  };
+  // Trigger the AJAX request, which will should return AJAX commands to insert
+  // any missing attachments.
+  $el.trigger('edit-internal.edit');
+}
+
 /**
  * Attempts to set up a "Quick edit" link and corresponding EntityModel.
  *
@@ -323,14 +359,16 @@ function initializeEntityContextualLink (contextualLink) {
     });
     fieldsAvailableQueue = _.difference(fieldsAvailableQueue, fields);
 
-    // Set up contextual link view.
-    var $links = $(contextualLink.el).find('.contextual-links');
-    var contextualLinkView = new Drupal.edit.ContextualLinkView($.extend({
-      el: $('<li class="quick-edit"><a href=""></a></li>').prependTo($links),
-      model: entityModel,
-      appModel: Drupal.edit.app.model
-    }, options));
-    entityModel.set('contextualLinkView', contextualLinkView);
+    // Set up contextual link view after loading any missing in-place editors.
+    loadMissingEditors(function () {
+      var $links = $(contextualLink.el).find('.contextual-links');
+      var contextualLinkView = new Drupal.edit.ContextualLinkView($.extend({
+        el: $('<li class="quick-edit"><a href=""></a></li>').prependTo($links),
+        model: entityModel,
+        appModel: Drupal.edit.app.model
+      }, options));
+      entityModel.set('contextualLinkView', contextualLinkView);
+    });
 
     return true;
   }
diff --git a/core/modules/edit/lib/Drupal/edit/Ajax/MetadataCommand.php b/core/modules/edit/lib/Drupal/edit/Ajax/MetadataCommand.php
deleted file mode 100644
index 5f291cacaf0f6349ef3da6396d8f372039693a57..0000000000000000000000000000000000000000
--- a/core/modules/edit/lib/Drupal/edit/Ajax/MetadataCommand.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\edit\Ajax\MetadataCommand.
- */
-
-namespace Drupal\edit\Ajax;
-
-use Drupal\Core\Ajax\CommandInterface;
-
-/**
- * AJAX command for passing fields metadata to Edit's JavaScript app.
- */
-class MetadataCommand extends BaseCommand {
-
-  /**
-   * Constructs a MetadataCommand object.
-   *
-   * @param string $metadata
-   *   The metadata to pass on to the client side.
-   */
-  public function __construct($metadata) {
-    parent::__construct('editMetadata', $metadata);
-  }
-
-}
diff --git a/core/modules/edit/lib/Drupal/edit/EditController.php b/core/modules/edit/lib/Drupal/edit/EditController.php
index da224bbeef97fd07e97ca1fe602230a792ff1578..7a7d1a6f464a16bb49abdaa94da2c0b6e0a891b3 100644
--- a/core/modules/edit/lib/Drupal/edit/EditController.php
+++ b/core/modules/edit/lib/Drupal/edit/EditController.php
@@ -30,12 +30,10 @@ class EditController extends ContainerAware {
    * entity and field level to determine whether the current user may edit them.
    * Also retrieves other metadata.
    *
-   * @return \Drupal\Core\Ajax\AjaxResponse
-   *   The Ajax response.
+   * @return \Symfony\Component\HttpFoundation\JsonResponse
+   *   The JSON response.
    */
   public function metadata(Request $request) {
-    $response = new AjaxResponse();
-
     $fields = $request->request->get('fields');
     if (!isset($fields)) {
       throw new NotFoundHttpException();
@@ -66,15 +64,25 @@ public function metadata(Request $request) {
       $metadata[$field] = $metadataGenerator->generate($entity, $instance, $langcode, $view_mode);
     }
 
-    $response->addCommand(new MetaDataCommand($metadata));
+    return new JsonResponse($metadata);
+  }
 
-    // Determine in-place editors and ensure their attachments are loaded.
-    $editors = array();
-    foreach ($metadata as $edit_id => $field_metadata) {
-      if (isset($field_metadata['editor'])) {
-        $editors[] = $field_metadata['editor'];
-      }
+  /**
+   * Returns AJAX commands to load in-place editors' attachments.
+   *
+   * Given a list of in-place editor IDs as POST parameters, render AJAX
+   * commands to load those in-place editors.
+   *
+   * @return \Drupal\Core\Ajax\AjaxResponse
+   *   The Ajax response.
+   */
+  public function attachments(Request $request) {
+    $response = new AjaxResponse();
+    $editors = $request->request->get('editors');
+    if (!isset($editors)) {
+      throw new NotFoundHttpException();
     }
+
     $editorSelector = $this->container->get('edit.editor.selector');
     $elements['#attached'] = $editorSelector->getEditorAttachments($editors);
     drupal_process_attached($elements);
diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
index 3d83d83976abf7c5bb176212c042c11ca1a125cc..df7f51aceb65b66da64205dfbc1408fb97284539 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
@@ -110,35 +110,36 @@ function testUserWithPermission() {
     $this->assertRaw('data-edit-entity="node/1"');
     $this->assertRaw('data-edit-id="node/1/body/und/full"');
 
-    // Retrieving the metadata should result in a 200 response, containing:
-    //  1. a settings command with useless metadata: AjaxController is dumb
-    //  2. an insert command that loads the required in-place editors
-    //  3. a metadata command with correct per-field metadata
+    // Retrieving the metadata should result in a 200 JSON response.
+    $htmlPageDrupalSettings = $this->drupalSettings;
     $response = $this->retrieveMetadata(array('node/1/body/und/full'));
     $this->assertResponse(200);
-    $ajax_commands = drupal_json_decode($response);
-    $this->assertIdentical(3, count($ajax_commands), 'The metadata HTTP request results in three AJAX commands.');
+    $expected = array(
+      'node/1/body/und/full' => array(
+        'label' => 'Body',
+        'access' => TRUE,
+        'editor' => 'form',
+        'aria' => 'Entity node 1, field Body',
+      )
+    );
+    $this->assertIdentical(drupal_json_decode($response), $expected, 'The metadata HTTP request answers with the correct JSON response.');
+    // Restore drupalSettings to build the next requests; simpletest wipes them
+    // after a JSON response.
+    $this->drupalSettings = $htmlPageDrupalSettings;
 
+    // Retrieving the attachments should result in a 200 response, containing:
+    //  1. a settings command with useless metadata: AjaxController is dumb
+    //  2. an insert command that loads the required in-place editors
+    $response = $this->retrieveAttachments(array('form'));
+    $ajax_commands = drupal_json_decode($response);
+    $this->assertIdentical(2, count($ajax_commands), 'The attachments HTTP request results in two AJAX commands.');
     // First command: settings.
     $this->assertIdentical('settings', $ajax_commands[0]['command'], 'The first AJAX command is a settings command.');
-
     // Second command: insert libraries into DOM.
     $this->assertIdentical('insert', $ajax_commands[1]['command'], 'The second AJAX command is an append command.');
     $command = new AppendCommand('body', '<script src="' . file_create_url('core/modules/edit/js/editors/formEditor.js') . '?v=' . VERSION . '"></script>' . "\n");
     $this->assertIdentical($command->render(), $ajax_commands[1], 'The append command contains the expected data.');
 
-    // Third command: actual metadata.
-    $this->assertIdentical('editMetadata', $ajax_commands[2]['command'], 'The third AJAX command is an Edit metadata command.');
-    $command = new MetadataCommand(array(
-      'node/1/body/und/full' => array(
-        'label' => 'Body',
-        'access' => TRUE,
-        'editor' => 'form',
-        'aria' => 'Entity node 1, field Body'
-      )
-    ));
-    $this->assertIdentical($command->render(), $ajax_commands[2], 'The Edit metadata command contains the expected metadata.');
-
     // Retrieving the form for this field should result in a 200 response,
     // containing only an editFieldForm command.
     $response = $this->retrieveFieldForm('node/1/body/und/full');
@@ -187,6 +188,43 @@ protected function retrieveMetadata($ids) {
     ));
   }
 
+  /**
+   * Retrieves AJAX commands to load attachments for the given in-place editors.
+   *
+   * @param array $editors
+   *   An array of in-place editor ids.
+   *
+   * @return string
+   *   The response body.
+   */
+  protected function retrieveAttachments($editors) {
+    // Build POST values.
+    $post = array();
+    for ($i = 0; $i < count($editors); $i++) {
+      $post['editors[' . $i . ']'] = $editors[$i];
+    }
+
+    // Serialize POST values.
+    foreach ($post as $key => $value) {
+      // Encode according to application/x-www-form-urlencoded
+      // Both names and values needs to be urlencoded, according to
+      // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1
+      $post[$key] = urlencode($key) . '=' . urlencode($value);
+    }
+    $post = implode('&', $post);
+
+    // Perform HTTP request.
+    return $this->curlExec(array(
+      CURLOPT_URL => url('edit/attachments', array('absolute' => TRUE)),
+      CURLOPT_POST => TRUE,
+      CURLOPT_POSTFIELDS => $post . $this->getAjaxPageStatePostData(),
+      CURLOPT_HTTPHEADER => array(
+        'Accept: application/vnd.drupal-ajax',
+        'Content-Type: application/x-www-form-urlencoded',
+      ),
+    ));
+  }
+
   /**
    * Retrieve field form from the server. May also result in additional
    * JavaScript settings and CSS/JS being loaded.
@@ -207,7 +245,7 @@ protected function retrieveFieldForm($field_id) {
       CURLOPT_POST => TRUE,
       CURLOPT_POSTFIELDS => $post . $this->getAjaxPageStatePostData(),
       CURLOPT_HTTPHEADER => array(
-        'Accept: application/json',
+        'Accept: application/vnd.drupal-ajax',
         'Content-Type: application/x-www-form-urlencoded',
       ),
     ));