diff --git a/core/modules/ckeditor/ckeditor.admin.inc b/core/modules/ckeditor/ckeditor.admin.inc
new file mode 100644
index 0000000..c77dda9
--- /dev/null
+++ b/core/modules/ckeditor/ckeditor.admin.inc
@@ -0,0 +1,154 @@
+<?php
+
+/**
+ * @file
+ * Callbacks and theming for the CKEditor toolbar configuration UI.
+ */
+
+use Drupal\Core\Template\Attribute;
+
+/**
+ * Preprocess variables for theme_ckeditor_settings_toolbar().
+ */
+function template_preprocess_ckeditor_settings_toolbar(&$variables) {
+  // Simplify the language direction information for toolbar buttons.
+  $language_interface = language(LANGUAGE_TYPE_INTERFACE);
+  $variables['language_direction'] = $language_interface->direction ? 'rtl' : 'ltr';
+
+  // Create lists of active and disabled buttons.
+  $editor = $variables['editor'];
+  $plugins = $variables['plugins'];
+  $buttons = array();
+  $variables['multiple_buttons'] = array();
+  foreach ($plugins as $plugin => $plugin_buttons) {
+    foreach ($plugin_buttons as $button_name => $button) {
+      $button['name'] = $button_name;
+      if (!empty($button['multiple'])) {
+        $variables['multiple_buttons'][$button_name] = $button;
+      }
+      $buttons[$button_name] = $button;
+    }
+  }
+  $variables['active_buttons'] = array();
+  foreach ($editor->settings['toolbar']['buttons'] as $row_number => $row) {
+    foreach ($row as $button_name) {
+      if (isset($buttons[$button_name])) {
+        $variables['active_buttons'][$row_number][] = $buttons[$button_name];
+        if (empty($buttons[$button_name]['multiple'])) {
+          unset($buttons[$button_name]);
+        }
+      }
+    }
+  }
+  $variables['disabled_buttons'] = array_diff_key($buttons, $variables['multiple_buttons']);
+}
+
+/**
+ * Displays the toolbar configuration for CKEditor.
+ */
+function theme_ckeditor_settings_toolbar($variables) {
+  $editor = $variables['editor'];
+  $plugins = $variables['plugins'];
+  $rtl = $variables['language_direction'] === 'rtl' ? '_rtl' : '';
+
+  $build_button_item = function($button, $rtl) {
+    // Value of the button item.
+    if (isset($button['image_alternative'])) {
+      $value = $button['image_alternative' . $rtl];
+    }
+    elseif (isset($button['image'])) {
+      $value = theme('image', array('uri' => $button['image' . $rtl], 'title' => $button['label']));
+    }
+    else {
+      $value = '?';
+    }
+
+    // Set additional attribute on the button if it can occur multiple times.
+    if (!empty($button['multiple'])) {
+     $button['attributes']['class'][] = 'ckeditor-multiple-button';
+    }
+
+    // Build the button item.
+    $button_item = array(
+      'value' => $value,
+      'data-button-name' => $button['name'],
+    );
+    if (!empty($button['attributes'])) {
+      $button_item = array_merge($button_item, $button['attributes']);
+    }
+
+    return $button_item;
+  };
+
+  // Assemble items to be added to active button rows.
+  $active_buttons = array();
+  foreach ($variables['active_buttons'] as $row_number => $row_buttons) {
+    foreach ($row_buttons as $button) {
+      $active_buttons[$row_number][] = $build_button_item($button, $rtl);
+    }
+  }
+  // Assemble list of disabled buttons (which are always a single row).
+  $disabled_buttons = array();
+  foreach ($variables['disabled_buttons'] as $button) {
+    $disabled_buttons[] = $build_button_item($button, $rtl);
+  }
+  // Assemble list of multiple buttons that may be added multiple times.
+  $multiple_buttons = array();
+  foreach ($variables['multiple_buttons'] as $button_name => $button) {
+    $multiple_buttons[] = $build_button_item($button, $rtl);
+  }
+
+  $print_buttons = function($buttons) {
+    $output = '';
+    foreach ($buttons as $button) {
+      $value = $button['value'];
+      unset($button['value']);
+      $attributes = (string) new Attribute($button);
+      $output .= '<li' . $attributes . '>' . $value . '</li>';
+    }
+    return $output;
+  };
+
+  // We don't use theme_item_list() below in case there are no buttons in the
+  // active or disabled list, as theme_item_list() will not print an empty UL.
+  $output = '';
+  $output .= '<fieldset role="form" aria-labelledby="ckeditor-button-configuration ckeditor-button-description">';
+  $output .= '<legend id="ckeditor-button-configuration">' . t('Button configuration') . '</legend>';
+  // aria-live region for outputing aural information about the state of the
+  // configuration.
+  $output .= '<div id="ckeditor-button-configuration-aria-live" class="element-invisible" aria-live="polite"></div>';
+  $output .= '<p id="ckeditor-button-description">' . t('Toolbar buttons may be moved by drag and drop or with the keyboard arrow keys. Move a button up into the active toolbar to enable it, or down into the available buttons list to disable it. Dividers are available to create logical button groups.') . '</p>';
+  $output .= '<label id="ckeditor-active-toolbar">' . t('Active toolbar') . '</label>';
+
+  $output .= '<div data-toolbar="active" class="ckeditor-toolbar-active clearfix">';
+  foreach ($active_buttons as $button_row) {
+    $output .= '<ul class="ckeditor-buttons" role="form" aria-labelledby="ckeditor-active-toolbar">';
+    $output .= $print_buttons($button_row);
+    $output .= '</ul>';
+  }
+  if (empty($active_buttons)) {
+    $output .= '<ul class="ckeditor-buttons">';
+    $output .= '</ul>';
+  }
+
+  $output .= '<div class="ckeditor-row-controls">';
+  $output .= '<a href="#" role="button" aria-label="' . t('Remove last button row') . '" class="ckeditor-row-remove" title="' . t('Remove row') . '">-</a>';
+  $output .= '<a href="#" role="button" aria-label="' . t('Add additional button row') . '" class="ckeditor-row-add" title="' . t('Add row') . '">+</a>';
+  $output .= '</div>';
+
+  $output .= '</div>';
+
+  $output .= '<div class="ckeditor-toolbar-disabled">';
+  $output .= '<label id="ckeditor-available-buttons">' . t('Available buttons') . '</label>';
+  $output .= '<ul class="ckeditor-buttons" role="form" aria-labelledby="ckeditor-available-buttons">';
+  $output .= $print_buttons($disabled_buttons);
+  $output .= '</ul>';
+  $output .= '<label id="ckeditor-multiple-label">' . t('Dividers') . '</label>';
+  $output .= '<ul class="ckeditor-multiple-buttons" role="form" aria-labelledby="ckeditor-multiple-label">';
+  $output .= $print_buttons($multiple_buttons);
+  $output .= '</ul>';
+  $output .= '</div>';
+  $output .= '</fieldset>';
+
+  return $output;
+}
diff --git a/core/modules/ckeditor/ckeditor.api.php b/core/modules/ckeditor/ckeditor.api.php
new file mode 100644
index 0000000..0b4f343
--- /dev/null
+++ b/core/modules/ckeditor/ckeditor.api.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Documentation for CKEditor module APIs.
+ */
+
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Modify the list of available CKEditor plugins.
+ *
+ * This hook may be used to modify plugin properties after they have been
+ * specified by other modules.
+ *
+ * @param $plugins
+ *   An array of all the existing plugin definitions, passed by reference.
+ *
+ * @see CKEditorPluginManager
+ */
+function hook_ckeditor_plugin_info_alter(array &$plugins) {
+  $plugins['someplugin']['label'] = t('Better name');
+}
+
+/**
+ * Modify the list of CSS files that will be added to a CKEditor instance.
+ *
+ * Modules may use this hook to provide their own custom CSS file without
+ * providing a CKEditor plugin. This list of CSS files is only used in the
+ * iframe versions of CKEditor.
+ *
+ * Front-end themes (and base themes) can easily specify CSS files to be used in
+ * iframe instances of CKEditor through an entry in their .info file:
+ *
+ * @code
+ * ckeditor_stylesheets[] = css/ckeditor-iframe.css
+ * @endcode
+ *
+ * @param array &$css
+ *   An array of CSS files, passed by reference. This is a flat list of file
+ *   paths relative to the Drupal root.
+ * @param $editor
+ *   The text editor object as returned by editor_load(), for which these files
+ *   are being loaded. Based on this information, it is possible to load the
+ *   corresponding text format object as returned by filter_format_load().
+ *
+ * @see _ckeditor_theme_css()
+ */
+function hook_ckeditor_css_alter(array &$css, Editor $editor) {
+  $css[] = drupal_get_path('module', 'mymodule') . '/css/mymodule-ckeditor.css';
+}
+
+/**
+ * @} End of "addtogroup hooks".
+ */
diff --git a/core/modules/ckeditor/ckeditor.info b/core/modules/ckeditor/ckeditor.info
new file mode 100644
index 0000000..9c40ac2
--- /dev/null
+++ b/core/modules/ckeditor/ckeditor.info
@@ -0,0 +1,6 @@
+name = CKEditor
+description = WYSIWYG editing for rich text fields using CKEditor.
+package = Core
+core = 8.x
+version = VERSION
+dependencies[] = editor
diff --git a/core/modules/ckeditor/ckeditor.module b/core/modules/ckeditor/ckeditor.module
new file mode 100644
index 0000000..6686dc3
--- /dev/null
+++ b/core/modules/ckeditor/ckeditor.module
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Provides integration with the CKEditor WYSIWYG editor.
+ */
+
+/**
+ * Implements hook_library_info().
+ */
+function ckeditor_library_info() {
+  $module_path = drupal_get_path('module', 'ckeditor');
+
+  $settings = array(
+    'ckeditor' => array(
+      'modulePath' => drupal_get_path('module', 'ckeditor'),
+    ),
+  );
+  $libraries['drupal.ckeditor'] = array(
+    'title' => 'Drupal behavior to enable CKEditor on textareas.',
+    'version' => VERSION,
+    'js' => array(
+      $module_path . '/js/ckeditor.js' => array(),
+      array('data' => $settings, 'type' => 'setting'),
+    ),
+    'dependencies' => array(
+      array('system', 'drupal'),
+      array('ckeditor', 'ckeditor'),
+      array('editor', 'drupal.editor'),
+    ),
+  );
+  $libraries['drupal.ckeditor.css'] = array(
+    'title' => 'Formatting CSS for common classes used in CKEditor.',
+    'version' => VERSION,
+    'css' => array(
+      $module_path . '/css/ckeditor.css' => array(),
+    ),
+  );
+  $libraries['drupal.ckeditor.admin'] = array(
+    'title' => 'Drupal behavior for drag-and-drop CKEditor toolbar builder UI.',
+    'version' => VERSION,
+    'js' => array(
+      $module_path . '/js/ckeditor.admin.js' => array(),
+    ),
+    'css' => array(
+      $module_path . '/css/ckeditor.admin.css' => array(),
+      'core/misc/ckeditor/skins/moono/editor.css' => array(),
+    ),
+    'dependencies' => array(
+      array('system', 'jquery'),
+      array('system', 'drupal'),
+      array('system', 'drupalSettings'),
+      array('system', 'jquery.once'),
+      array('system', 'jquery.ui.sortable'),
+      array('system', 'jquery.ui.draggable'),
+    ),
+  );
+  $libraries['drupal.ckeditor.stylescombo.admin'] = array(
+    'title' => 'Only show the "stylescombo" plugin settings when its button is enabled.',
+    'version' => VERSION,
+    'js' => array(
+      $module_path . '/js/ckeditor.stylescombo.admin.js' => array(),
+    ),
+    'dependencies' => array(
+      array('system', 'jquery'),
+      array('system', 'drupal'),
+      array('system', 'jquery.once'),
+      array('system', 'drupal.vertical-tabs'),
+    ),
+  );
+  $libraries['ckeditor'] = array(
+    'title' => 'Loads the main CKEditor library.',
+    'version' => '4.0.1',
+    'js' => array(
+      'core/misc/ckeditor/ckeditor.js' => array(),
+    ),
+  );
+
+  return $libraries;
+}
+
+/**
+ * Implements hook_theme().
+ */
+function ckeditor_theme() {
+  return array(
+    'ckeditor_settings_toolbar' => array(
+      'file' => 'ckeditor.admin.inc',
+      'variables' => array('editor' => NULL, 'plugins' => NULL),
+    ),
+  );
+}
+
+/**
+ * Retrieves the default theme's CKEditor stylesheets defined in the .info file.
+ *
+ * Themes may specify iframe-specific CSS files for use with CKEditor by
+ * including a "ckeditor_stylesheets" key in the theme .info file.
+ *
+ * @code
+ * ckeditor_stylesheets[] = css/ckeditor-iframe.css
+ * @endcode
+ */
+function _ckeditor_theme_css($theme = NULL) {
+  $css = array();
+  if (!isset($theme)) {
+    $theme = variable_get('theme_default');
+  }
+  if ($theme_path = drupal_get_path('theme', $theme)) {
+    $info = system_get_info('theme', $theme);
+    if (isset($info['ckeditor_stylesheets'])) {
+      $css = $info['ckeditor_stylesheets'];
+      foreach ($css as $key => $path) {
+        $css[$key] = $theme_path . '/' . $path;
+      }
+    }
+    if (isset($info['base theme'])) {
+      $css = array_merge(_ckeditor_theme_css($info['base theme'], $css));
+    }
+  }
+  return $css;
+}
diff --git a/core/modules/ckeditor/css/ckeditor-iframe.css b/core/modules/ckeditor/css/ckeditor-iframe.css
new file mode 100644
index 0000000..54f4b3f
--- /dev/null
+++ b/core/modules/ckeditor/css/ckeditor-iframe.css
@@ -0,0 +1,18 @@
+/**
+ * CSS added to iframe-based instances only.
+ */
+body {
+  font-family: Arial, Verdana, sans-serif;
+  font-size: 12px;
+  color: #222;
+  background-color: #fff;
+  margin: 8px;
+}
+
+ol, ul, dl {
+  /* IE7: reset rtl list margin. (CKEditor issue #7334) */
+  *margin-right: 0px;
+  /* Preserved spaces for list items with text direction other than the list.
+   * (CKEditor issues #6249,#8049) */
+  padding: 0 40px;
+}
diff --git a/core/modules/ckeditor/css/ckeditor-rtl.css b/core/modules/ckeditor/css/ckeditor-rtl.css
new file mode 100644
index 0000000..59f97b8
--- /dev/null
+++ b/core/modules/ckeditor/css/ckeditor-rtl.css
@@ -0,0 +1,18 @@
+/**
+ * RTL styles used by CKEditor. Added to front-end theme and iframe editors.
+ */
+.align-left {
+  text-align: right;
+}
+.align-right {
+  text-align: left;
+}
+.indent1 {
+  margin: 0 40px 0 0;
+}
+.indent2 {
+  margin: 0 80px 0 0;
+}
+.indent3 {
+  margin: 0 120px 0 0;
+}
diff --git a/core/modules/ckeditor/css/ckeditor.admin.css b/core/modules/ckeditor/css/ckeditor.admin.css
new file mode 100644
index 0000000..0900b2a
--- /dev/null
+++ b/core/modules/ckeditor/css/ckeditor.admin.css
@@ -0,0 +1,184 @@
+/**
+ * @file
+ * Styles for configuration of CKEditor module.
+ *
+ * Many of these styles are adapted directly from the default CKEditor theme
+ * "moono".
+ */
+
+.ckeditor-toolbar-active {
+  border: 1px solid #b6b6b6;
+  padding: 6px 8px 2px;
+  box-shadow: 0 1px 0 white inset;
+  background: #cfd1cf;
+  background-image: -webkit-gradient(linear, left top, left bottom, from(whiteSmoke), to(#cfd1cf));
+  background-image: -moz-linear-gradient(top, whiteSmoke, #cfd1cf);
+  background-image: -o-linear-gradient(top, whiteSmoke, #cfd1cf);
+  background-image: -ms-linear-gradient(top, whiteSmoke, #cfd1cf);
+  background-image: linear-gradient(top, whiteSmoke, #cfd1cf);
+  filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0, startColorstr='#fff5f5f5', endColorstr='#ffcfd1cf');
+  margin: 5px 0;
+  overflow: nowrap;
+}
+.ckeditor-toolbar-disabled ul.ckeditor-buttons {
+  border: 0;
+}
+
+.ckeditor-toolbar-disabled ul.ckeditor-buttons li {
+  margin: 5px;
+}
+
+.ckeditor-toolbar-disabled ul.ckeditor-buttons li a,
+ul.ckeditor-buttons {
+  border: 1px solid #a6a6a6;
+  border-bottom-color: #979797;
+  border-radius: 3px;
+  box-shadow: 0 1px 0 rgba(255, 255, 255, .5), 0 0 2px rgba(255, 255, 255, .15) inset, 0 1px 0 rgba(255, 255, 255, .15) inset;
+}
+
+ul.ckeditor-buttons {
+  min-height: 26px;
+  min-width: 26px;
+  list-style: none;
+
+  float: left; /* LTR */
+  clear: left; /* LTR */
+  padding: 0;
+  margin: 0 6px 5px 0;
+  border: 1px solid #a6a6a6;
+  border-bottom-color: #979797;
+  border-radius: 3px;
+  box-shadow: 0 1px 0 rgba(255, 255, 255, .5), 0 0 2px rgba(255, 255, 255, .15) inset, 0 1px 0 rgba(255, 255, 255, .15) inset;
+}
+ul.ckeditor-buttons li {
+  display: inline-block;
+  padding: 0;
+  margin: 0;
+  float: left; /* LTR */
+}
+ul.ckeditor-buttons li a {
+  position: relative;
+  display: block;
+  height: 18px;
+  padding: 4px 6px;
+  cursor: move;
+  border: 0;
+  white-space: nowrap;
+  text-decoration: none;
+  text-shadow: 0 1px 0 rgba(255,255,255,.5);
+  color: #474747;
+
+  background: #e4e4e4;
+  background-image: -webkit-gradient(linear,left top,left bottom,from(white),to(#e4e4e4));
+  background-image: -moz-linear-gradient(top,white,#e4e4e4);
+  background-image: -webkit-linear-gradient(top,white,#e4e4e4);
+  background-image: -o-linear-gradient(top,white,#e4e4e4);
+  background-image: -ms-linear-gradient(top,white,#e4e4e4);
+  background-image: linear-gradient(top,white,#e4e4e4);
+  filter: progid:DXImageTransform.Microsoft.gradient(gradientType=0,startColorstr='#ffffffff',endColorstr='#ffe4e4e4');
+}
+ul.ckeditor-buttons li .cke-icon-only {
+  text-indent: -9999px;
+  width: 16px;
+}
+ul.ckeditor-buttons li a:focus {
+  z-index: 11; /* Ensure focused buttons show their outline on all sides. */
+}
+ul.ckeditor-buttons li:first-child a {
+  border-top-left-radius: 2px; /* LTR */
+  border-bottom-left-radius: 2px; /* LTR */
+}
+ul.ckeditor-buttons li:last-child a {
+  border-top-right-radius: 2px; /* LTR */
+  border-bottom-right-radius: 2px; /* LTR */
+}
+ul.ckeditor-buttons li.ckeditor-button-placeholder a {
+  background: #333;
+  opacity: 0.3;
+}
+ul.ckeditor-multiple-buttons {
+  padding: 1px 2px;
+  margin: 5px;
+  list-style: none;
+  float: left; /* LTR */
+}
+ul.ckeditor-multiple-buttons li {
+  display: inline-block;
+  float: left; /* LTR */
+  margin: 0;
+  padding: 0;
+}
+ul.ckeditor-multiple-buttons li a {
+  cursor: move;
+  display: inline-block;
+  height: 18px;
+  margin: 0;
+  padding: 2px 0;
+}
+ul.ckeditor-buttons li.ckeditor-group-button-separator,
+ul.ckeditor-multiple-buttons li.ckeditor-group-button-separator {
+  margin: -1px -3px -2px;
+}
+ul.ckeditor-buttons li.ckeditor-group-button-separator a,
+ul.ckeditor-multiple-buttons li.ckeditor-group-button-separator a {
+  background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA0AAAAdCAMAAABG4xbVAAAAhFBMVEUAAACmpqampqampqb////l5eX////5+fmmpqatra2urq6vr6+1tbW2tra4uLi6urq8vLzb29ve3t7i4uLl5eXn5+fo6Ojp6enq6urr6+vs7Ozt7e3u7u7v7+/w8PDx8fHy8vLz8/P09PT19fX29vb39/f4+Pj5+fn6+vr7+/v8/Pz+/v7qIQO+AAAACHRSTlMATVmAi8XM29MuWToAAABjSURBVBiVrc5BCoAwDETRMKhtRBduev9LKm1xjItWRBBE6Nt9QkIwOTcUzk0Imi8aoMssxbgoTHMtqsFMLta0vPh2N49HyfdelPg6k9uvX/a+Bmggt1qJRNzQFVgjEnkUZDoBmH57VSypjg4AAAAASUVORK5CYII=) no-repeat center center;
+  width: 13px;
+  padding: 0;
+  height: 29px;
+  position: relative;
+  z-index: 10;
+}
+ul.ckeditor-buttons li.ckeditor-button-separator a {
+  background: #e4e4e4;
+  background-image: -webkit-linear-gradient(#e4e4e4, #b4b4b4);
+  background-image: linear-gradient(#e4e4e4, #b4b4b4);
+  height: 24px;
+  margin: 1px 0 0;
+  padding: 0;
+  position: relative;
+  width: 1px;
+  z-index: 10;
+}
+ul.ckeditor-multiple-buttons li.ckeditor-button-separator a {
+  width: 2px;
+  padding: 0;
+  height: 26px;
+  margin: 0 10px;
+}
+.ckeditor-separator {
+  background-color: silver;
+  background-color: rgba(0, 0, 0, .2);
+  margin: 5px 0;
+  height: 18px;
+  width: 1px;
+  display: block;
+  -webkit-box-shadow: 1px 0 1px rgba(255, 255, 255, .5);
+  -moz-box-shadow: 1px 0 1px rgba(255,255,255,.5);
+  box-shadow: 1px 0 1px rgba(255, 255, 255, .5)
+}
+.ckeditor-button-arrow {
+  width: 0;
+  text-align: center;
+  border-left: 3px solid transparent;
+  border-right: 3px solid transparent;
+  border-top: 3px solid #333;
+  display: inline-block;
+  margin: 0 4px 2px;
+}
+
+.ckeditor-row-controls {
+  float: right; /* LTR */
+  font-size: 18px;
+  width: 40px;
+  text-align: right; /* LTR */
+}
+.ckeditor-row-controls a {
+  display: inline-block;
+  padding: 6px 2px;
+  height: 16px;
+  width: 16px;
+  line-height: 0.9;
+  font-weight: bold;
+  color: #333;
+  text-decoration: none;
+}
diff --git a/core/modules/ckeditor/css/ckeditor.css b/core/modules/ckeditor/css/ckeditor.css
new file mode 100644
index 0000000..d7ca147
--- /dev/null
+++ b/core/modules/ckeditor/css/ckeditor.css
@@ -0,0 +1,53 @@
+/**
+ * Common styles used by CKEditor. Added to front-end theme and iframe editors.
+ */
+.align-left {
+  text-align: left; /* RTL */
+}
+.align-right {
+  text-align: right; /* RTL */
+}
+.align-center {
+  text-align: center;
+}
+.align-justify {
+  text-align: justify;
+}
+img.align-left {
+  float: left; /* RTL */
+}
+img.align-right {
+  float: right; /* RTL */
+}
+img.align-center {
+  margin-left: auto;
+  margin-right: auto;
+  display: block;
+}
+img.full-width {
+  width: 100%;
+  height: auto;
+}
+.underline {
+  text-decoration: underline;
+}
+.indent1 {
+  margin: 0 0 0 40px; /* RTL */
+}
+.indent2 {
+  margin: 0 0 0 80px; /* RTL */
+}
+.indent3 {
+  margin: 0 0 0 120px; /* RTL */
+}
+.caption-left {
+  float: left; /* RTL */
+}
+.caption-right {
+  float: right; /* RTL */
+}
+.caption-center {
+  text-align: center;
+  margin-left: auto;
+  margin-right: auto;
+}
diff --git a/core/modules/ckeditor/js/ckeditor.admin.js b/core/modules/ckeditor/js/ckeditor.admin.js
new file mode 100644
index 0000000..e13b19c
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.admin.js
@@ -0,0 +1,298 @@
+(function ($, Drupal, drupalSettings) {
+
+"use strict";
+
+Drupal.ckeditor = Drupal.ckeditor || {};
+
+// Aria-live element for speaking application state.
+var $messages;
+
+Drupal.behaviors.ckeditorAdmin = {
+  attach: function (context) {
+    var $context = $(context);
+    var $ckeditorToolbar = $context.find('.ckeditor-toolbar-configuration').once('ckeditor-toolbar');
+
+    /**
+     * Event callback for keypress. Move buttons based on arrow keys.
+     */
+    function adminToolbarMoveButton(event) {
+      var $target = $(event.target);
+      var label = Drupal.t('@label button', { '@label': $target.attr('aria-label') });
+      var $button = $target.parent();
+      var $currentRow = $button.closest('.ckeditor-buttons');
+      var $destinationRow = null;
+      var destinationPosition = $button.index();
+
+      switch (event.keyCode) {
+        case 37: // Left arrow.
+        case 63234: // Safari left arrow.
+          $destinationRow = $currentRow;
+          destinationPosition -= rtl;
+          break;
+
+        case 38: // Up arrow.
+        case 63232: // Safari up arrow.
+          $destinationRow = $($toolbarRows[$toolbarRows.index($currentRow) - 1]);
+          break;
+
+        case 39: // Right arrow.
+        case 63235: // Safari right arrow.
+          $destinationRow = $currentRow;
+          destinationPosition += rtl;
+          break;
+
+        case 40: // Down arrow.
+        case 63233: // Safari down arrow.
+          $destinationRow = $($toolbarRows[$toolbarRows.index($currentRow) + 1]);
+      }
+
+      if ($destinationRow && $destinationRow.length) {
+        // Detach the button from the DOM so its position doesn't interfere.
+        $button.detach();
+        // Move the button before the button whose position it should occupy.
+        var $targetButton = $destinationRow.children(':eq(' + destinationPosition + ')');
+        if ($targetButton.length) {
+          $targetButton.before($button);
+        }
+        else {
+          $destinationRow.append($button);
+        }
+        // Post the update to the aria-live message element.
+        $messages.text(Drupal.t('moved to @row, position @position of @totalPositions', {
+          '@row': getRowInfo($destinationRow),
+          '@position': (destinationPosition + 1),
+          '@totalPositions': $destinationRow.children().length
+        }));
+        // Update the toolbar value field.
+        adminToolbarValue(event, { item: $button });
+      }
+      event.preventDefault();
+    }
+
+    /**
+     * Event callback for keyup. Move a separator into the active toolbar.
+     */
+    function adminToolbarMoveSeparator(event) {
+      switch (event.keyCode) {
+        case 38: // Up arrow.
+        case 63232: // Safari up arrow.
+          var $button = $(event.target).parent().clone().appendTo($toolbarRows.eq(-2));
+          adminToolbarValue(event, { item: $button });
+          event.preventDefault();
+      }
+    }
+
+    /**
+     * Provide help when a button is clicked on.
+     */
+    function adminToolbarButtonHelp(event) {
+      // @todo The actual click target is the span element, not the link,
+      //   which breaks the entire UI.
+      var $link = $(event.target); // FIXME
+      var $button = $link.parent();
+      var $currentRow = $button.closest('.ckeditor-buttons');
+      var enabled = $button.closest('.ckeditor-toolbar-active').length > 0;
+      var position = $button.index() + 1; // 1-based index for humans.
+      var rowNumber = $toolbarRows.index($currentRow) + 1;
+      var type = event.data.type;
+      var message;
+
+      if (enabled) {
+        if (type === 'separator') {
+          message = Drupal.t('Separators are used to visually split individual buttons. This @name is currently enabled, in row @row and position @position.', { '@name': $link.attr('aria-label'), '@row': rowNumber, '@position': position }) + "\n\n" + Drupal.t('Drag and drop the separator or use the keyboard arrow keys to change the position of this separator.');
+        }
+        else {
+          message = Drupal.t('The @name button is currently enabled, in row @row and position @position.', { '@name': $link.attr('aria-label'), '@row': rowNumber, '@position': position }) + "\n\n" + Drupal.t('Drag and drop the buttons or use the keyboard arrow keys to change the position of this button.');
+        }
+      }
+      else {
+        if (type === 'separator') {
+          message = Drupal.t('Separators are used to visually split individual buttons. This @name is currently disabled.', { '@name': $link.attr('aria-label') }) + "\n\n" + Drupal.t('Drag the button or use the up arrow key to move this separator into the active toolbar. You may add multiple separators to each row.');
+        }
+        else {
+          message = Drupal.t('The @name button is currently disabled.', { '@name': $link.attr('aria-label') }) + "\n\n" + Drupal.t('Drag the button or use the up arrow key to move this button into the active toolbar.');
+        }
+      }
+      $messages.text(message);
+      $link.focus();
+      event.preventDefault();
+    }
+
+    /**
+     * Add a new row of buttons.
+     */
+    function adminToolbarAddRow(event) {
+      var $this = $(event.target);
+      var $rows = $this.closest('.ckeditor-toolbar-active').find('.ckeditor-buttons');
+      var $rowNew = $rows.last().clone().empty().sortable(sortableSettings);
+      $rows.last().after($rowNew);
+      $toolbarRows = $toolbarAdmin.find('.ckeditor-buttons');
+      $this.siblings('a').show();
+      redrawToolbarGradient();
+      // Post the update to the aria-live message element.
+      $messages.text(Drupal.t('row number @count added.', {'@count': ($rows.length + 1)}));
+      event.preventDefault();
+    }
+
+    /**
+     * Remove a row of buttons.
+     */
+    function adminToolbarRemoveRow(event) {
+      var $this = $(event.target);
+      var $rows = $this.closest('.ckeditor-toolbar-active').find('.ckeditor-buttons');
+      if ($rows.length === 2) {
+        $this.hide();
+      }
+      if ($rows.length > 1) {
+        var $lastRow = $rows.last();
+        var $disabledButtons = $ckeditorToolbar.find('.ckeditor-toolbar-disabled .ckeditor-buttons');
+        $lastRow.children(':not(.ckeditor-multiple-button)').prependTo($disabledButtons);
+        $lastRow.sortable('destroy').remove();
+        $toolbarRows = $toolbarAdmin.find('.ckeditor-buttons');
+        redrawToolbarGradient();
+      }
+      // Post the update to the aria-live message element.
+      $messages.text(Drupal.t('row removed. @count row@plural remaining.', {'@count': ($rows.length - 1), '@plural': ((($rows.length - 1) === 1 ) ? '' : 's')}));
+      event.preventDefault();
+    }
+
+    /**
+     * Browser quirk work-around to redraw CSS3 gradients.
+     */
+    function redrawToolbarGradient() {
+      $ckeditorToolbar.find('.ckeditor-toolbar-active').css('position', 'relative');
+      window.setTimeout(function () {
+        $ckeditorToolbar.find('.ckeditor-toolbar-active').css('position', '');
+      }, 10);
+    }
+
+    /**
+     * jQuery Sortable stop event. Save updated toolbar positions to the
+     * textarea.
+     */
+    function adminToolbarValue(event, ui) {
+      // Update the toolbar config after updating a sortable.
+      var toolbarConfig = [];
+      var $button = ui.item;
+      $button.find('a').focus();
+      $ckeditorToolbar.find('.ckeditor-toolbar-active ul').each(function () {
+        var $rowButtons = $(this).find('li');
+        var rowConfig = [];
+        if ($rowButtons.length) {
+          $rowButtons.each(function () {
+            rowConfig.push(this.getAttribute('data-button-name'));
+          });
+          toolbarConfig.push(rowConfig);
+        }
+      });
+      $textarea.val(JSON.stringify(toolbarConfig, null, '  '));
+
+      // Determine whether we should trigger an event.
+      var from = $(event.target).parents('div[data-toolbar]').attr('data-toolbar');
+      var to = $(event.toElement).parents('div[data-toolbar]').attr('data-toolbar');
+      if (from !== to) {
+        $ckeditorToolbar.find('.ckeditor-toolbar-active')
+          .trigger('CKEditorToolbarChanged', [
+            (to === 'active') ? 'added' : 'removed',
+            ui.item.get(0).getAttribute('data-button-name')
+          ]);
+      }
+    }
+
+    if ($ckeditorToolbar.length) {
+      var $textareaWrapper = $ckeditorToolbar.find('.form-item-editor-settings-toolbar-buttons').hide();
+      var $textarea = $textareaWrapper.find('textarea');
+      var $toolbarAdmin = $(drupalSettings.ckeditor.toolbarAdmin);
+      var sortableSettings = {
+        connectWith: '.ckeditor-buttons',
+        placeholder: 'ckeditor-button-placeholder',
+        forcePlaceholderSize: true,
+        tolerance: 'pointer',
+        cursor: 'move',
+        stop: adminToolbarValue
+      };
+      // Add the toolbar to the page.
+      $toolbarAdmin.insertAfter($textareaWrapper);
+
+      // Then determine if this is RTL or not.
+      var rtl = $toolbarAdmin.css('direction') === 'rtl' ? -1 : 1;
+      var $toolbarRows = $toolbarAdmin.find('.ckeditor-buttons');
+
+      // Add the drag and drop functionality.
+      $toolbarRows.sortable(sortableSettings);
+      $toolbarAdmin.find('.ckeditor-multiple-buttons li').draggable({
+        connectToSortable: '.ckeditor-toolbar-active .ckeditor-buttons',
+        helper: 'clone'
+      });
+
+      // Add keyboard arrow support.
+      $toolbarAdmin.on('keyup.ckeditorMoveButton', '.ckeditor-buttons a', adminToolbarMoveButton);
+      $toolbarAdmin.on('keyup.ckeditorMoveSeparator', '.ckeditor-multiple-buttons a', adminToolbarMoveSeparator);
+
+      // Add click for help.
+      $toolbarAdmin.on('click.ckeditorClickButton', '.ckeditor-buttons a', { type: 'button' }, adminToolbarButtonHelp);
+      $toolbarAdmin.on('click.ckeditorClickSeparator', '.ckeditor-multiple-buttons a', { type: 'separator' }, adminToolbarButtonHelp);
+
+      // Add/remove row button functionality.
+      $toolbarAdmin.on('click.ckeditorAddRow', 'a.ckeditor-row-add', adminToolbarAddRow);
+      $toolbarAdmin.on('click.ckeditorAddRow', 'a.ckeditor-row-remove', adminToolbarRemoveRow);
+      if ($toolbarAdmin.find('.ckeditor-toolbar-active ul').length > 1) {
+        $toolbarAdmin.find('a.ckeditor-row-remove').hide();
+      }
+
+      // Add aural UI focus updates when for individual toolbars.
+      $toolbarAdmin.on('focus.ckeditor', '.ckeditor-buttons', grantRowFocus);
+      // Identify the aria-live element for interaction updates for screen
+      // readers.
+      $messages = $('#ckeditor-button-configuration-aria-live');
+    }
+  }
+};
+
+/**
+ * Returns a string describing the type and index of a toolbar row.
+ *
+ * @param {jQuery} $row
+ *   A jQuery object containing a .ckeditor-button row.
+ *
+ * @return {String}
+ *   A string describing the type and index of a toolbar row.
+ */
+function getRowInfo($row) {
+  var output = '';
+  var row;
+  // Determine if this is an active row or an available row.
+  if ($row.closest('.ckeditor-toolbar-disabled').length > 0) {
+    row = $('.ckeditor-toolbar-disabled').find('.ckeditor-buttons').index($row) + 1;
+    output += Drupal.t('available button row @row', {'@row': row});
+  }
+  else {
+    row = $('.ckeditor-toolbar-active').find('.ckeditor-buttons').index($row) + 1;
+    output += Drupal.t('active button row @row', {'@row': row});
+  }
+  return output;
+}
+
+/**
+ * Applies or removes the focused class to a toolbar row.
+ *
+ * When a button in a toolbar is focused, focus is triggered on the containing
+ * toolbar row. When a row is focused, the state change is announced through
+ * the aria-live message area.
+ *
+ * @param {jQuery} event
+ *   A jQuery event.
+ */
+function grantRowFocus(event) {
+  var $row = $(event.target);
+  // Remove the focused class from all other toolbars.
+  $('.ckeditor-buttons.focused').not($row).removeClass('focused');
+  // Post the update to the aria-live message element.
+  if (!$row.hasClass('focused')) {
+    // Indicate that the current row has focus.
+    $row.addClass('focused');
+    $messages.text(Drupal.t('@row', {'@row': getRowInfo($row)}));
+  }
+}
+
+})(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/ckeditor/js/ckeditor.js b/core/modules/ckeditor/js/ckeditor.js
new file mode 100644
index 0000000..b8318be
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.js
@@ -0,0 +1,36 @@
+(function (Drupal, 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;
+    }
+    return !!CKEDITOR.replace(element, format.editorSettings);
+  },
+
+  detach: function (element, format, trigger) {
+    var editor = CKEDITOR.dom.element.get(element).getEditor();
+    if (editor) {
+      if (trigger === 'serialize') {
+        editor.updateElement();
+      }
+      else {
+        editor.destroy();
+      }
+    }
+    return !!editor;
+  }
+
+};
+
+})(Drupal, CKEDITOR);
diff --git a/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
new file mode 100644
index 0000000..5a71a77
--- /dev/null
+++ b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
@@ -0,0 +1,51 @@
+(function ($, Drupal) {
+
+"use strict";
+
+/**
+ * Shows the "stylescombo" plugin settings only when the button is enabled.
+ */
+Drupal.behaviors.ckeditorStylesComboSettingsVisibility = {
+  attach: function (context) {
+    var $stylesComboVerticalTab = $('#edit-editor-settings-plugins-stylescombo').data('verticalTab');
+
+    // Hide if the "Styles" button is disabled.
+    if ($('.ckeditor-toolbar-disabled li[data-button-name="Styles"]').length === 1) {
+      $stylesComboVerticalTab.tabHide();
+    }
+
+    // React to added/removed toolbar buttons.
+    $(context)
+      .find('.ckeditor-toolbar-active')
+      .on('CKEditorToolbarChanged', function (e, action, button) {
+        if (button === 'Styles') {
+          if (action === 'added') {
+            $stylesComboVerticalTab.tabShow();
+          }
+          else {
+            $stylesComboVerticalTab.tabHide();
+          }
+        }
+      });
+  }
+};
+
+/**
+ * Provides the summary for the "stylescombo" plugin settings vertical tab.
+ */
+Drupal.behaviors.ckeditorStylesComboSettingsSummary = {
+  attach: function () {
+    $('#edit-editor-settings-plugins-stylescombo').drupalSetSummary(function (context) {
+      var styles = $.trim($('#edit-editor-settings-plugins-stylescombo-styles').val());
+      if (styles.length === 0) {
+        return Drupal.t('No styles configured');
+      }
+      else {
+        var count = $.trim(styles).split("\n").length;
+        return Drupal.t('@count styles configured', { '@count': count});
+      }
+    });
+  }
+};
+
+})(jQuery, Drupal);
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php
new file mode 100644
index 0000000..4050be1
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginBase.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor\Plugin\CKEditorPluginBase.
+ */
+
+namespace Drupal\ckeditor;
+
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Defines a base CKEditor plugin implementation.
+ *
+ * No other CKEditor plugins can be internal, unless a different CKEditor build
+ * than the one provided by Drupal core is used. Most CKEditor plugins don't
+ * need to provide additional settings forms.
+ *
+ * This base assumes that your plugin has buttons that you want to be enabled
+ * through the toolbar builder UI. It is still possible to also implement the
+ * CKEditorPluginContextualInterface (for contextual enabling) and
+ * CKEditorPluginConfigurableInterface interfaces (for configuring plugin
+ * settings) though.
+ *
+ * NOTE: the Drupal plugin ID should correspond to the CKEditor plugin name.
+ *
+ * @see CKEditorPluginInterface
+ * @see CKEditorPluginButtonsInterface
+ * @see CKEditorPluginContextualInterface
+ * @see CKEditorPluginConfigurableInterface
+ */
+abstract class CKEditorPluginBase extends PluginBase implements CKEditorPluginInterface, CKEditorPluginButtonsInterface {
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
+   */
+  function isInternal() {
+    return FALSE;
+  }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginButtonsInterface.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginButtonsInterface.php
new file mode 100644
index 0000000..4464d15
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginButtonsInterface.php
@@ -0,0 +1,56 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor\CKEditorPluginButtonsInterface.
+ */
+
+namespace Drupal\ckeditor;
+
+/**
+ * Defines an interface for CKEditor plugins with buttons.
+ *
+ * This allows a CKEditor plugin to define which buttons it provides, so that
+ * users can configure a CKEditor toolbar instance via the toolbar builder UI.
+ * If at least one button that this plugin provides is added to the toolbar via
+ * the toolbar builder UI, then this plugin will be enabled automatically.
+ *
+ * If a CKEditor plugin implements this interface, it can still also implement
+ * CKEditorPluginContextualInterface if it wants a button to conditionally be
+ * added as well. The downside of conditionally adding buttons is that the user
+ * cannot see these buttons in the toolbar builder UI.
+ *
+ * @see CKEditorPluginContextualInterface
+ * @see CKEditorPluginConfigurableInterface
+ */
+interface CKEditorPluginButtonsInterface extends CKEditorPluginInterface {
+
+  /**
+   * Returns the buttons that this plugin provides, along with metadata.
+   *
+   * The metadata is used by the CKEditor module to generate a visual CKEditor
+   * toolbar builder UI.
+   *
+   * @return array
+   *   An array of buttons that are provided by this plugin. This will
+   *   only be used in the administrative section for assembling the toolbar.
+   *   Each button should by keyed by its CKEditor button name, and should
+   *   contain an array of button properties, including:
+   *   - label: A human-readable, translated button name.
+   *   - image: An image for the button to be used in the toolbar.
+   *   - image_rtl: If the image needs to have a right-to-left version, specify
+   *     an alternative file that will be used in RTL editors.
+   *   - image_alternative: If this button does not render as an image, specify
+   *     an HTML string representing the contents of this button.
+   *   - image_alternative_rtl: Similar to image_alternative, but a
+   *     right-to-left version.
+   *   - attributes: An array of HTML attributes which should be added to this
+   *     button when rendering the button in the administrative section for
+   *     assembling the toolbar.
+   *   - multiple: Boolean value indicating if this button may be added multiple
+   *     times to the toolbar. This typically is only applicable for dividers
+   *     and group indicators.
+   */
+  public function getButtons();
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginConfigurableInterface.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginConfigurableInterface.php
new file mode 100644
index 0000000..9d01625
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginConfigurableInterface.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor\CKEditorPluginConfigurableInterface.
+ */
+
+namespace Drupal\ckeditor;
+
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Defines an interface for configurable CKEditor plugins.
+ *
+ * This allows a CKEditor plugin to define a settings form. These settings can
+ * then be automatically passed on to the corresponding CKEditor instance via
+ * CKEditorPluginInterface::getConfig().
+ *
+ * @see CKEditorPluginInterface
+ * @see CKEditorPluginButtonsInterface
+ * @see CKEditorPluginContextualInterface
+ */
+interface CKEditorPluginConfigurableInterface extends CKEditorPluginInterface {
+
+  /**
+   * Returns a settings form to configure this CKEditor plugin.
+   *
+   * If the plugin's behavior depends on extensive options and/or external data,
+   * then the implementing module can choose to provide a separate, global
+   * configuration page rather than per-text-editor settings. In that case, this
+   * form should provide a link to the separate settings page.
+   *
+   * @param array $form
+   *   An empty form array to be populated with a configuration form, if any.
+   * @param array $form_state
+   *   The state of the entire filter administration form.
+   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+   *   A configured text editor object.
+   *
+   * @return array|FALSE
+   *   A render array for the settings form, or FALSE if there is none.
+   */
+  function settingsForm(array $form, array &$form_state, Editor $editor);
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginContextualInterface.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginContextualInterface.php
new file mode 100644
index 0000000..b1b88df
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginContextualInterface.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor\CKEditorPluginContextualInterface.
+ */
+
+namespace Drupal\ckeditor;
+
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Defines an interface for contextually enabled CKEditor plugins.
+ *
+ * Contextually enabled CKEditor plugins can be enabled via an explicit setting,
+ * or enable themselves based on the configuration of another setting, such as
+ * enabling based on a particular button being present in the toolbar.
+ *
+ * If a contextually enabled CKEditor plugin must also be configurable (e.g. in
+ * the case where it must be enabled based on an explicit setting), then one
+ * must also implement the CKEditorPluginConfigurableInterface interface.
+ *
+ * @see CKEditorPluginConfigurableInterface
+ */
+interface CKEditorPluginContextualInterface extends CKEditorPluginInterface {
+
+  /**
+   * Checks if this plugin should be enabled based on the editor configuration.
+   *
+   * The editor's settings can be found in $editor->settings.
+   *
+   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+   *   A configured text editor object.
+   *
+   * @return bool
+   */
+  public function isEnabled(Editor $editor);
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php
new file mode 100644
index 0000000..601cafb
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginInterface.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor\CKEditorPluginInterface.
+ */
+
+namespace Drupal\ckeditor;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Defines an interface for (loading of) CKEditor plugins.
+ *
+ * This is the most basic CKEditor plugin interface; it provides the bare
+ * minimum information. Solely implementing this interface is not sufficient to
+ * be able to enable the plugin though — a CKEditor plugin can either be enabled
+ * automatically when a button it provides is present in the toolbar, or when
+ * some programmatically defined condition is true. In the former case,
+ * implement the CKEditorPluginButtonsInterface interface, in the latter case,
+ * implement the CKEditorPluginContextualInterface interface. It is also
+ * possible to implement both, for advanced use cases.
+ *
+ * Finally, if your plugin must be configurable, you can also implement the
+ * CKEditorPluginConfigurableInterface interface.
+ *
+ * @see CKEditorPluginButtonsInterface
+ * @see CKEditorPluginContextualInterface
+ * @see CKEditorPluginConfigurableInterface
+ */
+interface CKEditorPluginInterface extends PluginInspectionInterface {
+
+  /**
+   * Indicates if this plugin is part of the optimized CKEditor build.
+   *
+   * Plugins marked as internal are implicitly loaded as part of CKEditor.
+   *
+   * @return bool
+   */
+  public function isInternal();
+
+  /**
+   * Returns the Drupal root-relative file path to the plugin JavaScript file.
+   *
+   * Note: this does not use a Drupal library because this uses CKEditor's API,
+   * see http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.resourceManager.html#addExternal.
+   *
+   * @return string|FALSE
+   *   The Drupal root-relative path to the file, FALSE if an internal plugin.
+   */
+  public function getFile();
+
+  /**
+   * Returns the additions to CKEDITOR.config for a specific CKEditor instance.
+   *
+   * The editor's settings can be found in $editor->settings, but be aware that
+   * it may not yet contain plugin-specific settings, because the user may not
+   * yet have configured the form.
+   * If there are plugin-specific settings (verify with isset()), they can be
+   * found at $editor->settings['plugins'][$plugin_id].
+   *
+   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+   *   A configured text editor object.
+   * @return array
+   *   A keyed array, whose keys will end up as keys under CKEDITOR.config.
+   */
+  public function getConfig(Editor $editor);
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php
new file mode 100644
index 0000000..57a3a4b
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CKEditorPluginManager.php
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor\CKEditorPluginManager.
+ */
+
+namespace Drupal\ckeditor;
+
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Component\Plugin\Discovery\ProcessDecorator;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Plugin\Discovery\AlterDecorator;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+use Drupal\Core\Plugin\Discovery\CacheDecorator;
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * CKEditor Plugin manager.
+ */
+class CKEditorPluginManager extends PluginManagerBase {
+
+  /**
+   * Overrides \Drupal\Component\Plugin\PluginManagerBase::__construct().
+   */
+  public function __construct() {
+    $this->discovery = new AnnotatedClassDiscovery('ckeditor', 'plugin');
+    $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
+    $this->discovery = new AlterDecorator($this->discovery, 'ckeditor_plugin_info');
+    $this->discovery = new CacheDecorator($this->discovery, 'ckeditor_plugin');
+    $this->factory = new DefaultFactory($this->discovery);
+  }
+
+  /**
+   * Determines which plug-ins are enabled.
+   *
+   * For CKEditor plugins that implement:
+   *  - CKEditorPluginButtonsInterface, not CKEditorPluginContextualInterface,
+   *     a plugin is enabled if at least one of its buttons is in the toolbar;
+   *  - CKEditorPluginContextualInterface, not CKEditorPluginButtonsInterface,
+   *     a plugin is enabled if its isEnabled() method returns TRUE
+   *  - both of these interfaces, a plugin is enabled if either is the case.
+   *
+   * Internal plugins (those that are part of the bundled build of CKEditor) are
+   * excluded by default, since they are loaded implicitly. If you need to know
+   * even implicitly loaded (i.e. internal) plugins, then set the optional
+   * second parameter.
+   *
+   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+   *   A configured text editor object.
+   * @param bool $include_internal_plugins
+   *   Defaults to FALSE. When set to TRUE, plugins whose isInternal() method
+   *   returns TRUE will also be included.
+   * @return array
+   *   A list of the enabled CKEditor plugins, with the plugin IDs as keys and
+   *   the Drupal root-relative plugin files as values.
+   *   For internal plugins, the value is NULL.
+   */
+  public function getEnabledPlugins(Editor $editor, $include_internal_plugins = FALSE) {
+    $plugins = array_keys($this->getDefinitions());
+    $toolbar_buttons = array_unique(NestedArray::mergeDeepArray($editor->settings['toolbar']['buttons']));
+    $enabled_plugins = array();
+
+    foreach ($plugins as $plugin_id) {
+      $plugin = $this->createInstance($plugin_id);
+
+      if (!$include_internal_plugins && $plugin->isInternal()) {
+        continue;
+      }
+
+      $enabled = FALSE;
+      if ($plugin instanceof CKEditorPluginButtonsInterface) {
+        $plugin_buttons = array_keys($plugin->getButtons());
+        $enabled = (count(array_intersect($toolbar_buttons, $plugin_buttons)) > 0);
+      }
+      if (!$enabled && $plugin instanceof CKEditorPluginContextualInterface) {
+        $enabled = $plugin->isEnabled($editor);
+      }
+
+      if ($enabled) {
+        $enabled_plugins[$plugin_id] = ($plugin->isInternal()) ? NULL : $plugin->getFile();
+      }
+    }
+
+    // Always return plugins in the same order.
+    asort($enabled_plugins);
+
+    return $enabled_plugins;
+  }
+
+  /**
+   * Retrieves all plugins that implement CKEditorPluginButtonsInterface.
+   *
+   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+   *   A configured text editor object.
+   * @return array
+   *   A list of the CKEditor plugins that implement buttons, with the plugin
+   *   IDs as keys and lists of button metadata (as implemented by getButtons())
+   *   as values.
+   *
+   * @see CKEditorPluginButtonsInterface::getButtons()
+   */
+  public function getButtonsPlugins(Editor $editor) {
+    $plugins = array_keys($this->getDefinitions());
+    $buttons_plugins = array();
+
+    foreach ($plugins as $plugin_id) {
+      $plugin = $this->createInstance($plugin_id);
+      if ($plugin instanceof CKEditorPluginButtonsInterface) {
+        $buttons_plugins[$plugin_id] = $plugin->getButtons();
+      }
+    }
+
+    return $buttons_plugins;
+  }
+
+  /**
+   * Injects the CKEditor plugins settings forms as a vertical tabs subform.
+   *
+   * @param array &$form
+   *   A reference to an associative array containing the structure of the form.
+   * @param array &$form_state
+   *   A reference to a keyed array containing the current state of the form.
+   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+   *   A configured text editor object.
+   */
+  public function injectPluginSettingsForm(array &$form, array &$form_state, Editor $editor) {
+    $definitions = $this->getDefinitions();
+
+    foreach (array_keys($definitions) as $plugin_id) {
+      $plugin = $this->createInstance($plugin_id);
+      if ($plugin instanceof CKEditorPluginConfigurableInterface) {
+        $plugin_settings_form = array();
+        $form['plugins'][$plugin_id] = array(
+          '#type' => 'details',
+          '#title' => $definitions[$plugin_id]['label'],
+          '#group' => 'editor][settings][plugin_settings',
+        );
+        $form['plugins'][$plugin_id] += $plugin->settingsForm($plugin_settings_form, $form_state, $editor);
+      }
+    }
+  }
+
+  /**
+   * Overrides Drupal\Component\Plugin\PluginManagerBase::processDefinition().
+   */
+  public function processDefinition(&$definition, $plugin_id) {
+    parent::processDefinition($definition, $plugin_id);
+
+    // @todo Remove this check once http://drupal.org/node/1780396 is resolved.
+    if (!module_exists($definition['module'])) {
+      $definition = NULL;
+      return;
+    }
+  }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/CkeditorBundle.php b/core/modules/ckeditor/lib/Drupal/ckeditor/CkeditorBundle.php
new file mode 100644
index 0000000..eb8af12
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/CkeditorBundle.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor\CKEditorBundle.
+ */
+
+namespace Drupal\ckeditor;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * CKEditor dependency injection container.
+ */
+class CKEditorBundle extends Bundle {
+
+  /**
+   * Overrides Symfony\Component\HttpKernel\Bundle\Bundle::build().
+   */
+  public function build(ContainerBuilder $container) {
+    $container->register('plugin.manager.ckeditor.plugin', 'Drupal\ckeditor\CKEditorPluginManager');
+  }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php
new file mode 100644
index 0000000..4cd1646
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/Internal.php
@@ -0,0 +1,291 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor\Plugin\ckeditor\plugin\Internal.
+ */
+
+namespace Drupal\ckeditor\Plugin\ckeditor\plugin;
+
+use Drupal\ckeditor\CKEditorPluginBase;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Defines the "internal" plugin (i.e. core plugins part of our CKEditor build).
+ *
+ * @Plugin(
+ *   id = "internal",
+ *   label = @Translation("CKEditor core"),
+ *   module = "ckeditor"
+ * )
+ */
+class Internal extends CKEditorPluginBase {
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
+   */
+  public function isInternal() {
+    return TRUE;
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+   */
+  public function getFile() {
+    // This plugin is already part of Drupal core's CKEditor build.
+    return FALSE;
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getConfig().
+   */
+  public function getConfig(Editor $editor) {
+    // Reasonable defaults that provide expected basic behavior.
+    $config = array(
+      'pasteFromWordPromptCleanup' => TRUE,
+      'indentClasses' => array('indent1', 'indent2', 'indent3'),
+      'justifyClasses' => array('align-left', 'align-center', 'align-right', 'align-justify'),
+      'coreStyles_underline' => array('element' => 'span', 'attributes' => array('class' => 'underline')),
+      'removeDialogTabs' => 'image:Link;image:advanced;link:advanced',
+      'resize_dir' => 'vertical',
+      'keystrokes' =>  array(
+        // 0x11000 is CKEDITOR.CTRL, see http://docs.ckeditor.com/#!/api/CKEDITOR-property-CTRL.
+        array(0x110000 + 75, 'link'),
+        array(0x110000 + 76, NULL),
+      ),
+    );
+
+    // Next, add the format_tags setting, if its button is enabled.
+    $toolbar_buttons = array_unique(NestedArray::mergeDeepArray($editor->settings['toolbar']['buttons']));
+    if (in_array('Format', $toolbar_buttons)) {
+      $config['format_tags'] = $this->generateFormatTagsSetting($editor);
+    }
+
+    return $config;
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
+   */
+  public function getButtons() {
+    $button = function($name, $direction = 'ltr') {
+      return '<a href="#" class="cke-icon-only cke_' . $direction . '" role="button" title="' . $name . '" aria-label="' . $name . '"><span class="cke_button_icon cke_button__' . str_replace(' ', '', $name) . '_icon">' . $name . '</span></a>';
+    };
+
+    return array(
+      // "basicstyles" plugin.
+      'Bold' => array(
+        'label' => t('Bold'),
+        'image_alternative' => $button('bold'),
+      ),
+      'Italic' => array(
+        'label' => t('Italic'),
+        'image_alternative' => $button('italic'),
+      ),
+      'Underline' => array(
+        'label' => t('Underline'),
+        'image_alternative' => $button('underline'),
+      ),
+      'Strike' => array(
+        'label' => t('Strike-through'),
+        'image_alternative' => $button('strike'),
+      ),
+      'Superscript' => array(
+        'label' => t('Superscript'),
+        'image_alternative' => $button('super script'),
+      ),
+      'Subscript' => array(
+        'label' => t('Subscript'),
+        'image_alternative' => $button('sub script'),
+      ),
+      // "removeformat" plugin.
+      'RemoveFormat' => array(
+        'label' => t('Remove format'),
+        'image_alternative' => $button('remove format'),
+      ),
+      // "justify" plugin.
+      'JustifyLeft' => array(
+        'label' => t('Align left'),
+        'image_alternative' => $button('justify left'),
+      ),
+      'JustifyCenter' => array(
+        'label' => t('Align center'),
+        'image_alternative' => $button('justify center'),
+      ),
+      'JustifyRight' => array(
+        'label' => t('Align right'),
+        'image_alternative' => $button('justify right'),
+      ),
+      'JustifyBlock' => array(
+        'label' => t('Justify'),
+        'image_alternative' => $button('justify block'),
+      ),
+      // "list" plugin.
+      'BulletedList' => array(
+        'label' => t('Bullet list'),
+        'image_alternative' => $button('bulleted list'),
+        'image_alternative_rtl' => $button('bulleted list', 'rtl'),
+      ),
+      'NumberedList' => array(
+        'label' => t('Numbered list'),
+        'image_alternative' => $button('numbered list'),
+        'image_alternative_rtl' => $button('numbered list', 'rtl'),
+      ),
+      // "indent" plugin.
+      'Outdent' => array(
+        'label' => t('Outdent'),
+        'image_alternative' => $button('outdent'),
+        'image_alternative_rtl' => $button('outdent', 'rtl'),
+      ),
+      'Indent' => array(
+        'label' => t('Indent'),
+        'image_alternative' => $button('indent'),
+        'image_alternative_rtl' => $button('indent', 'rtl'),
+      ),
+      // "undo" plugin.
+      'Undo' => array(
+        'label' => t('Undo'),
+        'image_alternative' => $button('undo'),
+        'image_alternative_rtl' => $button('undo', 'rtl'),
+      ),
+      'Redo' => array(
+        'label' => t('Redo'),
+        'image_alternative' => $button('redo'),
+        'image_alternative_rtl' => $button('redo', 'rtl'),
+      ),
+      // "link" plugin.
+      'Link' => array(
+        'label' => t('Link'),
+        'image_alternative' => $button('link'),
+      ),
+      'Unlink' => array(
+        'label' => t('Unlink'),
+        'image_alternative' => $button('unlink'),
+      ),
+      'Anchor' => array(
+        'label' => t('Anchor'),
+        'image_alternative' => $button('anchor'),
+        'image_alternative_rtl' => $button('anchor', 'rtl'),
+      ),
+      // "blockquote" plugin.
+      'Blockquote' => array(
+        'label' => t('Blockquote'),
+        'image_alternative' => $button('blockquote'),
+      ),
+      // "horizontalrule" plugin
+      'HorizontalRule' => array(
+        'label' => t('Horizontal rule'),
+        'image_alternative' => $button('horizontal rule'),
+      ),
+      // "clipboard" plugin.
+      'Cut' => array(
+        'label' => t('Cut'),
+        'image_alternative' => $button('cut'),
+        'image_alternative_rtl' => $button('cut', 'rtl'),
+      ),
+      'Copy' => array(
+        'label' => t('Copy'),
+        'image_alternative' => $button('copy'),
+        'image_alternative_rtl' => $button('copy', 'rtl'),
+      ),
+      'Paste' => array(
+        'label' => t('Paste'),
+        'image_alternative' => $button('paste'),
+        'image_alternative_rtl' => $button('paste', 'rtl'),
+      ),
+      // "pastetext" plugin.
+      'PasteText' => array(
+        'label' => t('Paste Text'),
+        'image_alternative' => $button('paste text'),
+        'image_alternative_rtl' => $button('paste text', 'rtl'),
+      ),
+      // "pastefromword" plugin.
+      'PasteFromWord' => array(
+        'label' => t('Paste from Word'),
+        'image_alternative' => $button('paste from word'),
+        'image_alternative_rtl' => $button('paste from word', 'rtl'),
+      ),
+      // "specialchar" plugin.
+      'SpecialChar' => array(
+        'label' => t('Character map'),
+        'image_alternative' => $button('special char'),
+      ),
+      'Format' => array(
+        'label' => t('HTML block format'),
+        'image_alternative' => '<a href="#" role="button" aria-label="' . t('Format') . '"><span class="ckeditor-button-dropdown">' . t('Format') . '<span class="ckeditor-button-arrow"></span></span></a>',
+      ),
+      // "image" plugin.
+      'Image' => array(
+        'label' => t('Image'),
+        'image_alternative' => $button('image'),
+      ),
+      // "table" plugin.
+      'Table' => array(
+        'label' => t('Table'),
+        'image_alternative' => $button('table'),
+      ),
+      // "showblocks" plugin.
+      'ShowBlocks' => array(
+        'label' => t('Show blocks'),
+        'image_alternative' => $button('show blocks'),
+        'image_alternative_rtl' => $button('show blocks', 'rtl'),
+      ),
+      // "sourcearea" plugin.
+      'Source' => array(
+        'label' => t('Source code'),
+        'image_alternative' => $button('source'),
+      ),
+      // "maximize" plugin.
+      'Maximize' => array(
+        'label' => t('Maximize'),
+        'image_alternative' => $button('maximize'),
+      ),
+      // No plugin, separator "buttons" for toolbar builder UI use only.
+      '|' => array(
+        'label' => t('Group separator'),
+        'image_alternative' => '<a href="#" role="button" aria-label="' . t('Button group separator') . '" class="ckeditor-group-separator"></a>',
+        'attributes' => array('class' => array('ckeditor-group-button-separator')),
+        'multiple' => TRUE,
+      ),
+      '-' => array(
+        'label' => t('Separator'),
+        'image_alternative' => '<a href="#" role="button" aria-label="' . t('Button separator') . '" class="ckeditor-separator"></a>',
+        'attributes' => array('class' => array('ckeditor-button-separator')),
+        'multiple' => TRUE,
+      ),
+    );
+  }
+
+  /**
+   * Builds the "format_tags" configuration part of the CKEditor JS settings.
+   *
+   * @see getConfig()
+   *
+   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+   *   A configured text editor object.
+   * @return array
+   *   An array containing the "format_tags" configuration.
+   */
+  protected function generateFormatTagsSetting(Editor $editor) {
+    // The <p> tag is always allowed — HTML without <p> tags is nonsensical.
+    $format_tags = array('p');
+
+    // Given the list of possible format tags, automatically determine whether
+    // the current text format allows this tag, and thus whether it should show
+    // up in the "Format" dropdown.
+    $possible_format_tags = array('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'pre');
+    foreach ($possible_format_tags as $tag) {
+      $input = '<' . $tag . '>TEST</' . $tag . '>';
+      $output = trim(check_markup($input, $editor->format));
+      if ($input == $output) {
+        $format_tags[] = $tag;
+      }
+    }
+
+    return implode(';', $format_tags);
+  }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php
new file mode 100644
index 0000000..f810cbb
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/ckeditor/plugin/StylesCombo.php
@@ -0,0 +1,159 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor\Plugin\ckeditor\plugin\StylesCombo.
+ */
+
+namespace Drupal\ckeditor\Plugin\ckeditor\plugin;
+
+use Drupal\ckeditor\CKEditorPluginBase;
+use Drupal\ckeditor\CKEditorPluginConfigurableInterface;
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Defines the "stylescombo" plugin.
+ *
+ * @Plugin(
+ *   id = "stylescombo",
+ *   label = @Translation("Styles dropdown"),
+ *   module = "ckeditor"
+ * )
+ */
+class StylesCombo extends CKEditorPluginBase implements CKEditorPluginConfigurableInterface {
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
+   */
+  public function isInternal() {
+    return TRUE;
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+   */
+  public function getFile() {
+    // This plugin is already part of Drupal core's CKEditor build.
+    return FALSE;
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getConfig().
+   */
+  public function getConfig(Editor $editor) {
+    $config = array();
+
+    // Next, add the stylesSet setting, if its button is enabled.
+    $toolbar_buttons = array_unique(NestedArray::mergeDeepArray($editor->settings['toolbar']['buttons']));
+    if (in_array('Styles', $toolbar_buttons)) {
+      $styles = $editor->settings['plugins']['stylescombo']['styles'];
+      $config['stylesSet'] = $this->generateStylesSetSetting($styles);
+    }
+
+    return $config;
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
+   */
+  public function getButtons() {
+    return array(
+      'Styles' => array(
+        'label' => t('Font style'),
+        'image_alternative' => '<a href="#" role="button" aria-label="' . t('Styles') . '"><span class="ckeditor-button-dropdown">' . t('Styles') . '<span class="ckeditor-button-arrow"></span></span></a>',
+      ),
+    );
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginConfigurableInterface::settingsForm().
+   */
+  public function settingsForm(array $form, array &$form_state, Editor $editor) {
+    // Defaults.
+    $config = array('styles' => '');
+    if (isset($editor->settings['plugins']['stylescombo'])) {
+      $config = $editor->settings['plugins']['stylescombo'];
+    }
+
+    $form['styles'] = array(
+      '#title' => t('Styles'),
+      '#title_display' => 'invisible',
+      '#type' => 'textarea',
+      '#default_value' => $config['styles'],
+      '#description' => t('A list of classes that will be provided in the "Styles" dropdown. Enter one class on each line in the format: label|element.class. Example: title|h1.title.<br />These styles should be available in your theme\'s CKEditor stylesheets as well as in your theme\'s main CSS file.'),
+      '#attached' => array(
+        'library' => array(array('ckeditor', 'drupal.ckeditor.stylescombo.admin')),
+      ),
+      '#element_validate' => array(
+        array($this, 'validateStylesValue'),
+      ),
+    );
+
+    return $form;
+  }
+
+  /**
+   * #element_validate handler for the "styles" element in settingsForm().
+   */
+  public function validateStylesValue(array $element, array &$form_state) {
+    if ($this->generateStylesSetSetting($element['#value']) === FALSE) {
+      form_error($element, t('The provided list of styles is syntactically incorrect.'));
+    }
+  }
+
+  /**
+   * Builds the "stylesSet" configuration part of the CKEditor JS settings.
+   *
+   * @see getConfig()
+   *
+   * @param string $styles
+   *   The "styles" setting.
+   * @return array|FALSE
+   *   An array containing the "stylesSet" configuration, or FALSE when the
+   *   syntax is invalid.
+   */
+  protected function generateStylesSetSetting($styles) {
+    $styles_set = array();
+
+    // Early-return when empty.
+    $styles = trim($styles);
+    if (empty($styles)) {
+      return $styles_set;
+    }
+
+    $styles = str_replace(array("\r\n", "\r"), "\n", $styles);
+    foreach (explode("\n", $styles) as $style) {
+      $style = trim($style);
+
+      // Ignore empty lines in between non-empty lines.
+      if (empty($style)) {
+        continue;
+      }
+
+      // Validate syntax: label|element.class[.class...] pattern expected.
+      if (!preg_match('@^.+\\| *[a-zA-Z0-9]+ *(\\.[a-zA-Z0-9_-]+ *)+$@', $style)) {
+        return FALSE;
+      }
+
+      // Parse.
+      list($label, $selector) = explode('|', $style, 2);
+      $classes = explode('.', $selector);
+      $element = array_shift($classes);
+
+      // Build the data structure CKEditor's stylescombo plugin expects.
+      // @see http://docs.cksource.com/CKEditor_3.x/Developers_Guide/Styles
+      $styles_set[] = array(
+        'name' => trim($label),
+        'element' => trim($element),
+        'attributes' => array(
+          'class' => implode(' ', array_map('trim', $classes))
+        ),
+      );
+    }
+    return $styles_set;
+  }
+
+}
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
new file mode 100644
index 0000000..df34b05
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/editor/editor/CKEditor.php
@@ -0,0 +1,201 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor\Plugin\editor\editor\CKEditor.
+ */
+
+namespace Drupal\ckeditor\Plugin\editor\editor;
+
+use Drupal\editor\Plugin\EditorBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Defines a CKEditor-based text editor for Drupal.
+ *
+ * @Plugin(
+ *   id = "ckeditor",
+ *   label = @Translation("CKEditor"),
+ *   module = "ckeditor"
+ * )
+ */
+class CKEditor extends EditorBase {
+
+  /**
+   * Implements \Drupal\editor\Plugin\EditorInterface::getDefaultSettings().
+   */
+  public function getDefaultSettings() {
+    return array(
+      'toolbar' => array(
+        'buttons' => array(
+          array(
+            'Bold', 'Italic',
+            '|', 'Link', 'Unlink',
+            '|', 'BulletedList', 'NumberedList',
+            '|', 'Blockquote', 'Image',
+            '|', 'Source',
+          ),
+        ),
+      ),
+      'plugins' => array(),
+    );
+  }
+
+  /**
+   * Implements \Drupal\editor\Plugin\EditorInterface::settingsForm().
+   */
+  public function settingsForm(array $form, array &$form_state, Editor $editor) {
+    $module_path = drupal_get_path('module', 'ckeditor');
+    $manager = drupal_container()->get('plugin.manager.ckeditor.plugin');
+
+    $form['toolbar'] = array(
+      '#type' => 'container',
+      '#attached' => array(
+        'library' => array(array('ckeditor', 'drupal.ckeditor.admin')),
+        'js' => array(
+          array(
+            'type' => 'setting',
+            'data' => array('ckeditor' => array(
+              'toolbarAdmin' => theme('ckeditor_settings_toolbar', array('editor' => $editor, 'plugins' => $manager->getButtonsPlugins($editor))),
+            )),
+          )
+        ),
+      ),
+      '#attributes' => array('class' => array('ckeditor-toolbar-configuration')),
+    );
+    $form['toolbar']['buttons'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Toolbar buttons'),
+      '#default_value' => json_encode($editor->settings['toolbar']['buttons']),
+      '#attributes' => array('class' => array('ckeditor-toolbar-textarea')),
+    );
+
+    // CKEditor plugin settings, if any.
+    $form['plugin_settings'] = array(
+      '#type' => 'vertical_tabs',
+      '#title' => t('Optional settings'),
+    );
+    $manager->injectPluginSettingsForm($form, $form_state, $editor);
+    if (count(element_children($form['plugins'])) === 0) {
+      unset($form['plugins']);
+      unset($form['plugin_settings']);
+    }
+
+    return $form;
+  }
+
+  /**
+   * Implements \Drupal\editor\Plugin\EditorInterface::settingsFormSubmit().
+   */
+  public function settingsFormSubmit(array $form, array &$form_state) {
+    // Modify the toolbar settings by reference. The values in
+    // $form_state['values']['editor']['settings'] will be saved directly by
+    // editor_form_filter_admin_format_submit().
+    $toolbar_settings = &$form_state['values']['editor']['settings']['toolbar'];
+
+    $toolbar_settings['buttons'] = json_decode($toolbar_settings['buttons'], FALSE);
+
+    // Remove the plugin settings' vertical tabs state; no need to save that.
+    if (isset($form_state['values']['editor']['settings']['plugins'])) {
+      unset($form_state['values']['editor']['settings']['plugin_settings']);
+    }
+  }
+
+  /**
+   * Implements \Drupal\editor\Plugin\EditorInterface::getJSSettings().
+   */
+  public function getJSSettings(Editor $editor) {
+    $language_interface = language(LANGUAGE_TYPE_INTERFACE);
+
+    $settings = array();
+    $manager = drupal_container()->get('plugin.manager.ckeditor.plugin');
+
+    // Get the settings for all enabled plugins, even the internal ones.
+    $enabled_plugins = array_keys($manager->getEnabledPlugins($editor, TRUE));
+    foreach ($enabled_plugins as $plugin_id) {
+      $plugin = $manager->createInstance($plugin_id);
+      $settings += $plugin->getConfig($editor);
+    }
+
+    // Next, set the most fundamental CKEditor settings.
+    $external_plugins = $manager->getEnabledPlugins($editor);
+    $settings += array(
+      'toolbar' => $this->buildToolbarJSSetting($editor),
+      'contentsCss' => $this->buildContentsCssJSSetting($editor),
+      'extraPlugins' => implode(',', array_keys($external_plugins)),
+      'language' => $language_interface->langcode,
+    );
+
+    // Finally, set Drupal-specific CKEditor settings.
+    $settings += array(
+      'drupalExternalPlugins' => array_map('file_create_url', $external_plugins),
+    );
+
+    return $settings;
+  }
+
+  /**
+   * Implements \Drupal\editor\Plugin\EditorInterface::getLibraries().
+   */
+  public function getLibraries(Editor $editor) {
+    return array(
+      array('ckeditor', 'drupal.ckeditor'),
+    );
+  }
+
+  /**
+   * Builds the "toolbar" configuration part of the CKEditor JS settings.
+   *
+   * @see getJSSettings()
+   *
+   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+   *   A configured text editor object.
+   * @return array
+   *   An array containing the "toolbar" configuration.
+   */
+  public function buildToolbarJSSetting(Editor $editor) {
+    $toolbar = array();
+    foreach ($editor->settings['toolbar']['buttons'] as $row_number => $row) {
+      $button_group = array();
+      foreach ($row as $button_name) {
+        // Change the toolbar separators into groups.
+        if ($button_name === '|') {
+          $toolbar[] = $button_group;
+          $button_group = array();
+        }
+        else {
+          $button_group['items'][] = $button_name;
+        }
+      }
+      $toolbar[] = $button_group;
+      $toolbar[] = '/';
+    }
+
+    return $toolbar;
+  }
+
+  /**
+   * Builds the "contentsCss" configuration part of the CKEditor JS settings.
+   *
+   * @see getJSSettings()
+   *
+   * @param \Drupal\editor\Plugin\Core\Entity\Editor $editor
+   *   A configured text editor object.
+   * @return array
+   *   An array containing the "contentsCss" configuration.
+   */
+  public function buildContentsCssJSSetting(Editor $editor) {
+    $css = array(
+      drupal_get_path('module', 'ckeditor') . '/css/ckeditor.css',
+      drupal_get_path('module', 'ckeditor') . '/css/ckeditor-iframe.css',
+    );
+    $css = array_merge($css, _ckeditor_theme_css());
+    drupal_alter('ckeditor_css', $css, $editor);
+    $css = array_map('file_create_url', $css);
+
+    return array_values($css);
+  }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorAdminTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorAdminTest.php
new file mode 100644
index 0000000..fa0adc9
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorAdminTest.php
@@ -0,0 +1,142 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\ckeditor\Tests\CKEditorAdminTest.
+ */
+
+namespace Drupal\ckeditor\Tests;
+
+use Drupal\editor\Plugin\Core\Entity\Editor;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests administration of CKEditor.
+ */
+class CKEditorAdminTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('filter', 'editor', 'ckeditor');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'CKEditor administration',
+      'description' => 'Tests administration of CKEditor.',
+      'group' => 'CKEditor',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    // Create text format.
+    $filtered_html_format = entity_create('filter_format', array(
+      'format' => 'filtered_html',
+      'name' => 'Filtered HTML',
+      'weight' => 0,
+      'filters' => array(),
+    ));
+    $filtered_html_format->save();
+
+    // Create admin user.
+    $this->admin_user = $this->drupalCreateUser(array('administer filters'));
+  }
+
+  function testAdmin() {
+    $manager = drupal_container()->get('plugin.manager.editor');
+    $ckeditor = $manager->createInstance('ckeditor');
+
+    $this->drupalLogin($this->admin_user);
+    $this->drupalGet('admin/config/content/formats/filtered_html');
+
+    // Ensure no Editor config entity exists yet.
+    $editor = entity_load('editor', 'filtered_html');
+    $this->assertFalse($editor, 'No Editor config entity exists yet.');
+
+    // Verify the "Text Editor" <select> when a text editor is available.
+    $select = $this->xpath('//select[@name="editor[editor]"]');
+    $select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
+    $options = $this->xpath('//select[@name="editor[editor]"]/option');
+    $this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
+    $this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
+    $this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
+    $this->assertTrue(((string) $options[0]) === 'None', 'Option 1 in the Text Editor select is "None".');
+    $this->assertTrue(((string) $options[1]) === 'CKEditor', 'Option 2 in the Text Editor select is "CKEditor".');
+    $this->assertTrue(((string) $options[0]['selected']) === 'selected', 'Option 1 ("None") is selected.');
+
+    // Select the "CKEditor" editor and click the "Configure" button.
+    $edit = array(
+      'editor[editor]' => 'ckeditor',
+    );
+    // $this->drupalPost(NULL, $edit, t('Configure'));
+    $this->drupalPostAjax(NULL, $edit, 'editor_configure');
+    $editor = entity_load('editor', 'filtered_html');
+    $this->assertFalse($editor, 'No Editor config entity exists yet.');
+
+    // Default configuration.
+    $default_buttons_value = '[["Source","|","Bold","Italic","|","NumberedList","BulletedList","Blockquote","|","JustifyLeft","JustifyCenter","JustifyRight","|","Link","Unlink","|","Image","Maximize"]]';
+    $this->assertFieldByName('editor[settings][toolbar][buttons]', $default_buttons_value, 'Buttons configuration');
+    $styles_textarea = $this->xpath('//textarea[@name="editor[settings][plugins][stylescombo][styles]"]');
+    $this->assertFieldByXPath('//textarea[@name="editor[settings][plugins][stylescombo][styles]"]', '', 'The styles textarea exists and is empty.');
+    $this->assertTrue(count($styles_textarea) === 1, 'The "styles" textarea exists.');
+    $this->drupalPost(NULL, $edit, t('Save configuration'));
+
+    // Ensure an Editor object exists now, with the proper settings.
+    $expected_settings = $ckeditor->getDefaultSettings();
+    $expected_settings['plugins']['stylescombo']['styles'] = '';
+    $editor = entity_load('editor', 'filtered_html');
+    $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists now.');
+    $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.');
+
+    // Configure a plugin.
+    $edit = array(
+      'editor[settings][plugins][stylescombo][styles]' => "Title|h1.title\nCallout|p.callout\n\n",
+    );
+    $this->drupalPost(NULL, $edit, t('Save configuration'));
+    $expected_settings['plugins']['stylescombo']['styles'] = "Title|h1.title\nCallout|p.callout\n\n";
+    $editor = entity_load('editor', 'filtered_html');
+    $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
+    $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.');
+
+    // "Drag and drop" buttons: remove one button, add a separator, add a button
+    // and finally, add a button to a new row.
+    $edit = array(
+      'editor[settings][toolbar][buttons]' => '[["Source","|","Bold","Italic","|","NumberedList","BulletedList","Blockquote","|","JustifyLeft","JustifyCenter","JustifyRight","|","Link","Unlink","|","Image","|","Undo"],["Redo"]]'
+    );
+    $this->drupalPost(NULL, $edit, t('Save configuration'));
+    $expected_settings['toolbar']['buttons'][0][17] = '|';
+    $expected_settings['toolbar']['buttons'][0][18] = 'Undo';
+    $expected_settings['toolbar']['buttons'][1][] = 'Redo';
+    $editor = entity_load('editor', 'filtered_html');
+    $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
+    $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.');
+
+    // Now enable the ckeditor_test module, which provides one configurable
+    // CKEditor plugin — this should not affect the Editor config entity.
+    module_enable(array('ckeditor_test'));
+    drupal_container()->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
+    $this->drupalGet('admin/config/content/formats/filtered_html');
+    $ultra_llama_mode_checkbox = $this->xpath('//input[@type="checkbox" and @name="editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]" and not(@checked)]');
+    $this->assertTrue(count($ultra_llama_mode_checkbox) === 1, 'The "Ultra llama mode" checkbox exists and is not checked.');
+    $editor = entity_load('editor', 'filtered_html');
+    $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
+    $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.');
+
+    // Finally, check the "Ultra llama mode" checkbox.
+    $edit = array(
+      'editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]' => '1',
+    );
+    $this->drupalPost(NULL, $edit, t('Save configuration'));
+    $ultra_llama_mode_checkbox = $this->xpath('//input[@type="checkbox" and @name="editor[settings][plugins][llama_contextual_and_button][ultra_llama_mode]" and @checked="checked"]');
+    $this->assertTrue(count($ultra_llama_mode_checkbox) === 1, 'The "Ultra llama mode" checkbox exists and is checked.');
+    $expected_settings['plugins']['llama_contextual_and_button']['ultra_llama_mode'] = '1';
+    $editor = entity_load('editor', 'filtered_html');
+    $this->assertTrue($editor instanceof Editor, 'An Editor config entity exists.');
+    $this->assertIdentical($expected_settings, $editor->settings, 'The Editor config entity has the correct settings.');
+  }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php
new file mode 100644
index 0000000..6ba92e4
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php
@@ -0,0 +1,149 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\ckeditor\Tests\CKEditorLoadingTest.
+ */
+
+namespace Drupal\ckeditor\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests loading of CKEditor.
+ */
+class CKEditorLoadingTest extends WebTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('filter', 'editor', 'ckeditor', 'node');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'CKEditor loading',
+      'description' => 'Tests loading of CKEditor.',
+      'group' => 'CKEditor',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    // Create text format, associate CKEditor.
+    $filtered_html_format = entity_create('filter_format', array(
+      'format' => 'filtered_html',
+      'name' => 'Filtered HTML',
+      'weight' => 0,
+      'filters' => array(),
+    ));
+    $filtered_html_format->save();
+    $editor = entity_create('editor', array(
+      'format' => 'filtered_html',
+      'editor' => 'ckeditor',
+    ));
+    $editor->save();
+
+    // Create node type.
+    $this->drupalCreateContentType(array(
+      'type' => 'article',
+      'name' => 'Article',
+    ));
+
+    // Create 2 users, each with access to different text formats:
+    //   - "untrusted": plain_text
+    //   - "normal": plain_text, filtered_html
+    $this->untrusted_user = $this->drupalCreateUser(array('create article content', 'edit any article content'));
+    $this->normal_user = $this->drupalCreateUser(array('create article content', 'edit any article content', 'use text format filtered_html'));
+  }
+
+  /**
+   * Tests loading of CKEditor CSS, JS and JS settings.
+   */
+  function testLoading() {
+    // The untrusted user:
+    // - has access to 1 text format (plain_text);
+    // - doesn't have access to the filtered_html text format, so: no text editor.
+    $this->drupalLogin($this->untrusted_user);
+    $this->drupalGet('node/add/article');
+    list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
+    $this->assertFalse($editor_settings_present, 'No Text Editor module settings.');
+    $this->assertFalse($editor_js_present, 'No Text Editor JavaScript.');
+    $this->assertTrue(count($body) === 1, 'A body field exists.');
+    $this->assertTrue(count($format_selector) === 0, 'No text format selector exists on the page.');
+    $hidden_input = $this->xpath('//input[@type="hidden" and contains(@class, "editor")]');
+    $this->assertTrue(count($hidden_input) === 0, 'A single text format hidden input does not exist on the page.');
+    $this->assertRaw(drupal_get_path('module', 'ckeditor') . '/css/ckeditor.css', 'CKEditor CSS is present, even if no text editor is available for the user');
+    $this->assertNoRaw(drupal_get_path('module', 'ckeditor') . '/js/ckeditor.js', 'CKEditor glue JS is absent.');
+
+    // Even on pages where there would never be a text editor, CKEditor CSS
+    // should be present, because it is blindly attached in hook_page_build().
+    $this->drupalGet('user');
+    $this->assertRaw(drupal_get_path('module', 'ckeditor') . '/css/ckeditor.css', 'CKEditor CSS is present, even if no text editor is available for the user.');
+    $this->assertNoRaw(drupal_get_path('module', 'ckeditor') . '/js/ckeditor.js', 'CKEditor glue JS is absent.');
+
+    // The normal user:
+    // - has access to 2 text formats;
+    // - does have access to the filtered_html text format, so: CKEditor.
+    $this->drupalLogin($this->normal_user);
+    $this->drupalGet('node/add/article');
+    list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
+    $ckeditor_plugin = drupal_container()->get('plugin.manager.editor')->createInstance('ckeditor');
+    $editor = entity_load('editor', 'filtered_html');
+    $expected = array('formats' => array('filtered_html' => array(
+      'editor' => 'ckeditor',
+      'editorSettings' => $ckeditor_plugin->getJSSettings($editor),
+    )));
+    $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
+    $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
+    $this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
+    $this->assertTrue(count($body) === 1, 'A body field exists.');
+    $this->assertTrue(count($format_selector) === 1, 'A single text format selector exists on the page.');
+    $specific_format_selector = $this->xpath('//select[contains(@class, "filter-list") and contains(@class, "editor") and @data-editor-for="edit-body-und-0-value"]');
+    $this->assertTrue(count($specific_format_selector) === 1, 'A single text format selector exists on the page and has the "editor" class and a "data-editor-for" attribute with the correct value.');
+    $this->assertTrue(isset($settings['ajaxPageState']['css']['ckeditor.css']), 'CKEditor CSS is present.');
+    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/ckeditor/js/ckeditor.js']), 'CKEditor glue JS is present.');
+    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/misc/ckeditor/ckeditor.js']), 'CKEditor lib JS is present.');
+
+    // Enable the ckeditor_test module, customize configuration. In this case,
+    // there is additional CSS and JS to be loaded.
+    // NOTE: the tests in CKEditorTest already ensure that changing the
+    // configuration also results in modified CKEditor configuration, so we
+    // don't test that here.
+    module_enable(array('ckeditor_test'));
+    drupal_container()->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
+    $editor->settings['toolbar']['buttons'][0][] = 'Llama';
+    $editor->settings['plugins']['internal']['link_shortcut'] = 'CTRL+K';
+    $editor->save();
+    $this->drupalGet('node/add/article');
+    list($settings, $editor_settings_present, $editor_js_present, $body, $format_selector) = $this->getThingsToCheck();
+    $expected = array('formats' => array('filtered_html' => array(
+      'editor' => 'ckeditor',
+      'editorSettings' => $ckeditor_plugin->getJSSettings($editor),
+    )));
+    $this->assertTrue($editor_settings_present, "Text Editor module's JavaScript settings are on the page.");
+    $this->assertIdentical($expected, $settings['editor'], "Text Editor module's JavaScript settings on the page are correct.");
+    $this->assertTrue($editor_js_present, 'Text Editor JavaScript is present.');
+    $this->assertTrue(isset($settings['ajaxPageState']['css']['ckeditor.css']), 'CKEditor CSS is present.');
+    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/modules/ckeditor/js/ckeditor.js']), 'CKEditor glue JS is present.');
+    $this->assertTrue(isset($settings['ajaxPageState']['js']['core/misc/ckeditor/ckeditor.js']), 'CKEditor lib JS is present.');
+  }
+
+  protected function getThingsToCheck() {
+    $settings = $this->drupalGetSettings();
+    return array(
+      // JavaScript settings.
+      $settings,
+      // Editor.module's JS settings present.
+      isset($settings['editor']),
+      // Editor.module's JS present.
+      isset($settings['ajaxPageState']['js']['core/modules/editor/js/editor.js']),
+      // Body field.
+      $this->xpath('//textarea[@id="edit-body-und-0-value"]'),
+      // Format selector.
+      $this->xpath('//select[contains(@class, "filter-list")]'),
+    );
+  }
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php
new file mode 100644
index 0000000..c3e8828
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorPluginManagerTest.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\ckeditor\Tests\CKEditorPluginManagerTest.
+ */
+
+namespace Drupal\ckeditor\Tests;
+
+use Drupal\simpletest\DrupalUnitTestBase;
+use Drupal\ckeditor\CKEditorPluginManager;
+
+/**
+ * Tests for the "CKEditor plugins" plugin manager.
+ */
+class CKEditorPluginManagerTest extends DrupalUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('system', 'editor', 'ckeditor');
+
+  /**
+   * The manager for "CKEditor plugin" plugins.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $manager;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'CKEditor plugin manager',
+      'description' => 'Tests different ways of enabling CKEditor plugins.',
+      'group' => 'CKEditor',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    // Install the Filter module.
+    $this->installSchema('system', 'url_alias');
+    $this->enableModules(array('user', 'filter'));
+
+    // Create text format, associate CKEditor.
+    $filtered_html_format = entity_create('filter_format', array(
+      'format' => 'filtered_html',
+      'name' => 'Filtered HTML',
+      'weight' => 0,
+      'filters' => array(),
+    ));
+    $filtered_html_format->save();
+    $editor = entity_create('editor', array(
+      'format' => 'filtered_html',
+      'editor' => 'ckeditor',
+    ));
+    $editor->save();
+  }
+
+  /**
+   * Tests the enabling of plugins.
+   */
+  function testEnabledPlugins() {
+    $this->manager = new CKEditorPluginManager();
+    $editor = entity_load('editor', 'filtered_html');
+
+    // Case 1: no CKEditor plugins.
+    $this->assertIdentical(array('internal', 'stylescombo'), array_keys($this->manager->getDefinitions()), 'No CKEditor plugins found besides the built-in ones.');
+    $this->assertIdentical(array(), $this->manager->getEnabledPlugins($editor), 'Only built-in plugins are enabled.');
+    $this->assertIdentical(array('internal' => NULL), $this->manager->getEnabledPlugins($editor, TRUE), 'Only the "internal" plugin is enabled.');
+
+    // Enable the CKEditor Test module, which has the Llama plugin (plus three
+    // variations of it, to cover all possible ways a plugin can be enabled) and
+    // clear the editor manager's cache so it is picked up.
+    $this->enableModules(array('ckeditor_test'));
+    $this->manager->clearCachedDefinitions();
+
+    // Case 2: CKEditor plugins are available.
+    $plugin_ids = array_keys($this->manager->getDefinitions());
+    sort($plugin_ids);
+    $this->assertIdentical(array('internal', 'llama', 'llama_button', 'llama_contextual', 'llama_contextual_and_button', 'stylescombo'), $plugin_ids, 'Additional CKEditor plugins found.');
+    $this->assertIdentical(array(), $this->manager->getEnabledPlugins($editor), 'Only the internal plugins are enabled.');
+    $this->assertIdentical(array('internal' => NULL), $this->manager->getEnabledPlugins($editor, TRUE), 'Only the "internal" plugin is enabled.');
+
+    // Case 3: enable each of the newly available plugins, if possible:
+    // a. Llama: cannot be enabled, since it does not implement
+    //    CKEditorPluginContextualInterface nor CKEditorPluginButtonsInterface.
+    // b. LlamaContextual: enabled by adding the 'Underline' button, which is
+    //    part of another plugin!
+    // c. LlamaButton: automatically enabled by adding its 'Llama' button.
+    // d. LlamaContextualAndButton: enabled by either b or c.
+    // Below, we will first enable the "Llama" button, which will cause the
+    // LlamaButton and LlamaContextualAndButton plugins to be enabled. Then we
+    // will remove the "Llama" button and add the "Underline" button, which will
+    // cause the LlamaContextual and LlamaContextualAndButton plugins to be
+    // enabled. Finally, we will add the "Underline" button back again, which
+    // would cause all three plugins to be enabled.
+    $original_toolbar = $editor->settings['toolbar']['buttons'][0];
+    $editor->settings['toolbar']['buttons'][0][] = 'Llama';
+    $editor->save();
+    $file = array();
+    $file['b'] = 'core/modules/ckeditor/tests/modules/js/llama_button.js';
+    $file['c'] = 'core/modules/ckeditor/tests/modules/js/llama_contextual.js';
+    $file['cb'] = 'core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js';
+    $expected = array('llama_button' => $file['b'], 'llama_contextual_and_button' => $file['cb']);
+    $this->assertIdentical($expected, $this->manager->getEnabledPlugins($editor), 'The LlamaButton and LlamaContextualAndButton plugins are enabled.');
+    $this->assertIdentical(array('internal' => NULL) + $expected, $this->manager->getEnabledPlugins($editor, TRUE), 'The LlamaButton and LlamaContextualAndButton plugins are enabled.');
+    $editor->settings['toolbar']['buttons'][0] = $original_toolbar;
+    $editor->settings['toolbar']['buttons'][0][] = 'Underline';
+    $editor->save();
+    $expected = array('llama_contextual' => $file['c'], 'llama_contextual_and_button' => $file['cb']);
+    $this->assertIdentical($expected, $this->manager->getEnabledPlugins($editor), 'The  LLamaContextual and LlamaContextualAndButton plugins are enabled.');
+    $this->assertIdentical(array('internal' => NULL) + $expected, $this->manager->getEnabledPlugins($editor, TRUE), 'The LlamaContextual and LlamaContextualAndButton plugins are enabled.');
+    $editor->settings['toolbar']['buttons'][0][] = 'Llama';
+    $editor->save();
+    $expected = array('llama_button' => $file['b'], 'llama_contextual' => $file['c'], 'llama_contextual_and_button' => $file['cb']);
+    $this->assertIdentical($expected, $this->manager->getEnabledPlugins($editor), 'The LlamaButton, LlamaContextual and LlamaContextualAndButton plugins are enabled.');
+    $this->assertIdentical(array('internal' => NULL) + $expected, $this->manager->getEnabledPlugins($editor, TRUE), 'The LLamaButton, LlamaContextual and LlamaContextualAndButton plugins are enabled.');
+  }
+
+}
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php
new file mode 100644
index 0000000..6fd31cc
--- /dev/null
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorTest.php
@@ -0,0 +1,261 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\ckeditor\Tests\CKEditorTest.
+ */
+
+namespace Drupal\ckeditor\Tests;
+
+use Drupal\simpletest\DrupalUnitTestBase;
+use Drupal\editor\Plugin\EditorManager;
+use Drupal\ckeditor\Plugin\editor\editor\CKEditor;
+
+/**
+ * Tests for the 'CKEditor' text editor plugin.
+ */
+class CKEditorTest extends DrupalUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('system', 'editor', 'ckeditor');
+
+  /**
+   * An instance of the "CKEditor" text editor plugin.
+   *
+   * @var \Drupal\ckeditor\Plugin\editor\editor\CKEditor;
+   */
+  protected $ckeditor;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'CKEditor text editor plugin',
+      'description' => 'Tests all aspects of the CKEditor text editor plugin.',
+      'group' => 'CKEditor',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    // Install the Filter module.
+    $this->installSchema('system', 'url_alias');
+    $this->enableModules(array('user', 'filter'));
+
+    // Create text format, associate CKEditor.
+    $filtered_html_format = entity_create('filter_format', array(
+      'format' => 'filtered_html',
+      'name' => 'Filtered HTML',
+      'weight' => 0,
+      'filters' => array(
+        'filter_html' => array(
+          'status' => 1,
+        ),
+      ),
+    ));
+    $filtered_html_format->save();
+    $editor = entity_create('editor', array(
+      'format' => 'filtered_html',
+      'editor' => 'ckeditor',
+    ));
+    $editor->save();
+
+    // Create "CKEditor" text editor plugin instance.
+    $manager = new EditorManager();
+    $this->ckeditor = $manager->createInstance('ckeditor');
+  }
+
+  /**
+   * Tests CKEditor::getJSSettings().
+   */
+  function testGetJSSettings() {
+    $editor = entity_load('editor', 'filtered_html');
+
+    // Default toolbar.
+    $expected_config = $this->getDefaultInternalConfig() + array(
+      'toolbar' => $this->getDefaultToolbarConfig(),
+      'contentsCss' => $this->getDefaultContentsCssConfig(),
+      'extraPlugins' => '',
+      'language' => 'en',
+      'drupalExternalPlugins' => array(),
+    );
+    $this->assertIdentical($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for default configuration.');
+
+    // Customize the configuration: add button, have two contextually enabled
+    // buttons, and configure a CKEditor plugin setting.
+    $this->enableModules(array('ckeditor_test'));
+    drupal_container()->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
+    $editor->settings['toolbar']['buttons'][0][] = 'Underline';
+    $editor->settings['toolbar']['buttons'][1][] = 'Format';
+    $editor->settings['plugins']['internal']['link_shortcut'] = 'CTRL+K';
+    $editor->save();
+    $expected_config['toolbar'][5]['items'][] = 'Underline';
+    $expected_config['toolbar'][7]['items'][] = 'Format';
+    $expected_config['toolbar'][8] = '/';
+    $expected_config['format_tags'] = 'p;h4;h5;h6';
+    $expected_config['extraPlugins'] = 'llama_contextual,llama_contextual_and_button';
+    $expected_config['drupalExternalPlugins']['llama_contextual'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual.js');
+    $expected_config['drupalExternalPlugins']['llama_contextual_and_button'] = file_create_url('core/modules/ckeditor/tests/modules/js/llama_contextual_and_button.js');
+    $expected_config['contentsCss'][] = file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css');
+    $expected_config['keystrokes'] = array(array(1114187, 'link'), array(1114188, NULL));
+    $this->assertEqual($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
+
+    // Change the allowed HTML tags; the "format_tags" setting for CKEditor
+    // should automatically be updated as well.
+    $format = entity_load('filter_format', 'filtered_html');
+    $format->filters['filter_html']['settings']['allowed_html'] .= '<pre> <h3>';
+    $format->save();
+    $expected_config['format_tags'] = 'p;h3;h4;h5;h6;pre';
+    $this->assertEqual($expected_config, $this->ckeditor->getJSSettings($editor), 'Generated JS settings are correct for customized configuration.');
+  }
+
+  /**
+   * Tests CKEditor::buildToolbarJSSetting().
+   */
+  function testBuildToolbarJSSetting() {
+    $editor = entity_load('editor', 'filtered_html');
+
+    // Default toolbar.
+    $expected = $this->getDefaultToolbarConfig();
+    $this->assertIdentical($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for default toolbar.');
+
+    // Customize the configuration.
+    $editor->settings['toolbar']['buttons'][0][] = 'Underline';
+    $editor->save();
+    $expected[5]['items'][] = 'Underline';
+    $this->assertIdentical($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for customized toolbar.');
+
+    // Enable the editor_test module, customize further.
+    $this->enableModules(array('ckeditor_test'));
+    drupal_container()->get('plugin.manager.ckeditor.plugin')->clearCachedDefinitions();
+    $editor->settings['toolbar']['buttons'][0][] = 'Llama';
+    $editor->save();
+    $expected[5]['items'][] = 'Llama';
+    $this->assertIdentical($expected, $this->ckeditor->buildToolbarJSSetting($editor), '"toolbar" configuration part of JS settings built correctly for customized toolbar with contrib module-provided CKEditor plugin.');
+  }
+
+  /**
+   * Tests CKEditor::buildContentsCssJSSetting().
+   */
+  function testBuildContentsCssJSSetting() {
+    $editor = entity_load('editor', 'filtered_html');
+
+    // Default toolbar.
+    $expected = $this->getDefaultContentsCssConfig();
+    $this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly for default toolbar.');
+
+    // Enable the editor_test module, which implements hook_ckeditor_css_alter().
+    $this->enableModules(array('ckeditor_test'));
+    $expected[] = file_create_url('core/modules/ckeditor/tests/modules/ckeditor_test.css');
+    $this->assertIdentical($expected, $this->ckeditor->buildContentsCssJSSetting($editor), '"contentsCss" configuration part of JS settings built correctly while a hook_ckeditor_css_alter() implementation exists.');
+
+    // @todo test coverage for _ckeditor_theme_css(), by including a custom theme in this test with a "ckeditor_stylesheets" entry in its .info file.
+  }
+
+  /**
+   * Tests Internal::getConfig().
+   */
+  function testInternalGetConfig() {
+    $editor = entity_load('editor', 'filtered_html');
+    $manager = drupal_container()->get('plugin.manager.ckeditor.plugin');
+    $internal_plugin = $manager->createInstance('internal');
+
+    // Default toolbar.
+    $expected = $this->getDefaultInternalConfig();
+    $this->assertIdentical($expected, $internal_plugin->getConfig($editor), '"Internal" plugin configuration built correctly for default toolbar.');
+
+    // Format dropdown/button enabled: new setting should be present.
+    $editor->settings['toolbar']['buttons'][0][] = 'Format';
+    $expected['format_tags'] = 'p;h4;h5;h6';
+    $this->assertIdentical($expected, $internal_plugin->getConfig($editor), '"Internal" plugin configuration built correctly for customized toolbar.');
+  }
+
+  /**
+   * Tests StylesCombo::getConfig().
+   */
+  function testStylesComboGetConfig() {
+    $editor = entity_load('editor', 'filtered_html');
+    $manager = drupal_container()->get('plugin.manager.ckeditor.plugin');
+    $stylescombo_plugin = $manager->createInstance('stylescombo');
+
+    // Default toolbar.
+    $expected = $this->getDefaultStylesComboConfig();
+    $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for default toolbar.');
+
+    // Styles dropdown/button enabled: new setting should be present.
+    $editor->settings['toolbar']['buttons'][0][] = 'Styles';
+    $editor->settings['plugins']['stylescombo']['styles'] = '';
+    $editor->save();
+    $expected['stylesSet'] = array();
+    $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
+
+    // Configure the optional "styles" setting in odd ways that shouldn't affect
+    // the end result.
+    $editor->settings['plugins']['stylescombo']['styles'] = "   \n";
+    $editor->save();
+    $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor));
+    $editor->settings['plugins']['stylescombo']['styles'] = "\r\n  \n  \r  \n ";
+    $editor->save();
+    $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
+
+    // Now configure it properly, the end result should change.
+    $editor->settings['plugins']['stylescombo']['styles'] = "Title|h1.title\nCallout|p.mAgical.Callout";
+    $editor->save();
+    $expected['stylesSet'] = array(
+      array('name' => 'Title', 'element' => 'h1', 'attributes' => array('class' => 'title')),
+      array('name' => 'Callout', 'element' => 'p', 'attributes' => array('class' => 'mAgical Callout')),
+    );
+    $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
+
+    // Same configuration, but now interspersed with nonsense. Should yield the
+    // same result.
+    $editor->settings['plugins']['stylescombo']['styles'] = "Title  |   h1 .title\r \n\r  \nCallout|p.mAgical  .Callout\r";
+    $editor->save();
+    $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
+
+    // Invalid syntax should cause stylesSet to be set to FALSE.
+    $editor->settings['plugins']['stylescombo']['styles'] = "Title|h1";
+    $editor->save();
+    $expected['stylesSet'] = FALSE;
+    $this->assertIdentical($expected, $stylescombo_plugin->getConfig($editor), '"StylesCombo" plugin configuration built correctly for customized toolbar.');
+  }
+
+  protected function getDefaultInternalConfig() {
+    return array(
+      'pasteFromWordPromptCleanup' => TRUE,
+      'indentClasses' => array('indent1', 'indent2', 'indent3'),
+      'justifyClasses' => array('align-left', 'align-center', 'align-right', 'align-justify'),
+      'coreStyles_underline' => array('element' => 'span', 'attributes' => array('class' => 'underline')),
+      'removeDialogTabs' => 'image:Link;image:advanced;link:advanced',
+      'resize_dir' => 'vertical',
+      'keystrokes' =>  array(array(0x110000 + 75, 'link'), array(0x110000 + 76, NULL)),
+    );
+  }
+
+  protected function getDefaultStylesComboConfig() {
+    return array();
+  }
+
+  protected function getDefaultToolbarConfig() {
+    return array(
+      0 => array('items' => array('Source')),
+      1 => array('items' => array('Bold', 'Italic')),
+      2 => array('items' => array('NumberedList', 'BulletedList', 'Blockquote')),
+      3 => array('items' => array('JustifyLeft', 'JustifyCenter', 'JustifyRight')),
+      4 => array('items' => array('Link', 'Unlink')),
+      5 => array('items' => array('Image', 'Maximize')),
+      6 => '/'
+    );
+  }
+
+  protected function getDefaultContentsCssConfig() {
+    return array(
+      file_create_url('core/modules/ckeditor/css/ckeditor.css'),
+      file_create_url('core/modules/ckeditor/css/ckeditor-iframe.css')
+    );
+  }
+
+}
diff --git a/core/modules/ckeditor/tests/modules/ckeditor_test.info b/core/modules/ckeditor/tests/modules/ckeditor_test.info
new file mode 100644
index 0000000..4fad10f
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/ckeditor_test.info
@@ -0,0 +1,6 @@
+name = CKEditor test
+description = Support module for the CKEditor module tests.
+core = 8.x
+package = Testing
+version = VERSION
+hidden = TRUE
diff --git a/core/modules/ckeditor/tests/modules/ckeditor_test.module b/core/modules/ckeditor/tests/modules/ckeditor_test.module
new file mode 100644
index 0000000..e97c019
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/ckeditor_test.module
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Helper module for the CKEditor tests.
+ */
+
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Implements hook_ckeditor_css_alter().
+ */
+function ckeditor_test_ckeditor_css_alter(array &$css, Editor $editor) {
+  $css[] = drupal_get_path('module', 'ckeditor_test') . '/ckeditor_test.css';
+}
diff --git a/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/Llama.php b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/Llama.php
new file mode 100644
index 0000000..d2e0732
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/Llama.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor_test\Plugin\ckeditor\plugin\Llama.
+ */
+
+namespace Drupal\ckeditor_test\Plugin\ckeditor\plugin;
+
+use Drupal\ckeditor\CKEditorPluginInterface;
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Defines the "Llama" plugin, with a CKEditor "llama" feature.
+ *
+ * This feature does not correspond to a toolbar button. Because this plugin
+ * does not implement the CKEditorPluginContextualInterface nor the
+ * CKEditorPluginButtonsInterface interface, there is no way of actually loading
+ * this plugin.
+ *
+ * @see MetaContextual
+ * @see MetaButton
+ * @see MetaContextualAndButton
+ *
+ * @Plugin(
+ *   id = "llama",
+ *   label = @Translation("Llama"),
+ *   module = "ckeditor_test"
+ * )
+ */
+class Llama extends PluginBase implements CKEditorPluginInterface {
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::isInternal().
+   */
+  function isInternal() {
+    return FALSE;
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+   */
+  function getFile() {
+    return drupal_get_path('module', 'ckeditor_test') . '/js/llama.js';
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getButtons().
+   */
+  public function getConfig(Editor $editor) {
+    return array();
+  }
+
+}
diff --git a/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaButton.php b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaButton.php
new file mode 100644
index 0000000..0bb10ce
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaButton.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor_test\Plugin\ckeditor\plugin\LlamaButton.
+ */
+
+namespace Drupal\ckeditor_test\Plugin\ckeditor\plugin;
+
+use Drupal\ckeditor\CKEditorPluginButtonsInterface;
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Defines a "LlamaButton" plugin, with a toolbar builder-enabled "llama" feature.
+ *
+ * @Plugin(
+ *   id = "llama_button",
+ *   label = @Translation("Llama Button"),
+ *   module = "ckeditor_test"
+ * )
+ */
+class LlamaButton extends Llama implements CKEditorPluginButtonsInterface {
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
+   */
+  function getButtons() {
+    return array(
+      'Llama' => array(
+        'label' => t('Insert Lllama'),
+      ),
+    );
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+   */
+  function getFile() {
+    return drupal_get_path('module', 'ckeditor_test') . '/js/llama_button.js';
+  }
+
+}
diff --git a/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextual.php b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextual.php
new file mode 100644
index 0000000..fb681a8
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextual.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor_test\Plugin\ckeditor\plugin\LlamaContextual.
+ */
+
+namespace Drupal\ckeditor_test\Plugin\ckeditor\plugin;
+
+use Drupal\ckeditor\CKEditorPluginContextualInterface;
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Defines a "Llama" plugin, with a contextually enabled "llama" feature.
+ *
+ * @Plugin(
+ *   id = "llama_contextual",
+ *   label = @Translation("Contextual Llama"),
+ *   module = "ckeditor_test"
+ * )
+ */
+class LlamaContextual extends Llama implements CKEditorPluginContextualInterface {
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginContextualInterface::isEnabled().
+   */
+  function isEnabled(Editor $editor) {
+    // Automatically enable this plugin if the Underline button is enabled.
+    foreach ($editor->settings['toolbar']['buttons'] as $row) {
+      if (in_array('Underline', $row)) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+   */
+  function getFile() {
+    return drupal_get_path('module', 'ckeditor_test') . '/js/llama_contextual.js';
+  }
+
+}
diff --git a/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextualAndButton.php b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextualAndButton.php
new file mode 100644
index 0000000..a0d55ec
--- /dev/null
+++ b/core/modules/ckeditor/tests/modules/lib/Drupal/ckeditor_test/Plugin/ckeditor/plugin/LlamaContextualAndButton.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\ckeditor_test\Plugin\ckeditor\plugin\LlamaContextualAndButton.
+ */
+
+namespace Drupal\ckeditor_test\Plugin\ckeditor\plugin;
+
+use Drupal\ckeditor\CKEditorPluginButtonsInterface;
+use Drupal\ckeditor\CKEditorPluginContextualInterface;
+use Drupal\ckeditor\CKEditorPluginConfigurableInterface;
+use Drupal\Component\Plugin\PluginBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\editor\Plugin\Core\Entity\Editor;
+
+/**
+ * Defines a "LlamaContextualAndbutton" plugin, with a contextually OR toolbar
+ * builder-enabled "llama" feature.
+ *
+ * @Plugin(
+ *   id = "llama_contextual_and_button",
+ *   label = @Translation("Contextual Llama With Button"),
+ *   module = "ckeditor_test"
+ * )
+ */
+class LlamaContextualAndButton extends Llama implements CKEditorPluginContextualInterface, CKEditorPluginButtonsInterface, CKEditorPluginConfigurableInterface {
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginContextualInterface::isEnabled().
+   */
+  function isEnabled(Editor $editor) {
+    // Automatically enable this plugin if the Underline button is enabled.
+    foreach ($editor->settings['toolbar']['buttons'] as $row) {
+      if (in_array('Underline', $row)) {
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginButtonsInterface::getButtons().
+   */
+  function getButtons() {
+    return array(
+      'Llama' => array(
+        'label' => t('Insert Llama'),
+      ),
+    );
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginInterface::getFile().
+   */
+  function getFile() {
+    return drupal_get_path('module', 'ckeditor_test') . '/js/llama_contextual_and_button.js';
+  }
+
+  /**
+   * Implements \Drupal\ckeditor\Plugin\CKEditorPluginConfigurableInterface::settingsForm().
+   */
+  function settingsForm(array $form, array &$form_state, Editor $editor) {
+    // Defaults.
+    $config = array('ultra_llama_mode' => FALSE);
+    if (isset($editor->settings['plugins']['llama_contextual_and_button'])) {
+      $config = $editor->settings['plugins']['llama_contextual_and_button'];
+    }
+
+    $form['ultra_llama_mode'] = array(
+      '#title' => t('Ultra llama mode'),
+      '#type' => 'checkbox',
+      '#default_value' => $config['ultra_llama_mode'],
+    );
+
+    return $form;
+  }
+
+}
diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module
index a40c249..89fd11b 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -119,21 +119,26 @@ function editor_form_filter_admin_format_form_alter(&$form, &$form_state) {
   // Associate a text editor with this text format.
   $editor_options = $manager->listOptions();
   $form['editor'] = array(
-    '#type' => 'select',
+    '#type' => 'fieldset',
     '#title' => t('Text editor'),
-    '#options' => $editor_options,
-    '#empty_option' => t('None'),
-    '#default_value' => $editor ? $editor->editor : '',
     // Position the editor selection before the filter settings (weight of 0),
     // but after the filter label and name (weight of -20).
     '#weight' => -9,
+  );
+  $form['editor']['editor'] = array(
+    '#type' => 'select',
+    '#title' => t('Current text editor'),
+    '#options' => $editor_options,
+    '#empty_option' => t('None'),
+    '#default_value' => $editor ? $editor->editor : '',
     '#ajax' => array(
       'trigger_as' => array('name' => 'editor_configure'),
       'callback' => 'editor_form_filter_admin_form_ajax',
       'wrapper' => 'editor-settings-wrapper',
     ),
+    '#weight' => -10,
   );
-  $form['editor_configure'] = array(
+  $form['editor']['configure'] = array(
     '#type' => 'submit',
     '#name' => 'editor_configure',
     '#value' => t('Configure'),
@@ -143,17 +148,17 @@ function editor_form_filter_admin_format_form_alter(&$form, &$form_state) {
       'callback' => 'editor_form_filter_admin_form_ajax',
       'wrapper' => 'editor-settings-wrapper',
     ),
-    '#weight' => -9,
+    '#weight' => -10,
     '#attributes' => array('class' => array('js-hide')),
   );
 
   // If there aren't any options (other than "None"), disable the select list.
   if (empty($editor_options)) {
-    $form['editor']['#disabled'] = TRUE;
-    $form['editor']['#description'] = t('This option is disabled because no modules that provide a text editor are currently enabled.');
+    $form['editor']['editor']['#disabled'] = TRUE;
+    $form['editor']['editor']['#description'] = t('This option is disabled because no modules that provide a text editor are currently enabled.');
   }
 
-  $form['editor_settings'] = array(
+  $form['editor']['settings'] = array(
     '#tree' => TRUE,
     '#weight' => -8,
     '#type' => 'container',
@@ -165,8 +170,8 @@ function editor_form_filter_admin_format_form_alter(&$form, &$form_state) {
     $plugin = $manager->createInstance($editor->editor);
     $settings_form = array();
     $settings_form['#element_validate'][] = array($plugin, 'settingsFormValidate');
-    $form['editor_settings']['settings'] = $plugin->settingsForm($settings_form, $form_state, $editor);
-    $form['editor_settings']['settings']['#parents'] = array('editor_settings');
+    $form['editor']['settings']['subform'] = $plugin->settingsForm($settings_form, $form_state, $editor);
+    $form['editor']['settings']['subform']['#parents'] = array('editor', 'settings');
     $form['#submit'][] = array($plugin, 'settingsFormSubmit');
   }
 
@@ -178,14 +183,14 @@ function editor_form_filter_admin_format_form_alter(&$form, &$form_state) {
  */
 function editor_form_filter_admin_format_editor_configure($form, &$form_state) {
   $editor = $form_state['editor'];
-  if (isset($form_state['values']['editor'])) {
-    if ($form_state['values']['editor'] === '') {
+  if (isset($form_state['values']['editor']['editor'])) {
+    if ($form_state['values']['editor']['editor'] === '') {
       $form_state['editor'] = FALSE;
     }
-    elseif (empty($editor) || $form_state['values']['editor'] !== $editor->editor) {
+    elseif (empty($editor) || $form_state['values']['editor']['editor'] !== $editor->editor) {
       $editor = entity_create('editor', array(
         'format' => $form['#format']->format,
-        'editor' => $form_state['values']['editor'],
+        'editor' => $form_state['values']['editor']['editor'],
       ));
       $form_state['editor'] = $editor;
     }
@@ -197,7 +202,7 @@ function editor_form_filter_admin_format_editor_configure($form, &$form_state) {
  * AJAX callback handler for filter_admin_format_form().
  */
 function editor_form_filter_admin_form_ajax($form, &$form_state) {
-  return $form['editor_settings'];
+  return $form['editor']['settings'];
 }
 
 /**
@@ -212,8 +217,8 @@ function editor_form_filter_admin_format_submit($form, &$form_state) {
   }
 
   // Create a new editor or update the existing editor.
-  if ($form_state['values']['editor'] !== '') {
-    $form_state['editor']->settings = $form_state['values']['editor_settings'];
+  if ($form_state['values']['editor']['editor'] !== '') {
+    $form_state['editor']->settings = $form_state['values']['editor']['settings'];
     $form_state['editor']->save();
   }
 }
diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js
index d22957d..32da290 100644
--- a/core/modules/editor/js/editor.js
+++ b/core/modules/editor/js/editor.js
@@ -60,7 +60,7 @@ Drupal.behaviors.editor = {
         if (event.isDefaultPrevented()) {
           return;
         }
-        Drupal.editorDetach(field, settings.editor.formats[activeFormatID]);
+        Drupal.editorDetach(field, settings.editor.formats[activeFormatID], 'serialize');
       });
     });
   },
diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/EditorInterface.php b/core/modules/editor/lib/Drupal/editor/Plugin/EditorInterface.php
index 1fcbec3..939d877 100644
--- a/core/modules/editor/lib/Drupal/editor/Plugin/EditorInterface.php
+++ b/core/modules/editor/lib/Drupal/editor/Plugin/EditorInterface.php
@@ -52,7 +52,7 @@ function settingsForm(array $form, array &$form_state, Editor $editor);
    * Validates the settings form for an editor.
    *
    * The contents of the editor settings are located in
-   * $form_state['values']['editor_settings']. Calls to form_error() should
+   * $form_state['values']['editor']['settings']. Calls to form_error() should
    * reflect this location in the settings form.
    *
    * @param array $form
@@ -65,7 +65,7 @@ function settingsFormValidate(array $form, array &$form_state);
   /**
    * Modifies any values in the form state to prepare them for saving.
    *
-   * Values in $form_state['values']['editor_settings'] are saved by Editor
+   * Values in $form_state['values']['editor']['settings'] are saved by Editor
    * module in editor_form_filter_admin_format_submit().
    *
    * @param array $form
diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php
index f3b834a..783fdd9 100644
--- a/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php
+++ b/core/modules/editor/lib/Drupal/editor/Tests/EditorAdminTest.php
@@ -56,9 +56,9 @@ function testWithoutEditorAvailable() {
     $this->assertTrue($roles_pos < $editor_pos && $editor_pos < $filters_pos, '"Text Editor" select appears in the correct location of the text format configuration UI.');
 
     // Verify the <select>.
-    $select = $this->xpath('//select[@name="editor"]');
-    $select_is_disabled = $this->xpath('//select[@name="editor" and @disabled="disabled"]');
-    $options = $this->xpath('//select[@name="editor"]/option');
+    $select = $this->xpath('//select[@name="editor[editor]"]');
+    $select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
+    $options = $this->xpath('//select[@name="editor[editor]"]/option');
     $this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
     $this->assertTrue(count($select_is_disabled) === 1, 'The Text Editor select is disabled.');
     $this->assertTrue(count($options) === 1, 'The Text Editor select has only one option.');
@@ -72,9 +72,9 @@ function testWithoutEditorAvailable() {
     $this->drupalGet('admin/config/content/formats/filtered_html');
 
     // Verify the <select> when a text editor is available.
-    $select = $this->xpath('//select[@name="editor"]');
-    $select_is_disabled = $this->xpath('//select[@name="editor" and @disabled="disabled"]');
-    $options = $this->xpath('//select[@name="editor"]/option');
+    $select = $this->xpath('//select[@name="editor[editor]"]');
+    $select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
+    $options = $this->xpath('//select[@name="editor[editor]"]/option');
     $this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
     $this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
     $this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
@@ -86,15 +86,15 @@ function testWithoutEditorAvailable() {
 
     // Select the "Unicorn Editor" editor and click the "Configure" button.
     $edit = array(
-      'editor' => 'unicorn',
+      'editor[editor]' => 'unicorn',
     );
     $this->drupalPostAjax(NULL, $edit, 'editor_configure');
-    $unicorn_setting_foo = $this->xpath('//input[@name="editor_settings[foo]" and @type="text" and @value="bar"]');
+    $unicorn_setting_foo = $this->xpath('//input[@name="editor[settings][foo]" and @type="text" and @value="bar"]');
     $this->assertTrue(count($unicorn_setting_foo), "Unicorn Editor's settings form is present.");
-    $options = $this->xpath('//select[@name="editor"]/option');
+    $options = $this->xpath('//select[@name="editor[editor]"]/option');
 
     // Now configure the setting to another value.
-    $edit['editor_settings[foo]'] = 'baz';
+    $edit['editor[settings][foo]'] = 'baz';
     $this->drupalPost(NULL, $edit, t('Save configuration'));
 
     // Verify the editor configuration is saved correctly.
@@ -105,9 +105,9 @@ function testWithoutEditorAvailable() {
     $this->assertIdentical($editor->settings['rainbows'], true, 'The text editor defaults added by hook_editor_settings_defaults() are retrieved correctly.');
     $this->assertIdentical($editor->settings['sparkles'], false, 'The text editor defaults modified by hook_editor_settings_defaults_alter() are retrieved correctly.');
     $this->drupalGet('admin/config/content/formats/filtered_html');
-    $select = $this->xpath('//select[@name="editor"]');
-    $select_is_disabled = $this->xpath('//select[@name="editor" and @disabled="disabled"]');
-    $options = $this->xpath('//select[@name="editor"]/option');
+    $select = $this->xpath('//select[@name="editor[editor]"]');
+    $select_is_disabled = $this->xpath('//select[@name="editor[editor]" and @disabled="disabled"]');
+    $options = $this->xpath('//select[@name="editor[editor]"]/option');
     $this->assertTrue(count($select) === 1, 'The Text Editor select exists.');
     $this->assertTrue(count($select_is_disabled) === 0, 'The Text Editor select is not disabled.');
     $this->assertTrue(count($options) === 2, 'The Text Editor select has two options.');
