From 046965dfc11b8dfc98f1f6d27f45325678a70a23 Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
Date: Tue, 28 Apr 2015 16:44:27 +0100
Subject: [PATCH] Issue #2478443 by Wim Leers: Set the 'is-active' class for
 anonymous users in a Response Filter instead of a #post_render_cache callback

---
 core/core.services.yml                        |   6 +
 .../ActiveLinkResponseFilter.php              | 246 ++++++++++++++++++
 .../Core/Render/MainContent/HtmlRenderer.php  |   1 +
 .../src/Controller/SystemController.php       | 128 ---------
 core/modules/system/system.module             |  17 +-
 .../ActiveLinkResponseFilterTest.php}         |  82 +++---
 6 files changed, 297 insertions(+), 183 deletions(-)
 create mode 100644 core/lib/Drupal/Core/EventSubscriber/ActiveLinkResponseFilter.php
 rename core/{modules/system/tests/src/Unit/Controller/SystemControllerTest.php => tests/Drupal/Tests/Core/EventSubscriber/ActiveLinkResponseFilterTest.php} (88%)

diff --git a/core/core.services.yml b/core/core.services.yml
index 657920409ce7..5959c4470693 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1362,3 +1362,9 @@ services:
     arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info', '@render_cache', '%renderer.config%']
   email.validator:
     class: Egulias\EmailValidator\EmailValidator
+
+  response_filter.active_link:
+    class: Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
+    arguments: ['@current_user', '@path.current', '@path.matcher', '@language_manager']
+    tags:
+      - { name: event_subscriber }
diff --git a/core/lib/Drupal/Core/EventSubscriber/ActiveLinkResponseFilter.php b/core/lib/Drupal/Core/EventSubscriber/ActiveLinkResponseFilter.php
new file mode 100644
index 000000000000..3c9fa5e417db
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/ActiveLinkResponseFilter.php
@@ -0,0 +1,246 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Component\Serialization\Json;
+use Drupal\Core\Language\LanguageInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Path\CurrentPathStack;
+use Drupal\Core\Path\PathMatcherInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Url;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Subscribes to filter HTML responses, to set the 'is-active' class on links.
+ *
+ * Only for anonymous users; for authenticated users, the active-link asset
+ * library is loaded.
+ *
+ * @see system_page_attachments()
+ */
+class ActiveLinkResponseFilter implements EventSubscriberInterface {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $currentUser;
+
+  /**
+   * The current path.
+   *
+   * @var \Drupal\Core\Path\CurrentPathStack
+   */
+  protected $currentPath;
+
+  /**
+   * The path matcher.
+   *
+   * @var \Drupal\Core\Path\PathMatcherInterface
+   */
+  protected $pathMatcher;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * Constructs a new ActiveLinkResponseFilter instance.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $current_user
+   *   The current user.
+   * @param \Drupal\Core\Path\CurrentPathStack $current_path
+   *   The current path.
+   * @param \Drupal\Core\Path\PathMatcherInterface $path_matcher
+   *   The path matcher.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   */
+  public function __construct(AccountInterface $current_user, CurrentPathStack $current_path, PathMatcherInterface $path_matcher, LanguageManagerInterface $language_manager) {
+    $this->currentUser = $current_user;
+    $this->currentPath = $current_path;
+    $this->pathMatcher = $path_matcher;
+    $this->languageManager = $language_manager;
+  }
+
+  /**
+   * Sets the 'is-active' class on links.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\FilterResponseEvent $event
+   *   The response event.
+   */
+  public function onResponse(FilterResponseEvent $event) {
+    // Only care about HTML responses.
+    if (stripos($event->getResponse()->headers->get('Content-Type'), 'text/html') === FALSE) {
+      return;
+    }
+
+    // For authenticated users, the 'is-active' class is set in JavaScript.
+    // @see system_page_attachments()
+    if ($this->currentUser->isAuthenticated()) {
+      return;
+    }
+
+    $response = $event->getResponse();
+    $response->setContent(static::setLinkActiveClass(
+      $response->getContent(),
+      ltrim($this->currentPath->getPath(), '/'),
+      $this->pathMatcher->isFrontPage(),
+      $this->languageManager->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(),
+      $event->getRequest()->query->all()
+    ));
+  }
+
+
+  /**
+   * Sets the "is-active" class on relevant links.
+   *
+   * This is a PHP implementation of the drupal.active-link JavaScript library.
+   *
+   * @param string $html_markup.
+   *   The HTML markup to update.
+   * @param string $current_path
+   *   The system path of the currently active page.
+   * @param bool $is_front
+   *   Whether the current page is the front page (which implies the current
+   *   path might also be <front>).
+   * @param string $url_language
+   *   The language code of the current URL.
+   * @param array $query
+   *   The query string for the current URL.
+   *
+   * @return string
+   *   The updated HTML markup.
+   *
+   * @todo Once a future version of PHP supports parsing HTML5 properly
+   *   (i.e. doesn't fail on https://drupal.org/comment/7938201#comment-7938201)
+   *   then we can get rid of this manual parsing and use DOMDocument instead.
+   */
+  public static function setLinkActiveClass($html_markup, $current_path, $is_front, $url_language, array $query) {
+    $search_key_current_path = 'data-drupal-link-system-path="' . $current_path . '"';
+    $search_key_front = 'data-drupal-link-system-path="&lt;front&gt;"';
+
+    // An active link's path is equal to the current path, so search the HTML
+    // for an attribute with that value.
+    $offset = 0;
+    while (strpos($html_markup, $search_key_current_path, $offset) !== FALSE || ($is_front && strpos($html_markup, $search_key_front, $offset) !== FALSE)) {
+      $pos_current_path = strpos($html_markup, $search_key_current_path, $offset);
+      $pos_front = strpos($html_markup, $search_key_front, $offset);
+
+      // Determine which of the two values is the next match: the exact path, or
+      // the <front> special case.
+      $pos_match = NULL;
+      if ($pos_front === FALSE) {
+        $pos_match = $pos_current_path;
+      }
+      elseif ($pos_current_path === FALSE) {
+        $pos_match = $pos_front;
+      }
+      elseif ($pos_current_path < $pos_front) {
+        $pos_match = $pos_current_path;
+      }
+      else {
+        $pos_match = $pos_front;
+      }
+
+      // Find beginning and ending of opening tag.
+      $pos_tag_start = NULL;
+      for ($i = $pos_match; $pos_tag_start === NULL && $i > 0; $i--) {
+        if ($html_markup[$i] === '<') {
+          $pos_tag_start = $i;
+        }
+      }
+      $pos_tag_end = NULL;
+      for ($i = $pos_match; $pos_tag_end === NULL && $i < strlen($html_markup); $i++) {
+        if ($html_markup[$i] === '>') {
+          $pos_tag_end = $i;
+        }
+      }
+
+      // Get the HTML: this will be the opening part of a single tag, e.g.:
+      //   <a href="/" data-drupal-link-system-path="&lt;front&gt;">
+      $tag = substr($html_markup, $pos_tag_start, $pos_tag_end - $pos_tag_start + 1);
+
+      // Parse it into a DOMDocument so we can reliably read and modify
+      // attributes.
+      $dom = new \DOMDocument();
+      @$dom->loadHTML('<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' . $tag . '</body></html>');
+      $node = $dom->getElementsByTagName('body')->item(0)->firstChild;
+
+      // Ensure we don't set the "active" class twice on the same element.
+      $class = $node->getAttribute('class');
+      $add_active = !in_array('is-active', explode(' ', $class));
+
+      // The language of an active link is equal to the current language.
+      if ($add_active && $url_language) {
+        if ($node->hasAttribute('hreflang') && $node->getAttribute('hreflang') !== $url_language) {
+          $add_active = FALSE;
+        }
+      }
+      // The query parameters of an active link are equal to the current
+      // parameters.
+      if ($add_active) {
+        if ($query) {
+          if (!$node->hasAttribute('data-drupal-link-query') || $node->getAttribute('data-drupal-link-query') !== Json::encode($query)) {
+            $add_active = FALSE;
+          }
+        }
+        else {
+          if ($node->hasAttribute('data-drupal-link-query')) {
+            $add_active = FALSE;
+          }
+        }
+      }
+
+      // Only if the path, the language and the query match, we set the
+      // "is-active" class.
+      if ($add_active) {
+        if (strlen($class) > 0) {
+          $class .= ' ';
+        }
+        $class .= 'is-active';
+        $node->setAttribute('class', $class);
+
+        // Get the updated tag.
+        $updated_tag = $dom->saveXML($node, LIBXML_NOEMPTYTAG);
+        // saveXML() added a closing tag, remove it.
+        $updated_tag = substr($updated_tag, 0, strrpos($updated_tag, '<'));
+
+        $html_markup = str_replace($tag, $updated_tag, $html_markup);
+
+        // Ensure we only search the remaining HTML.
+        $offset = $pos_tag_end - strlen($tag) + strlen($updated_tag);
+      }
+      else {
+        // Ensure we only search the remaining HTML.
+        $offset = $pos_tag_end + 1;
+      }
+    }
+
+    return $html_markup;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    // Should run after any other response subscriber that modifies the markup.
+    $events[KernelEvents::RESPONSE][] = ['onResponse', -512];
+
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
index 70846dd8c639..e5cdd8796fa1 100644
--- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -165,6 +165,7 @@ public function renderResponse(array $main_content, Request $request, RouteMatch
     list($version) = explode('.', \Drupal::VERSION, 2);
 
     $response = new CacheableResponse($content, 200,[
+      'Content-Type' => 'text/html; charset=UTF-8',
       'X-Generator' => 'Drupal ' . $version . ' (https://www.drupal.org)'
     ]);
 
diff --git a/core/modules/system/src/Controller/SystemController.php b/core/modules/system/src/Controller/SystemController.php
index 90c260cf28a4..13e2f73660eb 100644
--- a/core/modules/system/src/Controller/SystemController.php
+++ b/core/modules/system/src/Controller/SystemController.php
@@ -328,132 +328,4 @@ public function themesPage() {
     return $build;
   }
 
-  /**
-   * #post_render_cache callback; sets the "is-active" class on relevant links.
-   *
-   * This is a PHP implementation of the drupal.active-link JavaScript library.
-   *
-   * @param array $element
-   *  A renderable array with the following keys:
-   *    - #markup
-   *    - #attached
-   * @param array $context
-   *   An array with the following keys:
-   *   - path: the system path of the currently active page
-   *   - front: whether the current page is the front page (which implies the
-   *     current path might also be <front>)
-   *   - language: the language code of the currently active page
-   *   - query: the query string for the currently active page
-   *
-   * @return array
-   *   The updated renderable array.
-   *
-   * @todo Once a future version of PHP supports parsing HTML5 properly
-   *   (i.e. doesn't fail on https://drupal.org/comment/7938201#comment-7938201)
-   *   then we can get rid of this manual parsing and use DOMDocument instead.
-   */
-  public static function setLinkActiveClass(array $element, array $context) {
-    $search_key_current_path = 'data-drupal-link-system-path="' . $context['path'] . '"';
-    $search_key_front = 'data-drupal-link-system-path="&lt;front&gt;"';
-
-    // An active link's path is equal to the current path, so search the HTML
-    // for an attribute with that value.
-    $offset = 0;
-    while (strpos($element['#markup'], $search_key_current_path, $offset) !== FALSE || ($context['front'] && strpos($element['#markup'], $search_key_front, $offset) !== FALSE)) {
-      $pos_current_path = strpos($element['#markup'], $search_key_current_path, $offset);
-      $pos_front = strpos($element['#markup'], $search_key_front, $offset);
-
-      // Determine which of the two values is the next match: the exact path, or
-      // the <front> special case.
-      $pos_match = NULL;
-      if ($pos_front === FALSE) {
-        $pos_match = $pos_current_path;
-      }
-      elseif ($pos_current_path === FALSE) {
-        $pos_match = $pos_front;
-      }
-      elseif ($pos_current_path < $pos_front) {
-        $pos_match = $pos_current_path;
-      }
-      else {
-        $pos_match = $pos_front;
-      }
-
-      // Find beginning and ending of opening tag.
-      $pos_tag_start = NULL;
-      for ($i = $pos_match; $pos_tag_start === NULL && $i > 0; $i--) {
-        if ($element['#markup'][$i] === '<') {
-          $pos_tag_start = $i;
-        }
-      }
-      $pos_tag_end = NULL;
-      for ($i = $pos_match; $pos_tag_end === NULL && $i < strlen($element['#markup']); $i++) {
-        if ($element['#markup'][$i] === '>') {
-          $pos_tag_end = $i;
-        }
-      }
-
-      // Get the HTML: this will be the opening part of a single tag, e.g.:
-      //   <a href="/" data-drupal-link-system-path="&lt;front&gt;">
-      $tag = substr($element['#markup'], $pos_tag_start, $pos_tag_end - $pos_tag_start + 1);
-
-      // Parse it into a DOMDocument so we can reliably read and modify
-      // attributes.
-      $dom = new \DOMDocument();
-      @$dom->loadHTML('<!DOCTYPE html><html><head><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' . $tag . '</body></html>');
-      $node = $dom->getElementsByTagName('body')->item(0)->firstChild;
-
-      // Ensure we don't set the "active" class twice on the same element.
-      $class = $node->getAttribute('class');
-      $add_active = !in_array('is-active', explode(' ', $class));
-
-      // The language of an active link is equal to the current language.
-      if ($add_active && $context['language']) {
-        if ($node->hasAttribute('hreflang') && $node->getAttribute('hreflang') !== $context['language']) {
-          $add_active = FALSE;
-        }
-      }
-      // The query parameters of an active link are equal to the current
-      // parameters.
-      if ($add_active) {
-        if ($context['query']) {
-          if (!$node->hasAttribute('data-drupal-link-query') || $node->getAttribute('data-drupal-link-query') !== Json::encode($context['query'])) {
-            $add_active = FALSE;
-          }
-        }
-        else {
-          if ($node->hasAttribute('data-drupal-link-query')) {
-            $add_active = FALSE;
-          }
-        }
-      }
-
-      // Only if the path, the language and the query match, we set the
-      // "is-active" class.
-      if ($add_active) {
-        if (strlen($class) > 0) {
-          $class .= ' ';
-        }
-        $class .= 'is-active';
-        $node->setAttribute('class', $class);
-
-        // Get the updated tag.
-        $updated_tag = $dom->saveXML($node, LIBXML_NOEMPTYTAG);
-        // saveXML() added a closing tag, remove it.
-        $updated_tag = substr($updated_tag, 0, strrpos($updated_tag, '<'));
-
-        $element['#markup'] = str_replace($tag, $updated_tag, $element['#markup']);
-
-        // Ensure we only search the remaining HTML.
-        $offset = $pos_tag_end - strlen($tag) + strlen($updated_tag);
-      }
-      else {
-        // Ensure we only search the remaining HTML.
-        $offset = $pos_tag_end + 1;
-      }
-    }
-
-    return $element;
-  }
-
 }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 0575b1a648f6..f2dcc7c81521 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -515,7 +515,7 @@ function system_filetransfer_info() {
  * Implements hook_page_attachments().
  *
  * @see template_preprocess_maintenance_page()
- * @see \Drupal\system\Controller\SystemController::setLinkActiveClass()
+ * @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
  */
 function system_page_attachments(array &$page) {
   // Ensure the same CSS is loaded in template_preprocess_maintenance_page().
@@ -596,25 +596,14 @@ function system_page_attachments(array &$page) {
 
   // Handle setting the "active" class on links by:
   // - loading the active-link library if the current user is authenticated;
-  // - applying a post-render cache callback if the current user is anonymous.
+  // - applying a response filter if the current user is anonymous.
   // @see l()
   // @see \Drupal\Core\Utility\LinkGenerator::generate()
   // @see template_preprocess_links()
-  // @see \Drupal\system\Controller\SystemController::setLinkActiveClass
+  // @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
   if (\Drupal::currentUser()->isAuthenticated()) {
     $page['#attached']['library'][] = 'core/drupal.active-link';
   }
-  else {
-    $page['#post_render_cache']['\Drupal\system\Controller\SystemController::setLinkActiveClass'] = array(
-      // Collect the current state that determines whether a link is active.
-      array(
-        'path' => \Drupal::routeMatch()->getRouteName() ? Url::fromRouteMatch(\Drupal::routeMatch())->getInternalPath() : '',
-        'front' => \Drupal::service('path.matcher')->isFrontPage(),
-        'language' => \Drupal::languageManager()->getCurrentLanguage(LanguageInterface::TYPE_URL)->getId(),
-        'query' => \Drupal::request()->query->all(),
-      )
-    );
-  }
 }
 
 /**
diff --git a/core/modules/system/tests/src/Unit/Controller/SystemControllerTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/ActiveLinkResponseFilterTest.php
similarity index 88%
rename from core/modules/system/tests/src/Unit/Controller/SystemControllerTest.php
rename to core/tests/Drupal/Tests/Core/EventSubscriber/ActiveLinkResponseFilterTest.php
index 173a4300929f..f9e3eadc4c6b 100644
--- a/core/modules/system/tests/src/Unit/Controller/SystemControllerTest.php
+++ b/core/tests/Drupal/Tests/Core/EventSubscriber/ActiveLinkResponseFilterTest.php
@@ -2,27 +2,26 @@
 
 /**
  * @file
- * Contains \Drupal\Tests\system\Unit\Controller\SystemControllerTest.
+ * Contains \Drupal\Tests\Core\EventSubscriber\ActiveLinkResponseFilterTest.
  */
 
-namespace Drupal\Tests\system\Unit\Controller;
+namespace Drupal\Tests\Core\EventSubscriber;
 
 use Drupal\Component\Serialization\Json;
-use Symfony\Component\HttpFoundation\Request;
+use Drupal\Core\EventSubscriber\ActiveLinkResponseFilter;
 use Drupal\Core\Template\Attribute;
-use Drupal\system\Controller\SystemController;
 use Drupal\Tests\UnitTestCase;
 
 /**
- * @coversDefaultClass \Drupal\system\Controller\SystemController
- * @group system
+ * @coversDefaultClass \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter
+ * @group EventSubscriber
  */
-class SystemControllerTest extends UnitTestCase {
+class ActiveLinkResponseFilterTest extends UnitTestCase {
 
   /**
    * Provides test data for testSetLinkActiveClass().
    *
-   * @see \Drupal\system\Controller\SystemController::setLinkActiveClass()
+   * @see \Drupal\Core\EventSubscriber\ActiveLinkResponseFilter::setLinkActiveClass()
    */
   public function providerTestSetLinkActiveClass() {
     // Define all the variations that *don't* affect whether or not an
@@ -244,14 +243,6 @@ public function providerTestSetLinkActiveClass() {
     $situations[] = array('context' => $context, 'is active' => FALSE, 'attributes' => $attributes + array('hreflang' => 'en', 'data-drupal-link-query' => ""));
     $situations[] = array('context' => $context, 'is active' => FALSE, 'attributes' => $attributes + array('hreflang' => 'en', 'data-drupal-link-query' => TRUE));
 
-    // Helper function to generate a stubbed renderable array.
-    $create_element = function ($markup) {
-      return array(
-        '#markup' => $markup,
-        '#attached' => array(),
-      );
-    };
-
     // Loop over the surrounding HTML variations.
     $data = array();
     for ($h = 0; $h < count($html); $h++) {
@@ -292,7 +283,7 @@ public function providerTestSetLinkActiveClass() {
               $target_markup = $create_markup(new Attribute($active_attributes));
             }
 
-            $data[] = array($create_element($source_markup), $situation['context'], $create_element($target_markup));
+            $data[] = array($source_markup, $situation['context']['path'], $situation['context']['front'], $situation['context']['language'], $situation['context']['query'], $target_markup);
           }
         }
       }
@@ -301,9 +292,12 @@ public function providerTestSetLinkActiveClass() {
     // Test case to verify that the 'is-active' class is not added multiple
     // times.
     $data[] = [
-      0 => ['#markup' => '<a data-drupal-link-system-path="&lt;front&gt;">Once</a> <a data-drupal-link-system-path="&lt;front&gt;">Twice</a>'],
-      1 => ['path' => '', 'front' => TRUE, 'language' => 'en', 'query' => []],
-      2 => ['#markup' => '<a data-drupal-link-system-path="&lt;front&gt;" class="is-active">Once</a> <a data-drupal-link-system-path="&lt;front&gt;" class="is-active">Twice</a>'],
+      0 => '<a data-drupal-link-system-path="&lt;front&gt;">Once</a> <a data-drupal-link-system-path="&lt;front&gt;">Twice</a>',
+      1 => '',
+      2 => TRUE,
+      3 => 'en',
+      4 => [],
+      5 => '<a data-drupal-link-system-path="&lt;front&gt;" class="is-active">Once</a> <a data-drupal-link-system-path="&lt;front&gt;" class="is-active">Twice</a>',
     ];
 
     // Test cases to verify that the 'is-active' class is added when on the
@@ -316,14 +310,20 @@ public function providerTestSetLinkActiveClass() {
     $front_path_link = '<a data-drupal-link-system-path="myfrontpage">Front Path</a>';
     $front_path_link_active = '<a data-drupal-link-system-path="myfrontpage" class="is-active">Front Path</a>';
     $data[] = [
-      0 => ['#markup' => $front_path_link . ' ' . $front_special_link],
-      1 => ['path' => 'myfrontpage', 'front' => TRUE, 'language' => 'en', 'query' => []],
-      2 => ['#markup' => $front_path_link_active . ' ' . $front_special_link_active],
+      0 => $front_path_link . ' ' . $front_special_link,
+      1 => 'myfrontpage',
+      2 => TRUE,
+      3 => 'en',
+      4 => [],
+      5 => $front_path_link_active . ' ' . $front_special_link_active,
     ];
     $data[] = [
-      0 => ['#markup' => $front_special_link . ' ' . $front_path_link],
-      1 => ['path' => 'myfrontpage', 'front' => TRUE, 'language' => 'en', 'query' => []],
-      2 => ['#markup' => $front_special_link_active . ' ' . $front_path_link_active],
+      0 => $front_special_link . ' ' . $front_path_link,
+      1 => 'myfrontpage',
+      2 => TRUE,
+      3 => 'en',
+      4 => [],
+      5 => $front_special_link_active . ' ' . $front_path_link_active,
     ];
 
     return $data;
@@ -332,25 +332,25 @@ public function providerTestSetLinkActiveClass() {
   /**
    * Tests setLinkActiveClass().
    *
-   * @param array $element
-   *  A renderable array with the following keys:
-   *    - #markup
-   *    - #attached
-   * @param array $context
-   *   The page context to simulate. An array with the following keys:
-   *   - path: the system path of the currently active page
-   *   - front: whether the current page is the front page (which implies the
-   *     current path might also be <front>)
-   *   - language: the language code of the currently active page
-   *   - query: the query string for the currently active page
-   * @param array $expected_element
-   *   The returned renderable array.
+   * @param string $html_markup
+   *   The original HTML markup.
+   * @param string $current_path
+   *   The system path of the currently active page.
+   * @param bool $is_front
+   *   Whether the current page is the front page (which implies the current
+   *   path might also be <front>).
+   * @param string $url_language
+   *   The language code of the current URL.
+   * @param array $query
+   *   The query string for the current URL.
+   * @param string $expected_html_markup
+   *   The expected updated HTML markup.
    *
    * @dataProvider providerTestSetLinkActiveClass
    * @covers ::setLinkActiveClass
    */
-  public function testSetLinkActiveClass(array $element, array $context, $expected_element) {
-    $this->assertSame($expected_element, SystemController::setLinkActiveClass($element, $context));
+  public function testSetLinkActiveClass($html_markup, $current_path, $is_front, $url_language, array $query, $expected_html_markup) {
+    $this->assertSame($expected_html_markup, ActiveLinkResponseFilter::setLinkActiveClass($html_markup, $current_path, $is_front, $url_language, $query));
   }
 
 }
-- 
GitLab