diff --git a/core/core.services.yml b/core/core.services.yml
index 200972e73b32797b03efbfdc3d96c1a0e7667082..2f04124df99dd0c96e6d1105b1397609649ec834 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -749,6 +749,9 @@ services:
   plugin.manager.condition:
     class: Drupal\Core\Condition\ConditionManager
     parent: default_plugin_manager
+  plugin.manager.element_info:
+    class: Drupal\Core\Render\ElementInfoManager
+    parent: default_plugin_manager
   kernel_destruct_subscriber:
     class: Drupal\Core\EventSubscriber\KernelDestructionSubscriber
     tags:
@@ -907,8 +910,7 @@ services:
   info_parser:
     class: Drupal\Core\Extension\InfoParser
   element_info:
-    class: Drupal\Core\Render\ElementInfo
-    arguments: ['@module_handler']
+    alias: plugin.manager.element_info
   file.mime_type.guesser:
     class: Drupal\Core\File\MimeType\MimeTypeGuesser
     tags:
diff --git a/core/includes/ajax.inc b/core/includes/ajax.inc
index 4a03c6869b68b2a02d82da87cfda5fb093948fe7..3b5c2779b133e696bbfd193444c23e79efc7ed86 100644
--- a/core/includes/ajax.inc
+++ b/core/includes/ajax.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
 
 /**
  * @defgroup ajax Ajax API
@@ -162,20 +163,10 @@
 /**
  * Form element processing handler for the #ajax form property.
  *
- * @param $element
- *   An associative array containing the properties of the element.
- *
- * @return
- *   The processed element.
- *
- * @see ajax_pre_render_element()
+ * @deprecated Use \Drupal\Core\Render\Element\FormElement::processAjaxForm().
  */
-function ajax_process_form($element, FormStateInterface $form_state) {
-  $element = ajax_pre_render_element($element);
-  if (!empty($element['#ajax_processed'])) {
-    $form_state['cache'] = TRUE;
-  }
-  return $element;
+function ajax_process_form($element, FormStateInterface $form_state, &$complete_form) {
+  return Element\FormElement::processAjaxForm($element, $form_state, $complete_form);
 }
 
 /**
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 6c6c4a3a88af884027d6b5c41b3f6bef22103872..18ed1d510460cdd4479ea426cce8ed053c8435c3 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -2670,65 +2670,10 @@ function drupal_pre_render_html_tag($element) {
 /**
  * Pre-render callback: Renders a link into #markup.
  *
- * Doing so during pre_render gives modules a chance to alter the link parts.
- *
- * @param $elements
- *   A structured array whose keys form the arguments to l():
- *   - #title: The link text to pass as argument to l().
- *   - One of the following
- *     - #route_name and (optionally) and a #route_parameters array; The route
- *       name and route parameters which will be passed into the link generator.
- *     - #href: The system path or URL to pass as argument to l().
- *   - #options: (optional) An array of options to pass to l() or the link
- *     generator.
- *
- * @return
- *   The passed-in elements containing a rendered link in '#markup'.
+ * @deprecated Use \Drupal\Core\Render\Element\Link::preRenderLink().
  */
 function drupal_pre_render_link($element) {
-  // By default, link options to pass to l() are normally set in #options.
-  $element += array('#options' => array());
-  // However, within the scope of renderable elements, #attributes is a valid
-  // way to specify attributes, too. Take them into account, but do not override
-  // attributes from #options.
-  if (isset($element['#attributes'])) {
-    $element['#options'] += array('attributes' => array());
-    $element['#options']['attributes'] += $element['#attributes'];
-  }
-
-  // This #pre_render callback can be invoked from inside or outside of a Form
-  // API context, and depending on that, a HTML ID may be already set in
-  // different locations. #options should have precedence over Form API's #id.
-  // #attributes have been taken over into #options above already.
-  if (isset($element['#options']['attributes']['id'])) {
-    $element['#id'] = $element['#options']['attributes']['id'];
-  }
-  elseif (isset($element['#id'])) {
-    $element['#options']['attributes']['id'] = $element['#id'];
-  }
-
-  // Conditionally invoke ajax_pre_render_element(), if #ajax is set.
-  if (isset($element['#ajax']) && !isset($element['#ajax_processed'])) {
-    // If no HTML ID was found above, automatically create one.
-    if (!isset($element['#id'])) {
-      $element['#id'] = $element['#options']['attributes']['id'] = drupal_html_id('ajax-link');
-    }
-    // If #ajax['path] was not specified, use the href as Ajax request URL.
-    if (!isset($element['#ajax']['path'])) {
-      $element['#ajax']['path'] = $element['#href'];
-      $element['#ajax']['options'] = $element['#options'];
-    }
-    $element = ajax_pre_render_element($element);
-  }
-
-  if (isset($element['#route_name'])) {
-    $element['#route_parameters'] = empty($element['#route_parameters']) ? array() : $element['#route_parameters'];
-    $element['#markup'] = \Drupal::linkGenerator()->generate($element['#title'], $element['#route_name'], $element['#route_parameters'], $element['#options']);
-  }
-  else {
-    $element['#markup'] = l($element['#title'], $element['#href'], $element['#options']);
-  }
-  return $element;
+  return Element\Link::preRenderLink($element);
 }
 
 /**
diff --git a/core/includes/form.inc b/core/includes/form.inc
index f49dbd26fd16f8c51771df73fa96cea5346586d8..fb9add490ad613a63b0c3c06a4792fa5da5f4a93 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -566,22 +566,10 @@ function form_type_select_value($element, $input = FALSE) {
 /**
  * Determines the value for a textfield form element.
  *
- * @param $element
- *   The form element whose value is being populated.
- * @param $input
- *   The incoming input to populate the form element. If this is FALSE,
- *   the element's default value should be returned.
- *
- * @return
- *   The data that will appear in the $element_state['values'] collection
- *   for this element. Return nothing to use the default.
+ * @deprecated Use \Drupal\Core\Render\Element\Textfield::valueCallback().
  */
-function form_type_textfield_value($element, $input = FALSE) {
-  if ($input !== FALSE && $input !== NULL) {
-    // Equate $input to the form value to ensure it's marked for
-    // validation.
-    return str_replace(array("\r", "\n"), '', $input);
-  }
+function form_type_textfield_value(&$element, $input, &$form_state) {
+  return Element\Textfield::valueCallback($element, $input, $form_state);
 }
 
 /**
@@ -1306,24 +1294,10 @@ function form_pre_render_actions_dropbutton(array $element) {
 /**
  * #process callback for #pattern form element property.
  *
- * @param $element
- *   An associative array containing the properties and children of the
- *   generic input element.
- * @param $form_state
- *   The current state of the form for the form this element belongs to.
- *
- * @return
- *   The processed element.
- *
- * @see form_validate_pattern()
+ * @deprecated Use \Drupal\Core\Render\Element\FormElement::processPattern().
  */
-function form_process_pattern($element, FormStateInterface $form_state) {
-  if (isset($element['#pattern']) && !isset($element['#attributes']['pattern'])) {
-    $element['#attributes']['pattern'] = $element['#pattern'];
-    $element['#element_validate'][] = 'form_validate_pattern';
-  }
-
-  return $element;
+function form_process_pattern($element, FormStateInterface $form_state, &$complete_form) {
+  return Element\FormElement::processPattern($element, $form_state, $complete_form);
 }
 
 /**
@@ -1922,61 +1896,10 @@ function form_pre_render_details($element) {
 /**
  * Adds members of this group as actual elements for rendering.
  *
- * @param $element
- *   An associative array containing the properties and children of the
- *   element.
- *
- * @return
- *   The modified element with all group members.
+ * @deprecated Use \Drupal\Core\Render\ElementElementBase::preRenderGroup().
  */
 function form_pre_render_group($element) {
-  // The element may be rendered outside of a Form API context.
-  if (!isset($element['#parents']) || !isset($element['#groups'])) {
-    return $element;
-  }
-
-  // Inject group member elements belonging to this group.
-  $parents = implode('][', $element['#parents']);
-  $children = Element::children($element['#groups'][$parents]);
-  if (!empty($children)) {
-    foreach ($children as $key) {
-      // Break references and indicate that the element should be rendered as
-      // group member.
-      $child = (array) $element['#groups'][$parents][$key];
-      $child['#group_details'] = TRUE;
-      // Inject the element as new child element.
-      $element[] = $child;
-
-      $sort = TRUE;
-    }
-    // Re-sort the element's children if we injected group member elements.
-    if (isset($sort)) {
-      $element['#sorted'] = FALSE;
-    }
-  }
-
-  if (isset($element['#group'])) {
-    // Contains form element summary functionalities.
-    $element['#attached']['library'][] = 'core/drupal.form';
-
-    $group = $element['#group'];
-    // If this element belongs to a group, but the group-holding element does
-    // not exist, we need to render it (at its original location).
-    if (!isset($element['#groups'][$group]['#group_exists'])) {
-      // Intentionally empty to clarify the flow; we simply return $element.
-    }
-    // If we injected this element into the group, then we want to render it.
-    elseif (!empty($element['#group_details'])) {
-      // Intentionally empty to clarify the flow; we simply return $element.
-    }
-    // Otherwise, this element belongs to a group and the group exists, so we do
-    // not render it.
-    elseif (Element::children($element['#groups'][$group])) {
-      $element['#printed'] = TRUE;
-    }
-  }
-
-  return $element;
+  return Element\RenderElement::preRenderGroup($element);
 }
 
 /**
@@ -2064,43 +1987,10 @@ function template_preprocess_vertical_tabs(&$variables) {
  * Adds autocomplete functionality to elements with a valid
  * #autocomplete_route_name.
  *
- * Suppose your autocomplete route name is 'mymodule.autocomplete' and its path
- * is: '/mymodule/autocomplete/{a}/{b}'
- * In your form you have:
- * @code
- * '#autocomplete_route_name' => 'mymodule.autocomplete',
- * '#autocomplete_route_parameters' => array('a' => $some_key, 'b' => $some_id),
- * @endcode
- * The user types in "keywords" so the full path called is:
- * 'mymodule_autocomplete/$some_key/$some_id?q=keywords'
- *
- * @param array $element
- *   The form element to process. Properties used:
- *   - #autocomplete_route_name: A route to be used as callback URL by the
- *     autocomplete JavaScript library.
- *   - #autocomplete_route_parameters: The parameters to be used in conjunction
- *     with the route name.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- *   The current state of the form.
- *
- * @return array
- *   The form element.
+ * @deprecated Use \Drupal\Core\Render\Element\FormElement::processAutocomplete().
  */
-function form_process_autocomplete($element, FormStateInterface $form_state) {
-  $access = FALSE;
-  if (!empty($element['#autocomplete_route_name'])) {
-    $parameters = isset($element['#autocomplete_route_parameters']) ? $element['#autocomplete_route_parameters'] : array();
-
-    $path = \Drupal::urlGenerator()->generate($element['#autocomplete_route_name'], $parameters);
-    $access = \Drupal::service('access_manager')->checkNamedRoute($element['#autocomplete_route_name'], $parameters, \Drupal::currentUser());
-  }
-  if ($access) {
-    $element['#attributes']['class'][] = 'form-autocomplete';
-    $element['#attached']['library'][] = 'core/drupal.autocomplete';
-    // Provide a data attribute for the JavaScript behavior to bind to.
-    $element['#attributes']['data-autocomplete-path'] = $path;
-  }
-  return $element;
+function form_process_autocomplete($element, FormStateInterface $form_state, &$complete_form) {
+  return Element\FormElement::processAutocomplete($element, $form_state, $complete_form);
 }
 
 /**
@@ -2206,20 +2096,10 @@ function form_pre_render_hidden($element) {
 /**
  * Prepares a #type 'textfield' render element for theme_input().
  *
- * @param array $element
- *   An associative array containing the properties of the element.
- *   Properties used: #title, #value, #description, #size, #maxlength,
- *   #placeholder, #required, #attributes.
- *
- * @return array
- *   The $element with prepared variables ready for theme_input().
+ * @deprecated Use \Drupal\Core\Render\Element\Textfield::preRenderTextfield().
  */
 function form_pre_render_textfield($element) {
-  $element['#attributes']['type'] = 'text';
-  Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
-  _form_set_attributes($element, array('form-text'));
-
-  return $element;
+  return Element\Textfield::preRenderTextfield($element);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index adbb8f335681932d49c7fd704521f8adbe27dac5..d4170a412818c91f73b3d2e0535e0654e6c79659 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -938,7 +938,14 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
 
     // Set the element's #value property.
     if (!isset($element['#value']) && !array_key_exists('#value', $element)) {
+      // @todo Once all elements are converted to plugins in
+      //   https://www.drupal.org/node/2311393, rely on
+      //   $element['#value_callback'] directly.
       $value_callable = !empty($element['#value_callback']) ? $element['#value_callback'] : 'form_type_' . $element['#type'] . '_value';
+      if (!is_callable($value_callable)) {
+        $value_callable = '\Drupal\Core\Render\Element\FormElement::valueCallback';
+      }
+
       if ($process_input) {
         // Get the input for the current element. NULL values in the input need
         // to be explicitly distinguished from missing input. (see below)
@@ -962,9 +969,8 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
         // If we have input for the current element, assign it to the #value
         // property, optionally filtered through $value_callback.
         if ($input_exists) {
-          if (is_callable($value_callable)) {
-            $element['#value'] = call_user_func_array($value_callable, array(&$element, $input, &$form_state));
-          }
+          $element['#value'] = call_user_func_array($value_callable, array(&$element, $input, &$form_state));
+
           if (!isset($element['#value']) && isset($input)) {
             $element['#value'] = $input;
           }
@@ -978,9 +984,8 @@ protected function handleInputElement($form_id, &$element, FormStateInterface &$
       if (!isset($element['#value'])) {
         // Call #type_value without a second argument to request default_value
         // handling.
-        if (is_callable($value_callable)) {
-          $element['#value'] = call_user_func_array($value_callable, array(&$element, FALSE, &$form_state));
-        }
+        $element['#value'] = call_user_func_array($value_callable, array(&$element, FALSE, &$form_state));
+
         // Final catch. If we haven't set a value yet, use the explicit default
         // value. Avoid image buttons (which come with garbage value), so we
         // only get value for the button actually clicked.
diff --git a/core/lib/Drupal/Core/Render/Annotation/FormElement.php b/core/lib/Drupal/Core/Render/Annotation/FormElement.php
new file mode 100644
index 0000000000000000000000000000000000000000..4d4b59339c7d1590f93ba82c0beadbb2c29e79ee
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Annotation/FormElement.php
@@ -0,0 +1,32 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Annotation\FormElement.
+ */
+
+namespace Drupal\Core\Render\Annotation;
+
+/**
+ * Defines a form element plugin annotation object.
+ *
+ * See \Drupal\Core\Render\Element\FormElementInterface for more information
+ * about form element plugins.
+ *
+ * Plugin Namespace: Element
+ *
+ * For a working example, see \Drupal\Core\Render\Element\Textfield.
+ *
+ * @see \Drupal\Core\Render\ElementInfoManager
+ * @see \Drupal\Core\Render\Element\FormElementInterface
+ * @see \Drupal\Core\Render\Element\FormElement
+ * @see \Drupal\Core\Render\Annotation\RenderElement
+ * @see plugin_api
+ *
+ * @ingroup theme_render
+ *
+ * @Annotation
+ */
+class FormElement extends RenderElement {
+
+}
diff --git a/core/lib/Drupal/Core/Render/Annotation/RenderElement.php b/core/lib/Drupal/Core/Render/Annotation/RenderElement.php
new file mode 100644
index 0000000000000000000000000000000000000000..fc36b3234d0016d744e2ab196f015f8e77a271ed
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Annotation/RenderElement.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Annotation\RenderElement.
+ */
+
+namespace Drupal\Core\Render\Annotation;
+
+use Drupal\Component\Annotation\PluginID;
+
+/**
+ * Defines a render element plugin annotation object.
+ *
+ * See \Drupal\Core\Render\Element\ElementInterface for more information
+ * about render element plugins.
+ *
+ * Plugin Namespace: Element
+ *
+ * For a working example, see \Drupal\Core\Render\Element\Link.
+ *
+ * @see \Drupal\Core\Render\ElementInfoManager
+ * @see \Drupal\Core\Render\Element\ElementInterface
+ * @see \Drupal\Core\Render\Element\RenderElement
+ * @see \Drupal\Core\Render\Annotation\FormElement
+ * @see plugin_api
+ *
+ * @ingroup theme_render
+ *
+ * @Annotation
+ */
+class RenderElement extends PluginID {
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element.php b/core/lib/Drupal/Core/Render/Element.php
index 501f2de43ba0fcf1bd1387b4befee85a003f11b4..eed5c30fde840d5c06ced499f4cb0d28076382bc 100644
--- a/core/lib/Drupal/Core/Render/Element.php
+++ b/core/lib/Drupal/Core/Render/Element.php
@@ -10,7 +10,11 @@
 use Drupal\Component\Utility\String;
 
 /**
- * Deals with drupal render elements.
+ * Provides helper methods for Drupal render elements.
+ *
+ * @see \Drupal\Core\Render\Element\ElementInterface
+ *
+ * @ingroup theme_render
  */
 class Element {
 
@@ -47,7 +51,7 @@ public static function properties(array $element) {
    *   The key to check.
    *
    * @return bool
-   *    TRUE if the element is a child, FALSE otherwise.
+   *   TRUE if the element is a child, FALSE otherwise.
    */
   public static function child($key) {
     return !isset($key[0]) || $key[0] != '#';
@@ -143,11 +147,11 @@ public static function getVisibleChildren(array $elements) {
    * @param array $element
    *   The renderable element to process. Passed by reference.
    * @param array $map
-   *   An associative array whose keys are element property names and whose values
-   *   are the HTML attribute names to set for corresponding the property; e.g.,
-   *   array('#propertyname' => 'attributename'). If both names are identical
-   *   except for the leading '#', then an attribute name value is sufficient and
-   *   no property name needs to be specified.
+   *   An associative array whose keys are element property names and whose
+   *   values are the HTML attribute names to set on the corresponding
+   *   property; e.g., array('#propertyname' => 'attributename'). If both names
+   *   are identical except for the leading '#', then an attribute name value is
+   *   sufficient and no property name needs to be specified.
    */
   public static function setAttributes(array &$element, array $map) {
     foreach ($map as $property => $attribute) {
diff --git a/core/lib/Drupal/Core/Render/Element/ElementInterface.php b/core/lib/Drupal/Core/Render/Element/ElementInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..a1035f7b2b76a8b25ea4c9a3f684bfe6b93c23c2
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/ElementInterface.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\ElementInterface.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+
+/**
+ * Provides an interface for element plugins.
+ *
+ * Render element plugins allow modules to declare their own Render API element
+ * types and specify the default values for the properties. The values returned
+ * by the getInfo() method of the element plugin will be merged with the
+ * properties specified in render arrays. Thus, you can specify defaults for any
+ * Render API keys, in addition to those explicitly documented by
+ * \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
+ *
+ * Some render elements are specifically form input elements; see
+ * \Drupal\Core\Render\Element\FormElementInterface for more information.
+ *
+ * @see \Drupal\Core\Render\ElementInfoManager
+ * @see \Drupal\Core\Render\Annotation\RenderElement
+ * @see \Drupal\Core\Render\Element\RenderElement
+ * @see plugin_api
+ *
+ * @ingroup theme_render
+ */
+interface ElementInterface extends PluginInspectionInterface {
+
+  /**
+   * Returns the element properties for this element.
+   *
+   * @return array
+   *   An array of element properties. See
+   *   \Drupal\Core\Render\ElementInfoManagerInterface::getInfo() for
+   *   documentation of the standard properties of all elements, and the
+   *   return value format.
+   */
+  public function getInfo();
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/FormElement.php b/core/lib/Drupal/Core/Render/Element/FormElement.php
new file mode 100644
index 0000000000000000000000000000000000000000..d66cc89d6eaee4812108985a66ed1a4c3491751f
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/FormElement.php
@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\FormElement.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a base class for form render plugins.
+ *
+ * @see \Drupal\Core\Render\Annotation\FormElement
+ * @see \Drupal\Core\Render\Element\FormElementInterface
+ * @see \Drupal\Core\Render\ElementInfoManager
+ * @see plugin_api
+ *
+ * @ingroup theme_render
+ */
+abstract class FormElement extends RenderElement implements FormElementInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    return NULL;
+  }
+
+  /**
+   * Form element processing handler for the #ajax form property.
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   *
+   * @see ajax_pre_render_element()
+   */
+  public static function processAjaxForm(&$element, FormStateInterface $form_state, &$complete_form) {
+    $element = ajax_pre_render_element($element);
+    if (!empty($element['#ajax_processed'])) {
+      $form_state['cache'] = TRUE;
+    }
+    return $element;
+  }
+
+  /**
+   * Arranges elements into groups.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   element. Note that $element must be taken by reference here, so processed
+   *   child elements are taken over into $form_state.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function processGroup(&$element, FormStateInterface $form_state, &$complete_form) {
+    $parents = implode('][', $element['#parents']);
+
+    // Each details element forms a new group. The #type 'vertical_tabs' basically
+    // only injects a new details element.
+    $form_state['groups'][$parents]['#group_exists'] = TRUE;
+    $element['#groups'] = &$form_state['groups'];
+
+    // Process vertical tabs group member details elements.
+    if (isset($element['#group'])) {
+      // Add this details element to the defined group (by reference).
+      $group = $element['#group'];
+      $form_state['groups'][$group][] = &$element;
+    }
+
+    return $element;
+  }
+
+  /**
+   * #process callback for #pattern form element property.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   generic input element.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   *
+   * @see form_validate_pattern()
+   */
+  public static function processPattern(&$element, FormStateInterface $form_state, &$complete_form) {
+    if (isset($element['#pattern']) && !isset($element['#attributes']['pattern'])) {
+      $element['#attributes']['pattern'] = $element['#pattern'];
+      $element['#element_validate'][] = 'form_validate_pattern';
+    }
+
+    return $element;
+  }
+
+  /**
+   * Adds autocomplete functionality to elements.
+   *
+   * This sets up autocomplete functionality for elements with an
+   * #autocomplete_route_name property, using the #autocomplete_route_parameters
+   * property if present.
+   *
+   * For example, suppose your autocomplete route name is
+   * 'mymodule.autocomplete' and its path is
+   * '/mymodule/autocomplete/{a}/{b}'. In a form array, you would create a text
+   * field with properties:
+   * @code
+   * '#autocomplete_route_name' => 'mymodule.autocomplete',
+   * '#autocomplete_route_parameters' => array('a' => $some_key, 'b' => $some_id),
+   * @endcode
+   * If the user types "keywords" in that field, the full path called would be:
+   * 'mymodule_autocomplete/$some_key/$some_id?q=keywords'
+   *
+   * @param array $element
+   *   The form element to process. Properties used:
+   *   - #autocomplete_route_name: A route to be used as callback URL by the
+   *     autocomplete JavaScript library.
+   *   - #autocomplete_route_parameters: The parameters to be used in
+   *     conjunction with the route name.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The form element.
+   */
+  public static function processAutocomplete(&$element, FormStateInterface $form_state, &$complete_form) {
+    $access = FALSE;
+    if (!empty($element['#autocomplete_route_name'])) {
+      $parameters = isset($element['#autocomplete_route_parameters']) ? $element['#autocomplete_route_parameters'] : array();
+
+      $path = \Drupal::urlGenerator()->generate($element['#autocomplete_route_name'], $parameters);
+      $access = \Drupal::service('access_manager')->checkNamedRoute($element['#autocomplete_route_name'], $parameters, \Drupal::currentUser());
+    }
+    if ($access) {
+      $element['#attributes']['class'][] = 'form-autocomplete';
+      $element['#attached']['library'][] = 'core/drupal.autocomplete';
+      // Provide a data attribute for the JavaScript behavior to bind to.
+      $element['#attributes']['data-autocomplete-path'] = $path;
+    }
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/FormElementInterface.php b/core/lib/Drupal/Core/Render/Element/FormElementInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..2bbbde8ecbdafa870f0ac681cb25fde9749533f7
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/FormElementInterface.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\FormElementInterface.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides an interface for form element plugins.
+ *
+ * Form element plugins are a subset of render elements, specifically
+ * representing HTML elements that take input as part of a form. Form element
+ * plugins are discovered via the same mechanism as regular render element
+ * plugins. See \Drupal\Core\Render\Element\ElementInterface for general
+ * information about render element plugins.
+ *
+ * @see \Drupal\Core\Render\ElementInfoManager
+ * @see \Drupal\Core\Render\Element\FormElement
+ * @see \Drupal\Core\Render\Annotation\FormElement
+ * @see plugin_api
+ *
+ * @ingroup theme_render
+ */
+interface FormElementInterface extends ElementInterface {
+
+  /**
+   * Determines how user input is mapped to an element's #value property.
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   * @param mixed $input
+   *   The incoming input to populate the form element. If this is FALSE,
+   *   the element's default value should be returned.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   *
+   * @return mixed
+   *   The value to assign to the element.
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state);
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Link.php b/core/lib/Drupal/Core/Render/Element/Link.php
new file mode 100644
index 0000000000000000000000000000000000000000..2a06eb697e2ed45da5ebfd2de2235c970f0121ab
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Link.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Link.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+/**
+ * Provides a link render element.
+ *
+ * @RenderElement("link")
+ */
+class Link extends RenderElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#pre_render' => array(
+        array($class, 'preRenderLink'),
+      ),
+    );
+  }
+
+  /**
+   * Pre-render callback: Renders a link into #markup.
+   *
+   * Doing so during pre_render gives modules a chance to alter the link parts.
+   *
+   * @param array $element
+   *   A structured array whose keys form the arguments to l():
+   *   - #title: The link text to pass as argument to l().
+   *   - One of the following
+   *     - #route_name and (optionally) a #route_parameters array; The route
+   *       name and route parameters which will be passed into the link
+   *       generator.
+   *     - #href: The system path or URL to pass as argument to l().
+   *   - #options: (optional) An array of options to pass to l() or the link
+   *     generator.
+   *
+   * @return array
+   *   The passed-in element containing a rendered link in '#markup'.
+   */
+  public static function preRenderLink($element) {
+    // By default, link options to pass to l() are normally set in #options.
+    $element += array('#options' => array());
+    // However, within the scope of renderable elements, #attributes is a valid
+    // way to specify attributes, too. Take them into account, but do not override
+    // attributes from #options.
+    if (isset($element['#attributes'])) {
+      $element['#options'] += array('attributes' => array());
+      $element['#options']['attributes'] += $element['#attributes'];
+    }
+
+    // This #pre_render callback can be invoked from inside or outside of a Form
+    // API context, and depending on that, a HTML ID may be already set in
+    // different locations. #options should have precedence over Form API's #id.
+    // #attributes have been taken over into #options above already.
+    if (isset($element['#options']['attributes']['id'])) {
+      $element['#id'] = $element['#options']['attributes']['id'];
+    }
+    elseif (isset($element['#id'])) {
+      $element['#options']['attributes']['id'] = $element['#id'];
+    }
+
+    // Conditionally invoke ajax_pre_render_element(), if #ajax is set.
+    if (isset($element['#ajax']) && !isset($element['#ajax_processed'])) {
+      // If no HTML ID was found above, automatically create one.
+      if (!isset($element['#id'])) {
+        $element['#id'] = $element['#options']['attributes']['id'] = drupal_html_id('ajax-link');
+      }
+      // If #ajax['path] was not specified, use the href as Ajax request URL.
+      if (!isset($element['#ajax']['path'])) {
+        $element['#ajax']['path'] = $element['#href'];
+        $element['#ajax']['options'] = $element['#options'];
+      }
+      $element = ajax_pre_render_element($element);
+    }
+
+    if (isset($element['#route_name'])) {
+      $element['#route_parameters'] = empty($element['#route_parameters']) ? array() : $element['#route_parameters'];
+      $element['#markup'] = \Drupal::linkGenerator()->generate($element['#title'], $element['#route_name'], $element['#route_parameters'], $element['#options']);
+    }
+    else {
+      $element['#markup'] = l($element['#title'], $element['#href'], $element['#options']);
+    }
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/MachineName.php b/core/lib/Drupal/Core/Render/Element/MachineName.php
new file mode 100644
index 0000000000000000000000000000000000000000..6301f3527bb122485451d1199a3782e8635575e6
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/MachineName.php
@@ -0,0 +1,226 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\MachineName.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides a machine name render element.
+ *
+ * Provides a form element to enter a machine name, which is validated to ensure
+ * that the name is unique and does not contain disallowed characters. All
+ * disallowed characters are replaced with a replacement character via
+ * JavaScript.
+ *
+ * @FormElement("machine_name")
+ */
+class MachineName extends Textfield {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#default_value' => NULL,
+      '#required' => TRUE,
+      '#maxlength' => 64,
+      '#size' => 60,
+      '#autocomplete_route_name' => FALSE,
+      '#process' => array(
+        array($class, 'processMachineName'),
+        array($class, 'processAutocomplete'),
+        array($class, 'processAjaxForm'),
+      ),
+      '#element_validate' => array(
+        array($class, 'validateMachineName'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderTextfield'),
+      ),
+      '#theme' => 'input__textfield',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    return NULL;
+  }
+
+  /**
+   * Processes a machine-readable name form element.
+   *
+   * @param array $element
+   *   The form element to process. Properties used:
+   *   - #machine_name: An associative array containing:
+   *     - exists: A callable to invoke for checking whether a submitted machine
+   *       name value already exists. The submitted value is passed as an
+   *       argument. In most cases, an existing API or menu argument loader
+   *       function can be re-used. The callback is only invoked if the
+   *       submitted value differs from the element's #default_value.
+   *     - source: (optional) The #array_parents of the form element containing
+   *       the human-readable name (i.e., as contained in the $form structure)
+   *       to use as source for the machine name. Defaults to array('label').
+   *     - label: (optional) Text to display as label for the machine name value
+   *       after the human-readable name form element. Defaults to "Machine
+   *       name".
+   *     - replace_pattern: (optional) A regular expression (without delimiters)
+   *       matching disallowed characters in the machine name. Defaults to
+   *       '[^a-z0-9_]+'.
+   *     - replace: (optional) A character to replace disallowed characters in
+   *       the machine name via JavaScript. Defaults to '_' (underscore). When
+   *       using a different character, 'replace_pattern' needs to be set
+   *       accordingly.
+   *     - error: (optional) A custom form error message string to show, if the
+   *       machine name contains disallowed characters.
+   *     - standalone: (optional) Whether the live preview should stay in its
+   *       own form element rather than in the suffix of the source
+   *       element. Defaults to FALSE.
+   *   - #maxlength: (optional) Maximum allowed length of the machine name.
+   *     Defaults to 64.
+   *   - #disabled: (optional) Should be set to TRUE if an existing machine
+   *     name must not be changed after initial creation.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   * @param array $complete_form
+   *   The complete form structure.
+   *
+   * @return array
+   *   The processed element.
+   */
+  public static function processMachineName(&$element, FormStateInterface $form_state, &$complete_form) {
+    // We need to pass the langcode to the client.
+    $language = \Drupal::languageManager()->getCurrentLanguage();
+
+    // Apply default form element properties.
+    $element += array(
+      '#title' => t('Machine-readable name'),
+      '#description' => t('A unique machine-readable name. Can only contain lowercase letters, numbers, and underscores.'),
+      '#machine_name' => array(),
+      '#field_prefix' => '',
+      '#field_suffix' => '',
+      '#suffix' => '',
+    );
+    // A form element that only wants to set one #machine_name property (usually
+    // 'source' only) would leave all other properties undefined, if the defaults
+    // were defined in hook_element_info(). Therefore, we apply the defaults here.
+    $element['#machine_name'] += array(
+      'source' => array('label'),
+      'target' => '#' . $element['#id'],
+      'label' => t('Machine name'),
+      'replace_pattern' => '[^a-z0-9_]+',
+      'replace' => '_',
+      'standalone' => FALSE,
+      'field_prefix' => $element['#field_prefix'],
+      'field_suffix' => $element['#field_suffix'],
+    );
+
+    // By default, machine names are restricted to Latin alphanumeric characters.
+    // So, default to LTR directionality.
+    if (!isset($element['#attributes'])) {
+      $element['#attributes'] = array();
+    }
+    $element['#attributes'] += array('dir' => 'ltr');
+
+    // The source element defaults to array('name'), but may have been overidden.
+    if (empty($element['#machine_name']['source'])) {
+      return $element;
+    }
+
+    // Retrieve the form element containing the human-readable name from the
+    // complete form in $form_state. By reference, because we may need to append
+    // a #field_suffix that will hold the live preview.
+    $key_exists = NULL;
+    $source = NestedArray::getValue($form_state['complete_form'], $element['#machine_name']['source'], $key_exists);
+    if (!$key_exists) {
+      return $element;
+    }
+
+    $suffix_id = $source['#id'] . '-machine-name-suffix';
+    $element['#machine_name']['suffix'] = '#' . $suffix_id;
+
+    if ($element['#machine_name']['standalone']) {
+      $element['#suffix'] = SafeMarkup::set($element['#suffix'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');
+    }
+    else {
+      // Append a field suffix to the source form element, which will contain
+      // the live preview of the machine name.
+      $source += array('#field_suffix' => '');
+      $source['#field_suffix'] = SafeMarkup::set($source['#field_suffix'] . ' <small id="' . $suffix_id . '">&nbsp;</small>');
+
+      $parents = array_merge($element['#machine_name']['source'], array('#field_suffix'));
+      NestedArray::setValue($form_state['complete_form'], $parents, $source['#field_suffix']);
+    }
+
+    $js_settings = array(
+      'type' => 'setting',
+      'data' => array(
+        'machineName' => array(
+          '#' . $source['#id'] => $element['#machine_name'],
+        ),
+        'langcode' => $language->id,
+      ),
+    );
+    $element['#attached']['library'][] = 'core/drupal.machine-name';
+    $element['#attached']['js'][] = $js_settings;
+
+    return $element;
+  }
+
+  /**
+   * Form element validation handler for machine_name elements.
+   *
+   * Note that #maxlength is validated by _form_validate() already.
+   *
+   * This checks that the submitted value:
+   * - Does not contain the replacement character only.
+   * - Does not contain disallowed characters.
+   * - Is unique; i.e., does not already exist.
+   * - Does not exceed the maximum length (via #maxlength).
+   * - Cannot be changed after creation (via #disabled).
+   */
+  public static function validateMachineName(&$element, FormStateInterface $form_state, &$complete_form) {
+    // Verify that the machine name not only consists of replacement tokens.
+    if (preg_match('@^' . $element['#machine_name']['replace'] . '+$@', $element['#value'])) {
+      form_error($element, $form_state, t('The machine-readable name must contain unique characters.'));
+    }
+
+    // Verify that the machine name contains no disallowed characters.
+    if (preg_match('@' . $element['#machine_name']['replace_pattern'] . '@', $element['#value'])) {
+      if (!isset($element['#machine_name']['error'])) {
+        // Since a hyphen is the most common alternative replacement character,
+        // a corresponding validation error message is supported here.
+        if ($element['#machine_name']['replace'] == '-') {
+          form_error($element, $form_state, t('The machine-readable name must contain only lowercase letters, numbers, and hyphens.'));
+        }
+        // Otherwise, we assume the default (underscore).
+        else {
+          form_error($element, $form_state, t('The machine-readable name must contain only lowercase letters, numbers, and underscores.'));
+        }
+      }
+      else {
+        form_error($element, $form_state, $element['#machine_name']['error']);
+      }
+    }
+
+    // Verify that the machine name is unique.
+    if ($element['#default_value'] !== $element['#value']) {
+      $function = $element['#machine_name']['exists'];
+      if (call_user_func($function, $element['#value'], $element, $form_state)) {
+        form_error($element, $form_state, t('The machine-readable name is already in use. It must be unique.'));
+      }
+    }
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/RenderElement.php b/core/lib/Drupal/Core/Render/Element/RenderElement.php
new file mode 100644
index 0000000000000000000000000000000000000000..cf4d4546dd698b03a851670c907d18854973e7f4
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/RenderElement.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\RenderElement.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a base class for element render plugins.
+ *
+ * @see \Drupal\Core\Render\Annotation\RenderElement
+ * @see \Drupal\Core\Render\ElementInterface
+ * @see \Drupal\Core\Render\ElementInfoManager
+ * @see plugin_api
+ *
+ * @ingroup theme_render
+ */
+abstract class RenderElement extends PluginBase implements ElementInterface {
+
+  /**
+   * Adds members of this group as actual elements for rendering.
+   *
+   * @param array $element
+   *   An associative array containing the properties and children of the
+   *   element.
+   *
+   * @return array
+   *   The modified element with all group members.
+   */
+  public static function preRenderGroup($element) {
+    // The element may be rendered outside of a Form API context.
+    if (!isset($element['#parents']) || !isset($element['#groups'])) {
+      return $element;
+    }
+
+    // Inject group member elements belonging to this group.
+    $parents = implode('][', $element['#parents']);
+    $children = Element::children($element['#groups'][$parents]);
+    if (!empty($children)) {
+      foreach ($children as $key) {
+        // Break references and indicate that the element should be rendered as
+        // group member.
+        $child = (array) $element['#groups'][$parents][$key];
+        $child['#group_details'] = TRUE;
+        // Inject the element as new child element.
+        $element[] = $child;
+
+        $sort = TRUE;
+      }
+      // Re-sort the element's children if we injected group member elements.
+      if (isset($sort)) {
+        $element['#sorted'] = FALSE;
+      }
+    }
+
+    if (isset($element['#group'])) {
+      // Contains form element summary functionalities.
+      $element['#attached']['library'][] = 'core/drupal.form';
+
+      $group = $element['#group'];
+      // If this element belongs to a group, but the group-holding element does
+      // not exist, we need to render it (at its original location).
+      if (!isset($element['#groups'][$group]['#group_exists'])) {
+        // Intentionally empty to clarify the flow; we simply return $element.
+      }
+      // If we injected this element into the group, then we want to render it.
+      elseif (!empty($element['#group_details'])) {
+        // Intentionally empty to clarify the flow; we simply return $element.
+      }
+      // Otherwise, this element belongs to a group and the group exists, so we do
+      // not render it.
+      elseif (Element::children($element['#groups'][$group])) {
+        $element['#printed'] = TRUE;
+      }
+    }
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element/Textfield.php b/core/lib/Drupal/Core/Render/Element/Textfield.php
new file mode 100644
index 0000000000000000000000000000000000000000..d687a2cf8c2680f0fbeb237cad9d9344fbd39dfd
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Element/Textfield.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\Element\Textfield.
+ */
+
+namespace Drupal\Core\Render\Element;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides a one-line text field form element.
+ *
+ * @FormElement("textfield")
+ */
+class Textfield extends FormElement {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo() {
+    $class = get_class($this);
+    return array(
+      '#input' => TRUE,
+      '#size' => 60,
+      '#maxlength' => 128,
+      '#autocomplete_route_name' => FALSE,
+      '#process' => array(
+        array($class, 'processAutocomplete'),
+        array($class, 'processAjaxForm'),
+        array($class, 'processPattern'),
+        array($class, 'processGroup'),
+      ),
+      '#pre_render' => array(
+        array($class, 'preRenderTextfield'),
+        array($class, 'preRenderGroup'),
+      ),
+      '#theme' => 'input__textfield',
+      '#theme_wrappers' => array('form_element'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
+    if ($input !== FALSE && $input !== NULL) {
+      // Equate $input to the form value to ensure it's marked for
+      // validation.
+      return str_replace(array("\r", "\n"), '', $input);
+    }
+  }
+
+  /**
+   * Prepares a #type 'textfield' render element for theme_input().
+   *
+   * @param array $element
+   *   An associative array containing the properties of the element.
+   *   Properties used: #title, #value, #description, #size, #maxlength,
+   *   #placeholder, #required, #attributes.
+   *
+   * @return array
+   *   The $element with prepared variables ready for theme_input().
+   */
+  public static function preRenderTextfield($element) {
+    $element['#attributes']['type'] = 'text';
+    Element::setAttributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
+    _form_set_attributes($element, array('form-text'));
+
+    return $element;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/ElementInfo.php b/core/lib/Drupal/Core/Render/ElementInfo.php
deleted file mode 100644
index 8d6a3f283c0e9f8c522f559c1ca6462532dde860..0000000000000000000000000000000000000000
--- a/core/lib/Drupal/Core/Render/ElementInfo.php
+++ /dev/null
@@ -1,65 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Render\ElementInfo.
- */
-
-namespace Drupal\Core\Render;
-
-use Drupal\Core\Extension\ModuleHandlerInterface;
-
-/**
- * Provides the default element info implementation.
- */
-class ElementInfo implements ElementInfoInterface {
-
-  /**
-   * The module handler.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   */
-  protected $moduleHandler;
-
-  /**
-   * Stores the available element information
-   *
-   * @var array
-   */
-  protected $elementInfo;
-
-  /**
-   * Constructs a new ElementInfo instance.
-   *
-   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
-   *   The module handler.
-   */
-  public function __construct(ModuleHandlerInterface $module_handler) {
-    $this->moduleHandler = $module_handler;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getInfo($type) {
-    if (!isset($this->elementInfo)) {
-      $this->elementInfo = $this->buildInfo();
-    }
-    return isset($this->elementInfo[$type]) ? $this->elementInfo[$type] : array();
-  }
-
-  /**
-   * Builds up all element information.
-   */
-  protected function buildInfo() {
-    $info = $this->moduleHandler->invokeAll('element_info');
-    foreach ($info as $element_type => $element) {
-      $info[$element_type]['#type'] = $element_type;
-    }
-    // Allow modules to alter the element type defaults.
-    $this->moduleHandler->alter('element_info', $info);
-
-    return $info;
-  }
-
-}
diff --git a/core/lib/Drupal/Core/Render/ElementInfoInterface.php b/core/lib/Drupal/Core/Render/ElementInfoInterface.php
deleted file mode 100644
index ad05aebdfb3a6cf02aa794a785cc46840c18e470..0000000000000000000000000000000000000000
--- a/core/lib/Drupal/Core/Render/ElementInfoInterface.php
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Render\ElementInfoInterface.
- */
-
-namespace Drupal\Core\Render;
-
-/**
- * Defines available render array element types.
- */
-interface ElementInfoInterface {
-
-  /**
-   * Retrieves the default properties for the defined element type.
-   *
-   * Each of the form element types defined by this hook is assumed to have
-   * a matching theme function, e.g. theme_elementtype(), which should be
-   * registered with hook_theme() as normal.
-   *
-   * For more information about custom element types see the explanation at
-   * http://drupal.org/node/169815.
-   *
-   * @param string $type
-   *   An element type as defined by hook_element_info().
-   *
-   * @return array
-   *  An associative array describing the element types being defined. The array
-   *  contains a sub-array for each element type, with the machine-readable type
-   *  name as the key. Each sub-array has a number of possible attributes:
-   *  - "#input": boolean indicating whether or not this element carries a value
-   *    (even if it's hidden).
-   *  - "#process": array of callback functions taking $element, $form_state,
-   *    and $complete_form.
-   *  - "#after_build": array of callables taking $element and $form_state.
-   *  - "#validate": array of callback functions taking $form and $form_state.
-   *  - "#element_validate": array of callback functions taking $element and
-   *    $form_state.
-   *  - "#pre_render": array of callables taking $element.
-   *  - "#post_render": array of callables taking $children and $element.
-   *  - "#submit": array of callback functions taking $form and $form_state.
-   *  - "#title_display": optional string indicating if and how #title should be
-   *    displayed, see the form-element template and theme_form_element_label().
-   *
-   * @see hook_element_info()
-   * @see hook_element_info_alter()
-   */
-  public function getInfo($type);
-
-}
diff --git a/core/lib/Drupal/Core/Render/ElementInfoManager.php b/core/lib/Drupal/Core/Render/ElementInfoManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..1dd2be4e318213a92860025bca33416be7bba995
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/ElementInfoManager.php
@@ -0,0 +1,100 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\ElementInfoManager.
+ */
+
+namespace Drupal\Core\Render;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Plugin\DefaultPluginManager;
+use Drupal\Core\Render\Element\FormElementInterface;
+
+/**
+ * Provides a plugin manager for element plugins.
+ *
+ * @see \Drupal\Core\Render\Annotation\RenderElement
+ * @see \Drupal\Core\Render\Annotation\FormElement
+ * @see \Drupal\Core\Render\Element\RenderElement
+ * @see \Drupal\Core\Render\Element\FormElement
+ * @see \Drupal\Core\Render\Element\ElementInterface
+ * @see \Drupal\Core\Render\Element\FormElementInterface
+ * @see plugin_api
+ */
+class ElementInfoManager extends DefaultPluginManager implements ElementInfoManagerInterface {
+
+  /**
+   * Stores the available element information.
+   *
+   * @var array
+   */
+  protected $elementInfo;
+
+  /**
+   * Constructs a ElementInfoManager object.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
+    $this->setCacheBackend($cache_backend, 'element_info');
+
+    parent::__construct('Element', $namespaces, $module_handler, 'Drupal\Core\Render\Annotation\RenderElement');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getInfo($type) {
+    if (!isset($this->elementInfo)) {
+      $this->elementInfo = $this->buildInfo();
+    }
+    return isset($this->elementInfo[$type]) ? $this->elementInfo[$type] : array();
+  }
+
+  /**
+   * Builds up all element information.
+   */
+  protected function buildInfo() {
+    // @todo Remove this hook once all elements are converted to plugins in
+    //   https://www.drupal.org/node/2311393.
+    $info = $this->moduleHandler->invokeAll('element_info');
+
+    foreach ($this->getDefinitions() as $element_type => $definition) {
+      $element = $this->createInstance($element_type);
+      $element_info = $element->getInfo();
+
+      // If this is element is to be used exclusively in a form, denote that it
+      // will receive input, and assign the value callback.
+      if ($element instanceof FormElementInterface) {
+        $element_info['#input'] = TRUE;
+        $element_info['#value_callback'] = array($definition['class'], 'valueCallback');
+      }
+      $info[$element_type] = $element_info;
+    }
+    foreach ($info as $element_type => $element) {
+      $info[$element_type]['#type'] = $element_type;
+    }
+    // Allow modules to alter the element type defaults.
+    $this->moduleHandler->alter('element_info', $info);
+
+    return $info;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\Core\Render\Element\ElementInterface
+   */
+  public function createInstance($plugin_id, array $configuration = array()) {
+    return parent::createInstance($plugin_id, $configuration);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php b/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..d32acd690f93598349647c6905e55c0d5cec9d2f
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/ElementInfoManagerInterface.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Render\ElementInfoManagerInterface.
+ */
+
+namespace Drupal\Core\Render;
+
+/**
+ * Collects available render array element types.
+ */
+interface ElementInfoManagerInterface {
+
+  /**
+   * Retrieves the default properties for the defined element type.
+   *
+   * Each of the form element types defined by this hook is assumed to have
+   * a matching theme hook, which should be registered with hook_theme() as
+   * normal.
+   *
+   * For more information about custom element types see the explanation at
+   * http://drupal.org/node/169815.
+   *
+   * @param string $type
+   *   An element type as defined by hook_element_info() or the machine name
+   *   of an element type plugin.
+   *
+   * @return array
+   *   An associative array describing the element types being defined. The
+   *   array contains a sub-array for each element type, with the
+   *   machine-readable type name as the key. Each sub-array has a number of
+   *   possible attributes:
+   *   - #input: boolean indicating whether or not this element carries a value
+   *     (even if it's hidden).
+   *   - #process: array of callback functions taking $element, $form_state,
+   *     and $complete_form.
+   *   - #after_build: array of callables taking $element and $form_state.
+   *   - #validate: array of callback functions taking $form and $form_state.
+   *   - #element_validate: array of callback functions taking $element and
+   *     $form_state.
+   *   - #pre_render: array of callables taking $element.
+   *   - #post_render: array of callables taking $children and $element.
+   *   - #submit: array of callback functions taking $form and $form_state.
+   *   - #title_display: optional string indicating if and how #title should be
+   *     displayed (see form-element.html.twig).
+   *
+   * @see hook_element_info()
+   * @see hook_element_info_alter()
+   * @see \Drupal\Core\Render\Element\ElementInterface
+   * @see \Drupal\Core\Render\Element\ElementInterface::getInfo()
+   */
+  public function getInfo($type);
+
+}
diff --git a/core/lib/Drupal/Core/Render/Plugin/README.txt b/core/lib/Drupal/Core/Render/Plugin/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f761e0198cef57dbae101045b12e2bc350d208a9
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/Plugin/README.txt
@@ -0,0 +1,4 @@
+@todo This must be here because DrupalKernel will only allow namespaces to
+  provide plugins if there is a Plugin subdirectory, and git does not allow
+  empty subdirectories. This file should be removed once
+  https://www.drupal.org/node/2309889 is fixed.
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 3d52bbdd598146435310be9a9811663b5dce5daa..86336f66d5c48d87bebe9180040182ecd03933bc 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -193,11 +193,14 @@ function callback_queue_worker($queue_item_data) {
  * specify their default values. The values returned by this hook will be
  * merged with the elements returned by form constructor implementations and so
  * can return defaults for any Form APIs keys in addition to those explicitly
- * documented by \Drupal\Core\Render\ElementInfoInterface::getInfo().
+ * documented by \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
  *
  * @return array
  *   An associative array with structure identical to that of the return value
- *   of \Drupal\Core\Render\ElementInfoInterface::getInfo().
+ *   of \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
+ *
+ * @deprecated Use an annotated class instead, see
+ *   \Drupal\Core\Render\Element\ElementInterface.
  *
  * @see hook_element_info_alter()
  * @see system_element_info()
@@ -217,7 +220,7 @@ function hook_element_info() {
  *
  * @param array $types
  *   An associative array with structure identical to that of the return value
- *   of \Drupal\Core\Render\ElementInfoInterface::getInfo().
+ *   of \Drupal\Core\Render\ElementInfoManagerInterface::getInfo().
  *
  * @see hook_element_info()
  */
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index cad83e53265365aca72d5e9f282e96fdfaf3c335..6875449ff18b21c5efe358abaa87c4788773dc3c 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -368,16 +368,6 @@ function system_element_info() {
     '#pre_render' => array('form_pre_render_image_button'),
     '#theme_wrappers' => array('input__image_button'),
   );
-  $types['textfield'] = array(
-    '#input' => TRUE,
-    '#size' => 60,
-    '#maxlength' => 128,
-    '#autocomplete_route_name' => FALSE,
-    '#process' => array('form_process_autocomplete', 'ajax_process_form', 'form_process_pattern', 'form_process_group'),
-    '#pre_render' => array('form_pre_render_textfield', 'form_pre_render_group'),
-    '#theme' => 'input__textfield',
-    '#theme_wrappers' => array('form_element'),
-  );
   $types['tel'] = array(
     '#input' => TRUE,
     '#size' => 30,
@@ -448,19 +438,6 @@ function system_element_info() {
     '#theme' => 'input__color',
     '#theme_wrappers' => array('form_element'),
   );
-  $types['machine_name'] = array(
-    '#input' => TRUE,
-    '#default_value' => NULL,
-    '#required' => TRUE,
-    '#maxlength' => 64,
-    '#size' => 60,
-    '#autocomplete_route_name' => FALSE,
-    '#process' => array('form_process_machine_name', 'form_process_autocomplete', 'ajax_process_form'),
-    '#element_validate' => array('form_validate_machine_name'),
-    '#pre_render' => array('form_pre_render_textfield'),
-    '#theme' => 'input__textfield',
-    '#theme_wrappers' => array('form_element'),
-  );
   $types['password'] = array(
     '#input' => TRUE,
     '#size' => 60,
@@ -586,9 +563,6 @@ function system_element_info() {
   $types['value'] = array(
     '#input' => TRUE,
   );
-  $types['link'] = array(
-    '#pre_render' => array('drupal_pre_render_link'),
-  );
   $types['fieldset'] = array(
     '#value' => NULL,
     '#process' => array('form_process_group', 'ajax_process_form'),
diff --git a/core/modules/system/theme.api.php b/core/modules/system/theme.api.php
index 85eb7283a7a3def1e081f4479aca1c4962691531..fabde88462aba28ebb2e931ce96f50a9553e892d 100644
--- a/core/modules/system/theme.api.php
+++ b/core/modules/system/theme.api.php
@@ -163,11 +163,21 @@
  * requests and the CSS files used to style that markup. In order to ensure that
  * a theme can completely customize the markup, module developers should avoid
  * directly writing HTML markup for pages, blocks, and other user-visible output
- * in their modules, and instead return structured "render arrays" (described
- * below). Doing this also increases usability, by ensuring that the markup used
- * for similar functionality on different areas of the site is the same, which
- * gives users fewer user interface patterns to learn.
+ * in their modules, and instead return structured "render arrays" (see @ref
+ * arrays below). Doing this also increases usability, by ensuring that the
+ * markup used for similar functionality on different areas of the site is the
+ * same, which gives users fewer user interface patterns to learn.
  *
+ * For further information on the Theme and Render APIs, see:
+ * - https://drupal.org/documentation/theme
+ * - https://drupal.org/node/722174
+ * - https://drupal.org/node/933976
+ * - https://drupal.org/node/930760
+ *
+ * @todo Check these links. Some are for Drupal 7, and might need updates for
+ *   Drupal 8.
+ *
+ * @section arrays Render arrays
  * The core structure of the Render API is the render array, which is a
  * hierarchical associative array containing data to be rendered and properties
  * describing how the data should be rendered. A render array that is returned
@@ -194,11 +204,8 @@
  * - #type: Specifies that the array contains data and options for a particular
  *   type of "render element" (examples: 'form', for an HTML form; 'textfield',
  *   'submit', and other HTML form element types; 'table', for a table with
- *   rows, columns, and headers). Modules define render elements by implementing
- *   hook_element_info(), which specifies the properties that are used in render
- *   arrays to provide the data and options, and default values for these
- *   properties. Look through implementations of hook_element_info() to discover
- *   what render elements are available.
+ *   rows, columns, and headers). See @ref elements below for more on render
+ *   element types.
  * - #theme: Specifies that the array contains data to be themed by a particular
  *   theme hook. Modules define theme hooks by implementing hook_theme(), which
  *   specifies the input "variables" used to provide data and options; if a
@@ -214,15 +221,29 @@
  *   normally preferable to use #theme or #type instead, so that the theme can
  *   customize the markup.
  *
- * For further information on the Theme and Render APIs, see:
- * - https://drupal.org/documentation/theme
- * - https://drupal.org/developing/modules/8
- * - https://drupal.org/node/722174
- * - https://drupal.org/node/933976
- * - https://drupal.org/node/930760
+ * @section elements Render elements
+ * Render elements are defined by Drupal core and modules. The primary way to
+ * define a render element is to create a render element plugin. There are
+ * two types of render element plugins:
+ * - Generic elements: Generic render element plugins implement
+ *   \Drupal\Core\Render\Element\ElementInterface, are annotated with
+ *   \Drupal\Core\Render\Annotation\RenderElement annotation, go in plugin
+ *   namespace Element, and generally extend the
+ *   \Drupal\Core\Render\Element\RenderElement base class.
+ * - Form input elements: Render elements representing form input elements
+ *   implement \Drupal\Core\Render\Element\FormElementInterface, are annotated
+ *   with \Drupal\Core\Render\Annotation\FormElement annotation, go in plugin
+ *   namespace Element, and generally extend the
+ *   \Drupal\Core\Render\Element\FormElement base class.
+ * See the @link plugin_api Plugin API topic @endlink for general information
+ * on plugins, and look for classes with the RenderElement or FormElement
+ * annotation to discover what render elements are available.
+ *
+ * Modules can also currently define render elements by implementing
+ * hook_element_info(), although defining a plugin is preferred.
+ * properties. Look through implementations of hook_element_info() to discover
+ * elements defined this way.
  *
- * @todo Check these links. Some are for Drupal 7, and might need updates for
- *   Drupal 8.
  * @see themeable
  *
  * @}
diff --git a/core/tests/Drupal/Tests/Core/Render/ElementInfoManagerTest.php b/core/tests/Drupal/Tests/Core/Render/ElementInfoManagerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..fa9cbb3a23fcafb40578d4e090d2f23121402f12
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Render/ElementInfoManagerTest.php
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Render\ElementInfoManagerTest.
+ */
+
+namespace Drupal\Tests\Core\Render;
+
+use Drupal\Core\Render\ElementInfoManager;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\Core\Render\ElementInfoManager
+ * @group Render
+ */
+class ElementInfoManagerTest extends UnitTestCase {
+
+  /**
+   * The class under test.
+   *
+   * @var \Drupal\Core\Render\ElementInfoManagerInterface
+   */
+  protected $elementInfo;
+
+  /**
+   * The cache backend to use.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cache;
+
+  /**
+   * The mocked module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * {@inheritdoc}
+   *
+   * @covers ::__construct
+   */
+  protected function setUp() {
+    $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+
+    $this->elementInfo = new ElementInfoManager(new \ArrayObject(), $this->cache, $this->moduleHandler);
+  }
+
+  /**
+   * Tests the getInfo method.
+   *
+   * @covers ::getInfo
+   * @covers ::buildInfo
+   *
+   * @dataProvider providerTestGetInfo
+   */
+  public function testGetInfo($type, $expected_info, $element_info, callable $alter_callback = NULL) {
+    $this->moduleHandler->expects($this->once())
+      ->method('invokeAll')
+      ->with('element_info')
+      ->will($this->returnValue($element_info));
+    $this->moduleHandler->expects($this->once())
+      ->method('alter')
+      ->with('element_info', $this->anything())
+      ->will($this->returnCallback($alter_callback ?: function($info) {
+        return $info;
+      }));
+
+    $this->assertEquals($expected_info, $this->elementInfo->getInfo($type));
+  }
+
+  /**
+   * Provides tests data for getInfo.
+   *
+   * @return array
+   */
+  public function providerTestGetInfo() {
+    $data = array();
+    // Provide an element and expect it is returned.
+    $data[] = array(
+      'page',
+      array(
+        '#type' => 'page',
+        '#show_messages' => TRUE,
+        '#theme' => 'page',
+      ),
+      array('page' => array(
+        '#show_messages' => TRUE,
+        '#theme' => 'page',
+      )),
+    );
+    // Provide an element but request an non existent one.
+    $data[] = array(
+      'form',
+      array(
+      ),
+      array('page' => array(
+        '#show_messages' => TRUE,
+        '#theme' => 'page',
+      )),
+    );
+    // Provide an element and alter it to ensure it is altered.
+    $data[] = array(
+      'page',
+      array(
+        '#type' => 'page',
+        '#show_messages' => TRUE,
+        '#theme' => 'page',
+        '#number' => 597219,
+      ),
+      array('page' => array(
+        '#show_messages' => TRUE,
+        '#theme' => 'page',
+      )),
+      function ($alter_name, array &$info) {
+        $info['page']['#number'] = 597219;
+      }
+    );
+    return $data;
+  }
+
+  /**
+   * Tests the getInfo() method when render element plugins are used.
+   *
+   * @covers ::getInfo
+   * @covers ::buildInfo
+   *
+   * @dataProvider providerTestGetInfoElementPlugin
+   */
+  public function testGetInfoElementPlugin($plugin_class, $expected_info) {
+    $this->moduleHandler->expects($this->once())
+      ->method('invokeAll')
+      ->with('element_info')
+      ->willReturn(array());
+    $this->moduleHandler->expects($this->once())
+      ->method('alter')
+      ->with('element_info', $this->anything())
+      ->will($this->returnArgument(0));
+
+    $plugin = $this->getMock($plugin_class);
+    $plugin->expects($this->once())
+      ->method('getInfo')
+      ->willReturn(array(
+        '#show_messages' => TRUE,
+        '#theme' => 'page',
+      ));
+
+    $element_info = $this->getMockBuilder('Drupal\Core\Render\ElementInfoManager')
+      ->setConstructorArgs(array(new \ArrayObject(), $this->cache, $this->moduleHandler))
+      ->setMethods(array('getDefinitions', 'createInstance'))
+      ->getMock();
+    $element_info->expects($this->once())
+      ->method('createInstance')
+      ->with('page')
+      ->willReturn($plugin);
+    $element_info->expects($this->once())
+      ->method('getDefinitions')
+      ->willReturn(array(
+        'page' => array('class' => 'TestElementPlugin'),
+      ));
+
+    $this->assertEquals($expected_info, $element_info->getInfo('page'));
+  }
+
+  /**
+   * Provides tests data for testGetInfoElementPlugin().
+   *
+   * @return array
+   */
+  public function providerTestGetInfoElementPlugin() {
+    $data = array();
+    $data[] = array(
+      'Drupal\Core\Render\Element\ElementInterface',
+      array(
+        '#type' => 'page',
+        '#show_messages' => TRUE,
+        '#theme' => 'page',
+      ),
+    );
+
+    $data[] = array(
+      'Drupal\Core\Render\Element\FormElementInterface',
+      array(
+        '#type' => 'page',
+        '#show_messages' => TRUE,
+        '#theme' => 'page',
+        '#input' => TRUE,
+        '#value_callback' => array('TestElementPlugin', 'valueCallback'),
+      ),
+    );
+    return $data;
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Render/ElementInfoTest.php b/core/tests/Drupal/Tests/Core/Render/ElementInfoTest.php
deleted file mode 100644
index 86878f4322ae7a4d0591355542c4fca3947b5c2e..0000000000000000000000000000000000000000
--- a/core/tests/Drupal/Tests/Core/Render/ElementInfoTest.php
+++ /dev/null
@@ -1,118 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Tests\Core\Render\ElementInfoTest.
- */
-
-namespace Drupal\Tests\Core\Render;
-
-use Drupal\Core\Render\ElementInfo;
-use Drupal\Core\Render\ElementInfoInterface;
-use Drupal\Tests\UnitTestCase;
-
-/**
- * @coversDefaultClass \Drupal\Core\Render\ElementInfo
- * @group Render
- */
-class ElementInfoTest extends UnitTestCase {
-
-  /**
-   * The class under test.
-   *
-   * @var \Drupal\Core\Render\ElementInfoInterface
-   */
-  protected $elementInfo;
-
-  /**
-   * The mocked module handler.
-   *
-   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
-   */
-  protected $moduleHandler;
-
-  /**
-   * {@inheritdoc}
-   *
-   * @covers ::__construct
-   */
-  protected function setUp() {
-    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
-
-    $this->elementInfo = new ElementInfo($this->moduleHandler);
-  }
-
-  /**
-   * Tests the getInfo method.
-   *
-   * @covers ::getInfo
-   * @covers ::buildInfo
-   *
-   * @dataProvider providerTestGetInfo
-   */
-  public function testGetInfo($type, $expected_info, $element_info, callable $alter_callback = NULL) {
-    $this->moduleHandler->expects($this->once())
-      ->method('invokeAll')
-      ->with('element_info')
-      ->will($this->returnValue($element_info));
-    $this->moduleHandler->expects($this->once())
-      ->method('alter')
-      ->with('element_info', $this->anything())
-      ->will($this->returnCallback($alter_callback ?: function($info) {
-        return $info;
-      }));
-
-    $this->assertEquals($expected_info, $this->elementInfo->getInfo($type));
-  }
-
-  /**
-   * Provides tests data for getInfo.
-   *
-   * @return array
-   */
-  public function providerTestGetInfo() {
-    $data = array();
-    // Provide an element and expect it is returned.
-    $data[] = array(
-      'page',
-      array(
-        '#type' => 'page',
-        '#show_messages' => TRUE,
-        '#theme' => 'page',
-      ),
-      array('page' => array(
-        '#show_messages' => TRUE,
-        '#theme' => 'page',
-      )),
-    );
-    // Provide an element but request an non existent one.
-    $data[] = array(
-      'form',
-      array(
-      ),
-      array('page' => array(
-        '#show_messages' => TRUE,
-        '#theme' => 'page',
-      )),
-    );
-    // Provide an element and alter it to ensure it is altered.
-    $data[] = array(
-      'page',
-      array(
-        '#type' => 'page',
-        '#show_messages' => TRUE,
-        '#theme' => 'page',
-        '#number' => 597219,
-      ),
-      array('page' => array(
-        '#show_messages' => TRUE,
-        '#theme' => 'page',
-      )),
-      function ($alter_name, array &$info) {
-        $info['page']['#number'] = 597219;
-      }
-    );
-    return $data;
-  }
-
-}