 core/includes/common.inc                           |  100 +++++++++++++-----
 core/includes/menu.inc                             |    4 +
 core/misc/active-link.js                           |   60 +++++++++++
 core/misc/drupal.js                                |    2 +-
 .../Drupal/image/Tests/ImageFieldDisplayTest.php   |    2 +-
 core/modules/language/language.negotiation.inc     |    1 +
 .../language/Tests/LanguageSwitchingTest.php       |  107 ++++++++++++++++++++
 .../Tests/LanguageUILanguageNegotiationTest.php    |    7 +-
 .../Controller/LanguageTestController.php          |    3 +
 .../Drupal/system/Tests/Common/JavaScriptTest.php  |    2 +-
 .../Drupal/system/Tests/Theme/FunctionsTest.php    |   10 +-
 core/modules/system/system.module                  |   19 ++++
 .../Controller/CommonTestController.php            |    5 +
 13 files changed, 282 insertions(+), 40 deletions(-)

diff --git a/core/includes/common.inc b/core/includes/common.inc
index 8f60fe4..21e4107 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1282,12 +1282,25 @@ function drupal_http_header_attributes(array $attributes = array()) {
  *     internal to the site, $options['language'] is used to determine whether
  *     the link is "active", or pointing to the current page (the language as
  *     well as the path must match). This element is also used by url().
+ *   - 'set_active_class' (default FALSE): Whether l() should compare the $path,
+ *     language and query options to the current URL to determine whether the
+ *     link is "active", and if so, apply an "active" class to the link. It is
+ *     important to use this sparingly as any content containing a link
+ *     generated by l() with active class processing enabled is incompatible
+ *     with render caching elements on more than a per-page basis.
+ *     For anonymous users, the "active" class will be calculated on the server,
+ *     because most sites serve each anonymous user the same cached page anyway.
+ *     For authenticated users, the "active" class will be calculated on the
+ *     client (through JavaScript), only data- attributes are added to links to
+ *     prevent breaking the render cache. The JavaScript is added in
+ *     system_page_build().
  *   - Additional $options elements used by the url() function.
  *
  * @return string
  *   An HTML string containing a link to the given path.
  *
  * @see url()
+ * @see system_page_build()
  */
 function l($text, $path, array $options = array()) {
   // Start building a structured representation of our link to be altered later.
@@ -1303,6 +1316,7 @@ function l($text, $path, array $options = array()) {
     'query' => array(),
     'html' => FALSE,
     'language' => NULL,
+    'set_active_class' => FALSE,
   );
 
   // Add a hreflang attribute if we know the language of this link's url and
@@ -1311,35 +1325,57 @@ 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(),
-    );
-  }
+  // Set the "active" class if the 'set_active_class' option is not empty.
+  if (!empty($variables['options']['set_active_class'])) {
+    if (\Drupal::currentUser()->isAuthenticated()) {
+      // 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 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);
+      }
+    }
+    else {
+      // 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 the "active" class if appropriate.
-  if ($variables['url_is_active']) {
-    $variables['options']['attributes']['class'][] = 'active';
+      // 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';
+      }
+    }
   }
 
   // Remove all HTML and PHP tags from a tooltip, calling expensive strip_tags()
@@ -2213,13 +2249,21 @@ function drupal_add_js($data = NULL, $options = NULL) {
           // @todo Make this less hacky: http://drupal.org/node/1547376.
           $scriptPath = $GLOBALS['script_path'];
           $pathPrefix = '';
+          $current_query = Drupal::service('request')->query->all();
           url('', array('script' => &$scriptPath, 'prefix' => &$pathPrefix));
-          $javascript['settings']['data'][] = array(
+          $path = array(
             'basePath' => base_path(),
             'scriptPath' => $scriptPath,
             'pathPrefix' => $pathPrefix,
             'currentPath' => current_path(),
+            'isFront' => drupal_is_front_page(),
+            'currentLanguage' => Drupal::languageManager()->getLanguage(Language::TYPE_URL)->id,
           );
+          if (!empty($current_query)) {
+            ksort($current_query);
+            $path['currentQuery'] = (object) $current_query;
+          }
+          $javascript['settings']['data'][] = array('path' => $path);
         }
         // All JavaScript settings are placed in the header of the page with
         // the library weight so that inline scripts appear afterwards.
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index a8a30b6..53b85b4 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -1705,6 +1705,7 @@ function theme_menu_link(array $variables) {
   if ($element['#below']) {
     $sub_menu = drupal_render($element['#below']);
   }
+  $element['#localized_options']['set_active_class'] = TRUE;
   $output = l($element['#title'], $element['#href'], $element['#localized_options']);
   return '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
 }
@@ -1740,6 +1741,8 @@ function theme_menu_local_task($variables) {
     $link['localized_options']['html'] = TRUE;
     $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active));
   }
+  $link['localized_options']['set_active_class'] = TRUE;
+
   if (!empty($link['href'])) {
     // @todo - remove this once all pages are converted to routes.
     $a_tag = l($link_text, $link['href'], $link['localized_options']);
@@ -1771,6 +1774,7 @@ function theme_menu_local_action($variables) {
   );
   $link['localized_options']['attributes']['class'][] = 'button';
   $link['localized_options']['attributes']['class'][] = 'button-action';
+  $link['localized_options']['set_active_class'] = TRUE;
 
   $output = '<li>';
   // @todo Remove this check and the call to l() when all pages are converted to
diff --git a/core/misc/active-link.js b/core/misc/active-link.js
new file mode 100644
index 0000000..a3724e1
--- /dev/null
+++ b/core/misc/active-link.js
@@ -0,0 +1,60 @@
+/**
+ * @file
+ * Attaches behaviors for Drupal's active link marking.
+ */
+
+(function (Drupal, drupalSettings) {
+
+"use strict";
+
+/**
+ * Append active class.
+ *
+ * The link is only active if its path corresponds to the current path, the
+ * language of the linked path is equal to the current language, and if the
+ * query parameters of the link equal those of the current request, since the
+ * same request with different query parameters may yield a different page
+ * (e.g. pagers, exposed View filters).
+ */
+Drupal.behaviors.l = {
+  attach: function queryL (context) {
+    // Start by finding all potentially active links.
+    var path = drupalSettings.path;
+    var queryString = JSON.stringify(path.currentQuery);
+    var querySelector = path.currentQuery ? "[data-drupal-link-query='" + queryString + "']" : ':not([data-drupal-link-query])';
+    var originalSelectors = ['[data-drupal-link-system-path="' + path.currentPath + '"]'];
+    var selectors;
+
+    // If this is the front page, we have to check for the <front> path as well.
+    if (path.isFront) {
+      originalSelectors.push('[data-drupal-link-system-path="<front>"]');
+    }
+
+    // Add language filtering.
+    selectors = [].concat(
+      // Links without any hreflang attributes (most of them).
+      originalSelectors.map(function (selector) { return selector + ':not([hreflang])';}),
+      // Links with hreflang equals to the current language.
+      originalSelectors.map(function (selector) { return selector + '[hreflang="' + path.currentLanguage + '"]';})
+    );
+
+    // Add query string selector for pagers, exposed filters.
+    selectors = selectors.map(function (current) { return current + querySelector; });
+
+    // Query the DOM.
+    var activeLinks = context.querySelectorAll(selectors.join(','));
+    for (var i = 0, il = activeLinks.length; i < il; i += 1) {
+      activeLinks[i].classList.add('active');
+    }
+  },
+  detach: function (context, settings, trigger) {
+    if (trigger === 'unload') {
+      var activeLinks = context.querySelectorAll('a.active');
+      for (var i = 0, il = activeLinks.length; i < il; i += 1) {
+        activeLinks[i].classList.remove('active');
+      }
+    }
+  }
+};
+
+})(Drupal, drupalSettings);
diff --git a/core/misc/drupal.js b/core/misc/drupal.js
index 6c04130..e994ec6 100644
--- a/core/misc/drupal.js
+++ b/core/misc/drupal.js
@@ -267,7 +267,7 @@ Drupal.t = function (str, args, options) {
  * Returns the URL to a Drupal page.
  */
 Drupal.url = function (path) {
-  return drupalSettings.basePath + drupalSettings.scriptPath + path;
+  return drupalSettings.path.basePath + drupalSettings.path.scriptPath + path;
 };
 
 /**
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php
index 9ef2167..9844c34 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldDisplayTest.php
@@ -111,7 +111,7 @@ function _testImageFieldFormatters($scheme) {
       '#width' => 40,
       '#height' => 20,
     );
-    $default_output = l($image, 'node/' . $nid, array('html' => TRUE, 'attributes' => array('class' => 'active')));
+    $default_output = l($image, 'node/' . $nid, array('html' => TRUE));
     $this->drupalGet('node/' . $nid);
     $this->assertRaw($default_output, 'Image linked to content formatter displaying correctly on full node view.');
 
diff --git a/core/modules/language/language.negotiation.inc b/core/modules/language/language.negotiation.inc
index 7a8e99b..5cdd3e7 100644
--- a/core/modules/language/language.negotiation.inc
+++ b/core/modules/language/language.negotiation.inc
@@ -399,6 +399,7 @@ function language_switcher_url($type, $path) {
       'title'      => $language->name,
       'language'   => $language,
       'attributes' => array('class' => array('language-link')),
+      'set_active_class' => TRUE,
     );
   }
 
diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php
index 4613f43..d7cbf37 100644
--- a/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php
+++ b/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php
@@ -55,6 +55,52 @@ function testLanguageBlock() {
     $edit = array('language_interface[enabled][language-url]' => '1');
     $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
 
+    /**
+     * For authenticated users, the "active" class is set by JavaScript.
+     */
+
+    // Assert that the language switching block is displayed on the frontpage.
+    $this->drupalGet('');
+    $this->assertText($block->label(), 'Language switcher block found.');
+
+    // Assert that only the current language is marked as active.
+    list($language_switcher) = $this->xpath('//div[@id=:id]/div[contains(@class, "content")]', array(':id' => 'block-test-language-block'));
+    $links = array(
+      'active' => array(),
+      'inactive' => array(),
+    );
+    $anchors = array();
+    foreach ($language_switcher->ul->li as $link) {
+      $classes = explode(" ", (string) $link['class']);
+      list($langcode) = array_intersect($classes, array('en', 'fr'));
+      if (in_array('active', $classes)) {
+        $links['active'][] = $langcode;
+      }
+      else {
+        $links['inactive'][] = $langcode;
+      }
+      $anchors[] = array(
+        'hreflang' => (string) $link->a['hreflang'],
+        'data-drupal-link-system-path' => (string) $link->a['data-drupal-link-system-path'],
+      );
+    }
+    $this->assertIdentical($links, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language list item is marked as active on the language switcher block.');
+    $expected_anchors = array(
+      0 => array('hreflang' => 'en', 'data-drupal-link-system-path' => 'user/2'),
+      1 => array('hreflang' => 'fr', 'data-drupal-link-system-path' => 'user/2'),
+    );
+    $this->assertIdentical($anchors, $expected_anchors, 'The anchors have the correct attributes that will allow the drupal.active-link library to mark them as active.');
+    $settings = $this->drupalGetSettings();
+    $this->assertIdentical($settings['path']['currentPath'], 'user/2', 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
+    $this->assertIdentical($settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
+    $this->assertIdentical($settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
+
+    /**
+     * For anonymous users, the "active" class is set by PHP.
+     */
+
+    $this->drupalLogout();
+
     // Assert that the language switching block is displayed on the frontpage.
     $this->drupalGet('');
     $this->assertText($block->label(), 'Language switcher block found.');
@@ -105,6 +151,67 @@ function testLanguageLinkActiveClass() {
     $this->drupalPostForm('admin/config/regional/language/detection', $edit, t('Save settings'));
 
     $function_name = '#type link';
+    $path = 'language_test/type-link-active-class';
+
+    /**
+     * For authenticated users, the "active" class is set by JavaScript.
+     */
+
+    // Test links generated by l() on an English page.
+    $current_language = 'English';
+    $this->drupalGet($path);
+
+    // Language code 'none' link should be active.
+    $langcode = 'none';
+    $links = $this->xpath('//a[@id = :id and @data-drupal-link-system-path = :path]', array(':id' => 'no_lang_link', ':path' => $path));
+    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
+
+    // Language code 'en' link should be active.
+    $langcode = 'en';
+    $links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'en_link', ':lang' => 'en', ':path' => $path));
+    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
+
+    // Language code 'fr' link should not be active.
+    $langcode = 'fr';
+    $links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'fr_link', ':lang' => 'fr', ':path' => $path));
+    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to NOT mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
+
+    // Verify that drupalSettings contains the correct values.
+    $settings = $this->drupalGetSettings();
+    $this->assertIdentical($settings['path']['currentPath'], $path, 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
+    $this->assertIdentical($settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
+    $this->assertIdentical($settings['path']['currentLanguage'], 'en', 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
+
+    // Test links generated by l() on a French page.
+    $current_language = 'French';
+    $this->drupalGet('fr/language_test/type-link-active-class');
+
+    // Language code 'none' link should be active.
+    $langcode = 'none';
+    $links = $this->xpath('//a[@id = :id and @data-drupal-link-system-path = :path]', array(':id' => 'no_lang_link', ':path' => $path));
+    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
+
+    // Language code 'en' link should not be active.
+    $langcode = 'en';
+    $links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'en_link', ':lang' => 'en', ':path' => $path));
+    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to NOT mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
+
+    // Language code 'fr' link should be active.
+    $langcode = 'fr';
+    $links = $this->xpath('//a[@id = :id and @hreflang = :lang and @data-drupal-link-system-path = :path]', array(':id' => 'fr_link', ':lang' => 'fr', ':path' => $path));
+    $this->assertTrue(isset($links[0]), t('A link generated by :function to the current :language page with langcode :langcode has the correct attributes that will allow the drupal.active-link library to mark it as active.', array(':function' => $function_name, ':language' => $current_language, ':langcode' => $langcode)));
+
+    // Verify that drupalSettings contains the correct values.
+    $settings = $this->drupalGetSettings();
+    $this->assertIdentical($settings['path']['currentPath'], $path, 'drupalSettings.path.currentPath is set correctly to allow drupal.active-link to mark the correct links as active.');
+    $this->assertIdentical($settings['path']['isFront'], FALSE, 'drupalSettings.path.isFront is set correctly to allow drupal.active-link to mark the correct links as active.');
+    $this->assertIdentical($settings['path']['currentLanguage'], 'fr', 'drupalSettings.path.currentLanguage is set correctly to allow drupal.active-link to mark the correct links as active.');
+
+    /**
+     * For anonymous users, the "active" class is set by PHP.
+     */
+
+    $this->drupalLogout();
 
     // Test links generated by l() on an English page.
     $current_language = 'English';
diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php
index 7c037b5..19744c2 100644
--- a/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php
+++ b/core/modules/language/lib/Drupal/language/Tests/LanguageUILanguageNegotiationTest.php
@@ -411,6 +411,11 @@ function testUrlLanguageFallback() {
     // Enable the language switcher block.
     $this->drupalPlaceBlock('language_block:' . Language::TYPE_INTERFACE, array('id' => 'test_language_block'));
 
+    // Log out, because for anonymous users, the "active" class is set by PHP
+    // (which means we can easily test it here), whereas for authenticated users
+    // it is set by JavaScript.
+    $this->drupalLogout();
+
     // Access the front page without specifying any valid URL language prefix
     // and having as browser language preference a non-default language.
     $http_header = array("Accept-Language: $langcode_browser_fallback;q=1");
@@ -464,7 +469,7 @@ function testLanguageDomain() {
     $italian_url = url('admin', array('language' => $languages['it'], 'script' => ''));
     $url_scheme = $this->request->isSecure() ? 'https://' : 'http://';
     $correct_link = $url_scheme . $link;
-    $this->assertTrue($italian_url == $correct_link, format_string('The url() function returns the right URL (@url) in accordance with the chosen language', array('@url' => $italian_url)));
+    $this->assertEqual($italian_url, $correct_link, format_string('The url() function returns the right URL (@url) in accordance with the chosen language', array('@url' => $italian_url)));
 
     // Test HTTPS via options.
     $this->settingsSet('mixed_mode_sessions', TRUE);
diff --git a/core/modules/language/tests/language_test/lib/Drupal/language_test/Controller/LanguageTestController.php b/core/modules/language/tests/language_test/lib/Drupal/language_test/Controller/LanguageTestController.php
index 001fe39..781af06 100644
--- a/core/modules/language/tests/language_test/lib/Drupal/language_test/Controller/LanguageTestController.php
+++ b/core/modules/language/tests/language_test/lib/Drupal/language_test/Controller/LanguageTestController.php
@@ -58,6 +58,7 @@ public function typeLinkActiveClass() {
           'attributes' => array(
             'id' => 'no_lang_link',
           ),
+          'set_active_class' => TRUE,
         ),
       ),
       'fr' => array(
@@ -69,6 +70,7 @@ public function typeLinkActiveClass() {
           'attributes' => array(
             'id' => 'fr_link',
           ),
+          'set_active_class' => TRUE,
         ),
       ),
       'en' => array(
@@ -80,6 +82,7 @@ public function typeLinkActiveClass() {
           'attributes' => array(
             'id' => 'en_link',
           ),
+          'set_active_class' => TRUE,
         ),
       ),
     );
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
index 2d3d87b..0b458fd 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/JavaScriptTest.php
@@ -80,7 +80,7 @@ function testAddSetting() {
     drupal_add_library('system', 'drupalSettings');
     $javascript = drupal_add_js();
     $last_settings = reset($javascript['settings']['data']);
-    $this->assertTrue(array_key_exists('currentPath', $last_settings), 'The current path JavaScript setting is set correctly.');
+    $this->assertTrue(array_key_exists('currentPath', $last_settings['path']), 'The current path JavaScript setting is set correctly.');
 
     $javascript = drupal_add_js(array('drupal' => 'rocks', 'dries' => 280342800), 'setting');
     $last_settings = end($javascript['settings']['data']);
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/FunctionsTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/FunctionsTest.php
index 69d5ac6..73f2d97e 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/FunctionsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/FunctionsTest.php
@@ -151,12 +151,6 @@ function testLinks() {
     $expected = '';
     $this->assertThemeOutput('links', $variables, $expected, 'Empty %callback with heading generates no output.');
 
-    // Set the current path to the front page path.
-    // Required to verify the "active" class in expected links below, and
-    // because the current path is different when running tests manually via
-    // simpletest.module ('batch') and via the testing framework ('').
-    _current_path(\Drupal::config('system.site')->get('page.front'));
-
     // Verify that a list of links is properly rendered.
     $variables = array();
     $variables['attributes'] = array('id' => 'somelinks');
@@ -178,7 +172,7 @@ function testLinks() {
     $expected_links .= '<ul id="somelinks">';
     $expected_links .= '<li class="a-link odd first"><a href="' . url('a/link') . '">' . check_plain('A <link>') . '</a></li>';
     $expected_links .= '<li class="plain-text even">' . check_plain('Plain "text"') . '</li>';
-    $expected_links .= '<li class="front-page odd last active"><a href="' . url('<front>') . '" class="active">' . check_plain('Front page') . '</a></li>';
+    $expected_links .= '<li class="front-page odd last"><a href="' . url('<front>') . '">' . check_plain('Front page') . '</a></li>';
     $expected_links .= '</ul>';
 
     // Verify that passing a string as heading works.
@@ -210,7 +204,7 @@ function testLinks() {
     $expected_links .= '<ul id="somelinks">';
     $expected_links .= '<li class="a-link odd first"><a href="' . url('a/link') . '" class="a/class">' . check_plain('A <link>') . '</a></li>';
     $expected_links .= '<li class="plain-text even"><span class="a/class">' . check_plain('Plain "text"') . '</span></li>';
-    $expected_links .= '<li class="front-page odd last active"><a href="' . url('<front>') . '" class="active">' . check_plain('Front page') . '</a></li>';
+    $expected_links .= '<li class="front-page odd last"><a href="' . url('<front>') . '">' . check_plain('Front page') . '</a></li>';
     $expected_links .= '</ul>';
     $expected = $expected_heading . $expected_links;
     $this->assertThemeOutput('links', $variables, $expected);
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 9e2231b..b922169 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -926,6 +926,19 @@ function system_library_info() {
     ),
   );
 
+  // Drupal's active link marking.
+  $libraries['drupal.active-link'] = array(
+    'title' => 'Drupal active link marking',
+    'version' => \Drupal::VERSION,
+    'js' => array(
+      'core/misc/active-link.js' => array(),
+    ),
+    'dependencies' => array(
+      array('system', 'drupal'),
+      array('system', 'drupalSettings'),
+    ),
+  );
+
   // Drupal's Ajax framework.
   $libraries['drupal.ajax'] = array(
     'title' => 'Drupal AJAX',
@@ -2145,6 +2158,12 @@ function system_page_build(&$page) {
   if (path_is_admin(current_path())) {
     $page['#attached']['css'][$path . '/css/system.admin.css'] = array('weight' => CSS_COMPONENT - 10);
   }
+
+  // Load the active-link library if this is an authenticated user.
+  // @see l()
+  if (\Drupal::currentUser()->isAuthenticated()) {
+    $page['#attached']['library'][] = array('system', 'drupal.active-link');
+  }
 }
 
 /**
diff --git a/core/modules/system/tests/modules/common_test/lib/Drupal/common_test/Controller/CommonTestController.php b/core/modules/system/tests/modules/common_test/lib/Drupal/common_test/Controller/CommonTestController.php
index d842919..0f2a34c 100644
--- a/core/modules/system/tests/modules/common_test/lib/Drupal/common_test/Controller/CommonTestController.php
+++ b/core/modules/system/tests/modules/common_test/lib/Drupal/common_test/Controller/CommonTestController.php
@@ -35,6 +35,9 @@ public function typeLinkActiveClass() {
         '#type' => 'link',
         '#title' => t('Link with no query string'),
         '#href' => current_path(),
+        '#options' => array(
+          'set_active_class' => TRUE,
+        ),
       ),
       'with_query' => array(
         '#type' => 'link',
@@ -45,6 +48,7 @@ public function typeLinkActiveClass() {
             'foo' => 'bar',
             'one' => 'two',
           ),
+          'set_active_class' => TRUE,
         ),
       ),
       'with_query_reversed' => array(
@@ -56,6 +60,7 @@ public function typeLinkActiveClass() {
             'one' => 'two',
             'foo' => 'bar',
           ),
+          'set_active_class' => TRUE,
         ),
       ),
     );
