diff --git a/core/lib/Drupal/Component/Utility/Xss.php b/core/lib/Drupal/Component/Utility/Xss.php
index 0daab37..ef35e2a 100644
--- a/core/lib/Drupal/Component/Utility/Xss.php
+++ b/core/lib/Drupal/Component/Utility/Xss.php
@@ -13,6 +13,18 @@
 class Xss {
 
   /**
+   * Indicates that XSS filtering must be applied in whitelist mode: only
+   * specified HTML tags are allowed.
+   */
+  const FILTER_MODE_WHITELIST = TRUE;
+
+  /**
+   * Indicates that XSS filtering must be applied in blacklist mode: only
+   * specified HTML tags are disallowed.
+   */
+  const FILTER_MODE_BLACKLIST = FALSE;
+
+  /**
    * The list of html tags allowed by filterAdmin().
    *
    * @var array
@@ -35,10 +47,14 @@ class Xss {
    *   javascript:).
    *
    * @param $string
-   *   The string with raw HTML in it. It will be stripped of everything that can
-   *   cause an XSS attack.
-   * @param array $allowed_tags
-   *   An array of allowed tags.
+   *   The string with raw HTML in it. It will be stripped of everything that
+   *   can cause an XSS attack.
+   * @param array $html_tags
+   *   An array of HTML tags.
+   * @param bool $mode
+   *   (optional) Defaults to FILTER_MODE_WHITELIST ($html_tags is used as a
+   *   whitelist of allowed tags), but can also be set to FILTER_MODE_BLACKLIST
+   *   ($html_tags is used as a blacklist of disallowed tags).
    *
    * @return string
    *   An XSS safe version of $string, or an empty string if $string is not
@@ -48,14 +64,14 @@ class Xss {
    *
    * @ingroup sanitization
    */
-  public static function filter($string, $allowed_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd')) {
+  public static function filter($string, $html_tags = array('a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd'), $mode = Xss::FILTER_MODE_WHITELIST) {
     // Only operate on valid UTF-8 strings. This is necessary to prevent cross
     // site scripting issues on Internet Explorer 6.
     if (!Unicode::validateUtf8($string)) {
       return '';
     }
     // Store the text format.
-    static::split($allowed_tags, TRUE);
+    static::split($html_tags, TRUE, $mode);
     // Remove NULL characters (ignored by some browsers).
     $string = str_replace(chr(0), '', $string);
     // Remove Netscape 4 JS entities.
@@ -80,7 +96,7 @@ public static function filter($string, $allowed_tags = array('a', 'em', 'strong'
       <[^>]*(>|$)       # a string that starts with a <, up until the > or the end of the string
       |                 # or
       >                 # just a >
-      )%x', '\Drupal\Component\Utility\Xss::split', $string);
+      )%x', 'static::split', $string);
   }
 
   /**
@@ -112,17 +128,22 @@ public static function filterAdmin($string) {
    *   If $store is TRUE then the array contains the allowed tags.
    *   If $store is FALSE then the array has one element, the HTML tag to process.
    * @param bool $store
-   *   Whether to store $m.
+   *   Whether to store $matches.
+   * @param bool $mode
+   *   (optional) Ignored when $store is FALSE, otherwise used to determine
+   *   whether $matches is a list of allowed (if FILTER_MODE_WHITELIST) or
+   *   disallowed (if FILTER_MODE_BLACKLIST) HTML tags.
    *
    * @return string
    *   If the element isn't allowed, an empty string. Otherwise, the cleaned up
    *   version of the HTML element.
    */
-  protected static function split($matches, $store = FALSE) {
-    static $allowed_html;
+  protected static function split($matches, $store = FALSE, $mode = Xss::FILTER_MODE_WHITELIST) {
+    static $html_tags, $split_mode;
 
     if ($store) {
-      $allowed_html = array_flip($matches);
+      $html_tags = array_flip($matches);
+      $split_mode = $mode;
       return;
     }
 
@@ -151,8 +172,12 @@ protected static function split($matches, $store = FALSE) {
       $elem = '!--';
     }
 
-    if (!isset($allowed_html[strtolower($elem)])) {
-      // Disallowed HTML element.
+    // When in whitelist mode, an element is disallowed when not listed.
+    if ($split_mode === static::FILTER_MODE_WHITELIST && !isset($html_tags[strtolower($elem)])) {
+      return '';
+    }
+    // When in blacklist mode, an element is disallowed when listed.
+    elseif ($split_mode === static::FILTER_MODE_BLACKLIST && isset($html_tags[strtolower($elem)])) {
       return '';
     }
 
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php
index 6b880e3..21db722 100644
--- a/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Plugin/Editor/CKEditor.php
@@ -24,7 +24,8 @@
  *   id = "ckeditor",
  *   label = @Translation("CKEditor"),
  *   supports_content_filtering = TRUE,
- *   supports_inline_editing = TRUE
+ *   supports_inline_editing = TRUE,
+ *   is_xss_safe = FALSE
  * )
  */
 class CKEditor extends EditorBase implements ContainerFactoryPluginInterface {
diff --git a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php
index e97c7e7..fa3fc7a 100644
--- a/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php
+++ b/core/modules/ckeditor/lib/Drupal/ckeditor/Tests/CKEditorLoadingTest.php
@@ -104,6 +104,7 @@ function testLoading() {
       'editor' => 'ckeditor',
       'editorSettings' => $ckeditor_plugin->getJSSettings($editor),
       'editorSupportsContentFiltering' => TRUE,
+      'isXssSafe' => FALSE,
     )));
     $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.");
@@ -131,6 +132,7 @@ function testLoading() {
       'editor' => 'ckeditor',
       'editorSettings' => $ckeditor_plugin->getJSSettings($editor),
       'editorSupportsContentFiltering' => TRUE,
+      'isXssSafe' => FALSE,
     )));
     $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.");
diff --git a/core/modules/editor/editor.api.php b/core/modules/editor/editor.api.php
index fe9548b..681399a 100644
--- a/core/modules/editor/editor.api.php
+++ b/core/modules/editor/editor.api.php
@@ -5,6 +5,8 @@
  * Documentation for Text Editor API.
  */
 
+use Drupal\filter\FilterFormatInterface;
+
 /**
  * @addtogroup hooks
  * @{
@@ -104,5 +106,30 @@ function hook_editor_js_settings_alter(array &$settings, array $formats) {
 }
 
 /**
+ * Modifies the text editor XSS filter that will used for the given text format.
+ *
+ * Is only called when an EditorXssFilter will effectively be used; this hook
+ * does not allow one to alter that decision.
+ *
+ * @param string &$editor_xss_filter_class
+ *   The text editor XSS filter class that will be used.
+ * @param \Drupal\filter\FilterFormatInterface $format
+ *   The text format configuration entity. Provides context based upon which
+ *   one may want to adjust the filtering.
+ * @param \Drupal\filter\FilterFormatInterface $original_format|null
+ *   (optional) The original text format configuration entity (when switching
+ *   text formats/editors). Also provides context based upon which one may want
+ *   to adjust the filtering.
+ *
+ * @see \Drupal\editor\EditorXssFilterInterface
+ */
+function hook_editor_xss_filter_alter(&$editor_xss_filter_class, FilterFormatInterface $format, FilterFormatInterface $original_format = NULL) {
+  $filters = $format->filters()->getAll();
+  if (isset($filters['filter_wysiwyg']) && $filters['filter_wysiwyg']->status) {
+    $editor_xss_filter_class = '\Drupal\filter_wysiwyg\EditorXssFilter\WysiwygFilter';
+  }
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
diff --git a/core/modules/editor/editor.module b/core/modules/editor/editor.module
index 02bccef..0caeadb 100644
--- a/core/modules/editor/editor.module
+++ b/core/modules/editor/editor.module
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\field\Field;
+use Drupal\filter\FilterFormatInterface;
 
 /**
  * Implements hook_help().
@@ -370,10 +371,102 @@ function editor_pre_render_format($element) {
   $manager = \Drupal::service('plugin.manager.editor');
   $element['#attached'] = NestedArray::mergeDeep($element['#attached'], $manager->getAttachments($format_ids));
 
+  // Apply XSS filters when editing content if necessary. Some types of text
+  // editors cannot guarantee that the end user won't become a victim of XSS.
+  if (!empty($element['value']['#value'])) {
+    $original = $element['value']['#value'];
+    $format = entity_load('filter_format', $element['format']['format']['#value']);
+
+    // Ensure XSS-safety for the current text format/editor.
+    $filtered = editor_filter_xss($original, $format);
+    if ($filtered !== FALSE) {
+      $element['value']['#value'] = $filtered;
+    }
+
+    // Only when the user has access to multiple text formats, we must add data-
+    // attributes for the original value and change tracking, because they are
+    // only necessary when the end user can switch between text formats/editors.
+    if ($element['format']['format']['#access']) {
+      $element['value']['#attributes']['data-editor-value-is-changed'] = 'false';
+      $element['value']['#attributes']['data-editor-value-original'] = $original;
+    }
+  }
+
   return $element;
 }
 
 /**
+ * Applies text editor XSS filtering.
+ *
+ * @param string $html
+ *   The HTML string that will be passed to the text editor.
+ * @param \Drupal\filter\FilterFormatInterface $format
+ *   The text format whose text editor will be used.
+ * @param \Drupal\filter\FilterFormatInterface $original_format|null
+ *   (optional) The original text format (i.e. when switching text formats,
+ *   $format is the text format that is going to be used, $original_format is
+ *   the one that was being used initially, the one that is stored in the
+ *   database when editing).
+ *
+ * @return string|false
+ *   FALSE when no XSS filtering needs to be applied (either because no text
+ *   editor is associated with the text format, or because the text editor is
+ *   safe from XSS attacks, or because the text format does not use any XSS
+ *   protection filters), otherwise the XSS filtered string.
+ *
+ * @see https://drupal.org/node/2099741
+ */
+function editor_filter_xss($html, FilterFormatInterface $format, FilterFormatInterface $original_format = NULL) {
+  $editor = editor_load($format->id());
+
+  // If no text editor is associated with this text format, then we don't need
+  // text editor XSS filtering either.
+  if (!isset($editor)) {
+    return FALSE;
+  }
+
+  // If the text editor associated with this text format guarantees security,
+  // then we also don't need text editor XSS filtering.
+  $definition = \Drupal::service('plugin.manager.editor')->getDefinition($editor->editor);
+  if ($definition['is_xss_safe'] === TRUE) {
+    return FALSE;
+  }
+
+  // If there is no filter preventing XSS attacks in the text format being used,
+  // then no text editor XSS filtering is needed either. (Because then the
+  // editing user can already be attacked by merely viewing the content.)
+  // e.g.: an admin user creates content in Full HTML and then edits it, no text
+  // format switching happens; in this case, no text editor XSS filtering is
+  // desirable, because it would strip style attributes, amongst others.
+  $current_filter_types = filter_get_filter_types_by_format($format->id());
+  if (!in_array(FILTER_TYPE_HTML_RESTRICTOR, $current_filter_types, TRUE)) {
+    if ($original_format === NULL) {
+      return FALSE;
+    }
+    // Unless we are switching from another text format, in which case we must
+    // first check whether a filter preventing XSS attacks is used in that text
+    // format, and if so, we must still apply XSS filtering.
+    // e.g.: an anonymous user creates content in Restricted HTML, an admin user
+    // edits it (then no XSS filtering is applied because no text editor is
+    // used), and switches to Full HTML (for which a text editor is used). Then
+    // we must apply XSS filtering to protect the admin user.
+    else {
+      $original_filter_types = filter_get_filter_types_by_format($original_format->id());
+      if (!in_array(FILTER_TYPE_HTML_RESTRICTOR, $original_filter_types, TRUE)) {
+        return FALSE;
+      }
+    }
+  }
+
+  // Otherwise, apply the text editor XSS filter. We use the default one unless
+  // a module tells us to use a different one.
+  $editor_xss_filter_class = '\Drupal\editor\EditorXssFilter\Standard';
+  \Drupal::moduleHandler()->alter('editor_xss_filter', $editor_xss_filter_class, $format, $original_format);
+
+  return call_user_func($editor_xss_filter_class . '::filterXss', $html, $format, $original_format);
+}
+
+/**
  * Implements hook_entity_insert().
  */
 function editor_entity_insert(EntityInterface $entity) {
diff --git a/core/modules/editor/editor.routing.yml b/core/modules/editor/editor.routing.yml
index bf9d360..2a753f5 100644
--- a/core/modules/editor/editor.routing.yml
+++ b/core/modules/editor/editor.routing.yml
@@ -1,3 +1,10 @@
+editor.filter_xss:
+  path: '/editor/filter_xss/{filter_format}'
+  defaults:
+    _controller: '\Drupal\editor\EditorController::filterXss'
+  requirements:
+    _entity_access: 'filter_format.view'
+
 editor.field_untransformed_text:
   path: '/editor/{entity_type}/{entity}/{field_name}/{langcode}/{view_mode_id}'
   defaults:
diff --git a/core/modules/editor/js/editor.js b/core/modules/editor/js/editor.js
index ebaf5e4..74239d9 100644
--- a/core/modules/editor/js/editor.js
+++ b/core/modules/editor/js/editor.js
@@ -16,7 +16,7 @@
    * @return DOM
    *   The text area DOM element.
    */
-  function findFieldForFormatSelector($formatSelector) {
+  function findFieldForFormatSelector ($formatSelector) {
     var field_id = $formatSelector.attr('data-editor-for');
     return $('#' + field_id).get(0);
   }
@@ -33,15 +33,23 @@
    *   The text format we're changing to; its associated text editor will be
    *   attached.
    */
-  function changeTextEditor($formatSelector, activeFormatID, newFormatID) {
+  function changeTextEditor ($formatSelector, activeFormatID, newFormatID) {
+    var originalFormatID = activeFormatID;
     var field = findFieldForFormatSelector($formatSelector);
     // Detach the current editor (if any) and attach a new editor.
     if (drupalSettings.editor.formats[activeFormatID]) {
       Drupal.editorDetach(field, drupalSettings.editor.formats[activeFormatID]);
     }
+    // When no text editor is currently active, stop tracking changes.
+    else if (!drupalSettings.editor.formats[activeFormatID]) {
+      $(field).off('.editor');
+    }
     activeFormatID = newFormatID;
+
+    // Attach the new text editor (if any).
     if (drupalSettings.editor.formats[activeFormatID]) {
-      Drupal.editorAttach(field, drupalSettings.editor.formats[activeFormatID]);
+      var format = drupalSettings.editor.formats[activeFormatID];
+      filterXssWhenSwitching(field, format, originalFormatID, Drupal.editorAttach);
     }
     $formatSelector.attr('data-editor-active-text-format', newFormatID);
   }
@@ -51,7 +59,7 @@
    *
    * @param jQuery.Event event
    */
-  function onTextFormatChange(event) {
+  function onTextFormatChange (event) {
     var $select = $(event.target);
     var activeFormatID = $select.attr('data-editor-active-text-format');
     var newFormatID = $select.val();
@@ -135,10 +143,22 @@
         $this.attr('data-editor-active-text-format', activeFormatID);
         var field = findFieldForFormatSelector($this);
 
-        // Directly attach this editor, if the text format is enabled.
+        // Directly attach this text editor, if the text format is enabled.
         if (settings.editor.formats[activeFormatID]) {
+          // XSS protection for the current text format/editor is performed on the
+          // server side, so we don't need to do anything special here.
           Drupal.editorAttach(field, settings.editor.formats[activeFormatID]);
         }
+        // When there is no text editor for this text format, still track changes,
+        // because the user has the ability to switch to some text editor, other-
+        // wise this code would not be executed.
+        else {
+          $(field).on('change.editor keypress.editor', function () {
+            field.setAttribute('data-editor-value-is-changed', 'true');
+            // Just knowing that the value was changed is enough, stop tracking.
+            $(field).off('.editor');
+          });
+        }
 
         // Attach onChange handler to text format selector element.
         if ($this.is('select')) {
@@ -200,6 +220,10 @@
       // happen within the text editor.
       Drupal.editors[format.editor].onChange(field, function () {
         $(field).trigger('formUpdated');
+
+        // Keep track of changes, so we know what to do when switching text
+        // formats and guaranteeing XSS protection.
+        field.setAttribute('data-editor-value-is-changed', 'true');
       });
     }
   };
@@ -214,7 +238,51 @@
       }
 
       Drupal.editors[format.editor].detach(field, format, trigger);
+
+      // Restore the original value if the user didn't make any changes yet.
+      if (field.getAttribute('data-editor-value-is-changed') === 'false') {
+        field.value = field.getAttribute('data-editor-value-original');
+      }
     }
   };
 
+  /**
+   * Filter away XSS attack vectors when switching text formats.
+   *
+   * @param DOM field
+   *   The textarea DOM element.
+   * @param Object format
+   *   The text format that's being activated, from drupalSettings.editor.formats.
+   * @param String originalFormatID
+   *   The text format ID of the original text format.
+   * @param Function callback
+   *   A callback to be called (with no parameters) after the field's value has
+   *   been XSS filtered.
+   */
+  function filterXssWhenSwitching (field, format, originalFormatID, callback) {
+    // A text editor that already is XSS-safe needs no additional measures.
+    if (format.editor.isXssSafe) {
+      callback(field, format);
+    }
+    // Otherwise, ensure XSS safety: let the server XSS filter this value.
+    else {
+      $.ajax({
+        url: Drupal.url('editor/filter_xss/' + format.format),
+        type: 'POST',
+        data: {
+          'value': field.value,
+          'original_format_id': originalFormatID
+        },
+        dataType: 'json',
+        success: function (xssFilteredValue) {
+          // If the server returns false, then no XSS filtering is needed.
+          if (xssFilteredValue !== false) {
+            field.value = xssFilteredValue;
+          }
+          callback(field, format);
+        }
+      });
+    }
+  }
+
 })(jQuery, Drupal, drupalSettings);
diff --git a/core/modules/editor/lib/Drupal/editor/Annotation/Editor.php b/core/modules/editor/lib/Drupal/editor/Annotation/Editor.php
index be126b4..32ac3db 100644
--- a/core/modules/editor/lib/Drupal/editor/Annotation/Editor.php
+++ b/core/modules/editor/lib/Drupal/editor/Annotation/Editor.php
@@ -42,8 +42,15 @@ class Editor extends Plugin {
   /**
    * Whether the editor supports the inline editing provided by the Edit module.
    *
-   * @var boolean
+   * @var bool
    */
   public $supports_inline_editing;
 
+  /**
+   * Whether this text editor is not vulnerable to XSS attacks.
+   *
+   * @var bool
+   */
+  public $is_xss_safe;
+
 }
diff --git a/core/modules/editor/lib/Drupal/editor/EditorController.php b/core/modules/editor/lib/Drupal/editor/EditorController.php
index 27c874d..f68f2d4 100644
--- a/core/modules/editor/lib/Drupal/editor/EditorController.php
+++ b/core/modules/editor/lib/Drupal/editor/EditorController.php
@@ -10,17 +10,20 @@
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\OpenModalDialogCommand;
 use Drupal\Core\Ajax\CloseModalDialogCommand;
+use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\editor\Ajax\GetUntransformedTextCommand;
 use Drupal\editor\Form\EditorImageDialog;
 use Drupal\editor\Form\EditorLinkDialog;
-use Drupal\filter\Entity\FilterFormat;
-use Symfony\Component\DependencyInjection\ContainerAware;
+use Drupal\filter\FilterFormatInterface;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
  * Returns responses for Editor module routes.
  */
-class EditorController extends ContainerAware {
+class EditorController extends ControllerBase {
 
   /**
    * Returns an Ajax response to render a text field without transformation filters.
@@ -49,4 +52,35 @@ public function getUntransformedText(EntityInterface $entity, $field_name, $lang
     return $response;
   }
 
+  /**
+   * Apply the necessary XSS filtering for using a certain text format's editor.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request object.
+   * @param \Drupal\filter\FilterFormatInterface $filter_format
+   *   The text format whose text editor (if any) will be used.
+   *
+   * @return \Symfony\Component\HttpFoundation\JsonResponse
+   *   A JSON response containing the XSS-filtered value.
+   *
+   * @see editor_filter_xss()
+   */
+  public function filterXss(Request $request, FilterFormatInterface $filter_format) {
+    $value = $request->request->get('value');
+    if (!isset($value)) {
+      throw new NotFoundHttpException();
+    }
+
+    // The original_format parameter will only exist when switching text format.
+    $original_format_id = $request->request->get('original_format_id');
+    $original_format = NULL;
+    if (isset($original_format_id)) {
+      $original_format = $this->entityManager()
+        ->getStorageController('filter_format')
+        ->load($original_format_id);
+    }
+
+    return new JsonResponse(editor_filter_xss($value, $filter_format, $original_format));
+  }
+
 }
diff --git a/core/modules/editor/lib/Drupal/editor/EditorXssFilter/StandardTest.php b/core/modules/editor/lib/Drupal/editor/EditorXssFilter/StandardTest.php
new file mode 100644
index 0000000..54a9333
--- /dev/null
+++ b/core/modules/editor/lib/Drupal/editor/EditorXssFilter/StandardTest.php
@@ -0,0 +1,87 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\editor\Tests\editor\EditorXssFilter\StandardTest.
+ */
+
+namespace Drupal\editor\Tests\editor\EditorXssFilter;
+
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the standard text editor XSS filter.
+ *
+ * @group Drupal
+ * @group Editor
+ *
+ * @see \Drupal\editor\EditorXssFilter\Standard
+ */
+class StandardTest extends UnitTestCase {
+
+  /**
+   * The tested text editor XSS filter.
+   *
+   * @var \Drupal\editor\EditorXssFilter\Standard
+   */
+  protected $editorXssFilterClass;
+
+  /**
+   * The mocked text format configuration entity.
+   *
+   * @var \Drupal\filter\Entity\FilterFormat|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $format;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Standard text editor XSS filter test',
+      'description' => 'Unit test of standard text editor XSS filter.',
+      'group' => 'Text Editor'
+    );
+  }
+
+  protected function setUp() {
+    $this->editorXssFilterClass = '\Drupal\editor\EditorXssFilter\Standard';
+
+    // Mock text format configuration entity object.
+    $this->format = $this->getMockBuilder('\Drupal\filter\Entity\FilterFormat')
+      ->disableOriginalConstructor()
+      ->getMock();
+  }
+
+  /**
+   * Provides test data for testFilterXss().
+   *
+   * \Drupal\editor\EditorXssFilter\Standard uses
+   * Drupal\Component\Utility\Xss. See \Drupal\Tests\Component\Utility\XssTest::testBlacklistMode()
+   * for more detailed test coverage.
+   *
+   * @see \Drupal\editor\Tests\editor\EditorXssFilter\StandardTest::testFilterXss()
+   * @see \Drupal\Tests\Component\Utility\XssTest::testBlacklistMode()
+   */
+  public function providerTestFilterXss() {
+    $data = array();
+    $data[] = array('<p>Hello, world!</p><unknown>Pink Fairy Armadillo</unknown>', '<p>Hello, world!</p><unknown>Pink Fairy Armadillo</unknown>');
+    $data[] = array('<p style="color:red">Hello, world!</p><unknown>Pink Fairy Armadillo</unknown>', '<p>Hello, world!</p><unknown>Pink Fairy Armadillo</unknown>');
+    $data[] = array('<p>Hello, world!</p><unknown>Pink Fairy Armadillo</unknown><script>alert("evil");</script>', '<p>Hello, world!</p><unknown>Pink Fairy Armadillo</unknown>alert("evil");');
+    $data[] = array('<p>Hello, world!</p><unknown>Pink Fairy Armadillo</unknown><a href="javascript:alert(1)">test</a>', '<p>Hello, world!</p><unknown>Pink Fairy Armadillo</unknown><a href="alert(1)">test</a>');
+    return $data;
+  }
+
+  /**
+   * Tests the method for checking access to routes.
+   *
+   * @param string $input
+   *   The input.
+   * @param string $expected_output
+   *   The expected output.
+   *
+   * @dataProvider providerTestFilterXss
+   */
+  public function testFilterXss($input, $expected_output) {
+    $output = call_user_func($this->editorXssFilterClass . '::filterXss', $input, $this->format);
+    $this->assertSame($expected_output, $output);
+  }
+
+}
diff --git a/core/modules/editor/lib/Drupal/editor/EditorXssFilterInterface.php b/core/modules/editor/lib/Drupal/editor/EditorXssFilterInterface.php
new file mode 100644
index 0000000..00411f0
--- /dev/null
+++ b/core/modules/editor/lib/Drupal/editor/EditorXssFilterInterface.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\editor\EditorXssFilterInterface.
+ */
+
+namespace Drupal\editor;
+
+use Drupal\filter\FilterFormatInterface;
+
+/**
+ * Defines an interface for text editor XSS (Cross-site scripting) filters.
+ */
+interface EditorXssFilterInterface {
+
+  /**
+   * Filters HTML to prevent XSS attacks when a user edits it in a text editor.
+   *
+   * Should filter as minimally as possible, only to remove XSS attack vectors.
+   *
+   * @param string $html
+   *   The HTML to be filtered.
+   * @param \Drupal\filter\FilterFormatInterface $format
+   *   The text format configuration entity. Provides context based upon which
+   *   one may want to adjust the filtering.
+   * @param \Drupal\filter\FilterFormatInterface $original_format|null
+   *   (optional) The original text format configuration entity (when switching
+   *   text formats/editors). Also provides context based upon which one may
+   *   want to adjust the filtering.
+   *
+   * @return string
+   *   The filtered HTML that cannot cause any XSSes anymore.
+   */
+  public static function filterXss($html, FilterFormatInterface $format, FilterFormatInterface $original_format = NULL);
+
+}
diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/EditorBase.php b/core/modules/editor/lib/Drupal/editor/Plugin/EditorBase.php
index 20a4169..32e4408 100644
--- a/core/modules/editor/lib/Drupal/editor/Plugin/EditorBase.php
+++ b/core/modules/editor/lib/Drupal/editor/Plugin/EditorBase.php
@@ -28,6 +28,7 @@
  *   only" filtering.
  * - supports_inline_editing: Whether the editor supports the inline editing
  *   provided by the Edit module.
+ * - is_xss_safe: Whether this text editor is not vulnerable to XSS attacks.
  *
  * A complete sample plugin definition should be defined as in this example:
  *
@@ -36,7 +37,8 @@
  *   id = "myeditor",
  *   label = @Translation("My Editor"),
  *   supports_content_filtering = FALSE,
- *   supports_inline_editing = FALSE
+ *   supports_inline_editing = FALSE,
+ *   is_xss_safe = FALSE
  * )
  * @endcode
  */
diff --git a/core/modules/editor/lib/Drupal/editor/Plugin/EditorManager.php b/core/modules/editor/lib/Drupal/editor/Plugin/EditorManager.php
index 558cfde..5e33b7b 100644
--- a/core/modules/editor/lib/Drupal/editor/Plugin/EditorManager.php
+++ b/core/modules/editor/lib/Drupal/editor/Plugin/EditorManager.php
@@ -83,6 +83,7 @@ public function getAttachments(array $format_ids) {
         'editor' => $editor->editor,
         'editorSettings' => $plugin->getJSSettings($editor),
         'editorSupportsContentFiltering' => $plugin_definition['supports_content_filtering'],
+        'isXssSafe' => $plugin_definition['is_xss_safe'],
       );
     }
 
diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditorLoadingTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditorLoadingTest.php
index ffbf9a8..59dde52 100644
--- a/core/modules/editor/lib/Drupal/editor/Tests/EditorLoadingTest.php
+++ b/core/modules/editor/lib/Drupal/editor/Tests/EditorLoadingTest.php
@@ -97,6 +97,7 @@ public function testLoading() {
       'editor' => 'unicorn',
       'editorSettings' => array('ponyModeEnabled' => TRUE),
       'editorSupportsContentFiltering' => TRUE,
+      'isXssSafe' => FALSE,
     )));
     $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.");
@@ -125,6 +126,7 @@ public function testLoading() {
       'editor' => 'unicorn',
       'editorSettings' => array('ponyModeEnabled' => TRUE),
       'editorSupportsContentFiltering' => TRUE,
+      'isXssSafe' => FALSE,
     )));
     $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.");
diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditorManagerTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditorManagerTest.php
index 78140c7..d97f989 100644
--- a/core/modules/editor/lib/Drupal/editor/Tests/EditorManagerTest.php
+++ b/core/modules/editor/lib/Drupal/editor/Tests/EditorManagerTest.php
@@ -105,6 +105,7 @@ public function testManager() {
               'editor' => 'unicorn',
               'editorSettings' => $unicorn_plugin->getJSSettings($editor),
               'editorSupportsContentFiltering' => TRUE,
+              'isXssSafe' => FALSE,
             )
           )))
         )
diff --git a/core/modules/editor/lib/Drupal/editor/Tests/EditorSecurityTest.php b/core/modules/editor/lib/Drupal/editor/Tests/EditorSecurityTest.php
new file mode 100644
index 0000000..1ffc464
--- /dev/null
+++ b/core/modules/editor/lib/Drupal/editor/Tests/EditorSecurityTest.php
@@ -0,0 +1,345 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\editor\Tests\EditorSecurityTest.
+ */
+
+namespace Drupal\editor\Tests;
+
+use Drupal\simpletest\WebTestBase;
+use Drupal\Component\Utility\String;
+
+/**
+ * Tests XSS protection for content creators when using text editors.
+ */
+class EditorSecurityTest extends WebTestBase {
+
+  /**
+   * The sample content to use in all tests.
+   *
+   * @var string
+   */
+  protected static $sampleContent = '<p style="color: red">Hello, Dumbo Octopus!</p><script>alert(0)</script>';
+
+  /**
+   * The secured sample content to use in all tests.
+   *
+   * @var string
+   */
+  protected static $sampleContentSecured = '<p>Hello, Dumbo Octopus!</p>alert(0)';
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('filter', 'editor', 'editor_test', 'node');
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Text editor security',
+      'description' => 'Tests XSS protection for content creators when using text editors.',
+      'group' => 'Text Editor',
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    // Create 4 text formats, to cover all potential use cases:
+    //  1. restricted_without_editor (untrusted: anonymous)
+    //  2. restricted_with_editor (normal: authenticated)
+    //  3. unrestricted_without_editor (privileged: admin)
+    //  4. unrestricted_with_editor (privileged: admin)
+    // With text formats 2 and 4, we also associate a text editor that does not
+    // guarantee XSS safety. "restricted" means the text format has XSS filters
+    // on output, "unrestricted" means the opposite.
+    $format = entity_create('filter_format', array(
+      'format' => 'restricted_without_editor',
+      'name' => 'Restricted HTML, without text editor',
+      'weight' => 0,
+      'filters' => array(
+        // A filter of the FILTER_TYPE_HTML_RESTRICTOR type.
+        'filter_html' => array(
+          'status' => 1,
+          'settings' => array(
+            'allowed_html' => '<h4> <h5> <h6> <p> <br> <strong> <a>',
+          )
+        ),
+      ),
+    ));
+    $format->save();
+    $format = entity_create('filter_format', array(
+      'format' => 'restricted_with_editor',
+      'name' => 'Restricted HTML, with text editor',
+      'weight' => 1,
+      'filters' => array(
+        // A filter of the FILTER_TYPE_HTML_RESTRICTOR type.
+        'filter_html' => array(
+          'status' => 1,
+          'settings' => array(
+            'allowed_html' => '<h4> <h5> <h6> <p> <br> <strong> <a>',
+          )
+        ),
+      ),
+    ));
+    $format->save();
+        $editor = entity_create('editor', array(
+      'format' => 'restricted_with_editor',
+      'editor' => 'unicorn',
+    ));
+    $editor->save();
+    $format = entity_create('filter_format', array(
+      'format' => 'unrestricted_without_editor',
+      'name' => 'Unrestricted HTML, without text editor',
+      'weight' => 0,
+      'filters' => array(),
+    ));
+    $format->save();
+    $format = entity_create('filter_format', array(
+      'format' => 'unrestricted_with_editor',
+      'name' => 'Unrestricted HTML, with text editor',
+      'weight' => 1,
+      'filters' => array(),
+    ));
+    $format->save();
+        $editor = entity_create('editor', array(
+      'format' => 'unrestricted_with_editor',
+      'editor' => 'unicorn',
+    ));
+    $editor->save();
+
+
+    // Create node type.
+    $this->drupalCreateContentType(array(
+      'type' => 'article',
+      'name' => 'Article',
+    ));
+
+    // Create 3 users, each with access to different text formats/editors:
+    //   - "untrusted": restricted_without_editor
+    //   - "normal": restricted_with_editor
+    //   - "privileged": restricted_without_editor, restricted_with_editor,
+    //      unrestricted_without_editor and unrestricted_with_editor
+    $this->untrusted_user = $this->drupalCreateUser(array(
+      'create article content',
+      'edit any article content',
+      'use text format restricted_without_editor',
+    ));
+    $this->normal_user = $this->drupalCreateUser(array(
+      'create article content',
+      'edit any article content',
+      'use text format restricted_with_editor',
+    ));
+    $this->privileged_user = $this->drupalCreateUser(array(
+      'create article content',
+      'edit any article content',
+      'use text format restricted_without_editor',
+      'use text format restricted_with_editor',
+      'use text format unrestricted_without_editor',
+      'use text format unrestricted_with_editor',
+    ));
+
+    // Create an "article" node for each possible text format, with the same
+    // sample content, to do our tests on.
+    $samples = array(
+      array('author' => $this->untrusted_user->id(), 'format' => 'restricted_without_editor'),
+      array('author' => $this->normal_user->id(), 'format' => 'restricted_with_editor'),
+      array('author' => $this->privileged_user->id(), 'format' => 'unrestricted_without_editor'),
+      array('author' => $this->privileged_user->id(), 'format' => 'unrestricted_with_editor'),
+    );
+    foreach ($samples as $sample) {
+      $this->drupalCreateNode(array(
+        'type' => 'article',
+        'body' => array(
+          array('value' => self::$sampleContent, 'format' => $sample['format'])
+        ),
+        'uid' => $sample['author']
+      ));
+    }
+  }
+
+  /**
+   * Tests initial security: is the user safe without switching text formats?
+   *
+   * Tests 6 scenarios. Tests only with a text editor that is not XSS-safe.
+   */
+  function testInitialSecurity() {
+    $expected = array(
+      array(
+        'node_id' => 1,
+        'format' => 'restricted_without_editor',
+        // No text editor => no XSS filtering.
+        'value' => self::$sampleContent,
+        'users' => array(
+          $this->untrusted_user,
+          $this->privileged_user,
+        ),
+      ),
+      array(
+        'node_id' => 2,
+        'format' => 'restricted_with_editor',
+        // Text editor => XSS filtering.
+        'value' => self::$sampleContentSecured,
+        'users' => array(
+          $this->normal_user,
+          $this->privileged_user,
+        ),
+      ),
+      array(
+        'node_id' => 3,
+        'format' => 'unrestricted_without_editor',
+        // No text editor => no XSS filtering.
+        'value' => self::$sampleContent,
+        'users' => array(
+          $this->privileged_user,
+        ),
+      ),
+      array(
+        'node_id' => 4,
+        'format' => 'unrestricted_with_editor',
+        // Text editor, no security filter => no XSS filtering.
+        'value' => self::$sampleContent,
+        'users' => array(
+          $this->privileged_user,
+        ),
+      ),
+    );
+
+    // Log in as each user that may edit the content, and assert the value.
+    foreach ($expected as $case) {
+      foreach ($case['users'] as $account) {
+        $this->pass(format_string('Scenario: sample %sample_id, %format.', array(
+          '%sample_id' => $case['node_id'],
+          '%format' => $case['format'],
+        )));
+        $this->drupalLogin($account);
+        $this->drupalGet('node/' . $case['node_id'] . '/edit');
+        $dom_node = $this->xpath('//textarea[@id="edit-body-0-value"]');
+        $this->assertIdentical($case['value'], (string) $dom_node[0], 'The value was correctly filtered for XSS attack vectors.');
+      }
+    }
+  }
+
+  /**
+   * Tests administrator security: is the user safe when switching text formats?
+   *
+   * Tests 16 scenarios. Tests only with a text editor that is not XSS-safe.
+   *
+   * When changing from a more restrictive text format with a text editor (or a
+   * text format without a text editor) to a less restrictive text format, it is
+   * possible that a malicious user could trigger an XSS.
+   *
+   * E.g. when switching a piece of text that uses the Restricted HTML text
+   * format and contains a <script> tag to the Full HTML text format, the
+   * <script> tag would be executed. Unless we apply appropriate filtering.
+   */
+  function testSwitchingSecurity() {
+    $expected = array(
+      array(
+        'node_id' => 1,
+        'value' => self::$sampleContent, // No text editor => no XSS filtering.
+        'format' => 'restricted_without_editor',
+        'switch_to' => array(
+          'restricted_with_editor' => self::$sampleContentSecured,
+           // No text editor => no XSS filtering.
+          'unrestricted_without_editor' => FALSE,
+          'unrestricted_with_editor' => self::$sampleContentSecured,
+        ),
+      ),
+      array(
+        'node_id' => 2,
+        'value' => self::$sampleContentSecured, // Text editor => XSS filtering.
+        'format' => 'restricted_with_editor',
+        'switch_to' => array(
+          // No text editor => no XSS filtering.
+          'restricted_without_editor' => FALSE,
+          // No text editor => no XSS filtering.
+          'unrestricted_without_editor' => FALSE,
+          'unrestricted_with_editor' => self::$sampleContentSecured,
+        ),
+      ),
+      array(
+        'node_id' => 3,
+        'value' => self::$sampleContent, // No text editor => no XSS filtering.
+        'format' => 'unrestricted_without_editor',
+        'switch_to' => array(
+          // No text editor => no XSS filtering.
+          'restricted_without_editor' => FALSE,
+          'restricted_with_editor' => self::$sampleContentSecured,
+          // From no editor, no security filters, to editor, still no security
+          // filters: resulting content when viewed was already vulnerable, so
+          // it must be intentional.
+          'unrestricted_with_editor' => FALSE,
+        ),
+      ),
+      array(
+        'node_id' => 4,
+        'value' => self::$sampleContentSecured, // Text editor => XSS filtering.
+        'format' => 'unrestricted_with_editor',
+        'switch_to' => array(
+          // From editor, no security filters to security filters, no editor: no
+          // risk.
+          'restricted_without_editor' => FALSE,
+          'restricted_with_editor' => self::$sampleContentSecured,
+          // From no editor, no security filters, to editor, still no security
+          // filters: resulting content when viewed was already vulnerable, so
+          // it must be intentional.
+          'unrestricted_without_editor' => FALSE,
+        ),
+      ),
+    );
+
+    // Log in as the privileged user, and for every sample, do the following:
+    //  - switch to every other text format/editor
+    //  - assert the XSS-filtered values that we get from the server
+    $value_original_attribute = String::checkPlain(self::$sampleContent);
+    $this->drupalLogin($this->privileged_user);
+    foreach ($expected as $case) {
+      $this->drupalGet('node/' . $case['node_id'] . '/edit');
+
+      // Verify data- attributes.
+      $dom_node = $this->xpath('//textarea[@id="edit-body-0-value"]');
+      $this->assertIdentical(self::$sampleContent, (string) $dom_node[0]['data-editor-value-original'], 'The data-editor-value-original attribute is correctly set.');
+      $this->assertIdentical('false', (string) $dom_node[0]['data-editor-value-is-changed'], 'The data-editor-value-is-changed attribute is correctly set.');
+
+      // Switch to every other text format/editor and verify the results.
+      foreach ($case['switch_to'] as $format => $expected_filtered_value) {
+        $this->pass(format_string('Scenario: sample %sample_id, switch from %original_format to %format.', array(
+          '%sample_id' => $case['node_id'],
+          '%original_format' => $case['format'],
+          '%format' => $format,
+        )));
+        $post = array(
+          'value' => self::$sampleContent,
+          'original_format_id' => $case['format'],
+        );
+        $response = $this->drupalPost('editor/filter_xss/' . $format, 'application/json', $post);
+        $this->assertResponse(200);
+        $json = drupal_json_decode($response);
+        $this->assertIdentical($json, $expected_filtered_value, 'The value was correctly filtered for XSS attack vectors.');
+      }
+    }
+  }
+
+  /**
+   * Tests the standard text editor XSS filter being overridden.
+   */
+  function testEditorXssFilterOverride() {
+    // First: the Standard text editor XSS filter.
+    $this->drupalLogin($this->normal_user);
+    $this->drupalGet('node/2/edit');
+    $dom_node = $this->xpath('//textarea[@id="edit-body-0-value"]');
+    $this->assertIdentical(self::$sampleContentSecured, (string) $dom_node[0], 'The value was filtered by the Standard text editor XSS filter.');
+
+    // Enable editor_test.module's hook_editor_xss_filter_alter() implementation
+    // to ater the text editor XSS filter class being used.
+    \Drupal::state()->set('editor_test_editor_xss_filter_alter_enabled', TRUE);
+
+    // First: the Insecure text editor XSS filter.
+    $this->drupalGet('node/2/edit');
+    $dom_node = $this->xpath('//textarea[@id="edit-body-0-value"]');
+    $this->assertIdentical(self::$sampleContent, (string) $dom_node[0], 'The value was filtered by the Insecure text editor XSS filter.');
+  }
+}
diff --git a/core/modules/editor/lib/Drupal/editor/Tests/editor/EditorXssFilter/Standard.php b/core/modules/editor/lib/Drupal/editor/Tests/editor/EditorXssFilter/Standard.php
new file mode 100644
index 0000000..d632ccf
--- /dev/null
+++ b/core/modules/editor/lib/Drupal/editor/Tests/editor/EditorXssFilter/Standard.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\editor\EditorXssFilter\Standard.
+ */
+
+namespace Drupal\editor\EditorXssFilter;
+
+use Drupal\Component\Utility\Xss;
+use Drupal\filter\FilterFormatInterface;
+use Drupal\editor\EditorXssFilterInterface;
+
+/**
+ * Defines the standard text editor XSS filter.
+ */
+class Standard implements EditorXssFilterInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function filterXss($html, FilterFormatInterface $format, FilterFormatInterface $original_format = NULL) {
+    // Apply XSS filtering, but only blacklist the <script> tag.
+    return Xss::filter($html, array('script'), Xss::FILTER_MODE_BLACKLIST);
+  }
+
+}
diff --git a/core/modules/editor/tests/modules/editor_test.module b/core/modules/editor/tests/modules/editor_test.module
index fc2d748..4feffa1 100644
--- a/core/modules/editor/tests/modules/editor_test.module
+++ b/core/modules/editor/tests/modules/editor_test.module
@@ -5,6 +5,8 @@
  * Helper module for the Text Editor tests.
  */
 
+use Drupal\filter\FilterFormatInterface;
+
 /**
  * Implements hook_editor_default_settings().
  */
@@ -39,3 +41,18 @@ function editor_test_editor_js_settings_alter(&$settings) {
     $settings['full_html']['editorSettings']['ponyModeEnabled'] = FALSE;
   }
 }
+
+/**
+ * Implements hook_editor_xss_filter_alter().
+ */
+function editor_test_editor_xss_filter_alter(&$editor_xss_filter_class, FilterFormatInterface $format, FilterFormatInterface $original_format = NULL) {
+  // Allow tests to enable or disable this alter hook.
+  if (!\Drupal::state()->get('editor_test_editor_xss_filter_alter_enabled', FALSE)) {
+    return;
+  }
+
+  $filters = $format->filters()->getAll();
+  if (isset($filters['filter_html']) && $filters['filter_html']->status) {
+    $editor_xss_filter_class = '\Drupal\editor_test\EditorXssFilter\Insecure';
+  }
+}
diff --git a/core/modules/editor/tests/modules/lib/Drupal/editor_test/EditorXssFilter/Insecure.php b/core/modules/editor/tests/modules/lib/Drupal/editor_test/EditorXssFilter/Insecure.php
new file mode 100644
index 0000000..8f97d27
--- /dev/null
+++ b/core/modules/editor/tests/modules/lib/Drupal/editor_test/EditorXssFilter/Insecure.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\editor_test\EditorXssFilter\Insecure.
+ */
+
+namespace Drupal\editor_test\EditorXssFilter;
+
+use Drupal\filter\FilterFormatInterface;
+use Drupal\editor\EditorXssFilterInterface;
+
+/**
+ * Defines an insecure text editor XSS filter (for testing purposes).
+ */
+class Insecure implements EditorXssFilterInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function filterXss($html, FilterFormatInterface $format, FilterFormatInterface $original_format = NULL) {
+    // Don't apply any XSS filtering, just return the string we received.
+    return $html;
+  }
+
+}
diff --git a/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/Editor/UnicornEditor.php b/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/Editor/UnicornEditor.php
index e6b34cd..53d565c 100644
--- a/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/Editor/UnicornEditor.php
+++ b/core/modules/editor/tests/modules/lib/Drupal/editor_test/Plugin/Editor/UnicornEditor.php
@@ -11,13 +11,14 @@
 use Drupal\editor\Entity\Editor as EditorEntity;
 
 /**
- * Defines a Unicorn-powered text editor for Drupal.
+ * Defines a Unicorn-powered text editor for Drupal (for testing purposes).
  *
  * @Editor(
  *   id = "unicorn",
  *   label = @Translation("Unicorn Editor"),
  *   supports_content_filtering = TRUE,
- *   supports_inline_editing = TRUE
+ *   supports_inline_editing = TRUE,
+ *   is_xss_safe = FALSE
  * )
  */
 class UnicornEditor extends EditorBase {
diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module
index 64bc781..822e970 100644
--- a/core/modules/filter/filter.module
+++ b/core/modules/filter/filter.module
@@ -20,7 +20,7 @@
 const FILTER_TYPE_MARKUP_LANGUAGE = 0;
 
 /**
- * HTML tag and attribute restricting filters.
+ * HTML tag and attribute restricting filters to prevent XSS attacks.
  *
  * @todo Move into \Drupal\filter\Plugin\Filter\FilterInterface
  */
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php
index e19071b..8b6442d 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlCorrector.php
@@ -15,7 +15,7 @@
  * @Filter(
  *   id = "filter_htmlcorrector",
  *   title = @Translation("Correct faulty and chopped off HTML"),
- *   type = FILTER_TYPE_HTML_RESTRICTOR,
+ *   type = FILTER_TYPE_TRANSFORM_IRREVERSIBLE,
  *   weight = 10
  * )
  */
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlImageSecure.php b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlImageSecure.php
index 7a0e998..713af73 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlImageSecure.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Filter/FilterHtmlImageSecure.php
@@ -16,7 +16,7 @@
  *   id = "filter_html_image_secure",
  *   title = @Translation("Restrict images to this site"),
  *   description = @Translation("Disallows usage of &lt;img&gt; tag sources that are not hosted on this site by replacing them with a placeholder image."),
- *   type = FILTER_TYPE_HTML_RESTRICTOR,
+ *   type = FILTER_TYPE_TRANSFORM_IRREVERSIBLE,
  *   weight = 9
  * )
  */
diff --git a/core/tests/Drupal/Tests/Component/Utility/XssTest.php b/core/tests/Drupal/Tests/Component/Utility/XssTest.php
index 2614c5d..f14a06c 100644
--- a/core/tests/Drupal/Tests/Component/Utility/XssTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/XssTest.php
@@ -107,7 +107,7 @@ public function providerTestFilterXssNormalized() {
   }
 
   /**
-   * Tests limiting allowed tags and XSS prevention.
+   * Tests limiting to allowed tags and XSS prevention.
    *
    * XSS tests assume that script is disallowed by default and src is allowed
    * by default, but on* and style attributes are disallowed.
@@ -115,11 +115,11 @@ public function providerTestFilterXssNormalized() {
    * @param string $value
    *   The value to filter.
    * @param string $expected
-   *   The expected result.
+   *   The string that is expected to be missing.
    * @param string $message
    *   The assertion message to display upon failure.
    * @param array $allowed_tags
-   *   (Optional) The allowed tags to be passed on Xss::filter().
+   *   (optional) The allowed HTML tags to be passed to Xss::filter().
    *
    * @dataProvider providerTestFilterXssNotNormalized
    */
@@ -140,11 +140,11 @@ public function testFilterXssNotNormalized($value, $expected, $message, array $a
    *
    * @return array
    *   An array of arrays containing the following elements:
-   *     - The value to filter string.
-   *     - The value to expect after filtering string.
-   *     - The assertion message string.
-   *     - (optional) The allowed html tags array that should be passed to
-   *        Xss::filter().
+   *     - The value to filter.
+   *     - The value to expect that's missing after filtering.
+   *     - The assertion message.
+   *     - (optional) The allowed HTML HTML tags array that should be passed to
+   *       Xss::filter().
    */
   public function providerTestFilterXssNotNormalized() {
     $cases = array(
@@ -435,6 +435,65 @@ public function providerTestFilterXssNotNormalized() {
   }
 
   /**
+   * Tests removing disallowed tags and XSS prevention.
+   *
+   * Xss::filter() has the ability to run in blacklist mode, in which it still
+   * applies the exact same filtering, with one exception: it no longer works
+   * with a list of allowed tags, but with a list of disallowed tags.
+   *
+   * @param string $value
+   *   The value to filter.
+   * @param string $expected
+   *   The string that is expected to be missing.
+   * @param string $message
+   *   The assertion message to display upon failure.
+   * @param array $disallowed_tags
+   *   (optional) The disallowed HTML tags to be passed to Xss::filter().
+   *
+   * @dataProvider providerTestBlackListMode
+   */
+  public function testBlacklistMode($value, $expected, $message, array $disallowed_tags) {
+    $value = Xss::filter($value, $disallowed_tags, Xss::FILTER_MODE_BLACKLIST);
+    $this->assertSame($expected, $value, $message);
+  }
+
+  /**
+   * Data provider for testBlacklistMode().
+   *
+   * @see testBlacklistMode()
+   *
+   * @return array
+   *   An array of arrays containing the following elements:
+   *     - The value to filter.
+   *     - The value to expect after filtering.
+   *     - The assertion message.
+   *     - (optional) The disallowed HTML tags to be passed to Xss::filter().
+   */
+  public function providerTestBlackListMode() {
+    return array(
+      array(
+        '<unknown style="visibility:hidden">Pink Fairy Armadillo</unknown><video src="gerenuk.mp4"><script>alert(0)</script>',
+        '<unknown>Pink Fairy Armadillo</unknown><video src="gerenuk.mp4">alert(0)',
+        'Disallow only the script tag',
+        array('script')
+      ),
+      array(
+        '<unknown style="visibility:hidden">Pink Fairy Armadillo</unknown><video src="gerenuk.mp4"><script>alert(0)</script>',
+        '<unknown>Pink Fairy Armadillo</unknown>alert(0)',
+        'Disallow both the script and video tags',
+        array('script', 'video')
+      ),
+      // No real use case for this, but it is an edge case we must ensure works.
+      array(
+        '<unknown style="visibility:hidden">Pink Fairy Armadillo</unknown><video src="gerenuk.mp4"><script>alert(0)</script>',
+        '<unknown>Pink Fairy Armadillo</unknown><video src="gerenuk.mp4"><script>alert(0)</script>',
+        'Disallow no tags',
+        array()
+      ),
+    );
+  }
+
+  /**
    * Checks that invalid multi-byte sequences are rejected.
    *
    * @param string $value
@@ -521,7 +580,7 @@ public function providerTestFilterXssAdminNotNormalized() {
   }
 
   /**
-   * Asserts that a text transformed to lowercase with HTML entities decoded does contains a given string.
+   * Asserts that a text transformed to lowercase with HTML entities decoded does contain a given string.
    *
    * Otherwise fails the test with a given message, similar to all the
    * SimpleTest assert* functions.
