diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php
index d4bf13e..2efad30 100644
--- a/core/lib/Drupal/Component/Utility/Html.php
+++ b/core/lib/Drupal/Component/Utility/Html.php
@@ -348,4 +348,48 @@ public static function decodeEntities($text) {
     return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
   }
 
+  /**
+   * Encodes all HTML entities.
+   *
+   * Already escaped entities will be double-escaped ("&lt;" becomes
+   * "&amp;lt;").
+   *
+   * @param string $text
+   *   The text to encode entities in.
+   *
+   * @return string
+   *   The input $text, with all HTML entities encoded.
+   *
+   * @ingroup sanitization
+   */
+  public static function encodeEntities($text) {
+    return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
+  }
+
+  /**
+   * Encodes all HTML entities if $text is not marked safe.
+   *
+   * Already escaped entities will be double-escaped ("&lt;" becomes
+   * "&amp;lt;"), unless $text is marked safe.
+   *
+   * @param string $text
+   *   The text to encode entities in if $text is not marked safe.
+   *
+   * @return string
+   *   The input $text, with all HTML entities encoded if $text is not marked
+   *   safe.
+   *
+   * @ingroup sanitization
+   *
+   * @see \Drupal\Component\Utility\SafeMarkup::isSafe()
+   * @see \Drupal\Component\Utility\SafeStringInterface
+   */
+  public static function encodeEntitiesIfUnsafe($text) {
+    if (!SafeMarkup::isSafe($text)) {
+      $text = static::encodeEntities($text);
+    }
+    return $text;
+  }
+
+
 }
diff --git a/core/lib/Drupal/Component/Utility/SafeMarkup.php b/core/lib/Drupal/Component/Utility/SafeMarkup.php
index af9170d..f047012 100644
--- a/core/lib/Drupal/Component/Utility/SafeMarkup.php
+++ b/core/lib/Drupal/Component/Utility/SafeMarkup.php
@@ -125,20 +125,6 @@ public static function setMultiple(array $safe_strings) {
   }
 
   /**
-   * Encodes special characters in a plain-text string for display as HTML.
-   *
-   * @param string $string
-   *   A string.
-   *
-   * @return string
-   *   The escaped string. If $string was already set as safe with
-   *   self::set(), it won't be escaped again.
-   */
-  public static function escape($string) {
-    return static::isSafe($string) ? $string : static::checkPlain($string);
-  }
-
-  /**
    * Filters HTML for XSS vulnerabilities and marks the result as safe.
    *
    * Calling this method unnecessarily will result in bloating the safe string
@@ -280,7 +266,7 @@ public static function format($string, array $args = array()) {
       switch ($key[0]) {
         case '@':
           // Escaped only.
-          $args[$key] = static::escape($value);
+          $args[$key] = Html::encodeEntitiesIfUnsafe($value);
           break;
 
         case '%':
@@ -317,7 +303,7 @@ public static function format($string, array $args = array()) {
    *   The formatted text (html).
    */
   public static function placeholder($text) {
-    $string = '<em class="placeholder">' . static::escape($text) . '</em>';
+    $string = '<em class="placeholder">' . Html::encodeEntitiesIfUnsafe($text) . '</em>';
     static::$safeStrings[$string]['html'] = TRUE;
     return $string;
   }
diff --git a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
index 741ab0c..a40a55b 100644
--- a/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\EventSubscriber;
 
+use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
@@ -110,7 +111,7 @@ protected function onHtml(GetResponseForExceptionEvent $event) {
 
         // Generate a backtrace containing only scalar argument values. Make
         // sure the backtrace is escaped as it can contain user submitted data.
-        $message .= '<pre class="backtrace">' . SafeMarkup::escape(Error::formatBacktrace($backtrace)) . '</pre>';
+        $message .= '<pre class="backtrace">' . Html::encodeEntities(Error::formatBacktrace($backtrace)) . '</pre>';
       }
     }
 
diff --git a/core/lib/Drupal/Core/Form/FormErrorHandler.php b/core/lib/Drupal/Core/Form/FormErrorHandler.php
index df736b0..03c86c9 100644
--- a/core/lib/Drupal/Core/Form/FormErrorHandler.php
+++ b/core/lib/Drupal/Core/Form/FormErrorHandler.php
@@ -82,9 +82,7 @@ protected function displayErrorMessages(array $form, FormStateInterface $form_st
         unset($errors[$name]);
       }
       elseif ($is_visible_element && $has_title && $has_id) {
-        // We need to pass this through SafeMarkup::escape() so
-        // drupal_set_message() does not encode the links.
-        $error_links[] = SafeMarkup::escape($this->l($title, Url::fromRoute('<none>', [], ['fragment' => $form_element['#id'], 'external' => TRUE])));
+        $error_links[] = $this->l($title, Url::fromRoute('<none>', [], ['fragment' => $form_element['#id'], 'external' => TRUE]));
         unset($errors[$name]);
       }
     }
diff --git a/core/modules/content_translation/src/ContentTranslationHandler.php b/core/modules/content_translation/src/ContentTranslationHandler.php
index 10cf5f3..3979fff 100644
--- a/core/modules/content_translation/src/ContentTranslationHandler.php
+++ b/core/modules/content_translation/src/ContentTranslationHandler.php
@@ -290,8 +290,8 @@ public function entityFormAlter(array &$form, FormStateInterface $form_state, En
       $title = $this->entityFormTitle($entity);
       // When editing the original values display just the entity label.
       if ($form_langcode != $entity_langcode) {
-        $t_args = array('%language' => $languages[$form_langcode]->getName(), '%title' => $entity->label(), '!title' => $title);
-        $title = empty($source_langcode) ? t('!title [%language translation]', $t_args) : t('Create %language translation of %title', $t_args);
+        $t_args = array('%language' => $languages[$form_langcode]->getName(), '%title' => $entity->label(), '@title' => $title);
+        $title = empty($source_langcode) ? t('@title [%language translation]', $t_args) : t('Create %language translation of %title', $t_args);
       }
       $form['#title'] = $title;
     }
diff --git a/core/modules/field_ui/src/FieldStorageConfigListBuilder.php b/core/modules/field_ui/src/FieldStorageConfigListBuilder.php
index 6e74442..7cc2275 100644
--- a/core/modules/field_ui/src/FieldStorageConfigListBuilder.php
+++ b/core/modules/field_ui/src/FieldStorageConfigListBuilder.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\field_ui;
 
+use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
 use Drupal\Core\Entity\EntityInterface;
@@ -122,7 +123,7 @@ public function buildRow(EntityInterface $field_storage) {
     $usage_escaped = '';
     $separator = '';
     foreach ($usage as $usage_item) {
-      $usage_escaped .=  $separator . SafeMarkup::escape($usage_item);
+      $usage_escaped .=  $separator . Html::encodeEntitiesIfUnsafe($usage_item);
       $separator = ', ';
     }
     $row['data']['usage'] = SafeMarkup::set($usage_escaped);
diff --git a/core/modules/node/src/Plugin/Search/NodeSearch.php b/core/modules/node/src/Plugin/Search/NodeSearch.php
index 87a3851..bd390c6 100644
--- a/core/modules/node/src/Plugin/Search/NodeSearch.php
+++ b/core/modules/node/src/Plugin/Search/NodeSearch.php
@@ -335,7 +335,8 @@ protected function prepareResults(StatementInterface $found) {
       // Fetch comment count for snippet.
       $rendered = SafeMarkup::set(
         $this->renderer->renderPlain($build) . ' ' .
-        SafeMarkup::escape($this->moduleHandler->invoke('comment', 'node_update_index', array($node, $item->langcode)))
+        // Returns the result of a render so definitely safe.
+        $this->moduleHandler->invoke('comment', 'node_update_index', array($node, $item->langcode))
       );
 
       $extra = $this->moduleHandler->invokeAll('node_search_result', array($node, $item->langcode));
diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module
index 1beb1a1..b01dc3c 100644
--- a/core/modules/rdf/rdf.module
+++ b/core/modules/rdf/rdf.module
@@ -247,6 +247,9 @@ function rdf_comment_storage_load($comments) {
  */
 function rdf_theme() {
   return array(
+    'rdf_wrapper' => array(
+      'variables' => array('attributes' => array(), 'content' => NULL),
+    ),
     'rdf_metadata' => array(
       'variables' => array('metadata' => array()),
     ),
@@ -440,11 +443,19 @@ function rdf_preprocess_comment(&$variables) {
   // Adds RDFa markup for the relation between the comment and its author.
   $author_mapping = $mapping->getPreparedFieldMapping('uid');
   if (!empty($author_mapping)) {
-    $author_attributes = array('rel' => $author_mapping['properties']);
-    // Wraps the author variable and the submitted variable which are both
-    // available in comment.html.twig.
-    $variables['author'] = SafeMarkup::set('<span ' . new Attribute($author_attributes) . '>' . $variables['author'] . '</span>');
-    $variables['submitted'] = SafeMarkup::set('<span ' . new Attribute($author_attributes) . '>' . $variables['submitted'] . '</span>');
+    $author_attributes = ['rel' => $author_mapping['properties']];
+    // Wraps the 'author' and 'submitted' variables which are both available in
+    // comment.html.twig.
+    $variables['author'] = [
+      '#theme' => 'rdf_wrapper',
+      '#content' => $variables['author'],
+      '#attributes' => $author_attributes,
+    ];
+    $variables['submitted'] = [
+      '#theme' => 'rdf_wrapper',
+      '#content' => $variables['submitted'],
+      '#attributes' => $author_attributes,
+    ];
   }
   // Adds RDFa markup for the date of the comment.
   $created_mapping = $mapping->getPreparedFieldMapping('created');
@@ -457,11 +468,13 @@ function rdf_preprocess_comment(&$variables) {
       '#theme' => 'rdf_metadata',
       '#metadata' => array($date_attributes),
     );
-    $created_metadata_markup = drupal_render($rdf_metadata);
-    // Appends the markup to the created variable and the submitted variable
-    // which are both available in comment.html.twig.
-    $variables['created'] = SafeMarkup::set(SafeMarkup::escape($variables['created']) . $created_metadata_markup);
-    $variables['submitted'] = SafeMarkup::set($variables['submitted'] . $created_metadata_markup);
+    // Ensure the original variable is represented as a render array.
+    $created = !is_array($variables['created']) ? ['#markup' => $variables['created']] : $variables['created'];
+    $submitted = !is_array($variables['submitted']) ? ['#markup' => $variables['submitted']] : $variables['submitted'];
+    // Append RDF metadata to elements which are both available
+    // in comment.html.twig.
+    $variables['created'] = [$created, $rdf_metadata];
+    $variables['submitted'] = [$submitted, $rdf_metadata];
   }
   $title_mapping = $mapping->getPreparedFieldMapping('subject');
   if (!empty($title_mapping)) {
diff --git a/core/modules/rdf/templates/rdf-wrapper.html.twig b/core/modules/rdf/templates/rdf-wrapper.html.twig
new file mode 100644
index 0000000..9ac5542
--- /dev/null
+++ b/core/modules/rdf/templates/rdf-wrapper.html.twig
@@ -0,0 +1,13 @@
+{#
+/**
+ * @file
+ * Default theme implementation for wrapping content with RDF attributes.
+ *
+ * Available variables:
+ * - content: The content being wrapped with RDF attributes.
+ * - attributes: HTML attributes for the containing element.
+ *
+ * @ingroup themeable
+ */
+#}
+<span{{ attributes }}>{{ content }}</span>
diff --git a/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.module b/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.module
index 6ce623d..58c90e5 100644
--- a/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.module
+++ b/core/modules/search/tests/modules/search_embedded_form/search_embedded_form.module
@@ -9,12 +9,12 @@
  * individual product (node) listed in the search results.
  */
 
-use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\Html;
 
 /**
  * Adds the test form to search results.
  */
 function search_embedded_form_preprocess_search_result(&$variables) {
   $form = \Drupal::formBuilder()->getForm('Drupal\search_embedded_form\Form\SearchEmbeddedForm');
-  $variables['snippet'] = ['#markup' => SafeMarkup::escape($variables['snippet']), $form];
+  $variables['snippet'] = ['#markup' => Html::encodeEntitiesIfUnsafe($variables['snippet']), $form];
 }
diff --git a/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc b/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc
index b1f2796..605e0d8 100644
--- a/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc
+++ b/core/modules/system/tests/modules/batch_test/batch_test.callbacks.inc
@@ -5,6 +5,7 @@
  * Batch callbacks for the Batch API tests.
  */
 
+use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Url;
 use Symfony\Component\HttpFoundation\RedirectResponse;
@@ -91,7 +92,7 @@ function _batch_test_finished_helper($batch_id, $success, $results, $operations)
   $messages = array("results for batch $batch_id");
   if ($results) {
     foreach ($results as $op => $op_results) {
-      $messages[] = 'op '. SafeMarkup::escape($op) . ': processed ' . count($op_results) . ' elements';
+      $messages[] = 'op '. Html::encodeEntities($op) . ': processed ' . count($op_results) . ' elements';
     }
   }
   else {
diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
index 7728d89..2195a41 100644
--- a/core/modules/views/views.theme.inc
+++ b/core/modules/views/views.theme.inc
@@ -578,7 +578,7 @@ function template_preprocess_views_view_table(&$variables) {
         $field_output = $handler->getField($num, $field);
         $element_type = $fields[$field]->elementType(TRUE, TRUE);
         if ($element_type) {
-          $field_output = SafeMarkup::set('<' . $element_type . '>' .  SafeMarkup::escape($field_output) . '</' . $element_type . '>');
+          $field_output = SafeMarkup::set('<' . $element_type . '>' .  Html::encodeEntitiesIfUnsafe($field_output) . '</' . $element_type . '>');
         }
 
         // Only bother with separators and stuff if the field shows up.
@@ -586,7 +586,7 @@ function template_preprocess_views_view_table(&$variables) {
           // Place the field into the column, along with an optional separator.
           if (!empty($column_reference['content'])) {
             if (!empty($options['info'][$column]['separator'])) {
-              $safe_content = SafeMarkup::escape($column_reference['content']);
+              $safe_content = Html::encodeEntitiesIfUnsafe($column_reference['content']);
               $safe_separator = Xss::filterAdmin($options['info'][$column]['separator']);
               $column_reference['content'] = SafeMarkup::set($safe_content . $safe_separator);
             }
@@ -594,8 +594,8 @@ function template_preprocess_views_view_table(&$variables) {
           else {
             $column_reference['content'] = '';
           }
-          $safe_content = SafeMarkup::escape($column_reference['content']);
-          $safe_field_output = SafeMarkup::escape($field_output);
+          $safe_content = Html::encodeEntitiesIfUnsafe($column_reference['content']);
+          $safe_field_output = Html::encodeEntitiesIfUnsafe($field_output);
           $column_reference['content'] = SafeMarkup::set($safe_content . $safe_field_output);
         }
       }
diff --git a/core/modules/views_ui/src/Tests/DisplayPathTest.php b/core/modules/views_ui/src/Tests/DisplayPathTest.php
index 004b9f0..62c1f2f 100644
--- a/core/modules/views_ui/src/Tests/DisplayPathTest.php
+++ b/core/modules/views_ui/src/Tests/DisplayPathTest.php
@@ -35,6 +35,7 @@ class DisplayPathTest extends UITestBase {
   public function testPathUI() {
     $this->doBasicPathUITest();
     $this->doAdvancedPathsValidationTest();
+    $this->doPathXssFilterTest();
   }
 
   /**
@@ -60,6 +61,29 @@ protected function doBasicPathUITest() {
   }
 
   /**
+   * Tests that View paths are properly filtered for XSS.
+   */
+  public function doPathXssFilterTest() {
+    global $base_path;
+    $this->drupalGet('admin/structure/views/view/test_view');
+    $this->drupalPostForm(NULL, array(), 'Add Page');
+    $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_2/path', array('path' => '<object>malformed_path</object>'), t('Apply'));
+    $this->drupalPostForm(NULL, array(), 'Add Page');
+    $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_3/path', array('path' => '<script>alert("hello");</script>'), t('Apply'));
+    $this->drupalPostForm(NULL, array(), 'Add Page');
+    $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_4/path', array('path' => '<script>alert("hello I have placeholders %");</script>'), t('Apply'));
+    $this->drupalPostForm('admin/structure/views/view/test_view', array(), t('Save'));
+    $this->drupalGet('admin/structure/views');
+    // The anchor text should be escaped.
+    $this->assertEscaped('/<object>malformed_path</object>');
+    $this->assertEscaped('/<script>alert("hello");</script>');
+    $this->assertEscaped('/<script>alert("hello I have placeholders %");</script>');
+    // Links should be url-encoded.
+    $this->assertRaw('/%3Cobject%3Emalformed_path%3C/object%3E');
+    $this->assertRaw('/%3Cscript%3Ealert%28%22hello%22%29%3B%3C/script%3E');
+  }
+
+  /**
    * Tests a couple of invalid path patterns.
    */
   protected function doAdvancedPathsValidationTest() {
diff --git a/core/modules/views_ui/src/ViewListBuilder.php b/core/modules/views_ui/src/ViewListBuilder.php
index 00be178..9590f19 100644
--- a/core/modules/views_ui/src/ViewListBuilder.php
+++ b/core/modules/views_ui/src/ViewListBuilder.php
@@ -91,12 +91,6 @@ public function load() {
    */
   public function buildRow(EntityInterface $view) {
     $row = parent::buildRow($view);
-    $display_paths = '';
-    $separator = '';
-    foreach ($this->getDisplayPaths($view) as $display_path) {
-      $display_paths .= $separator . SafeMarkup::escape($display_path);
-      $separator = ', ';
-    }
     return array(
       'data' => array(
         'view_name' => array(
@@ -113,7 +107,13 @@ public function buildRow(EntityInterface $view) {
           'class' => array('views-table-filter-text-source'),
         ),
         'tag' => $view->get('tag'),
-        'path' => SafeMarkup::set($display_paths),
+        'path' => array(
+          'data' => array(
+            '#type' => 'inline_template',
+            '#template' => '{{ display_paths|safe_join(", ") }}',
+            '#context' => array('display_paths' => $this->getDisplayPaths($view)),
+          ),
+        ),
         'operations' => $row['operations'],
       ),
       'title' => $this->t('Machine name: @name', array('@name' => $view->id())),
diff --git a/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php b/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
index a42a879..e51e681 100644
--- a/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
+++ b/core/modules/views_ui/tests/src/Unit/ViewListBuilderTest.php
@@ -89,7 +89,10 @@ public function testBuildRowEntityList() {
     );
     $page_display->expects($this->any())
       ->method('getPath')
-      ->will($this->returnValue('test_page'));
+      ->will($this->onConsecutiveCalls(
+        $this->returnValue('test_page'),
+        $this->returnValue('<object>malformed_path</object>'),
+        $this->returnValue('<script>alert("placeholder_page/%")</script>')));
 
     $embed_display = $this->getMock('Drupal\views\Plugin\views\display\Embed', array('initDisplay'),
       array(array(), 'default', $display_manager->getDefinition('embed'))
@@ -106,6 +109,16 @@ public function testBuildRowEntityList() {
     $values['display']['page_1']['display_plugin'] = 'page';
     $values['display']['page_1']['display_options']['path'] = 'test_page';
 
+    $values['display']['page_2']['id'] = 'page_2';
+    $values['display']['page_2']['display_title'] = 'Page 2';
+    $values['display']['page_2']['display_plugin'] = 'page';
+    $values['display']['page_2']['display_options']['path'] = '<object>malformed_path</object>';
+
+    $values['display']['page_3']['id'] = 'page_3';
+    $values['display']['page_3']['display_title'] = 'Page 3';
+    $values['display']['page_3']['display_plugin'] = 'page';
+    $values['display']['page_3']['display_options']['path'] = '<script>alert("placeholder_page/%")</script>';
+
     $values['display']['embed']['id'] = 'embed';
     $values['display']['embed']['display_title'] = 'Embedded';
     $values['display']['embed']['display_plugin'] = 'embed';
@@ -115,6 +128,8 @@ public function testBuildRowEntityList() {
       ->will($this->returnValueMap(array(
         array('default', $values['display']['default'], $default_display),
         array('page', $values['display']['page_1'], $page_display),
+        array('page', $values['display']['page_2'], $page_display),
+        array('page', $values['display']['page_3'], $page_display),
         array('embed', $values['display']['embed'], $embed_display),
       )));
 
@@ -141,8 +156,16 @@ public function testBuildRowEntityList() {
 
     $row = $view_list_builder->buildRow($view);
 
-    $this->assertEquals(array('Embed admin label', 'Page admin label'), $row['data']['view_name']['data']['#displays'], 'Wrong displays got added to view list');
-    $this->assertEquals($row['data']['path'], '/test_page', 'The path of the page display is not added.');
+    $expected_displays = array(
+      'Embed admin label',
+      'Page admin label',
+      'Page admin label',
+      'Page admin label',
+    );
+    $this->assertEquals($expected_displays, $row['data']['view_name']['data']['#displays']);
+
+    $display_paths = $row['data']['path']['data']['#context']['display_paths'];
+    $this->assertEquals('/test_page, /&lt;object&gt;malformed_path&lt;/object&gt;, /&lt;script&gt;alert(&quot;placeholder_page/%&quot;)&lt;/script&gt;', implode(', ', $display_paths));
   }
 
 }
diff --git a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
index 90a475f..e40cbe5 100644
--- a/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/SafeMarkupTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Component\Utility;
 
+use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\Xss;
 use Drupal\Tests\UnitTestCase;
@@ -217,7 +218,7 @@ public function testReplace($search, $replace, $subject, $expected, $is_safe) {
    * Tests the interaction between the safe list and XSS filtering.
    *
    * @covers ::xssFilter
-   * @covers ::escape
+   * @covers Drupal\Component\Utility\Html::encodeEntitiesIfUnsafe
    */
   public function testAdminXss() {
     // Use the predefined XSS admin tag list. This strips the <marquee> tags.
@@ -234,9 +235,9 @@ public function testAdminXss() {
     // <marquee> tag even though the string was marked safe above.
     $this->assertEquals('text', SafeMarkup::xssFilter('<marquee>text</marquee>'));
 
-    // SafeMarkup::escape() will not escape the markup tag since the string was
+    // Html::encodeEntitiesIfUnsafe() will not escape the markup tag since the string was
     // marked safe above.
-    $this->assertEquals('<marquee>text</marquee>', SafeMarkup::escape($filtered));
+    $this->assertEquals('<marquee>text</marquee>', Html::encodeEntitiesIfUnsafe($filtered));
 
     // SafeMarkup::checkPlain() will escape the markup tag even though the
     // string was marked safe above.
diff --git a/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php b/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
index 53d7e76..87de169 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormErrorHandlerTest.php
@@ -7,7 +7,10 @@
 
 namespace Drupal\Tests\Core\Form;
 
+use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Form\FormState;
+use Drupal\Core\Url;
 use Drupal\Tests\UnitTestCase;
 
 /**
@@ -24,7 +27,10 @@ public function testDisplayErrorMessages() {
     $link_generator = $this->getMock('Drupal\Core\Utility\LinkGeneratorInterface');
     $link_generator->expects($this->any())
       ->method('generate')
-      ->willReturnArgument(0);
+      ->willReturnCallback(function($text, Url $url, $collect_bubbleable_metadata = FALSE) {
+        return Html::encodeEntities($text);
+      });
+
     $form_error_handler = $this->getMockBuilder('Drupal\Core\Form\FormErrorHandler')
       ->setConstructorArgs([$this->getStringTranslationStub(), $link_generator])
       ->setMethods(['drupalSetMessage'])
@@ -95,6 +101,9 @@ public function testDisplayErrorMessages() {
     $form_state->setErrorByName('missing_element', 'this missing element is invalid');
     $form_error_handler->handleFormErrors($form, $form_state);
     $this->assertSame('invalid', $form['test1']['#errors']);
+    // Ensure that error messages have not been marked safe so that the render
+    // system will do the escaping.
+    $this->assertFalse(SafeMarkup::isSafe('3 errors have been found: Test 1, Test 2 & a half, Test 3'));
   }
 
   /**
diff --git a/core/themes/engines/twig/twig.engine b/core/themes/engines/twig/twig.engine
index af5ebe7..f2c3369 100644
--- a/core/themes/engines/twig/twig.engine
+++ b/core/themes/engines/twig/twig.engine
@@ -5,8 +5,10 @@
  * Handles integration of Twig templates with the Drupal theme system.
  */
 
+use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Extension\Extension;
+use Drupal\Core\Render\SafeString;
 
 /**
  * Implements hook_theme().
@@ -161,16 +163,16 @@ function twig_without($element) {
  *   This value is expected to be safe for output and user provided data should
  *   never be used as a glue.
  *
- * @return \Drupal\Component\Utility\SafeMarkup|string
+ * @return \Drupal\Core\Render\SafeString|string
  *   The imploded string, which is now also marked as safe.
  */
 function twig_drupal_join_filter($value, $glue = '') {
   $separator = '';
   $output = '';
   foreach ($value as $item) {
-    $output .= $separator . SafeMarkup::escape($item);
+    $output .= $separator . Html::encodeEntitiesIfUnsafe($item);
     $separator = $glue;
   }
 
-  return SafeMarkup::set($output);
+  return SafeString::create($output);
 }
