diff --git a/core/includes/common.inc b/core/includes/common.inc
index 4163da2..be5f322 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -639,6 +639,8 @@ function drupal_http_header_attributes(array $attributes = array()) {
  *
  * @param string|array $text
  *   The link text for the anchor tag as a translated string or render array.
+ *   Strings will be sanitized automatically. If you need to output HTML in the
+ *   link text you should use a render array.
  * @param string $path
  *   The internal path or external URL being linked to, such as "node/34" or
  *   "http://example.com/foo". After the url() function is called to construct
@@ -654,11 +656,6 @@ function drupal_http_header_attributes(array $attributes = array()) {
  *     must be a string; other elements are more flexible, as they just need
  *     to work as an argument for the constructor of the class
  *     Drupal\Core\Template\Attribute($options['attributes']).
- *   - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
- *     example, to make an image tag into a link, this must be set to TRUE, or
- *     you will see the escaped HTML image tag. $text is not sanitized if
- *     'html' is TRUE. The calling function must ensure that $text is already
- *     safe.
  *   - 'language': An optional language object. If the path being linked to is
  *     internal to the site, $options['language'] is used to determine whether
  *     the link is "active", or pointing to the current page (the language as
@@ -711,7 +708,6 @@ function _l($text, $path, array $options = array()) {
   $variables['options'] += array(
     'attributes' => array(),
     'query' => array(),
-    'html' => FALSE,
     'language' => NULL,
     'set_active_class' => FALSE,
   );
@@ -756,8 +752,9 @@ function _l($text, $path, array $options = array()) {
   // in an HTML argument context, we need to encode it properly.
   $url = String::checkPlain(_url($variables['path'], $variables['options']));
 
-  // Sanitize the link text if necessary.
-  $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']);
+  // Sanitize the link text.
+  $text = SafeMarkup::escape($variables['text']);
+
   return SafeMarkup::set('<a href="' . $url . '"' . $attributes . '>' . $text . '</a>');
 }
 
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 76aafd0..bf9c769 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -329,26 +329,30 @@ function template_preprocess_menu_local_task(&$variables) {
   $link += array(
     'localized_options' => array(),
   );
-  $link_text = $link['title'];
 
   if (!empty($variables['element']['#active'])) {
     // Add text to indicate active tab for non-visual users.
-    $active = '<span class="visually-hidden">' . t('(active tab)') . '</span>';
     $variables['attributes']['class'] = array('active');
 
-    // If the link does not contain HTML already, String::checkPlain() it now.
-    // After we set 'html'=TRUE the link will not be sanitized by l().
-    if (empty($link['localized_options']['html'])) {
-      $link['title'] = String::checkPlain($link['title']);
-    }
-    $link['localized_options']['html'] = TRUE;
-    $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active));
+    // Build up an inline template which will be autoescaped.
+    $link_text = array(
+      '#type' => 'inline_template',
+      '#template' => '{{ title }}<span class="visually-hidden">{% trans %}(active tab){% endtrans %}></span>',
+      '#context' => array('title' => $link['title']),
+    );
+    $title = drupal_render($link_text);
   }
+  else {
+    // @todo Remove expicit escaping when https://www.drupal.org/node/2338081
+    //   gets fixed.
+    $title = String::checkPlain($link['title']);
+  }
+
   $link['localized_options']['set_active_class'] = TRUE;
 
   $variables['link'] = array(
     '#type' => 'link',
-    '#title' => $link_text,
+    '#title' => $title,
     '#url' => $link['url'],
     '#options' => $link['localized_options'],
   );
diff --git a/core/includes/tablesort.inc b/core/includes/tablesort.inc
index 0258a76..db8eff6 100644
--- a/core/includes/tablesort.inc
+++ b/core/includes/tablesort.inc
@@ -43,6 +43,11 @@ function tablesort_init($header) {
 function tablesort_header(&$cell_content, array &$cell_attributes, array $header, array $ts) {
   // Special formatting for the currently sorted column header.
   if (isset($cell_attributes['field'])) {
+    $text = array(
+      'cell_content' => array(
+        '#markup' => $cell_content,
+      ),
+    );
     $title = t('sort by @s', array('@s' => $cell_content));
     if ($cell_content == $ts['name']) {
       // aria-sort is a WAI-ARIA property that indicates if items in a table
@@ -51,24 +56,24 @@ function tablesort_header(&$cell_content, array &$cell_attributes, array $header
       $cell_attributes['aria-sort'] = ($ts['sort'] == 'asc') ? 'ascending' : 'descending';
       $ts['sort'] = (($ts['sort'] == 'asc') ? 'desc' : 'asc');
       $cell_attributes['class'][] = 'active';
-      $tablesort_indicator = array(
-        '#theme' => 'tablesort_indicator',
-        '#style' => $ts['sort'],
-      );
-      $image = drupal_render($tablesort_indicator);
     }
     else {
-      // If the user clicks a different header, we want to sort ascending initially.
+      // If the user clicks a different header, we want to sort ascending
+      // initially.
       $ts['sort'] = 'asc';
-      $image = '';
     }
-    $cell_content = \Drupal::l($cell_content . $image, new Url('<current>', [], [
+
+    // Append the sort indicator to the cell content.
+    $text['image'] = [
+      '#theme' => 'tablesort_indicator',
+      '#style' => $ts['sort'],
+    ];
+    $cell_content = \Drupal::l($text, new Url('<current>', [], [
       'attributes' => array('title' => $title),
       'query' => array_merge($ts['query'], array(
         'sort' => $ts['sort'],
         'order' => $cell_content,
       )),
-      'html' => TRUE,
     ]));
 
     unset($cell_attributes['field'], $cell_attributes['sort']);
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index e772289..e5ddf10 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -904,9 +904,6 @@ function template_preprocess_status_messages(&$variables) {
  *     - title: The link text.
  *     - url: (optional) The url object to link to. If omitted, no a tag is
  *       printed out.
- *     - html: (optional) Whether or not 'title' is HTML. If set, the title
- *       will not be passed through
- *       \Drupal\Component\Utility\String::checkPlain().
  *     - attributes: (optional) Attributes for the anchor, or for the <span>
  *       tag used in its place if no 'href' is supplied. If element 'class' is
  *       included, it must be an array of one or more class names.
@@ -988,7 +985,7 @@ function template_preprocess_links(&$variables) {
       $keys = ['title', 'url'];
       $link_element = array(
         '#type' => 'link',
-        '#title' => $link['title'],
+        '#title' => is_array($link['title']) ? drupal_render($link['title']) : SafeMarkup::escape($link['title']),
         '#options' => array_diff_key($link, array_combine($keys, $keys)),
         '#url' => $link['url'],
         '#ajax' => $link['ajax'],
@@ -1030,8 +1027,7 @@ function template_preprocess_links(&$variables) {
       }
 
       // Handle title-only text items.
-      $text = (!empty($link['html']) ? $link['title'] : String::checkPlain($link['title']));
-      $item['text'] = $text;
+      $item['text'] = $link_element['#title'];
       if (isset($link['attributes'])) {
         $item['text_attributes'] = new Attribute($link['attributes']);
       }
diff --git a/core/lib/Drupal/Core/Render/Element/Actions.php b/core/lib/Drupal/Core/Render/Element/Actions.php
index 1aab7d1..8cac9e0 100644
--- a/core/lib/Drupal/Core/Render/Element/Actions.php
+++ b/core/lib/Drupal/Core/Render/Element/Actions.php
@@ -97,7 +97,6 @@ public static function preRenderActionsDropbutton(&$element, FormStateInterface
         $button = drupal_render($element[$key]);
         $dropbuttons[$dropbutton]['#links'][$key] = array(
           'title' => $button,
-          'html' => TRUE,
         );
       }
     }
diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php
index a6e8326..35f729d 100644
--- a/core/lib/Drupal/Core/Utility/LinkGenerator.php
+++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php
@@ -75,8 +75,7 @@ public function generate($text, Url $url) {
 
     // Start building a structured representation of our link to be altered later.
     $variables = array(
-      // @todo Inject the service when drupal_render() is converted to one.
-      'text' => is_array($text) ? drupal_render($text) : $text,
+      'text' => is_array($text) ? $this->drupalRender($text) : $text,
       'url' => $url,
       'options' => $url->getOptions(),
     );
@@ -85,7 +84,6 @@ public function generate($text, Url $url) {
     $variables['options'] += array(
       'attributes' => array(),
       'query' => array(),
-      'html' => FALSE,
       'language' => NULL,
       'set_active_class' => FALSE,
       'absolute' => FALSE,
@@ -135,9 +133,31 @@ public function generate($text, Url $url) {
     // it here in an HTML argument context, we need to encode it properly.
     $url = String::checkPlain($url->toString());
 
-    // Sanitize the link text if necessary.
-    $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']);
+    // Make sure the link text is sanitized.
+    $text = SafeMarkup::escape($variables['text']);
+
+    if (array_key_exists('html', $variables['options'])) {
+      throw new \Exception('$variables[options][html] found');
+    }
+
     return SafeMarkup::set('<a href="' . $url . '"' . $attributes . '>' . $text . '</a>');
   }
 
+  /**
+   * Wraps drupal_render().
+   *
+   * @param array $elements
+   *   The structured array describing the data to be rendered.
+   * @param bool $is_root_call
+   *   (Internal use only.) Whether this is a recursive call or not. See
+   *   drupal_render_root().
+   *
+   * @return string
+   *   The rendered HTML.
+   *
+   * @see drupal_render()
+   */
+  protected function drupalRender(&$elements, $is_root_call = FALSE) {
+    return drupal_render($elements, $is_root_call);
+  }
 }
diff --git a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
index ba47197..142ce23 100644
--- a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
+++ b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
@@ -18,16 +18,28 @@
   /**
    * Renders a link to a URL.
    *
+   * Examples:
+   * @code
+   * $link_generator = \Drupal::service('link_generator');
+   * $installer_url = \Drupal\Core\Url::fromUri('base://core/install.php');
+   * $installer_link = $link_generator->generate($text, $installer_url);
+   * $external_url = \Drupal\Core\Url::fromUri('http://example.com', ['query' => ['foo' => 'bar']]);
+   * $external_link = $link_generator->generate($text, $external_url);
+   * $internal_url = \Drupal\Core\Url::fromRoute('system.admin');
+   * $internal_link = $link_generator->generate($text, $internal_url);
+   * @endcode
    * However, for links enclosed in translatable text you should use t() and
    * embed the HTML anchor tag directly in the translated string. For example:
    * @code
-   * t('Visit the <a href="@url">content types</a> page', array('@url' => \Drupal::url('node.overview_types')));
+   * $text = t('Visit the <a href="@url">content types</a> page', array('@url' => \Drupal::url('node.overview_types')));
    * @endcode
    * This keeps the context of the link title ('settings' in the example) for
    * translators.
    *
    * @param string|array $text
    *   The link text for the anchor tag as a translated string or render array.
+   *   Strings will be sanitized automatically. If you need to output HTML in
+   *   the link text you should use a render array.
    * @param \Drupal\Core\Url $url
    *   The URL object used for the link. Amongst its options, the following may
    *   be set to affect the generated link:
@@ -36,11 +48,6 @@
    *     must be a string; other elements are more flexible, as they just need
    *     to work as an argument for the constructor of the class
    *     Drupal\Core\Template\Attribute($options['attributes']).
-   *   - html: Whether $text is HTML or just plain-text. For
-   *     example, to make an image tag into a link, this must be set to TRUE, or
-   *     you will see the escaped HTML image tag. $text is not sanitized if
-   *     'html' is TRUE. The calling function must ensure that $text is already
-   *     safe. Defaults to FALSE.
    *   - language: An optional language object. If the path being linked to is
    *     internal to the site, $options['language'] is used to determine whether
    *     the link is "active", or pointing to the current page (the language as
diff --git a/core/modules/aggregator/src/FeedViewBuilder.php b/core/modules/aggregator/src/FeedViewBuilder.php
index 944d9a8..051ae36 100644
--- a/core/modules/aggregator/src/FeedViewBuilder.php
+++ b/core/modules/aggregator/src/FeedViewBuilder.php
@@ -103,7 +103,6 @@ public function buildComponents(array &$build, array $entities, array $displays,
             '#url' => Url::fromUri($link_href),
             '#options' => array(
               'attributes' => array('class' => array('feed-image')),
-              'html' => TRUE,
             ),
           );
         }
@@ -120,14 +119,16 @@ public function buildComponents(array &$build, array $entities, array $displays,
 
       if ($display->getComponent('more_link')) {
         $title_stripped = strip_tags($entity->label());
+        $title = array(
+          '#type' => 'inline_template',
+          '#template' => '{% trans %}More<span class="visually-hidden"> posts about {{title}}</span>{% endtrans %}',
+          '#context' => array('title' => $title_stripped),
+        );
         $build[$id]['more_link'] = array(
           '#type' => 'link',
-          '#title' => t('More<span class="visually-hidden"> posts about @title</span>', array(
-            '@title' => $title_stripped,
-          )),
+          '#title' => $title,
           '#url' => Url::fromRoute('entity.aggregator_feed.canonical', ['aggregator_feed' => $entity->id()]),
           '#options' => array(
-            'html' => TRUE,
             'attributes' => array(
               'title' => $title_stripped,
             ),
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index 83f4a40..0d1641d 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -42,7 +42,7 @@ function block_help($route_name, RouteMatchInterface $route_match) {
     $demo_theme = $route_match->getParameter('theme') ?: \Drupal::config('system.theme')->get('default');
     $themes = list_themes();
     $output = '<p>' . t('This page provides a drag-and-drop interface for adding a block to a region, and for controlling the order of blocks within regions. To add a block to a region, or to configure its specific title and visibility settings, click the block title under <em>Place blocks</em>. Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the <em>Save blocks</em> button at the bottom of the page.') . '</p>';
-    $output .= '<p>' . \Drupal::l(t('Demonstrate block regions (!theme)', array('!theme' => $themes[$demo_theme]->info['name'])), new Url('block.admin_demo', array('theme' => $demo_theme))) . '</p>';
+    $output .= '<p>' . \Drupal::l(t('Demonstrate block regions (@theme)', array('@theme' => $themes[$demo_theme]->info['name'])), new Url('block.admin_demo', array('theme' => $demo_theme))) . '</p>';
     return $output;
   }
 }
diff --git a/core/modules/book/src/Tests/BookTest.php b/core/modules/book/src/Tests/BookTest.php
index b229104..6c1e454 100644
--- a/core/modules/book/src/Tests/BookTest.php
+++ b/core/modules/book/src/Tests/BookTest.php
@@ -202,24 +202,34 @@ function checkBookNode(EntityInterface $node, $nodes, $previous = FALSE, $up = F
 
     // Check previous, up, and next links.
     if ($previous) {
+      $text = array(
+        '#type' => 'inline_template',
+        '#template' => '<b>‹</b> {{ label }}',
+        '#context' => array('label' => $previous->label()),
+      );
       /** @var \Drupal\Core\Url $url */
       $url = $previous->urlInfo();
-      $url->setOptions(array('html' => TRUE, 'attributes' => array('rel' => array('prev'), 'title' => t('Go to previous page'))));
-      $this->assertRaw(\Drupal::l('<b>‹</b> ' . $previous->label(), $url), 'Previous page link found.');
+      $url->setOptions(array('attributes' => array('rel' => array('prev'), 'title' => t('Go to previous page'))));
+      $this->assertRaw(\Drupal::l($text, $url), 'Previous page link found.');
     }
 
     if ($up) {
       /** @var \Drupal\Core\Url $url */
       $url = $up->urlInfo();
-      $url->setOptions(array('html'=> TRUE, 'attributes' => array('title' => t('Go to parent page'))));
+        $url->setOptions(array('attributes' => array('title' => t('Go to parent page'))));
       $this->assertRaw(\Drupal::l('Up', $url), 'Up page link found.');
     }
 
     if ($next) {
+      $text = array(
+        '#type' => 'inline_template',
+        '#template' => '{{ label }} <b>›</b>',
+        '#context' => array('label' => $next->label()),
+      );
       /** @var \Drupal\Core\Url $url */
       $url = $next->urlInfo();
-      $url->setOptions(array('html'=> TRUE, 'attributes' => array('rel' => array('next'), 'title' => t('Go to next page'))));
-      $this->assertRaw(\Drupal::l($next->label() . ' <b>›</b>', $url), 'Next page link found.');
+      $url->setOptions(array('attributes' => array('rel' => array('next'), 'title' => t('Go to next page'))));
+      $this->assertRaw(\Drupal::l($text, $url), 'Next page link found.');
     }
 
     // Compute the expected breadcrumb.
diff --git a/core/modules/comment/comment.api.php b/core/modules/comment/comment.api.php
index b460a71..563cc5d 100644
--- a/core/modules/comment/comment.api.php
+++ b/core/modules/comment/comment.api.php
@@ -39,7 +39,6 @@ function hook_comment_links_alter(array &$links, CommentInterface $entity, array
       'comment-report' => array(
         'title' => t('Report'),
         'url' => Url::fromRoute('comment_test.report', ['comment' => $entity->id()], ['query' => ['token' => \Drupal::getContainer()->get('csrf_token')->get("comment/{$entity->id()}/report")]]),
-        'html' => TRUE,
       ),
     ),
   );
diff --git a/core/modules/comment/src/CommentLinkBuilder.php b/core/modules/comment/src/CommentLinkBuilder.php
index f689b1a..0dae833 100644
--- a/core/modules/comment/src/CommentLinkBuilder.php
+++ b/core/modules/comment/src/CommentLinkBuilder.php
@@ -151,7 +151,6 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
             elseif ($this->currentUser->isAnonymous()) {
               $links['comment-forbidden'] = array(
                 'title' => $this->commentManager->forbiddenMessage($entity, $field_name),
-                'html' => TRUE,
               );
             }
           }
@@ -186,7 +185,6 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
             elseif ($this->currentUser->isAnonymous()) {
               $links['comment-forbidden'] = array(
                 'title' => $this->commentManager->forbiddenMessage($entity, $field_name),
-                'html' => TRUE,
               );
             }
           }
diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php
index cd9a5b9..db21136 100644
--- a/core/modules/comment/src/CommentViewBuilder.php
+++ b/core/modules/comment/src/CommentViewBuilder.php
@@ -247,7 +247,6 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
         $links['comment-delete'] = array(
           'title' => t('Delete'),
           'url' => $entity->urlInfo('delete-form'),
-          'html' => TRUE,
         );
       }
 
@@ -255,7 +254,6 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
         $links['comment-edit'] = array(
           'title' => t('Edit'),
           'url' => $entity->urlInfo('edit-form'),
-          'html' => TRUE,
         );
       }
       if ($entity->access('create')) {
@@ -267,19 +265,16 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
             'field_name' => $entity->getFieldName(),
             'pid' => $entity->id(),
           ]),
-          'html' => TRUE,
         );
       }
       if (!$entity->isPublished() && $entity->access('approve')) {
         $links['comment-approve'] = array(
           'title' => t('Approve'),
           'url' => Url::fromRoute('comment.approve', ['comment' => $entity->id()]),
-          'html' => TRUE,
         );
       }
       if (empty($links) && \Drupal::currentUser()->isAnonymous()) {
         $links['comment-forbidden']['title'] = \Drupal::service('comment.manager')->forbiddenMessage($commented_entity, $entity->getFieldName());
-        $links['comment-forbidden']['html'] = TRUE;
       }
     }
 
@@ -288,7 +283,6 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
       $links['comment-translations'] = array(
         'title' => t('Translate'),
         'url' => $entity->urlInfo('drupal:content-translation-overview'),
-        'html' => TRUE,
       );
     }
 
diff --git a/core/modules/comment/tests/modules/comment_test/comment_test.module b/core/modules/comment/tests/modules/comment_test/comment_test.module
index 9fb600f..d54814b 100644
--- a/core/modules/comment/tests/modules/comment_test/comment_test.module
+++ b/core/modules/comment/tests/modules/comment_test/comment_test.module
@@ -38,7 +38,6 @@ function comment_test_comment_links_alter(array &$links, CommentInterface &$enti
       'comment-report' => array(
         'title' => t('Report'),
         'url' => Url::fromRoute('comment_test.report', ['comment' => $entity->id()], ['query' => ['token' => \Drupal::getContainer()->get('csrf_token')->get("comment/{$entity->id()}/report")]]),
-        'html' => TRUE,
       ),
     ),
   );
diff --git a/core/modules/dblog/src/Controller/DbLogController.php b/core/modules/dblog/src/Controller/DbLogController.php
index 2ef2d06..bf7e612 100644
--- a/core/modules/dblog/src/Controller/DbLogController.php
+++ b/core/modules/dblog/src/Controller/DbLogController.php
@@ -7,8 +7,9 @@
 
 namespace Drupal\dblog\Controller;
 
-use Drupal\Component\Utility\Unicode;
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\String;
+use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Controller\ControllerBase;
 use Drupal\Core\Database\Connection;
@@ -174,14 +175,15 @@ public function overview() {
     foreach ($result as $dblog) {
       $message = $this->formatMessage($dblog);
       if ($message && isset($dblog->wid)) {
-        // Truncate link_text to 56 chars of message.
-        $log_text = Unicode::truncate(Xss::filter($message, array()), 56, TRUE, TRUE);
+        // Truncate link_text to 56 chars of message. This is a rare case where
+        // it is acceptable to call SafeMarkup::set() as we are truncating text
+        // that has already passed through SafeMarkup::set().
+        $log_text = SafeMarkup::set(Unicode::truncate(Xss::filter($message, array()), 56, TRUE, TRUE));
         $message = $this->l($log_text, new Url('dblog.event', array('event_id' => $dblog->wid), array(
           'attributes' => array(
             // Provide a title for the link for useful hover hints.
             'title' => Unicode::truncate(strip_tags($message), 256, TRUE, TRUE),
           ),
-          'html' => TRUE,
         )));
       }
       $username = array(
diff --git a/core/modules/dblog/src/Tests/Views/ViewsIntegrationTest.php b/core/modules/dblog/src/Tests/Views/ViewsIntegrationTest.php
index a8fcb5b..3e3ede3 100644
--- a/core/modules/dblog/src/Tests/Views/ViewsIntegrationTest.php
+++ b/core/modules/dblog/src/Tests/Views/ViewsIntegrationTest.php
@@ -77,7 +77,10 @@ public function testIntegration() {
       'variables' => array(
         '@token1' => $this->randomMachineName(),
         '!token2' => $this->randomMachineName(),
-        'link' => \Drupal::l('<object>Link</object>', new Url('<front>')),
+        'link' => \Drupal::l(array(
+          '#type' => 'inline_template',
+          '#template' => '<object>Link</object>',
+        ), new Url('<front>')),
       ),
     );
     $logger_factory = $this->container->get('logger.factory');
diff --git a/core/modules/field_ui/src/EntityDisplayModeListBuilder.php b/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
index 6056508..453da32 100644
--- a/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
+++ b/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
@@ -121,9 +121,6 @@ public function render() {
           '#type' => 'link',
           '#url' => Url::fromRoute($short_type == 'view' ? 'field_ui.entity_view_mode_add_type' : 'field_ui.entity_form_mode_add_type', ['entity_type_id' => $entity_type]),
           '#title' => t('Add new %label @entity-type', array('%label' => $this->entityTypes[$entity_type]->getLabel(), '@entity-type' => $this->entityType->getLowercaseLabel())),
-          '#options' => array(
-            'html' => TRUE,
-          ),
         ),
         'colspan' => count($table['#header']),
       );
diff --git a/core/modules/image/src/Tests/ImageFieldDisplayTest.php b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
index c49af3c..cc25824 100644
--- a/core/modules/image/src/Tests/ImageFieldDisplayTest.php
+++ b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\image\Tests;
 
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Url;
 use Drupal\field\Entity\FieldStorageConfig;
 
 /**
@@ -27,6 +28,22 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
   public static $modules = array('field_ui');
 
   /**
+   * The link generator.
+   *
+   * @var \Drupal\Core\Utility\LinkGenerator
+   */
+  protected $linkGenerator;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->linkGenerator = $this->container->get('link_generator');
+  }
+
+  /**
    * Test image formatters on node display for public files.
    */
   function testImageFieldFormattersPublic() {
@@ -85,7 +102,8 @@ function _testImageFieldFormatters($scheme) {
       '#width' => 40,
       '#height' => 20,
     );
-    $default_output = '<a href="' . file_create_url($image_uri) . '">' . drupal_render($image) . '</a>';
+
+    $default_output = $this->linkGenerator->generate($image, Url::fromUri(file_create_url($image_uri)));
     $this->drupalGet('node/' . $nid);
     $cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
     $this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index 759c5cd..20a9f6b 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -550,7 +550,6 @@ function hook_node_links_alter(array &$links, NodeInterface $entity, array &$con
       'node-report' => array(
         'title' => t('Report'),
         'href' => "node/{$entity->id()}/report",
-        'html' => TRUE,
         'query' => array('token' => \Drupal::getContainer()->get('csrf_token')->get("node/{$entity->id()}/report")),
       ),
     ),
diff --git a/core/modules/node/src/NodeViewBuilder.php b/core/modules/node/src/NodeViewBuilder.php
index 54f57b4..2026c2f 100644
--- a/core/modules/node/src/NodeViewBuilder.php
+++ b/core/modules/node/src/NodeViewBuilder.php
@@ -148,7 +148,6 @@ protected static function buildLinks(NodeInterface $entity, $view_mode) {
         )),
         'url' => $entity->urlInfo(),
         'language' => $entity->language(),
-        'html' => TRUE,
         'attributes' => array(
           'rel' => 'tag',
           'title' => $node_title_stripped,
diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module
index bdb2a83..5097309 100644
--- a/core/modules/responsive_image/responsive_image.module
+++ b/core/modules/responsive_image/responsive_image.module
@@ -152,7 +152,6 @@ function theme_responsive_image_formatter($variables) {
   if (isset($variables['path']['path'])) {
     $path = $variables['path']['path'];
     $options = isset($variables['path']['options']) ? $variables['path']['options'] : array();
-    $options['html'] = TRUE;
     return \Drupal::l($responsive_image, Url::fromUri($path, $options));
   }
 
diff --git a/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php b/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
index 5f85234..28c890c 100644
--- a/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
+++ b/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\responsive_image\Tests;
 
 use Drupal\Component\Utility\Unicode;
+use Drupal\Core\Url;
 use Drupal\image\Tests\ImageFieldTestBase;
 
 /**
@@ -27,11 +28,20 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
   public static $modules = array('field_ui', 'responsive_image', 'responsive_image_test_module');
 
   /**
-   * Drupal\simpletest\WebTestBase\setUp().
+   * The link generator.
+   *
+   * @var \Drupal\Core\Utility\LinkGenerator
+   */
+  protected $linkGenerator;
+
+  /**
+   * {@inheritdoc}
    */
   protected function setUp() {
     parent::setUp();
 
+    $this->linkGenerator = $this->container->get('link_generator');
+
     // Create user.
     $this->admin_user = $this->drupalCreateUser(array(
       'administer responsive images',
@@ -116,7 +126,8 @@ public function _testResponsiveImageFieldFormatters($scheme) {
       '#width' => 40,
       '#height' => 20,
     );
-    $default_output = '<a href="' . file_create_url($image_uri) . '">' . drupal_render($image) . '</a>';
+
+    $default_output = $this->linkGenerator->generate($image, Url::fromUri(file_create_url($image_uri)));
     $this->drupalGet('node/' . $nid);
     $cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
     $this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index 601a149..4d42450 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -8,8 +8,8 @@
 use Drupal\Core\Access\AccessResult;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Cache\Cache;
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\Core\Routing\UrlMatcher;
 use Drupal\Core\Url;
 use Drupal\shortcut\Entity\ShortcutSet;
 use Drupal\shortcut\ShortcutSetInterface;
@@ -339,9 +339,13 @@ function shortcut_preprocess_page(&$variables) {
         ),
         '#prefix' => '<div class="add-or-remove-shortcuts ' . $link_mode . '-shortcut">',
         '#type' => 'link',
-        '#title' => '<span class="icon"></span><span class="text">'. $link_text .'</span>',
+        '#title' => array(
+          '#type' => 'inline_template',
+          '#template' => '<span class="icon"></span><span class="text">{{ link_text }}</span>',
+          '#context' => array('link_text' => $link_text),
+        ),
         '#url' => Url::fromRoute($route_name, $route_parameters),
-        '#options' => array('query' => $query, 'html' => TRUE),
+        '#options' => array('query' => $query),
         '#suffix' => '</div>',
       );
     }
diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
index 0836987..9ea433c 100644
--- a/core/modules/simpletest/src/AssertContentTrait.php
+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -284,6 +284,9 @@ protected function getAllOptions(\SimpleXMLElement $element) {
    *   TRUE if the assertion succeeded, FALSE otherwise.
    */
   protected function assertLink($label, $index = 0, $message = '', $group = 'Other') {
+    // $this->xpath will escape entities, so we need to decode them first
+    // to avoid double escaping leading to failed tests.
+    $label = html_entity_decode($label);
     $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
     $message = ($message ? $message : String::format('Link with label %label found.', array('%label' => $label)));
     return $this->assert(isset($links[$index]), $message, $group);
diff --git a/core/modules/system/src/Tests/Theme/FunctionsTest.php b/core/modules/system/src/Tests/Theme/FunctionsTest.php
index f9a4a83..1d8522f 100644
--- a/core/modules/system/src/Tests/Theme/FunctionsTest.php
+++ b/core/modules/system/src/Tests/Theme/FunctionsTest.php
@@ -211,6 +211,15 @@ function testLinks() {
           'key' => 'value',
         )
       ),
+      'render array' => array(
+        'title' => array(
+          '#type' => 'inline_template',
+          '#template' => '<span class="unescaped">{{ text }}</span>',
+          '#context' => array(
+            'text' => 'potentially unsafe text that <should> be escaped',
+          ),
+        ),
+      ),
     );
 
     $expected_links = '';
@@ -221,6 +230,7 @@ function testLinks() {
     $expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . String::checkPlain('Test route') . '</a></li>';
     $query = array('key' => 'value');
     $expected_links .= '<li class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . String::checkPlain('Query test route') . '</a></li>';
+    $expected_links .= '<li class="render-array"><span class="unescaped">' . String::checkPlain('potentially unsafe text that <should> be escaped') . '</span></li>';
     $expected_links .= '</ul>';
 
     // Verify that passing a string as heading works.
@@ -260,6 +270,7 @@ function testLinks() {
     $expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . String::checkPlain('Test route') . '</a></li>';
     $query = array('key' => 'value');
     $expected_links .= '<li class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . String::checkPlain('Query test route') . '</a></li>';
+    $expected_links .= '<li class="render-array"><span class="unescaped">' . String::checkPlain('potentially unsafe text that <should> be escaped') . '</span></li>';
     $expected_links .= '</ul>';
     $expected = $expected_heading . $expected_links;
     $this->assertThemeOutput('links', $variables, $expected);
@@ -276,6 +287,7 @@ function testLinks() {
     $query = array('key' => 'value');
     $encoded_query = String::checkPlain(Json::encode($query));
     $expected_links .= '<li data-drupal-link-query="'.$encoded_query.'" data-drupal-link-system-path="router_test/test1" class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '" data-drupal-link-query="'.$encoded_query.'" data-drupal-link-system-path="router_test/test1">' . String::checkPlain('Query test route') . '</a></li>';
+    $expected_links .= '<li class="render-array"><span class="unescaped">' . String::checkPlain('potentially unsafe text that <should> be escaped') . '</span></li>';
     $expected_links .= '</ul>';
     $expected = $expected_heading . $expected_links;
     $this->assertThemeOutput('links', $variables, $expected);
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 6d3c6f3..6b7bfcb 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -1444,7 +1444,6 @@ function user_toolbar() {
       'account' => array(
         'title' => t('View profile'),
         'url' => Url::fromRoute('user.page'),
-        'html' => TRUE,
         'attributes' => array(
           'title' => t('User account'),
         ),
@@ -1452,7 +1451,6 @@ function user_toolbar() {
       'account_edit' => array(
         'title' => t('Edit profile'),
         'url' => Url::fromRoute('entity.user.edit_form', ['user' => $user->id()]),
-        'html' => TRUE,
         'attributes' => array(
           'title' => t('Edit user account'),
         ),
diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
index de9cac6..ab22553 100644
--- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
@@ -1024,19 +1024,34 @@ public function overrideOption($option, $value) {
    * an easy URL to exactly the right section. Don't override this.
    */
   public function optionLink($text, $section, $class = '', $title = '') {
-    if (!empty($class)) {
-      $text = '<span>' . $text . '</span>';
-    }
-
     if (!trim($text)) {
       $text = $this->t('Broken field');
     }
 
+    if (!empty($class)) {
+      $text = [
+        '#type' => 'inline_template',
+        '#template' => '<span>{{ text }}</span>',
+        '#context' => array('text' => $text),
+      ];
+    }
+
     if (empty($title)) {
       $title = $text;
     }
 
-    return \Drupal::l($text, new Url('views_ui.form_display', ['js' => 'nojs', 'view' => $this->view->storage->id(), 'display_id' => $this->display['id'], 'type' => $section], array('attributes' => array('class' => array('views-ajax-link', $class), 'title' => $title, 'id' => drupal_html_id('views-' . $this->display['id'] . '-' . $section)), 'html' => TRUE)));
+    return \Drupal::l($text, new Url('views_ui.form_display', array(
+        'js' => 'nojs',
+        'view' => $this->view->storage->id(),
+        'display_id' => $this->display['id'],
+        'type' => $section
+      ), array(
+        'attributes' => array(
+          'class' => array('views-ajax-link', $class),
+          'title' => $title,
+          'id' => drupal_html_id('views-' . $this->display['id'] . '-' . $section)
+        )
+    )));
   }
 
   /**
@@ -1120,12 +1135,12 @@ public function optionsSummary(&$categories, &$options) {
       $options['display_id'] = array(
         'category' => 'other',
         'title' => $this->t('Machine Name'),
-        'value' => !empty($this->display['new_id']) ? String::checkPlain($this->display['new_id']) : String::checkPlain($this->display['id']),
+        'value' => !empty($this->display['new_id']) ? $this->display['new_id'] : $this->display['id'],
         'desc' => $this->t('Change the machine name of this display.'),
       );
     }
 
-    $display_comment = String::checkPlain(Unicode::substr($this->getOption('display_comment'), 0, 10));
+    $display_comment = Unicode::substr($this->getOption('display_comment'), 0, 10);
     $options['display_comment'] = array(
       'category' => 'other',
       'title' => $this->t('Administrative comment'),
@@ -1319,7 +1334,7 @@ public function optionsSummary(&$categories, &$options) {
         $display_id = $this->getLinkDisplay();
         $displays = $this->view->storage->get('display');
         if (!empty($displays[$display_id])) {
-          $link_display = String::checkPlain($displays[$display_id]['display_title']);
+          $link_display = $displays[$display_id]['display_title'];
         }
       }
 
@@ -1360,7 +1375,7 @@ public function optionsSummary(&$categories, &$options) {
       $options['exposed_form']['links']['exposed_form_options'] = $this->t('Exposed form settings for this exposed form style.');
     }
 
-    $css_class = String::checkPlain(trim($this->getOption('css_class')));
+    $css_class = trim($this->getOption('css_class'));
     if (!$css_class) {
       $css_class = $this->t('None');
     }
diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
index f13b8db..defd281 100644
--- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
+++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
@@ -1348,7 +1348,6 @@ protected function renderAsLink($alter, $text, $tokens) {
     }
 
     $options = array(
-      'html' => TRUE,
       'absolute' => !empty($alter['absolute']) ? TRUE : FALSE,
     );
 
diff --git a/core/modules/views/src/Plugin/views/field/Url.php b/core/modules/views/src/Plugin/views/field/Url.php
index cf66117..9e4c4a9 100644
--- a/core/modules/views/src/Plugin/views/field/Url.php
+++ b/core/modules/views/src/Plugin/views/field/Url.php
@@ -8,6 +8,7 @@
 namespace Drupal\views\Plugin\views\field;
 
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url as DrupalUrl;
 use Drupal\views\ResultRow;
 
 /**
@@ -45,7 +46,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
   public function render(ResultRow $values) {
     $value = $this->getValue($values);
     if (!empty($this->options['display_as_link'])) {
-      return _l($this->sanitizeValue($value), $value, array('html' => TRUE));
+      return \Drupal::l($this->sanitizeValue($value), DrupalUrl::fromUri('base://' . $value));
     }
     else {
       return $this->sanitizeValue($value, 'url');
diff --git a/core/modules/views/src/Tests/Plugin/DisplayTest.php b/core/modules/views/src/Tests/Plugin/DisplayTest.php
index bacfdd4..8f8c4e7 100644
--- a/core/modules/views/src/Tests/Plugin/DisplayTest.php
+++ b/core/modules/views/src/Tests/Plugin/DisplayTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests\Plugin;
 
+use Drupal\Component\Utility\String;
 use Drupal\views\Views;
 use Drupal\views_test_data\Plugin\views\display\DisplayTest as DisplayTestPlugin;
 
@@ -122,12 +123,12 @@ public function testDisplayPlugin() {
 
     $this->clickLink('Test option title');
 
-    $this->randomString = $this->randomString();
-    $this->drupalPostForm(NULL, array('test_option' => $this->randomString), t('Apply'));
+    $test_option = $this->randomString();
+    $this->drupalPostForm(NULL, array('test_option' => $test_option), t('Apply'));
 
     // Check the new value has been saved by checking the UI summary text.
     $this->drupalGet('admin/structure/views/view/test_view/edit/display_test_1');
-    $this->assertRaw($this->randomString);
+    $this->assertRaw(String::checkPlain($test_option));
 
     // Test the enable/disable status of a display.
     $view->display_handler->setOption('enabled', FALSE);
diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
index d4fb797..3aac5bf 100644
--- a/core/modules/views/views.theme.inc
+++ b/core/modules/views/views.theme.inc
@@ -477,7 +477,6 @@ function template_preprocess_views_view_table(&$variables) {
         $query['order'] = $field;
         $query['sort'] = $initial;
         $link_options = array(
-          'html' => TRUE,
           'attributes' => array('title' => $title),
           'query' => $query,
         );
diff --git a/core/modules/views_ui/src/Form/Ajax/Rearrange.php b/core/modules/views_ui/src/Form/Ajax/Rearrange.php
index cff75fd..ca213d1 100644
--- a/core/modules/views_ui/src/Form/Ajax/Rearrange.php
+++ b/core/modules/views_ui/src/Form/Ajax/Rearrange.php
@@ -125,7 +125,19 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         '#id' => 'views-removed-' . $id,
         '#attributes' => array('class' => array('views-remove-checkbox')),
         '#default_value' => 0,
-        '#suffix' => \Drupal::l('<span>' . $this->t('Remove') . '</span>', Url::fromRoute('<none>', [], array('attributes' => array('id' => 'views-remove-link-' . $id, 'class' => array('views-hidden', 'views-button-remove', 'views-remove-link'), 'alt' => $this->t('Remove this item'), 'title' => $this->t('Remove this item')), 'html' => TRUE))),
+        '#suffix' => \Drupal::l(
+          array(
+            '#type' => 'inline_template',
+            '#template' => '<span>{{ text }}</span>',
+            '#context' => array('text' => $this->t('Remove')),
+          ),
+          Url::fromRoute('<none>', array(), array('attributes' => array(
+            'id' => 'views-remove-link-' . $id,
+            'class' => array('views-hidden', 'views-button-remove', 'views-remove-link'),
+            'alt' => $this->t('Remove this item'),
+            'title' => $this->t('Remove this item')),
+          ))
+        ),
       );
     }
 
diff --git a/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php b/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
index 72dae7c..06be203 100644
--- a/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
+++ b/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
@@ -120,11 +120,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
         ),
         'link' => array(
           '#type' => 'link',
-          '#title' => '<span>' . $this->t('Remove') . '</span>',
-          '#url' => Url::fromRoute('<none>'),
-          '#options' => array(
-            'html' => TRUE,
+          '#title' => array(
+            '#type' => 'inline_template',
+            '#template' => '<span>{{ label }}</span>',
+            '#context' => array('label' => $this->t('Remove')),
           ),
+          '#url' => Url::fromRoute('<none>'),
+          '#href' => 'javascript:void()',
           '#attributes' => array(
             'id' => 'display-remove-link-' . $id,
             'class' => array('views-button-remove', 'display-remove-link'),
diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php
index 8b1e4a2..e656925 100644
--- a/core/modules/views_ui/src/ViewEditForm.php
+++ b/core/modules/views_ui/src/ViewEditForm.php
@@ -998,7 +998,6 @@ public function getFormBucket(ViewUI $view, $type, $display) {
       'title' => $add_text,
       'url' => Url::fromRoute('views_ui.form_add_handler', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id'], 'type' => $type]),
       'attributes' => array('class' => array('icon compact add', 'views-ajax-link'), 'id' => 'views-add-' . $type),
-      'html' => TRUE,
     );
     if ($count_handlers > 0) {
       // Create the rearrange text variable for the rearrange action.
@@ -1008,7 +1007,6 @@ public function getFormBucket(ViewUI $view, $type, $display) {
         'title' => $rearrange_text,
         'url' => $rearrange_url,
         'attributes' => array('class' => array($class, 'views-ajax-link'), 'id' => 'views-rearrange-' . $type),
-        'html' => TRUE,
       );
     }
 
@@ -1070,7 +1068,7 @@ public function getFormBucket(ViewUI $view, $type, $display) {
           'display_id' => $display['id'],
           'type' => $type,
           'id' => $id,
-        ), array('attributes' => array('class' => array('views-ajax-link')), 'html' => TRUE)));
+        ), array('attributes' => array('class' => array('views-ajax-link')))));
         continue;
       }
 
@@ -1093,27 +1091,37 @@ public function getFormBucket(ViewUI $view, $type, $display) {
         'display_id' => $display['id'],
         'type' => $type,
         'id' => $id,
-      ), array('attributes' => $link_attributes, 'html' => TRUE)));
+      ), array('attributes' => $link_attributes)));
       $build['fields'][$id]['#class'][] = drupal_clean_css_identifier($display['id']. '-' . $type . '-' . $id);
 
       if ($executable->display_handler->useGroupBy() && $handler->usesGroupBy()) {
-        $build['fields'][$id]['#settings_links'][] = $this->l('<span class="label">' . $this->t('Aggregation settings') . '</span>', new Url('views_ui.form_handler_group', array(
+        $build['fields'][$id]['#settings_links'][] = $this->l(array(
+          '#type' => 'inline_template',
+          '#template' => '<span class="label">{{ label }}</span>',
+          '#context' => array('label' => $this->t('Aggregation settings')),
+        ),
+        new Url('views_ui.form_handler_group', array(
           'js' => 'nojs',
           'view' => $view->id(),
           'display_id' => $display['id'],
           'type' => $type,
           'id' => $id,
-        ), array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => $this->t('Aggregation settings')), 'html' => TRUE)));
+        ), array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => $this->t('Aggregation settings')))));
       }
 
       if ($handler->hasExtraOptions()) {
-        $build['fields'][$id]['#settings_links'][] = $this->l('<span class="label">' . $this->t('Settings') . '</span>', new Url('views_ui.form_handler_extra', array(
+        $build['fields'][$id]['#settings_links'][] = $this->l(array(
+          '#type' => 'inline_template',
+          '#template' => '<span class="label">{{ label }}</span>',
+          '#context' => array('label' => $this->t('Settings')),
+        ),
+        new Url('views_ui.form_handler_extra', array(
           'js' => 'nojs',
           'view' => $view->id(),
           'display_id' => $display['id'],
           'type' => $type,
           'id' => $id,
-        ), array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => $this->t('Settings')), 'html' => TRUE)));
+        ), array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => $this->t('Settings')))));
       }
 
       if ($grouping) {
diff --git a/core/modules/views_ui/views_ui.theme.inc b/core/modules/views_ui/views_ui.theme.inc
index f56b3b5..b33f188 100644
--- a/core/modules/views_ui/views_ui.theme.inc
+++ b/core/modules/views_ui/views_ui.theme.inc
@@ -158,7 +158,17 @@ function theme_views_ui_build_group_filter_form($variables) {
       'value' => drupal_render($form['group_items'][$group_id]['value']),
       'remove' => array(
         'data' => array(
-          '#markup' => drupal_render($form['group_items'][$group_id]['remove']) . \Drupal::l('<span>' . t('Remove') . '</span>', Url::fromRoute('<none>', [], array('attributes' => array('id' => 'views-remove-link-' . $group_id, 'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => true))),
+          '#markup' => drupal_render($form['group_items'][$group_id]['remove']) . \Drupal::l(
+            array(
+              '#type' => 'inline_template',
+              '#template' => '<span>{% trans %}Remove{% endtrans %}</span>',
+            ),
+            Url::fromRoute('<none>', array(), array('attributes' => array(
+              'id' => 'views-remove-link-' . $group_id,
+              'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'),
+              'alt' => t('Remove this item'),
+              'title' => t('Remove this item')),
+            ))),
         ),
       ),
     );
@@ -278,7 +288,10 @@ function template_preprocess_views_ui_rearrange_filter_form(&$variables) {
         $remove_link = array(
           '#type' => 'link',
           '#url' => Url::fromRoute('<none>'),
-          '#title' => '<span>' . t('Remove') . '</span>',
+          '#title' => array(
+            '#type' => 'inline_template',
+            '#template' => '<span>{% trans %}Remove{% endtrans %}</span>',
+          ),
           '#weight' => '1',
           '#options' => array(
             'attributes' => array(
@@ -292,7 +305,6 @@ function template_preprocess_views_ui_rearrange_filter_form(&$variables) {
               'alt' => t('Remove this item'),
               'title' => t('Remove this item'),
             ),
-            'html' => TRUE,
           ),
         );
         $row[]['data'] = array(
diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
index 5721010..70577ae 100644
--- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
+++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Utility {
 
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Url;
 use Drupal\Core\Utility\LinkGenerator;
@@ -51,7 +52,6 @@ class LinkGeneratorTest extends UnitTestCase {
    */
   protected $defaultOptions = array(
     'query' => array(),
-    'html' => FALSE,
     'language' => NULL,
     'set_active_class' => FALSE,
     'absolute' => FALSE,
@@ -66,7 +66,15 @@ protected function setUp() {
     $this->urlGenerator = $this->getMock('\Drupal\Core\Routing\UrlGenerator', array(), array(), '', FALSE);
     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
 
-    $this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->moduleHandler);
+    $this->linkGenerator = $this->getMock('Drupal\Core\Utility\LinkGenerator', array('drupalRender'),
+      array($this->urlGenerator, $this->moduleHandler));
+
+    // The last step of drupal_render() is to mark the final string as safe,
+    // so we need to explicitly to that as part of the mocked return value.
+    $this->linkGenerator->method('drupalRender')
+      ->with($this->isType('array'))
+      ->will($this->returnValue(SafeMarkup::set('<em>HTML output</em>')));
+
     $this->urlAssembler = $this->getMock('\Drupal\Core\Utility\UnroutedUrlAssemblerInterface');
   }
 
@@ -325,7 +333,7 @@ public function testGenerateWithHtml() {
       ));
     $this->urlGenerator->expects($this->at(1))
       ->method('generateFromRoute')
-      ->with('test_route_5', array(), array('html' => TRUE) + $this->defaultOptions)
+      ->with('test_route_5', array(), $this->defaultOptions)
       ->will($this->returnValue(
         '/test-route-5'
       ));
@@ -344,10 +352,14 @@ public function testGenerateWithHtml() {
       ),
     ), $result);
 
-    // Test that the 'html' option allows unsanitized HTML link text.
-    $url = new Url('test_route_5', array(), array('html' => TRUE));
+    // Test that HTML link text can be used in a render array.
+    $url = new Url('test_route_5', array());
     $url->setUrlGenerator($this->urlGenerator);
-    $result = $this->linkGenerator->generate('<em>HTML output</em>', $url);
+    $html = [
+      '#type' => 'inline_template',
+      '#template' => '<em>HTML output</em>',
+    ];
+    $result = $this->linkGenerator->generate($html, $url);
     $this->assertTag(array(
       'tag' => 'a',
       'attributes' => array('href' => '/test-route-5'),
diff --git a/remove_html_true-2273923-128.patch b/remove_html_true-2273923-128.patch
new file mode 100644
index 0000000..9662791
--- /dev/null
+++ b/remove_html_true-2273923-128.patch
@@ -0,0 +1,1138 @@
+diff --git a/core/includes/common.inc b/core/includes/common.inc
+index 4163da2..be5f322 100644
+--- a/core/includes/common.inc
++++ b/core/includes/common.inc
+@@ -639,6 +639,8 @@ function drupal_http_header_attributes(array $attributes = array()) {
+  *
+  * @param string|array $text
+  *   The link text for the anchor tag as a translated string or render array.
++ *   Strings will be sanitized automatically. If you need to output HTML in the
++ *   link text you should use a render array.
+  * @param string $path
+  *   The internal path or external URL being linked to, such as "node/34" or
+  *   "http://example.com/foo". After the url() function is called to construct
+@@ -654,11 +656,6 @@ function drupal_http_header_attributes(array $attributes = array()) {
+  *     must be a string; other elements are more flexible, as they just need
+  *     to work as an argument for the constructor of the class
+  *     Drupal\Core\Template\Attribute($options['attributes']).
+- *   - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
+- *     example, to make an image tag into a link, this must be set to TRUE, or
+- *     you will see the escaped HTML image tag. $text is not sanitized if
+- *     'html' is TRUE. The calling function must ensure that $text is already
+- *     safe.
+  *   - 'language': An optional language object. If the path being linked to is
+  *     internal to the site, $options['language'] is used to determine whether
+  *     the link is "active", or pointing to the current page (the language as
+@@ -711,7 +708,6 @@ function _l($text, $path, array $options = array()) {
+   $variables['options'] += array(
+     'attributes' => array(),
+     'query' => array(),
+-    'html' => FALSE,
+     'language' => NULL,
+     'set_active_class' => FALSE,
+   );
+@@ -756,8 +752,9 @@ function _l($text, $path, array $options = array()) {
+   // in an HTML argument context, we need to encode it properly.
+   $url = String::checkPlain(_url($variables['path'], $variables['options']));
+ 
+-  // Sanitize the link text if necessary.
+-  $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']);
++  // Sanitize the link text.
++  $text = SafeMarkup::escape($variables['text']);
++
+   return SafeMarkup::set('<a href="' . $url . '"' . $attributes . '>' . $text . '</a>');
+ }
+ 
+diff --git a/core/includes/menu.inc b/core/includes/menu.inc
+index 76aafd0..bf9c769 100644
+--- a/core/includes/menu.inc
++++ b/core/includes/menu.inc
+@@ -329,26 +329,30 @@ function template_preprocess_menu_local_task(&$variables) {
+   $link += array(
+     'localized_options' => array(),
+   );
+-  $link_text = $link['title'];
+ 
+   if (!empty($variables['element']['#active'])) {
+     // Add text to indicate active tab for non-visual users.
+-    $active = '<span class="visually-hidden">' . t('(active tab)') . '</span>';
+     $variables['attributes']['class'] = array('active');
+ 
+-    // If the link does not contain HTML already, String::checkPlain() it now.
+-    // After we set 'html'=TRUE the link will not be sanitized by l().
+-    if (empty($link['localized_options']['html'])) {
+-      $link['title'] = String::checkPlain($link['title']);
+-    }
+-    $link['localized_options']['html'] = TRUE;
+-    $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active));
++    // Build up an inline template which will be autoescaped.
++    $link_text = array(
++      '#type' => 'inline_template',
++      '#template' => '{{ title }}<span class="visually-hidden">{% trans %}(active tab){% endtrans %}></span>',
++      '#context' => array('title' => $link['title']),
++    );
++    $title = drupal_render($link_text);
+   }
++  else {
++    // @todo Remove expicit escaping when https://www.drupal.org/node/2338081
++    //   gets fixed.
++    $title = String::checkPlain($link['title']);
++  }
++
+   $link['localized_options']['set_active_class'] = TRUE;
+ 
+   $variables['link'] = array(
+     '#type' => 'link',
+-    '#title' => $link_text,
++    '#title' => $title,
+     '#url' => $link['url'],
+     '#options' => $link['localized_options'],
+   );
+diff --git a/core/includes/tablesort.inc b/core/includes/tablesort.inc
+index 0258a76..db8eff6 100644
+--- a/core/includes/tablesort.inc
++++ b/core/includes/tablesort.inc
+@@ -43,6 +43,11 @@ function tablesort_init($header) {
+ function tablesort_header(&$cell_content, array &$cell_attributes, array $header, array $ts) {
+   // Special formatting for the currently sorted column header.
+   if (isset($cell_attributes['field'])) {
++    $text = array(
++      'cell_content' => array(
++        '#markup' => $cell_content,
++      ),
++    );
+     $title = t('sort by @s', array('@s' => $cell_content));
+     if ($cell_content == $ts['name']) {
+       // aria-sort is a WAI-ARIA property that indicates if items in a table
+@@ -51,24 +56,24 @@ function tablesort_header(&$cell_content, array &$cell_attributes, array $header
+       $cell_attributes['aria-sort'] = ($ts['sort'] == 'asc') ? 'ascending' : 'descending';
+       $ts['sort'] = (($ts['sort'] == 'asc') ? 'desc' : 'asc');
+       $cell_attributes['class'][] = 'active';
+-      $tablesort_indicator = array(
+-        '#theme' => 'tablesort_indicator',
+-        '#style' => $ts['sort'],
+-      );
+-      $image = drupal_render($tablesort_indicator);
+     }
+     else {
+-      // If the user clicks a different header, we want to sort ascending initially.
++      // If the user clicks a different header, we want to sort ascending
++      // initially.
+       $ts['sort'] = 'asc';
+-      $image = '';
+     }
+-    $cell_content = \Drupal::l($cell_content . $image, new Url('<current>', [], [
++
++    // Append the sort indicator to the cell content.
++    $text['image'] = [
++      '#theme' => 'tablesort_indicator',
++      '#style' => $ts['sort'],
++    ];
++    $cell_content = \Drupal::l($text, new Url('<current>', [], [
+       'attributes' => array('title' => $title),
+       'query' => array_merge($ts['query'], array(
+         'sort' => $ts['sort'],
+         'order' => $cell_content,
+       )),
+-      'html' => TRUE,
+     ]));
+ 
+     unset($cell_attributes['field'], $cell_attributes['sort']);
+diff --git a/core/includes/theme.inc b/core/includes/theme.inc
+index e772289..e5ddf10 100644
+--- a/core/includes/theme.inc
++++ b/core/includes/theme.inc
+@@ -904,9 +904,6 @@ function template_preprocess_status_messages(&$variables) {
+  *     - title: The link text.
+  *     - url: (optional) The url object to link to. If omitted, no a tag is
+  *       printed out.
+- *     - html: (optional) Whether or not 'title' is HTML. If set, the title
+- *       will not be passed through
+- *       \Drupal\Component\Utility\String::checkPlain().
+  *     - attributes: (optional) Attributes for the anchor, or for the <span>
+  *       tag used in its place if no 'href' is supplied. If element 'class' is
+  *       included, it must be an array of one or more class names.
+@@ -988,7 +985,7 @@ function template_preprocess_links(&$variables) {
+       $keys = ['title', 'url'];
+       $link_element = array(
+         '#type' => 'link',
+-        '#title' => $link['title'],
++        '#title' => is_array($link['title']) ? drupal_render($link['title']) : SafeMarkup::escape($link['title']),
+         '#options' => array_diff_key($link, array_combine($keys, $keys)),
+         '#url' => $link['url'],
+         '#ajax' => $link['ajax'],
+@@ -1030,8 +1027,7 @@ function template_preprocess_links(&$variables) {
+       }
+ 
+       // Handle title-only text items.
+-      $text = (!empty($link['html']) ? $link['title'] : String::checkPlain($link['title']));
+-      $item['text'] = $text;
++      $item['text'] = $link_element['#title'];
+       if (isset($link['attributes'])) {
+         $item['text_attributes'] = new Attribute($link['attributes']);
+       }
+diff --git a/core/lib/Drupal/Core/Render/Element/Actions.php b/core/lib/Drupal/Core/Render/Element/Actions.php
+index 1aab7d1..8cac9e0 100644
+--- a/core/lib/Drupal/Core/Render/Element/Actions.php
++++ b/core/lib/Drupal/Core/Render/Element/Actions.php
+@@ -97,7 +97,6 @@ public static function preRenderActionsDropbutton(&$element, FormStateInterface
+         $button = drupal_render($element[$key]);
+         $dropbuttons[$dropbutton]['#links'][$key] = array(
+           'title' => $button,
+-          'html' => TRUE,
+         );
+       }
+     }
+diff --git a/core/lib/Drupal/Core/Utility/LinkGenerator.php b/core/lib/Drupal/Core/Utility/LinkGenerator.php
+index a6e8326..1bc18a6 100644
+--- a/core/lib/Drupal/Core/Utility/LinkGenerator.php
++++ b/core/lib/Drupal/Core/Utility/LinkGenerator.php
+@@ -75,8 +75,7 @@ public function generate($text, Url $url) {
+ 
+     // Start building a structured representation of our link to be altered later.
+     $variables = array(
+-      // @todo Inject the service when drupal_render() is converted to one.
+-      'text' => is_array($text) ? drupal_render($text) : $text,
++      'text' => is_array($text) ? $this->drupalRender($text) : $text,
+       'url' => $url,
+       'options' => $url->getOptions(),
+     );
+@@ -85,7 +84,6 @@ public function generate($text, Url $url) {
+     $variables['options'] += array(
+       'attributes' => array(),
+       'query' => array(),
+-      'html' => FALSE,
+       'language' => NULL,
+       'set_active_class' => FALSE,
+       'absolute' => FALSE,
+@@ -135,9 +133,27 @@ public function generate($text, Url $url) {
+     // it here in an HTML argument context, we need to encode it properly.
+     $url = String::checkPlain($url->toString());
+ 
+-    // Sanitize the link text if necessary.
+-    $text = $variables['options']['html'] ? $variables['text'] : String::checkPlain($variables['text']);
++    // Make sure the link text is sanitized.
++    $text = SafeMarkup::escape($variables['text']);
++
+     return SafeMarkup::set('<a href="' . $url . '"' . $attributes . '>' . $text . '</a>');
+   }
+ 
++  /**
++   * Wraps drupal_render().
++   *
++   * @param array $elements
++   *   The structured array describing the data to be rendered.
++   * @param bool $is_root_call
++   *   (Internal use only.) Whether this is a recursive call or not. See
++   *   drupal_render_root().
++   *
++   * @return string
++   *   The rendered HTML.
++   *
++   * @see drupal_render()
++   */
++  protected function drupalRender(&$elements, $is_root_call = FALSE) {
++    return drupal_render($elements, $is_root_call);
++  }
+ }
+diff --git a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
+index ba47197..142ce23 100644
+--- a/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
++++ b/core/lib/Drupal/Core/Utility/LinkGeneratorInterface.php
+@@ -18,16 +18,28 @@
+   /**
+    * Renders a link to a URL.
+    *
++   * Examples:
++   * @code
++   * $link_generator = \Drupal::service('link_generator');
++   * $installer_url = \Drupal\Core\Url::fromUri('base://core/install.php');
++   * $installer_link = $link_generator->generate($text, $installer_url);
++   * $external_url = \Drupal\Core\Url::fromUri('http://example.com', ['query' => ['foo' => 'bar']]);
++   * $external_link = $link_generator->generate($text, $external_url);
++   * $internal_url = \Drupal\Core\Url::fromRoute('system.admin');
++   * $internal_link = $link_generator->generate($text, $internal_url);
++   * @endcode
+    * However, for links enclosed in translatable text you should use t() and
+    * embed the HTML anchor tag directly in the translated string. For example:
+    * @code
+-   * t('Visit the <a href="@url">content types</a> page', array('@url' => \Drupal::url('node.overview_types')));
++   * $text = t('Visit the <a href="@url">content types</a> page', array('@url' => \Drupal::url('node.overview_types')));
+    * @endcode
+    * This keeps the context of the link title ('settings' in the example) for
+    * translators.
+    *
+    * @param string|array $text
+    *   The link text for the anchor tag as a translated string or render array.
++   *   Strings will be sanitized automatically. If you need to output HTML in
++   *   the link text you should use a render array.
+    * @param \Drupal\Core\Url $url
+    *   The URL object used for the link. Amongst its options, the following may
+    *   be set to affect the generated link:
+@@ -36,11 +48,6 @@
+    *     must be a string; other elements are more flexible, as they just need
+    *     to work as an argument for the constructor of the class
+    *     Drupal\Core\Template\Attribute($options['attributes']).
+-   *   - html: Whether $text is HTML or just plain-text. For
+-   *     example, to make an image tag into a link, this must be set to TRUE, or
+-   *     you will see the escaped HTML image tag. $text is not sanitized if
+-   *     'html' is TRUE. The calling function must ensure that $text is already
+-   *     safe. Defaults to FALSE.
+    *   - language: An optional language object. If the path being linked to is
+    *     internal to the site, $options['language'] is used to determine whether
+    *     the link is "active", or pointing to the current page (the language as
+diff --git a/core/modules/aggregator/src/FeedViewBuilder.php b/core/modules/aggregator/src/FeedViewBuilder.php
+index 944d9a8..051ae36 100644
+--- a/core/modules/aggregator/src/FeedViewBuilder.php
++++ b/core/modules/aggregator/src/FeedViewBuilder.php
+@@ -103,7 +103,6 @@ public function buildComponents(array &$build, array $entities, array $displays,
+             '#url' => Url::fromUri($link_href),
+             '#options' => array(
+               'attributes' => array('class' => array('feed-image')),
+-              'html' => TRUE,
+             ),
+           );
+         }
+@@ -120,14 +119,16 @@ public function buildComponents(array &$build, array $entities, array $displays,
+ 
+       if ($display->getComponent('more_link')) {
+         $title_stripped = strip_tags($entity->label());
++        $title = array(
++          '#type' => 'inline_template',
++          '#template' => '{% trans %}More<span class="visually-hidden"> posts about {{title}}</span>{% endtrans %}',
++          '#context' => array('title' => $title_stripped),
++        );
+         $build[$id]['more_link'] = array(
+           '#type' => 'link',
+-          '#title' => t('More<span class="visually-hidden"> posts about @title</span>', array(
+-            '@title' => $title_stripped,
+-          )),
++          '#title' => $title,
+           '#url' => Url::fromRoute('entity.aggregator_feed.canonical', ['aggregator_feed' => $entity->id()]),
+           '#options' => array(
+-            'html' => TRUE,
+             'attributes' => array(
+               'title' => $title_stripped,
+             ),
+diff --git a/core/modules/block/block.module b/core/modules/block/block.module
+index 83f4a40..0d1641d 100644
+--- a/core/modules/block/block.module
++++ b/core/modules/block/block.module
+@@ -42,7 +42,7 @@ function block_help($route_name, RouteMatchInterface $route_match) {
+     $demo_theme = $route_match->getParameter('theme') ?: \Drupal::config('system.theme')->get('default');
+     $themes = list_themes();
+     $output = '<p>' . t('This page provides a drag-and-drop interface for adding a block to a region, and for controlling the order of blocks within regions. To add a block to a region, or to configure its specific title and visibility settings, click the block title under <em>Place blocks</em>. Since not all themes implement the same regions, or display regions in the same way, blocks are positioned on a per-theme basis. Remember that your changes will not be saved until you click the <em>Save blocks</em> button at the bottom of the page.') . '</p>';
+-    $output .= '<p>' . \Drupal::l(t('Demonstrate block regions (!theme)', array('!theme' => $themes[$demo_theme]->info['name'])), new Url('block.admin_demo', array('theme' => $demo_theme))) . '</p>';
++    $output .= '<p>' . \Drupal::l(t('Demonstrate block regions (@theme)', array('@theme' => $themes[$demo_theme]->info['name'])), new Url('block.admin_demo', array('theme' => $demo_theme))) . '</p>';
+     return $output;
+   }
+ }
+diff --git a/core/modules/book/src/Tests/BookTest.php b/core/modules/book/src/Tests/BookTest.php
+index b229104..6c1e454 100644
+--- a/core/modules/book/src/Tests/BookTest.php
++++ b/core/modules/book/src/Tests/BookTest.php
+@@ -202,24 +202,34 @@ function checkBookNode(EntityInterface $node, $nodes, $previous = FALSE, $up = F
+ 
+     // Check previous, up, and next links.
+     if ($previous) {
++      $text = array(
++        '#type' => 'inline_template',
++        '#template' => '<b>‹</b> {{ label }}',
++        '#context' => array('label' => $previous->label()),
++      );
+       /** @var \Drupal\Core\Url $url */
+       $url = $previous->urlInfo();
+-      $url->setOptions(array('html' => TRUE, 'attributes' => array('rel' => array('prev'), 'title' => t('Go to previous page'))));
+-      $this->assertRaw(\Drupal::l('<b>‹</b> ' . $previous->label(), $url), 'Previous page link found.');
++      $url->setOptions(array('attributes' => array('rel' => array('prev'), 'title' => t('Go to previous page'))));
++      $this->assertRaw(\Drupal::l($text, $url), 'Previous page link found.');
+     }
+ 
+     if ($up) {
+       /** @var \Drupal\Core\Url $url */
+       $url = $up->urlInfo();
+-      $url->setOptions(array('html'=> TRUE, 'attributes' => array('title' => t('Go to parent page'))));
++        $url->setOptions(array('attributes' => array('title' => t('Go to parent page'))));
+       $this->assertRaw(\Drupal::l('Up', $url), 'Up page link found.');
+     }
+ 
+     if ($next) {
++      $text = array(
++        '#type' => 'inline_template',
++        '#template' => '{{ label }} <b>›</b>',
++        '#context' => array('label' => $next->label()),
++      );
+       /** @var \Drupal\Core\Url $url */
+       $url = $next->urlInfo();
+-      $url->setOptions(array('html'=> TRUE, 'attributes' => array('rel' => array('next'), 'title' => t('Go to next page'))));
+-      $this->assertRaw(\Drupal::l($next->label() . ' <b>›</b>', $url), 'Next page link found.');
++      $url->setOptions(array('attributes' => array('rel' => array('next'), 'title' => t('Go to next page'))));
++      $this->assertRaw(\Drupal::l($text, $url), 'Next page link found.');
+     }
+ 
+     // Compute the expected breadcrumb.
+diff --git a/core/modules/comment/comment.api.php b/core/modules/comment/comment.api.php
+index b460a71..563cc5d 100644
+--- a/core/modules/comment/comment.api.php
++++ b/core/modules/comment/comment.api.php
+@@ -39,7 +39,6 @@ function hook_comment_links_alter(array &$links, CommentInterface $entity, array
+       'comment-report' => array(
+         'title' => t('Report'),
+         'url' => Url::fromRoute('comment_test.report', ['comment' => $entity->id()], ['query' => ['token' => \Drupal::getContainer()->get('csrf_token')->get("comment/{$entity->id()}/report")]]),
+-        'html' => TRUE,
+       ),
+     ),
+   );
+diff --git a/core/modules/comment/src/CommentLinkBuilder.php b/core/modules/comment/src/CommentLinkBuilder.php
+index f689b1a..0dae833 100644
+--- a/core/modules/comment/src/CommentLinkBuilder.php
++++ b/core/modules/comment/src/CommentLinkBuilder.php
+@@ -151,7 +151,6 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
+             elseif ($this->currentUser->isAnonymous()) {
+               $links['comment-forbidden'] = array(
+                 'title' => $this->commentManager->forbiddenMessage($entity, $field_name),
+-                'html' => TRUE,
+               );
+             }
+           }
+@@ -186,7 +185,6 @@ public function buildCommentedEntityLinks(FieldableEntityInterface $entity, arra
+             elseif ($this->currentUser->isAnonymous()) {
+               $links['comment-forbidden'] = array(
+                 'title' => $this->commentManager->forbiddenMessage($entity, $field_name),
+-                'html' => TRUE,
+               );
+             }
+           }
+diff --git a/core/modules/comment/src/CommentViewBuilder.php b/core/modules/comment/src/CommentViewBuilder.php
+index cd9a5b9..db21136 100644
+--- a/core/modules/comment/src/CommentViewBuilder.php
++++ b/core/modules/comment/src/CommentViewBuilder.php
+@@ -247,7 +247,6 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
+         $links['comment-delete'] = array(
+           'title' => t('Delete'),
+           'url' => $entity->urlInfo('delete-form'),
+-          'html' => TRUE,
+         );
+       }
+ 
+@@ -255,7 +254,6 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
+         $links['comment-edit'] = array(
+           'title' => t('Edit'),
+           'url' => $entity->urlInfo('edit-form'),
+-          'html' => TRUE,
+         );
+       }
+       if ($entity->access('create')) {
+@@ -267,19 +265,16 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
+             'field_name' => $entity->getFieldName(),
+             'pid' => $entity->id(),
+           ]),
+-          'html' => TRUE,
+         );
+       }
+       if (!$entity->isPublished() && $entity->access('approve')) {
+         $links['comment-approve'] = array(
+           'title' => t('Approve'),
+           'url' => Url::fromRoute('comment.approve', ['comment' => $entity->id()]),
+-          'html' => TRUE,
+         );
+       }
+       if (empty($links) && \Drupal::currentUser()->isAnonymous()) {
+         $links['comment-forbidden']['title'] = \Drupal::service('comment.manager')->forbiddenMessage($commented_entity, $entity->getFieldName());
+-        $links['comment-forbidden']['html'] = TRUE;
+       }
+     }
+ 
+@@ -288,7 +283,6 @@ protected static function buildLinks(CommentInterface $entity, EntityInterface $
+       $links['comment-translations'] = array(
+         'title' => t('Translate'),
+         'url' => $entity->urlInfo('drupal:content-translation-overview'),
+-        'html' => TRUE,
+       );
+     }
+ 
+diff --git a/core/modules/comment/tests/modules/comment_test/comment_test.module b/core/modules/comment/tests/modules/comment_test/comment_test.module
+index 9fb600f..d54814b 100644
+--- a/core/modules/comment/tests/modules/comment_test/comment_test.module
++++ b/core/modules/comment/tests/modules/comment_test/comment_test.module
+@@ -38,7 +38,6 @@ function comment_test_comment_links_alter(array &$links, CommentInterface &$enti
+       'comment-report' => array(
+         'title' => t('Report'),
+         'url' => Url::fromRoute('comment_test.report', ['comment' => $entity->id()], ['query' => ['token' => \Drupal::getContainer()->get('csrf_token')->get("comment/{$entity->id()}/report")]]),
+-        'html' => TRUE,
+       ),
+     ),
+   );
+diff --git a/core/modules/dblog/src/Controller/DbLogController.php b/core/modules/dblog/src/Controller/DbLogController.php
+index 2ef2d06..bf7e612 100644
+--- a/core/modules/dblog/src/Controller/DbLogController.php
++++ b/core/modules/dblog/src/Controller/DbLogController.php
+@@ -7,8 +7,9 @@
+ 
+ namespace Drupal\dblog\Controller;
+ 
+-use Drupal\Component\Utility\Unicode;
++use Drupal\Component\Utility\SafeMarkup;
+ use Drupal\Component\Utility\String;
++use Drupal\Component\Utility\Unicode;
+ use Drupal\Component\Utility\Xss;
+ use Drupal\Core\Controller\ControllerBase;
+ use Drupal\Core\Database\Connection;
+@@ -174,14 +175,15 @@ public function overview() {
+     foreach ($result as $dblog) {
+       $message = $this->formatMessage($dblog);
+       if ($message && isset($dblog->wid)) {
+-        // Truncate link_text to 56 chars of message.
+-        $log_text = Unicode::truncate(Xss::filter($message, array()), 56, TRUE, TRUE);
++        // Truncate link_text to 56 chars of message. This is a rare case where
++        // it is acceptable to call SafeMarkup::set() as we are truncating text
++        // that has already passed through SafeMarkup::set().
++        $log_text = SafeMarkup::set(Unicode::truncate(Xss::filter($message, array()), 56, TRUE, TRUE));
+         $message = $this->l($log_text, new Url('dblog.event', array('event_id' => $dblog->wid), array(
+           'attributes' => array(
+             // Provide a title for the link for useful hover hints.
+             'title' => Unicode::truncate(strip_tags($message), 256, TRUE, TRUE),
+           ),
+-          'html' => TRUE,
+         )));
+       }
+       $username = array(
+diff --git a/core/modules/dblog/src/Tests/Views/ViewsIntegrationTest.php b/core/modules/dblog/src/Tests/Views/ViewsIntegrationTest.php
+index a8fcb5b..3e3ede3 100644
+--- a/core/modules/dblog/src/Tests/Views/ViewsIntegrationTest.php
++++ b/core/modules/dblog/src/Tests/Views/ViewsIntegrationTest.php
+@@ -77,7 +77,10 @@ public function testIntegration() {
+       'variables' => array(
+         '@token1' => $this->randomMachineName(),
+         '!token2' => $this->randomMachineName(),
+-        'link' => \Drupal::l('<object>Link</object>', new Url('<front>')),
++        'link' => \Drupal::l(array(
++          '#type' => 'inline_template',
++          '#template' => '<object>Link</object>',
++        ), new Url('<front>')),
+       ),
+     );
+     $logger_factory = $this->container->get('logger.factory');
+diff --git a/core/modules/field_ui/src/EntityDisplayModeListBuilder.php b/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
+index 6056508..453da32 100644
+--- a/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
++++ b/core/modules/field_ui/src/EntityDisplayModeListBuilder.php
+@@ -121,9 +121,6 @@ public function render() {
+           '#type' => 'link',
+           '#url' => Url::fromRoute($short_type == 'view' ? 'field_ui.entity_view_mode_add_type' : 'field_ui.entity_form_mode_add_type', ['entity_type_id' => $entity_type]),
+           '#title' => t('Add new %label @entity-type', array('%label' => $this->entityTypes[$entity_type]->getLabel(), '@entity-type' => $this->entityType->getLowercaseLabel())),
+-          '#options' => array(
+-            'html' => TRUE,
+-          ),
+         ),
+         'colspan' => count($table['#header']),
+       );
+diff --git a/core/modules/image/src/Tests/ImageFieldDisplayTest.php b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
+index c49af3c..cc25824 100644
+--- a/core/modules/image/src/Tests/ImageFieldDisplayTest.php
++++ b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
+@@ -8,6 +8,7 @@
+ namespace Drupal\image\Tests;
+ 
+ use Drupal\Core\Field\FieldStorageDefinitionInterface;
++use Drupal\Core\Url;
+ use Drupal\field\Entity\FieldStorageConfig;
+ 
+ /**
+@@ -27,6 +28,22 @@ class ImageFieldDisplayTest extends ImageFieldTestBase {
+   public static $modules = array('field_ui');
+ 
+   /**
++   * The link generator.
++   *
++   * @var \Drupal\Core\Utility\LinkGenerator
++   */
++  protected $linkGenerator;
++
++  /**
++   * {@inheritdoc}
++   */
++  protected function setUp() {
++    parent::setUp();
++
++    $this->linkGenerator = $this->container->get('link_generator');
++  }
++
++  /**
+    * Test image formatters on node display for public files.
+    */
+   function testImageFieldFormattersPublic() {
+@@ -85,7 +102,8 @@ function _testImageFieldFormatters($scheme) {
+       '#width' => 40,
+       '#height' => 20,
+     );
+-    $default_output = '<a href="' . file_create_url($image_uri) . '">' . drupal_render($image) . '</a>';
++
++    $default_output = $this->linkGenerator->generate($image, Url::fromUri(file_create_url($image_uri)));
+     $this->drupalGet('node/' . $nid);
+     $cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
+     $this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
+diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
+index 759c5cd..20a9f6b 100644
+--- a/core/modules/node/node.api.php
++++ b/core/modules/node/node.api.php
+@@ -550,7 +550,6 @@ function hook_node_links_alter(array &$links, NodeInterface $entity, array &$con
+       'node-report' => array(
+         'title' => t('Report'),
+         'href' => "node/{$entity->id()}/report",
+-        'html' => TRUE,
+         'query' => array('token' => \Drupal::getContainer()->get('csrf_token')->get("node/{$entity->id()}/report")),
+       ),
+     ),
+diff --git a/core/modules/node/src/NodeViewBuilder.php b/core/modules/node/src/NodeViewBuilder.php
+index 54f57b4..2026c2f 100644
+--- a/core/modules/node/src/NodeViewBuilder.php
++++ b/core/modules/node/src/NodeViewBuilder.php
+@@ -148,7 +148,6 @@ protected static function buildLinks(NodeInterface $entity, $view_mode) {
+         )),
+         'url' => $entity->urlInfo(),
+         'language' => $entity->language(),
+-        'html' => TRUE,
+         'attributes' => array(
+           'rel' => 'tag',
+           'title' => $node_title_stripped,
+diff --git a/core/modules/responsive_image/responsive_image.module b/core/modules/responsive_image/responsive_image.module
+index bdb2a83..5097309 100644
+--- a/core/modules/responsive_image/responsive_image.module
++++ b/core/modules/responsive_image/responsive_image.module
+@@ -152,7 +152,6 @@ function theme_responsive_image_formatter($variables) {
+   if (isset($variables['path']['path'])) {
+     $path = $variables['path']['path'];
+     $options = isset($variables['path']['options']) ? $variables['path']['options'] : array();
+-    $options['html'] = TRUE;
+     return \Drupal::l($responsive_image, Url::fromUri($path, $options));
+   }
+ 
+diff --git a/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php b/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
+index 5f85234..28c890c 100644
+--- a/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
++++ b/core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php
+@@ -8,6 +8,7 @@
+ namespace Drupal\responsive_image\Tests;
+ 
+ use Drupal\Component\Utility\Unicode;
++use Drupal\Core\Url;
+ use Drupal\image\Tests\ImageFieldTestBase;
+ 
+ /**
+@@ -27,11 +28,20 @@ class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase {
+   public static $modules = array('field_ui', 'responsive_image', 'responsive_image_test_module');
+ 
+   /**
+-   * Drupal\simpletest\WebTestBase\setUp().
++   * The link generator.
++   *
++   * @var \Drupal\Core\Utility\LinkGenerator
++   */
++  protected $linkGenerator;
++
++  /**
++   * {@inheritdoc}
+    */
+   protected function setUp() {
+     parent::setUp();
+ 
++    $this->linkGenerator = $this->container->get('link_generator');
++
+     // Create user.
+     $this->admin_user = $this->drupalCreateUser(array(
+       'administer responsive images',
+@@ -116,7 +126,8 @@ public function _testResponsiveImageFieldFormatters($scheme) {
+       '#width' => 40,
+       '#height' => 20,
+     );
+-    $default_output = '<a href="' . file_create_url($image_uri) . '">' . drupal_render($image) . '</a>';
++
++    $default_output = $this->linkGenerator->generate($image, Url::fromUri(file_create_url($image_uri)));
+     $this->drupalGet('node/' . $nid);
+     $cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags');
+     $this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.');
+diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
+index 601a149..4d42450 100644
+--- a/core/modules/shortcut/shortcut.module
++++ b/core/modules/shortcut/shortcut.module
+@@ -8,8 +8,8 @@
+ use Drupal\Core\Access\AccessResult;
+ use Drupal\Component\Utility\UrlHelper;
+ use Drupal\Core\Cache\Cache;
++use Drupal\Component\Utility\NestedArray;
+ use Drupal\Core\Routing\RouteMatchInterface;
+-use Drupal\Core\Routing\UrlMatcher;
+ use Drupal\Core\Url;
+ use Drupal\shortcut\Entity\ShortcutSet;
+ use Drupal\shortcut\ShortcutSetInterface;
+@@ -339,9 +339,13 @@ function shortcut_preprocess_page(&$variables) {
+         ),
+         '#prefix' => '<div class="add-or-remove-shortcuts ' . $link_mode . '-shortcut">',
+         '#type' => 'link',
+-        '#title' => '<span class="icon"></span><span class="text">'. $link_text .'</span>',
++        '#title' => array(
++          '#type' => 'inline_template',
++          '#template' => '<span class="icon"></span><span class="text">{{ link_text }}</span>',
++          '#context' => array('link_text' => $link_text),
++        ),
+         '#url' => Url::fromRoute($route_name, $route_parameters),
+-        '#options' => array('query' => $query, 'html' => TRUE),
++        '#options' => array('query' => $query),
+         '#suffix' => '</div>',
+       );
+     }
+diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
+index 0836987..9ea433c 100644
+--- a/core/modules/simpletest/src/AssertContentTrait.php
++++ b/core/modules/simpletest/src/AssertContentTrait.php
+@@ -284,6 +284,9 @@ protected function getAllOptions(\SimpleXMLElement $element) {
+    *   TRUE if the assertion succeeded, FALSE otherwise.
+    */
+   protected function assertLink($label, $index = 0, $message = '', $group = 'Other') {
++    // $this->xpath will escape entities, so we need to decode them first
++    // to avoid double escaping leading to failed tests.
++    $label = html_entity_decode($label);
+     $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
+     $message = ($message ? $message : String::format('Link with label %label found.', array('%label' => $label)));
+     return $this->assert(isset($links[$index]), $message, $group);
+diff --git a/core/modules/system/src/Tests/Theme/FunctionsTest.php b/core/modules/system/src/Tests/Theme/FunctionsTest.php
+index f9a4a83..1d8522f 100644
+--- a/core/modules/system/src/Tests/Theme/FunctionsTest.php
++++ b/core/modules/system/src/Tests/Theme/FunctionsTest.php
+@@ -211,6 +211,15 @@ function testLinks() {
+           'key' => 'value',
+         )
+       ),
++      'render array' => array(
++        'title' => array(
++          '#type' => 'inline_template',
++          '#template' => '<span class="unescaped">{{ text }}</span>',
++          '#context' => array(
++            'text' => 'potentially unsafe text that <should> be escaped',
++          ),
++        ),
++      ),
+     );
+ 
+     $expected_links = '';
+@@ -221,6 +230,7 @@ function testLinks() {
+     $expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . String::checkPlain('Test route') . '</a></li>';
+     $query = array('key' => 'value');
+     $expected_links .= '<li class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . String::checkPlain('Query test route') . '</a></li>';
++    $expected_links .= '<li class="render-array"><span class="unescaped">' . String::checkPlain('potentially unsafe text that <should> be escaped') . '</span></li>';
+     $expected_links .= '</ul>';
+ 
+     // Verify that passing a string as heading works.
+@@ -260,6 +270,7 @@ function testLinks() {
+     $expected_links .= '<li class="router-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1') . '">' . String::checkPlain('Test route') . '</a></li>';
+     $query = array('key' => 'value');
+     $expected_links .= '<li class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '">' . String::checkPlain('Query test route') . '</a></li>';
++    $expected_links .= '<li class="render-array"><span class="unescaped">' . String::checkPlain('potentially unsafe text that <should> be escaped') . '</span></li>';
+     $expected_links .= '</ul>';
+     $expected = $expected_heading . $expected_links;
+     $this->assertThemeOutput('links', $variables, $expected);
+@@ -276,6 +287,7 @@ function testLinks() {
+     $query = array('key' => 'value');
+     $encoded_query = String::checkPlain(Json::encode($query));
+     $expected_links .= '<li data-drupal-link-query="'.$encoded_query.'" data-drupal-link-system-path="router_test/test1" class="query-test"><a href="' . \Drupal::urlGenerator()->generate('router_test.1', $query) . '" data-drupal-link-query="'.$encoded_query.'" data-drupal-link-system-path="router_test/test1">' . String::checkPlain('Query test route') . '</a></li>';
++    $expected_links .= '<li class="render-array"><span class="unescaped">' . String::checkPlain('potentially unsafe text that <should> be escaped') . '</span></li>';
+     $expected_links .= '</ul>';
+     $expected = $expected_heading . $expected_links;
+     $this->assertThemeOutput('links', $variables, $expected);
+diff --git a/core/modules/user/user.module b/core/modules/user/user.module
+index 6d3c6f3..6b7bfcb 100644
+--- a/core/modules/user/user.module
++++ b/core/modules/user/user.module
+@@ -1444,7 +1444,6 @@ function user_toolbar() {
+       'account' => array(
+         'title' => t('View profile'),
+         'url' => Url::fromRoute('user.page'),
+-        'html' => TRUE,
+         'attributes' => array(
+           'title' => t('User account'),
+         ),
+@@ -1452,7 +1451,6 @@ function user_toolbar() {
+       'account_edit' => array(
+         'title' => t('Edit profile'),
+         'url' => Url::fromRoute('entity.user.edit_form', ['user' => $user->id()]),
+-        'html' => TRUE,
+         'attributes' => array(
+           'title' => t('Edit user account'),
+         ),
+diff --git a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+index de9cac6..ab22553 100644
+--- a/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
++++ b/core/modules/views/src/Plugin/views/display/DisplayPluginBase.php
+@@ -1024,19 +1024,34 @@ public function overrideOption($option, $value) {
+    * an easy URL to exactly the right section. Don't override this.
+    */
+   public function optionLink($text, $section, $class = '', $title = '') {
+-    if (!empty($class)) {
+-      $text = '<span>' . $text . '</span>';
+-    }
+-
+     if (!trim($text)) {
+       $text = $this->t('Broken field');
+     }
+ 
++    if (!empty($class)) {
++      $text = [
++        '#type' => 'inline_template',
++        '#template' => '<span>{{ text }}</span>',
++        '#context' => array('text' => $text),
++      ];
++    }
++
+     if (empty($title)) {
+       $title = $text;
+     }
+ 
+-    return \Drupal::l($text, new Url('views_ui.form_display', ['js' => 'nojs', 'view' => $this->view->storage->id(), 'display_id' => $this->display['id'], 'type' => $section], array('attributes' => array('class' => array('views-ajax-link', $class), 'title' => $title, 'id' => drupal_html_id('views-' . $this->display['id'] . '-' . $section)), 'html' => TRUE)));
++    return \Drupal::l($text, new Url('views_ui.form_display', array(
++        'js' => 'nojs',
++        'view' => $this->view->storage->id(),
++        'display_id' => $this->display['id'],
++        'type' => $section
++      ), array(
++        'attributes' => array(
++          'class' => array('views-ajax-link', $class),
++          'title' => $title,
++          'id' => drupal_html_id('views-' . $this->display['id'] . '-' . $section)
++        )
++    )));
+   }
+ 
+   /**
+@@ -1120,12 +1135,12 @@ public function optionsSummary(&$categories, &$options) {
+       $options['display_id'] = array(
+         'category' => 'other',
+         'title' => $this->t('Machine Name'),
+-        'value' => !empty($this->display['new_id']) ? String::checkPlain($this->display['new_id']) : String::checkPlain($this->display['id']),
++        'value' => !empty($this->display['new_id']) ? $this->display['new_id'] : $this->display['id'],
+         'desc' => $this->t('Change the machine name of this display.'),
+       );
+     }
+ 
+-    $display_comment = String::checkPlain(Unicode::substr($this->getOption('display_comment'), 0, 10));
++    $display_comment = Unicode::substr($this->getOption('display_comment'), 0, 10);
+     $options['display_comment'] = array(
+       'category' => 'other',
+       'title' => $this->t('Administrative comment'),
+@@ -1319,7 +1334,7 @@ public function optionsSummary(&$categories, &$options) {
+         $display_id = $this->getLinkDisplay();
+         $displays = $this->view->storage->get('display');
+         if (!empty($displays[$display_id])) {
+-          $link_display = String::checkPlain($displays[$display_id]['display_title']);
++          $link_display = $displays[$display_id]['display_title'];
+         }
+       }
+ 
+@@ -1360,7 +1375,7 @@ public function optionsSummary(&$categories, &$options) {
+       $options['exposed_form']['links']['exposed_form_options'] = $this->t('Exposed form settings for this exposed form style.');
+     }
+ 
+-    $css_class = String::checkPlain(trim($this->getOption('css_class')));
++    $css_class = trim($this->getOption('css_class'));
+     if (!$css_class) {
+       $css_class = $this->t('None');
+     }
+diff --git a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
+index f13b8db..defd281 100644
+--- a/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
++++ b/core/modules/views/src/Plugin/views/field/FieldPluginBase.php
+@@ -1348,7 +1348,6 @@ protected function renderAsLink($alter, $text, $tokens) {
+     }
+ 
+     $options = array(
+-      'html' => TRUE,
+       'absolute' => !empty($alter['absolute']) ? TRUE : FALSE,
+     );
+ 
+diff --git a/core/modules/views/src/Plugin/views/field/Url.php b/core/modules/views/src/Plugin/views/field/Url.php
+index cf66117..9e4c4a9 100644
+--- a/core/modules/views/src/Plugin/views/field/Url.php
++++ b/core/modules/views/src/Plugin/views/field/Url.php
+@@ -8,6 +8,7 @@
+ namespace Drupal\views\Plugin\views\field;
+ 
+ use Drupal\Core\Form\FormStateInterface;
++use Drupal\Core\Url as DrupalUrl;
+ use Drupal\views\ResultRow;
+ 
+ /**
+@@ -45,7 +46,7 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) {
+   public function render(ResultRow $values) {
+     $value = $this->getValue($values);
+     if (!empty($this->options['display_as_link'])) {
+-      return _l($this->sanitizeValue($value), $value, array('html' => TRUE));
++      return \Drupal::l($this->sanitizeValue($value), DrupalUrl::fromUri('base://' . $value));
+     }
+     else {
+       return $this->sanitizeValue($value, 'url');
+diff --git a/core/modules/views/src/Tests/Plugin/DisplayTest.php b/core/modules/views/src/Tests/Plugin/DisplayTest.php
+index bacfdd4..8f8c4e7 100644
+--- a/core/modules/views/src/Tests/Plugin/DisplayTest.php
++++ b/core/modules/views/src/Tests/Plugin/DisplayTest.php
+@@ -7,6 +7,7 @@
+ 
+ namespace Drupal\views\Tests\Plugin;
+ 
++use Drupal\Component\Utility\String;
+ use Drupal\views\Views;
+ use Drupal\views_test_data\Plugin\views\display\DisplayTest as DisplayTestPlugin;
+ 
+@@ -122,12 +123,12 @@ public function testDisplayPlugin() {
+ 
+     $this->clickLink('Test option title');
+ 
+-    $this->randomString = $this->randomString();
+-    $this->drupalPostForm(NULL, array('test_option' => $this->randomString), t('Apply'));
++    $test_option = $this->randomString();
++    $this->drupalPostForm(NULL, array('test_option' => $test_option), t('Apply'));
+ 
+     // Check the new value has been saved by checking the UI summary text.
+     $this->drupalGet('admin/structure/views/view/test_view/edit/display_test_1');
+-    $this->assertRaw($this->randomString);
++    $this->assertRaw(String::checkPlain($test_option));
+ 
+     // Test the enable/disable status of a display.
+     $view->display_handler->setOption('enabled', FALSE);
+diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
+index d4fb797..3aac5bf 100644
+--- a/core/modules/views/views.theme.inc
++++ b/core/modules/views/views.theme.inc
+@@ -477,7 +477,6 @@ function template_preprocess_views_view_table(&$variables) {
+         $query['order'] = $field;
+         $query['sort'] = $initial;
+         $link_options = array(
+-          'html' => TRUE,
+           'attributes' => array('title' => $title),
+           'query' => $query,
+         );
+diff --git a/core/modules/views_ui/src/Form/Ajax/Rearrange.php b/core/modules/views_ui/src/Form/Ajax/Rearrange.php
+index cff75fd..ca213d1 100644
+--- a/core/modules/views_ui/src/Form/Ajax/Rearrange.php
++++ b/core/modules/views_ui/src/Form/Ajax/Rearrange.php
+@@ -125,7 +125,19 @@ public function buildForm(array $form, FormStateInterface $form_state) {
+         '#id' => 'views-removed-' . $id,
+         '#attributes' => array('class' => array('views-remove-checkbox')),
+         '#default_value' => 0,
+-        '#suffix' => \Drupal::l('<span>' . $this->t('Remove') . '</span>', Url::fromRoute('<none>', [], array('attributes' => array('id' => 'views-remove-link-' . $id, 'class' => array('views-hidden', 'views-button-remove', 'views-remove-link'), 'alt' => $this->t('Remove this item'), 'title' => $this->t('Remove this item')), 'html' => TRUE))),
++        '#suffix' => \Drupal::l(
++          array(
++            '#type' => 'inline_template',
++            '#template' => '<span>{{ text }}</span>',
++            '#context' => array('text' => $this->t('Remove')),
++          ),
++          Url::fromRoute('<none>', array(), array('attributes' => array(
++            'id' => 'views-remove-link-' . $id,
++            'class' => array('views-hidden', 'views-button-remove', 'views-remove-link'),
++            'alt' => $this->t('Remove this item'),
++            'title' => $this->t('Remove this item')),
++          ))
++        ),
+       );
+     }
+ 
+diff --git a/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php b/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
+index 72dae7c..06be203 100644
+--- a/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
++++ b/core/modules/views_ui/src/Form/Ajax/ReorderDisplays.php
+@@ -120,11 +120,13 @@ public function buildForm(array $form, FormStateInterface $form_state) {
+         ),
+         'link' => array(
+           '#type' => 'link',
+-          '#title' => '<span>' . $this->t('Remove') . '</span>',
+-          '#url' => Url::fromRoute('<none>'),
+-          '#options' => array(
+-            'html' => TRUE,
++          '#title' => array(
++            '#type' => 'inline_template',
++            '#template' => '<span>{{ label }}</span>',
++            '#context' => array('label' => $this->t('Remove')),
+           ),
++          '#url' => Url::fromRoute('<none>'),
++          '#href' => 'javascript:void()',
+           '#attributes' => array(
+             'id' => 'display-remove-link-' . $id,
+             'class' => array('views-button-remove', 'display-remove-link'),
+diff --git a/core/modules/views_ui/src/ViewEditForm.php b/core/modules/views_ui/src/ViewEditForm.php
+index 8b1e4a2..e656925 100644
+--- a/core/modules/views_ui/src/ViewEditForm.php
++++ b/core/modules/views_ui/src/ViewEditForm.php
+@@ -998,7 +998,6 @@ public function getFormBucket(ViewUI $view, $type, $display) {
+       'title' => $add_text,
+       'url' => Url::fromRoute('views_ui.form_add_handler', ['js' => 'nojs', 'view' => $view->id(), 'display_id' => $display['id'], 'type' => $type]),
+       'attributes' => array('class' => array('icon compact add', 'views-ajax-link'), 'id' => 'views-add-' . $type),
+-      'html' => TRUE,
+     );
+     if ($count_handlers > 0) {
+       // Create the rearrange text variable for the rearrange action.
+@@ -1008,7 +1007,6 @@ public function getFormBucket(ViewUI $view, $type, $display) {
+         'title' => $rearrange_text,
+         'url' => $rearrange_url,
+         'attributes' => array('class' => array($class, 'views-ajax-link'), 'id' => 'views-rearrange-' . $type),
+-        'html' => TRUE,
+       );
+     }
+ 
+@@ -1070,7 +1068,7 @@ public function getFormBucket(ViewUI $view, $type, $display) {
+           'display_id' => $display['id'],
+           'type' => $type,
+           'id' => $id,
+-        ), array('attributes' => array('class' => array('views-ajax-link')), 'html' => TRUE)));
++        ), array('attributes' => array('class' => array('views-ajax-link')))));
+         continue;
+       }
+ 
+@@ -1093,27 +1091,37 @@ public function getFormBucket(ViewUI $view, $type, $display) {
+         'display_id' => $display['id'],
+         'type' => $type,
+         'id' => $id,
+-      ), array('attributes' => $link_attributes, 'html' => TRUE)));
++      ), array('attributes' => $link_attributes)));
+       $build['fields'][$id]['#class'][] = drupal_clean_css_identifier($display['id']. '-' . $type . '-' . $id);
+ 
+       if ($executable->display_handler->useGroupBy() && $handler->usesGroupBy()) {
+-        $build['fields'][$id]['#settings_links'][] = $this->l('<span class="label">' . $this->t('Aggregation settings') . '</span>', new Url('views_ui.form_handler_group', array(
++        $build['fields'][$id]['#settings_links'][] = $this->l(array(
++          '#type' => 'inline_template',
++          '#template' => '<span class="label">{{ label }}</span>',
++          '#context' => array('label' => $this->t('Aggregation settings')),
++        ),
++        new Url('views_ui.form_handler_group', array(
+           'js' => 'nojs',
+           'view' => $view->id(),
+           'display_id' => $display['id'],
+           'type' => $type,
+           'id' => $id,
+-        ), array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => $this->t('Aggregation settings')), 'html' => TRUE)));
++        ), array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => $this->t('Aggregation settings')))));
+       }
+ 
+       if ($handler->hasExtraOptions()) {
+-        $build['fields'][$id]['#settings_links'][] = $this->l('<span class="label">' . $this->t('Settings') . '</span>', new Url('views_ui.form_handler_extra', array(
++        $build['fields'][$id]['#settings_links'][] = $this->l(array(
++          '#type' => 'inline_template',
++          '#template' => '<span class="label">{{ label }}</span>',
++          '#context' => array('label' => $this->t('Settings')),
++        ),
++        new Url('views_ui.form_handler_extra', array(
+           'js' => 'nojs',
+           'view' => $view->id(),
+           'display_id' => $display['id'],
+           'type' => $type,
+           'id' => $id,
+-        ), array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => $this->t('Settings')), 'html' => TRUE)));
++        ), array('attributes' => array('class' => array('views-button-configure', 'views-ajax-link'), 'title' => $this->t('Settings')))));
+       }
+ 
+       if ($grouping) {
+diff --git a/core/modules/views_ui/views_ui.theme.inc b/core/modules/views_ui/views_ui.theme.inc
+index f56b3b5..b33f188 100644
+--- a/core/modules/views_ui/views_ui.theme.inc
++++ b/core/modules/views_ui/views_ui.theme.inc
+@@ -158,7 +158,17 @@ function theme_views_ui_build_group_filter_form($variables) {
+       'value' => drupal_render($form['group_items'][$group_id]['value']),
+       'remove' => array(
+         'data' => array(
+-          '#markup' => drupal_render($form['group_items'][$group_id]['remove']) . \Drupal::l('<span>' . t('Remove') . '</span>', Url::fromRoute('<none>', [], array('attributes' => array('id' => 'views-remove-link-' . $group_id, 'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => true))),
++          '#markup' => drupal_render($form['group_items'][$group_id]['remove']) . \Drupal::l(
++            array(
++              '#type' => 'inline_template',
++              '#template' => '<span>{% trans %}Remove{% endtrans %}</span>',
++            ),
++            Url::fromRoute('<none>', array(), array('attributes' => array(
++              'id' => 'views-remove-link-' . $group_id,
++              'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'),
++              'alt' => t('Remove this item'),
++              'title' => t('Remove this item')),
++            ))),
+         ),
+       ),
+     );
+@@ -278,7 +288,10 @@ function template_preprocess_views_ui_rearrange_filter_form(&$variables) {
+         $remove_link = array(
+           '#type' => 'link',
+           '#url' => Url::fromRoute('<none>'),
+-          '#title' => '<span>' . t('Remove') . '</span>',
++          '#title' => array(
++            '#type' => 'inline_template',
++            '#template' => '<span>{% trans %}Remove{% endtrans %}</span>',
++          ),
+           '#weight' => '1',
+           '#options' => array(
+             'attributes' => array(
+@@ -292,7 +305,6 @@ function template_preprocess_views_ui_rearrange_filter_form(&$variables) {
+               'alt' => t('Remove this item'),
+               'title' => t('Remove this item'),
+             ),
+-            'html' => TRUE,
+           ),
+         );
+         $row[]['data'] = array(
+diff --git a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
+index 5721010..70577ae 100644
+--- a/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
++++ b/core/tests/Drupal/Tests/Core/Utility/LinkGeneratorTest.php
+@@ -7,6 +7,7 @@
+ 
+ namespace Drupal\Tests\Core\Utility {
+ 
++use Drupal\Component\Utility\SafeMarkup;
+ use Drupal\Core\Language\Language;
+ use Drupal\Core\Url;
+ use Drupal\Core\Utility\LinkGenerator;
+@@ -51,7 +52,6 @@ class LinkGeneratorTest extends UnitTestCase {
+    */
+   protected $defaultOptions = array(
+     'query' => array(),
+-    'html' => FALSE,
+     'language' => NULL,
+     'set_active_class' => FALSE,
+     'absolute' => FALSE,
+@@ -66,7 +66,15 @@ protected function setUp() {
+     $this->urlGenerator = $this->getMock('\Drupal\Core\Routing\UrlGenerator', array(), array(), '', FALSE);
+     $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+ 
+-    $this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->moduleHandler);
++    $this->linkGenerator = $this->getMock('Drupal\Core\Utility\LinkGenerator', array('drupalRender'),
++      array($this->urlGenerator, $this->moduleHandler));
++
++    // The last step of drupal_render() is to mark the final string as safe,
++    // so we need to explicitly to that as part of the mocked return value.
++    $this->linkGenerator->method('drupalRender')
++      ->with($this->isType('array'))
++      ->will($this->returnValue(SafeMarkup::set('<em>HTML output</em>')));
++
+     $this->urlAssembler = $this->getMock('\Drupal\Core\Utility\UnroutedUrlAssemblerInterface');
+   }
+ 
+@@ -325,7 +333,7 @@ public function testGenerateWithHtml() {
+       ));
+     $this->urlGenerator->expects($this->at(1))
+       ->method('generateFromRoute')
+-      ->with('test_route_5', array(), array('html' => TRUE) + $this->defaultOptions)
++      ->with('test_route_5', array(), $this->defaultOptions)
+       ->will($this->returnValue(
+         '/test-route-5'
+       ));
+@@ -344,10 +352,14 @@ public function testGenerateWithHtml() {
+       ),
+     ), $result);
+ 
+-    // Test that the 'html' option allows unsanitized HTML link text.
+-    $url = new Url('test_route_5', array(), array('html' => TRUE));
++    // Test that HTML link text can be used in a render array.
++    $url = new Url('test_route_5', array());
+     $url->setUrlGenerator($this->urlGenerator);
+-    $result = $this->linkGenerator->generate('<em>HTML output</em>', $url);
++    $html = [
++      '#type' => 'inline_template',
++      '#template' => '<em>HTML output</em>',
++    ];
++    $result = $this->linkGenerator->generate($html, $url);
+     $this->assertTag(array(
+       'tag' => 'a',
+       'attributes' => array('href' => '/test-route-5'),
