diff --git a/core/includes/common.inc b/core/includes/common.inc
index 90b9412..3817f55 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -365,6 +365,44 @@ function _drupal_default_html_head() {
   );
   // Also send the generator in the HTTP header.
   $elements['system_meta_generator']['#attached']['drupal_add_http_header'][] = array('X-Generator', $elements['system_meta_generator']['#attributes']['content']);
+
+
+  // Add active link rules.
+
+  $current_query = Drupal::service('request')->query->all();
+  $current_language = Drupal::languageManager()->getLanguage(Language::TYPE_URL)->id;
+
+  $original_rules = array('[data-drupal-link-system-path="' . current_path() . '"]');
+  if (drupal_is_front_page()) {
+    $original_rules[] = '[data-drupal-link-system-path="<front>"]';
+  }
+
+  $active_rules = array();
+  foreach ($original_rules as $rule) {
+    $active_rules[] = $rule . ':not([hreflang])';
+    $active_rules[] = $rule . '[hreflang="' . $current_language . '"]';
+  }
+
+  $query_selector = ':not([data-drupal-link-query])';
+  if (!empty($current_query)) {
+    ksort($current_query);
+    $current_query = Json::encode((object) $current_query);
+    $query_selector = "[data-drupal-link-query='" . $current_query . "']";
+  }
+
+  foreach ($active_rules as &$rule) {
+    $rule = $rule . $query_selector;
+  }
+
+  $elements['active_style'] = array(
+    '#type' => 'html_tag',
+    '#tag' => 'style',
+    '#attributes' => array(
+      'type' => 'text/css',
+    ),
+    '#value' => implode($active_rules, ', ') . ' { outline: 5px solid blue; }',
+    '#weight' => 100,
+  );
   return $elements;
 }
 
@@ -1321,35 +1359,19 @@ function l($text, $path, array $options = array()) {
     $variables['options']['attributes']['hreflang'] = $variables['options']['language']->id;
   }
 
-  // Because l() is called very often we statically cache values that require an
-  // extra function call.
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast['active'])) {
-    $drupal_static_fast['active'] = &drupal_static(__FUNCTION__);
-  }
-  $active = &$drupal_static_fast['active'];
-  if (!isset($active)) {
-    $active = array(
-      'path' => current_path(),
-      'front_page' => drupal_is_front_page(),
-      'language' => language(Language::TYPE_URL)->id,
-      'query' => Drupal::service('request')->query->all(),
-    );
+  // Add a data-drupal-link-type attribute to let JavaScript know this link is
+  // used for navigation (as opposed to?) and so is potentially eligible for an
+  // active class.
+  if (!empty($variables['options']['query'])) {
+    $query = $variables['options']['query'];
+    ksort($query);
+    $variables['options']['attributes']['data-drupal-link-query'] = Json::encode($query);
   }
 
-  // Determine whether this link is "active', meaning that it links to the
-  // current page. It is important that we stop checking "active" conditions if
-  // we know the link is not active. This helps ensure that l() remains fast.
-  // An active link's path is equal to the current path.
-  $variables['url_is_active'] = ($path == $active['path'] || ($path == '<front>' && $active['front_page']))
-  // The language of an active link is equal to the current language.
-  && (empty($variables['options']['language']) || $variables['options']['language']->id == $active['language'])
-  // The query parameters of an active link are equal to the current parameters.
-  && ($variables['options']['query'] == $active['query']);
-
-  // Add the "active" class if appropriate.
-  if ($variables['url_is_active']) {
-    $variables['options']['attributes']['class'][] = 'active';
+  // Add a data-drupal-link-system-path attribute to expose the system path for
+  // this link to JavaScript.
+  if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
+    $variables['options']['attributes']['data-drupal-link-system-path'] = Drupal::service('path.alias_manager.cached')->getSystemPath($path);
   }
 
   // Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags()
