diff --git a/core/includes/common.inc b/core/includes/common.inc
index c946eaf..2d00aad 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1,6 +1,7 @@
 <?php
 
 use Drupal\Component\Utility\Json;
+use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\Number;
 use Drupal\Component\Utility\Settings;
 use Drupal\Component\Utility\SortArray;
@@ -1812,25 +1813,11 @@ function drupal_delete_file_if_stale($uri) {
  *
  * @return
  *   The cleaned identifier.
+ *
+ * @deprecated as of Drupal 8.0. Use \Drupal\Component\Utility\Html::cleanCssIdentifier()
  */
 function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '__' => '__', '/' => '-', '[' => '-', ']' => '')) {
-  // By default, we filter using Drupal's coding standards.
-  $identifier = strtr($identifier, $filter);
-
-  // Valid characters in a CSS identifier are:
-  // - the hyphen (U+002D)
-  // - a-z (U+0030 - U+0039)
-  // - A-Z (U+0041 - U+005A)
-  // - the underscore (U+005F)
-  // - 0-9 (U+0061 - U+007A)
-  // - ISO 10646 characters U+00A1 and higher
-  // We strip out any character not in the above list.
-  $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);
-
-  // Identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit.
-  $identifier = preg_replace(array('/^[0-9]/', '/^(-[0-9])|^(--)/'), array('_', '__') , $identifier);
-
-  return $identifier;
+  return Html::cleanCssIdentifier($identifier, $filter);
 }
 
 /**
@@ -1844,16 +1831,11 @@ function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_
  *
  * @return
  *   The cleaned class name.
+ *
+ * @deprecated as of Drupal 8.0. Use \Drupal\Component\Utility\Html::getClass()
  */
 function drupal_html_class($class) {
-  // The output of this function will never change, so this uses a normal
-  // static instead of drupal_static().
-  static $classes = array();
-
-  if (!isset($classes[$class])) {
-    $classes[$class] = drupal_clean_css_identifier(drupal_strtolower($class));
-  }
-  return $classes[$class];
+  return Html::getClass($class);
 }
 
 /**
@@ -1882,77 +1864,11 @@ function drupal_html_class($class) {
  *
  * @return
  *   The cleaned ID.
+ *
+ * @deprecated as of Drupal 8.0. Use \Drupal\Component\Utility\Html::getId()
  */
 function drupal_html_id($id) {
-  // If this is an Ajax request, then content returned by this page request will
-  // be merged with content already on the base page. The HTML IDs must be
-  // unique for the fully merged content. Therefore, initialize $seen_ids to
-  // take into account IDs that are already in use on the base page.
-  $seen_ids_init = &drupal_static(__FUNCTION__ . ':init');
-  if (!isset($seen_ids_init)) {
-    $ajax_html_ids = \Drupal::request()->request->get('ajax_html_ids');
-    // Ideally, Drupal would provide an API to persist state information about
-    // prior page requests in the database, and we'd be able to add this
-    // function's $seen_ids static variable to that state information in order
-    // to have it properly initialized for this page request. However, no such
-    // page state API exists, so instead, ajax.js adds all of the in-use HTML
-    // IDs to the POST data of Ajax submissions. Direct use of $_POST is
-    // normally not recommended as it could open up security risks, but because
-    // the raw POST data is cast to a number before being returned by this
-    // function, this usage is safe.
-    if (empty($ajax_html_ids)) {
-      $seen_ids_init = array();
-    }
-    else {
-      // This function ensures uniqueness by appending a counter to the base id
-      // requested by the calling function after the first occurrence of that
-      // requested id. $_POST['ajax_html_ids'] contains the ids as they were
-      // returned by this function, potentially with the appended counter, so
-      // we parse that to reconstruct the $seen_ids array.
-      $ajax_html_ids = explode(' ', $ajax_html_ids);
-      foreach ($ajax_html_ids as $seen_id) {
-        // We rely on '--' being used solely for separating a base id from the
-        // counter, which this function ensures when returning an id.
-        $parts = explode('--', $seen_id, 2);
-        if (!empty($parts[1]) && is_numeric($parts[1])) {
-          list($seen_id, $i) = $parts;
-        }
-        else {
-          $i = 1;
-        }
-        if (!isset($seen_ids_init[$seen_id]) || ($i > $seen_ids_init[$seen_id])) {
-          $seen_ids_init[$seen_id] = $i;
-        }
-      }
-    }
-  }
-  $seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init);
-
-  $id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
-
-  // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can
-  // only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"),
-  // colons (":"), and periods ("."). We strip out any character not in that
-  // list. Note that the CSS spec doesn't allow colons or periods in identifiers
-  // (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two
-  // characters as well.
-  $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id);
-
-  // Removing multiple consecutive hyphens.
-  $id = preg_replace('/\-+/', '-', $id);
-  // Ensure IDs are unique by appending a counter after the first occurrence.
-  // The counter needs to be appended with a delimiter that does not exist in
-  // the base ID. Requiring a unique delimiter helps ensure that we really do
-  // return unique IDs and also helps us re-create the $seen_ids array during
-  // Ajax requests.
-  if (isset($seen_ids[$id])) {
-    $id = $id . '--' . ++$seen_ids[$id];
-  }
-  else {
-    $seen_ids[$id] = 1;
-  }
-
-  return $id;
+  return Html::getId($id);
 }
 
 /**
diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php
new file mode 100644
index 0000000..3fa2dcf
--- /dev/null
+++ b/core/lib/Drupal/Component/Utility/Html.php
@@ -0,0 +1,196 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Component\Utility\Html.
+ */
+
+namespace Drupal\Component\Utility;
+
+/**
+ * Provides helpers to generate code related to markup.
+ */
+class Html {
+
+  /**
+   * An array of previously cleaned HTML classes.
+   *
+   * @var array
+   */
+  protected static $classes = array();
+
+  /**
+   * An array of the initial IDs used in one request.
+   *
+   * @var array
+   */
+  protected static $seenIdsInit;
+
+  /**
+   * An array of IDs, including incremented versions when an ID is duplicated.
+   * @var array
+   */
+  protected static $seenIds;
+
+  /**
+   * Prepares a string for use as a valid class name.
+   *
+   * Do not pass one string containing multiple classes as they will be
+   * incorrectly concatenated with dashes, i.e. "one two" will become "one-two".
+   *
+   * @param string $class
+   *   The class name to clean.
+   *
+   * @return string
+   *   The cleaned class name.
+   */
+  public static function getClass($class) {
+    if (!isset(static::$classes[$class])) {
+      static::$classes[$class] = static::cleanCssIdentifier(Unicode::strtolower($class));
+    }
+    return static::$classes[$class];
+  }
+
+  /**
+   * Prepares a string for use as a CSS identifier (element, class, or ID name).
+   *
+   * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for
+   * valid CSS identifiers (including element names, classes, and IDs in
+   * selectors.)
+   *
+   * @param string $identifier
+   *   The identifier to clean.
+   * @param array $filter
+   *   An array of string replacements to use on the identifier.
+   *
+   * @return string
+   *   The cleaned identifier.
+   */
+  public static function cleanCssIdentifier($identifier, array $filter = array(' ' => '-', '_' => '-', '__' => '__', '/' => '-', '[' => '-', ']' => '')) {
+    $identifier = strtr($identifier, $filter);
+
+    // Valid characters in a CSS identifier are:
+    // - the hyphen (U+002D)
+    // - a-z (U+0030 - U+0039)
+    // - A-Z (U+0041 - U+005A)
+    // - the underscore (U+005F)
+    // - 0-9 (U+0061 - U+007A)
+    // - ISO 10646 characters U+00A1 and higher
+    // We strip out any character not in the above list.
+    $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier);
+
+    // Identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit.
+    $identifier = preg_replace(array('/^[0-9]/', '/^(-[0-9])|^(--)/'), array('_', '__') , $identifier);
+
+    return $identifier;
+  }
+
+  /**
+   * Prepares a string for use as a valid HTML ID and guarantees uniqueness.
+   *
+   * This function ensures that each passed HTML ID value only exists once on
+   * the page. By tracking the already returned ids, this function enables
+   * forms, blocks, and other content to be output multiple times on the same
+   * page, without breaking (X)HTML validation.
+   *
+   * For already existing IDs, a counter is appended to the ID string.
+   * Therefore, JavaScript and CSS code should not rely on any value that was
+   * generated by this function and instead should rely on manually added CSS
+   * classes or similarly reliable constructs.
+   *
+   * Two consecutive hyphens separate the counter from the original ID. To
+   * manage uniqueness across multiple Ajax requests on the same page, Ajax
+   * requests POST an array of all IDs currently present on the page, which are
+   * used to prime this function's cache upon first invocation.
+   *
+   * To allow reverse-parsing of IDs submitted via Ajax, any multiple
+   * consecutive hyphens in the originally passed $id are replaced with a
+   * single hyphen.
+   *
+   * @param string $id
+   *   The ID to clean.
+   *
+   * @return string
+   *   The cleaned ID.
+   */
+  public static function getId($id) {
+    // If this is an Ajax request, then content returned by this page request
+    // will be merged with content already on the base page. The HTML IDs must
+    // be unique for the fully merged content. Therefore, initialize $seen_ids
+    // to take into account IDs that are already in use on the base page.
+    if (!isset(static::$seenIdsInit)) {
+      // Ideally, Drupal would provide an API to persist state information about
+      // prior page requests in the database, and we'd be able to add this
+      // function's $seen_ids static variable to that state information in order
+      // to have it properly initialized for this page request. However, no such
+      // page state API exists, so instead, ajax.js adds all of the in-use HTML
+      // IDs to the POST data of Ajax submissions. Direct use of $_POST is
+      // normally not recommended as it could open up security risks, but
+      // because the raw POST data is cast to a number before being returned by
+      // this function, this usage is safe.
+      if (empty($_POST['ajax_html_ids'])) {
+        static::$seenIdsInit = array();
+      }
+      else {
+        // This function ensures uniqueness by appending a counter to the base
+        // id requested by the calling function after the first occurrence of
+        // that requested id. $_POST['ajax_html_ids'] contains the ids as they
+        // were returned by this function, potentially with the appended
+        // counter, so we parse that to reconstruct the $seen_ids array.
+        $ajax_html_ids = explode(' ', $_POST['ajax_html_ids']);
+        foreach ($ajax_html_ids as $seen_id) {
+          // We rely on '--' being used solely for separating a base id from the
+          // counter, which this function ensures when returning an id.
+          $parts = explode('--', $seen_id, 2);
+          if (!empty($parts[1]) && is_numeric($parts[1])) {
+            list($seen_id, $i) = $parts;
+          }
+          else {
+            $i = 1;
+          }
+          if (!isset(static::$seenIdsInit[$seen_id]) || ($i > static::$seenIdsInit[$seen_id])) {
+            static::$seenIdsInit[$seen_id] = $i;
+          }
+        }
+      }
+    }
+
+    if (!isset(static::$seenIds)) {
+      static::$seenIds = static::$seenIdsInit;
+    }
+
+    $id = strtr(Unicode::strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => ''));
+
+    // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs
+    // can only contain letters, digits ([0-9]), hyphens ("-"), underscores
+    // ("_"), colons (":"), and periods ("."). We strip out any character not in
+    // that list. Note that the CSS spec doesn't allow colons or periods in
+    // identifiers (http://www.w3.org/TR/CSS21/syndata.html#characters), so we
+    // strip those two characters as well.
+    $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id);
+
+    // Removing multiple consecutive hyphens.
+    $id = preg_replace('/\-+/', '-', $id);
+    // Ensure IDs are unique by appending a counter after the first occurrence.
+    // The counter needs to be appended with a delimiter that does not exist in
+    // the base ID. Requiring a unique delimiter helps ensure that we really do
+    // return unique IDs and also helps us re-create the $seen_ids array during
+    // Ajax requests.
+    if (isset(static::$seenIds[$id])) {
+      $id = $id . '--' . ++static::$seenIds[$id];
+    }
+    else {
+      static::$seenIds[$id] = 1;
+    }
+
+    return $id;
+  }
+
+  /**
+   * Resets the list of seen IDs.
+   */
+  public static function resetSeenIds() {
+    static::$seenIds = NULL;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index f01f78b..e08294e 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Form;
 
 use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\Url;
@@ -528,10 +529,10 @@ public function retrieveForm($form_id, &$form_state) {
     // Assign a default CSS class name based on $form_id.
     // This happens here and not in self::prepareForm() in order to allow the
     // form constructor function to override or remove the default class.
-    $form['#attributes']['class'][] = $this->drupalHtmlClass($form_id);
+    $form['#attributes']['class'][] = Html::getClass($form_id);
     // Same for the base form ID, if any.
     if (isset($form_state['build_info']['base_form_id'])) {
-      $form['#attributes']['class'][] = $this->drupalHtmlClass($form_state['build_info']['base_form_id']);
+      $form['#attributes']['class'][] = Html::getClass($form_state['build_info']['base_form_id']);
     }
 
     // We need to pass $form_state by reference in order for forms to modify it,
@@ -606,7 +607,7 @@ public function processForm($form_id, &$form, &$form_state) {
       // element IDs needlessly.
       if (!$this->getAnyErrors()) {
         // In case of errors, do not break HTML IDs of other forms.
-        $this->drupalStaticReset('drupal_html_id');
+        Html::resetSeenIds();
       }
 
       if ($form_state['submitted'] && !$this->getAnyErrors() && !$form_state['rebuild']) {
@@ -747,7 +748,7 @@ public function prepareForm($form_id, &$form, &$form_state) {
       else {
         $form['#token'] = $form_id;
         $form['form_token'] = array(
-          '#id' => $this->drupalHtmlId('edit-' . $form_id . '-form-token'),
+          '#id' => Html::getId('edit-' . $form_id . '-form-token'),
           '#type' => 'token',
           '#default_value' => $this->csrfToken->get($form['#token']),
           // Form processing and validation requires this value, so ensure the
@@ -762,7 +763,7 @@ public function prepareForm($form_id, &$form, &$form_state) {
       $form['form_id'] = array(
         '#type' => 'hidden',
         '#value' => $form_id,
-        '#id' => $this->drupalHtmlId("edit-$form_id"),
+        '#id' => Html::getId("edit-$form_id"),
         // Form processing and validation requires this value, so ensure the
         // submitted form value appears literally, regardless of custom #tree
         // and #parents being set elsewhere.
@@ -770,7 +771,7 @@ public function prepareForm($form_id, &$form, &$form_state) {
       );
     }
     if (!isset($form['#id'])) {
-      $form['#id'] = $this->drupalHtmlId($form_id);
+      $form['#id'] = Html::getId($form_id);
     }
 
     $form += $this->getElementInfo('form');
@@ -1342,7 +1343,7 @@ public function doBuildForm($form_id, &$element, &$form_state) {
     }
 
     if (!isset($element['#id'])) {
-      $element['#id'] = $this->drupalHtmlId('edit-' . implode('-', $element['#parents']));
+      $element['#id'] = Html::getId('edit-' . implode('-', $element['#parents']));
     }
 
     // Add the aria-describedby attribute to associate the form control with its
@@ -1776,31 +1777,6 @@ protected function drupalSetMessage($message = NULL, $type = 'status', $repeat =
   }
 
   /**
-   * Wraps drupal_html_class().
-   *
-   * @return string
-   */
-  protected function drupalHtmlClass($class) {
-    return drupal_html_class($class);
-  }
-
-  /**
-   * Wraps drupal_html_id().
-   *
-   * @return string
-   */
-  protected function drupalHtmlId($id) {
-    return drupal_html_id($id);
-  }
-
-  /**
-   * Wraps drupal_static_reset().
-   */
-  protected function drupalStaticReset($name = NULL) {
-    drupal_static_reset($name);
-  }
-
-  /**
    * Gets the current active user.
    *
    * @return \Drupal\Core\Session\AccountInterface
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php
index 82827ab..5021d7e 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockStorageUnitTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\block\Tests;
 
+use Drupal\Component\Utility\Html;
 use Drupal\Core\Config\Entity\ConfigStorageController;
 use Drupal\simpletest\DrupalUnitTestBase;
 use Drupal\block_test\Plugin\Block\TestHtmlIdBlock;
@@ -147,7 +148,7 @@ protected function renderTests() {
     $this->assertEqual(drupal_render($output), $expected_output, 'The block rendered correctly.');
 
     // Reset the HTML IDs so that the next render is not affected.
-    drupal_static_reset('drupal_html_id');
+    Html::resetSeenIds();
 
     // Test the rendering of a block with a given title.
     $entity = $this->controller->create(array(
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/HtmlIdentifierUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/HtmlIdentifierUnitTest.php
deleted file mode 100644
index ec9bbb4..0000000
--- a/core/modules/system/lib/Drupal/system/Tests/Common/HtmlIdentifierUnitTest.php
+++ /dev/null
@@ -1,97 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\system\Tests\Common\HtmlIdentifierUnitTest.
- */
-
-namespace Drupal\system\Tests\Common;
-
-use Drupal\simpletest\UnitTestBase;
-use Symfony\Component\HttpFoundation\Request;
-
-/**
- * Tests cleaning HTML identifiers.
- */
-class HtmlIdentifierUnitTest extends UnitTestBase {
-  public static function getInfo() {
-    return array(
-      'name' => 'HTML identifiers',
-      'description' => 'Test the functions drupal_html_class(), drupal_html_id() and drupal_clean_css_identifier() for expected behavior',
-      'group' => 'Common',
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setUp() {
-    parent::setUp();
-
-    $container = \Drupal::getContainer();
-    $request = new Request();
-    $container->set('request', $request);
-    \Drupal::setContainer($container);
-  }
-
-  /**
-   * Tests that drupal_clean_css_identifier() cleans the identifier properly.
-   */
-  function testDrupalCleanCSSIdentifier() {
-    // Verify that no valid ASCII characters are stripped from the identifier.
-    $identifier = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789';
-    $this->assertIdentical(drupal_clean_css_identifier($identifier, array()), $identifier, 'Verify valid ASCII characters pass through.');
-
-    // Verify that valid UTF-8 characters are not stripped from the identifier.
-    $identifier = '¡¢£¤¥';
-    $this->assertIdentical(drupal_clean_css_identifier($identifier, array()), $identifier, 'Verify valid UTF-8 characters pass through.');
-
-    // Verify that invalid characters (including non-breaking space) are stripped from the identifier.
-    $this->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), 'invalididentifier', 'Strip invalid characters.');
-
-    // Verify that double underscores are not stripped from the identifier.
-    $identifier = 'css__identifier__with__double__underscores';
-    $this->assertIdentical(drupal_clean_css_identifier($identifier), $identifier, 'Verify double underscores pass through.');
-
-    // Verify that an identifier starting with a digit is replaced.
-    $this->assertIdentical(drupal_clean_css_identifier('1cssidentifier', array()), '_cssidentifier', 'Verify identifier starting with a digit is replaced.');
-
-    // Verify that an identifier starting with a hyphen followed by a digit is
-    // replaced.
-    $this->assertIdentical(drupal_clean_css_identifier('-1cssidentifier', array()), '__cssidentifier', 'Verify identifier starting with a hyphen followed by a digit is replaced.');
-
-    // Verify that an identifier starting with two hyphens is replaced.
-    $this->assertIdentical(drupal_clean_css_identifier('--cssidentifier', array()), '__cssidentifier', 'Verify identifier starting with two hyphens is replaced.');
-  }
-
-  /**
-   * Tests that drupal_html_class() cleans the class name properly.
-   */
-  function testDrupalHTMLClass() {
-    // Verify Drupal coding standards are enforced.
-    $this->assertIdentical(drupal_html_class('CLASS NAME_[Ü]'), 'class-name--ü', 'Enforce Drupal coding standards.');
-  }
-
-  /**
-   * Tests that drupal_html_id() cleans the ID properly.
-   */
-  function testDrupalHTMLId() {
-    // Verify that letters, digits, and hyphens are not stripped from the ID.
-    $id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
-    $this->assertIdentical(drupal_html_id($id), $id, 'Verify valid characters pass through.');
-
-    // Verify that invalid characters are stripped from the ID.
-    $this->assertIdentical(drupal_html_id('invalid,./:@\\^`{Üidentifier'), 'invalididentifier', 'Strip invalid characters.');
-
-    // Verify Drupal coding standards are enforced.
-    $this->assertIdentical(drupal_html_id('ID NAME_[1]'), 'id-name-1', 'Enforce Drupal coding standards.');
-
-    // Reset the static cache so we can ensure the unique id count is at zero.
-    drupal_static_reset('drupal_html_id');
-
-    // Clean up IDs with invalid starting characters.
-    $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id', 'Test the uniqueness of IDs #1.');
-    $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--2', 'Test the uniqueness of IDs #2.');
-    $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--3', 'Test the uniqueness of IDs #3.');
-  }
-}
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ViewsFormBase.php b/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ViewsFormBase.php
index cfb0a8b..eb412ac 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ViewsFormBase.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ViewsFormBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views_ui\Form\Ajax;
 
+use Drupal\Component\Utility\Html;
 use Drupal\Core\Form\FormBase;
 use Drupal\views\ViewStorageInterface;
 use Drupal\views\Ajax;
@@ -94,8 +95,7 @@ public function getForm(ViewStorageInterface $view, $display_id, $js) {
     // Reset the cache of IDs. Drupal rather aggressively prevents ID
     // duplication but this causes it to remember IDs that are no longer even
     // being used.
-    $seen_ids_init = &drupal_static('drupal_html_id:init');
-    $seen_ids_init = array();
+    Html::resetSeenIds();
 
     // check to see if this is the top form of the stack. If it is, pop
     // it off; if it isn't, the user clicked somewhere else and the stack is
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewPreviewFormController.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewPreviewFormController.php
index 4533f76..937a4bc 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewPreviewFormController.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewPreviewFormController.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views_ui;
 
+use Drupal\Component\Utility\Html;
 use Drupal\user\TempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
@@ -54,8 +55,7 @@ public function form(array $form, array &$form_state) {
     // Reset the cache of IDs. Drupal rather aggressively prevents ID
     // duplication but this causes it to remember IDs that are no longer even
     // being used.
-    $seen_ids_init = &drupal_static('drupal_html_id:init');
-    $seen_ids_init = array();
+    Html::resetSeenIds();
 
     $form_state['no_cache'] = TRUE;
 
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
index 3efd362..1c2ee53 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views_ui;
 
+use Drupal\Component\Utility\Html;
 use Drupal\views\Views;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\views\ViewExecutable;
@@ -415,8 +416,7 @@ public function addFormToStack($key, $display_id, $type, $id = NULL, $top = FALS
     // Reset the cache of IDs. Drupal rather aggressively prevents ID
     // duplication but this causes it to remember IDs that are no longer even
     // being used.
-    $seen_ids_init = &drupal_static('drupal_html_id:init');
-    $seen_ids_init = array();
+    Html::resetSeenIds();
 
     if (empty($this->stack)) {
       $this->stack = array();
diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
index f9a6718..c7329b0 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
@@ -112,7 +112,6 @@ public function setUp() {
 
     $this->account = $this->getMock('Drupal\Core\Session\AccountInterface');
     $this->formBuilder->setCurrentUser($this->account);
-
   }
 
   /**
@@ -336,7 +335,7 @@ public function testGetFormWithString() {
 
     $form = $this->formBuilder->getForm($form_id);
     $this->assertFormElement($expected_form, $form, 'test');
-    $this->assertSame($form_id, $form['#id']);
+    $this->assertArrayHasKey('#id', $form);
   }
 
   /**
@@ -350,7 +349,7 @@ public function testGetFormWithObject() {
 
     $form = $this->formBuilder->getForm($form_arg);
     $this->assertFormElement($expected_form, $form, 'test');
-    $this->assertSame($form_id, $form['#id']);
+    $this->assertArrayHasKey('#id', $form);
   }
 
   /**
@@ -368,7 +367,7 @@ public function testBuildFormWithObject() {
     $form = $this->formBuilder->buildForm($form_id, $form_state);
     $this->assertFormElement($expected_form, $form, 'test');
     $this->assertSame($form_id, $form_state['build_info']['form_id']);
-    $this->assertSame($form_id, $form['#id']);
+    $this->assertArrayHasKey('#id', $form);
   }
 
   /**
@@ -393,7 +392,7 @@ public function testBuildFormWithHookForms() {
     $form = $this->formBuilder->buildForm($form_id, $form_state);
     $this->assertFormElement($expected_form, $form, 'test');
     $this->assertSame($form_id, $form_state['build_info']['form_id']);
-    $this->assertSame($form_id, $form['#id']);
+    $this->assertArrayHasKey('#id', $form);
     $this->assertSame($base_form_id, $form_state['build_info']['base_form_id']);
   }
 
@@ -813,26 +812,6 @@ protected function drupalSetMessage($message = NULL, $type = 'status', $repeat =
   protected function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) {
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function drupalHtmlClass($class) {
-    return $class;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function drupalHtmlId($id) {
-    return $id;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function drupalStaticReset($name = NULL) {
-  }
-
 }
 
 class TestForm implements FormInterface {
diff --git a/core/tests/Drupal/Tests/Core/Utility/HtmlTest.php b/core/tests/Drupal/Tests/Core/Utility/HtmlTest.php
new file mode 100644
index 0000000..05c492e
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Utility/HtmlTest.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Utility\HtmlTest.
+ */
+
+namespace Drupal\Tests\Core\Utility;
+
+use Drupal\Component\Utility\Html;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests \Drupal\Component\Utility\Html.
+ *
+ * @group Drupal
+ */
+class HtmlTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'HTML identifiers',
+      'description' => 'Test the methods Html::getClass(), Html::getId() and Html::cleanCssIdentifier() for expected behavior.',
+      'group' => 'Common',
+    );
+  }
+
+  /**
+   * Tests the Html::cleanCssIdentifier() method.
+   *
+   * @param string $expected
+   *   The expected result.
+   * @param string $source
+   *   The string being transformed to an ID.
+   * @param array|null $filter
+   *   (optional) An array of string replacements to use on the identifier. If
+   *   NULL, no filter will be passed and a default will be used.
+   *
+   * @dataProvider providerTestCleanCssIdentifier
+   */
+  public function testCleanCssIdentifier($expected, $source, $filter = NULL) {
+    if ($filter !== NULL) {
+      $this->assertSame($expected, Html::cleanCssIdentifier($source, $filter));
+    }
+    else {
+      $this->assertSame($expected, Html::cleanCssIdentifier($source));
+    }
+  }
+
+  /**
+   * Provides test data for testCleanCssIdentifier().
+   *
+   * @return array
+   *   Test data.
+   */
+  public function providerTestCleanCssIdentifier() {
+    $id1 = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789';
+    $id2 = '¡¢£¤¥';
+    $id3 = 'css__identifier__with__double__underscores';
+    return array(
+      // Verify that no valid ASCII characters are stripped from the identifier.
+      array($id1, $id1, array()),
+      // Verify that valid UTF-8 characters are not stripped from the identifier.
+      array($id2, $id2, array()),
+      // Verify that invalid characters (including non-breaking space) are stripped from the identifier.
+      array($id3, $id3),
+      // Verify that double underscores are not stripped from the identifier.
+      array('invalididentifier', 'invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()),
+      // Verify that an identifier starting with a digit is replaced.
+      array('_cssidentifier', '1cssidentifier', array()),
+      // Verify that an identifier starting with a hyphen followed by a digit is
+      // replaced.
+      array('__cssidentifier', '-1cssidentifier', array()),
+      // Verify that an identifier starting with two hyphens is replaced.
+      array('__cssidentifier', '--cssidentifier', array())
+    );
+  }
+
+  /**
+   * Tests that Html::getClass() cleans the class name properly.
+   */
+  public function testHtmlClass() {
+    // Verify Drupal coding standards are enforced.
+    $this->assertSame(Html::getClass('CLASS NAME_[Ü]'), 'class-name--ü', 'Enforce Drupal coding standards.');
+  }
+
+  /**
+   * Tests the Html::getId() method.
+   *
+   * @param string $expected
+   *   The expected result.
+   * @param string $source
+   *   The string being transformed to an ID.
+   * @param bool $reset
+   *   (optional) If TRUE, reset the list of seen IDs. Defaults to FALSE.
+   *
+   * @dataProvider providerTestHtmlGetId
+   */
+  public function testHtmlGetId($expected, $source, $reset = FALSE) {
+    if ($reset) {
+      Html::resetSeenIds();
+    }
+    $this->assertSame($expected, Html::getId($source));
+  }
+
+  /**
+   * Provides test data for testHtmlGetId().
+   *
+   * @return array
+   *   Test data.
+   */
+  public function providerTestHtmlGetId() {
+    $id = 'abcdefghijklmnopqrstuvwxyz-0123456789';
+    return array(
+      // Verify that letters, digits, and hyphens are not stripped from the ID.
+      array($id, $id),
+      // Verify that invalid characters are stripped from the ID.
+      array('invalididentifier', 'invalid,./:@\\^`{Üidentifier'),
+      // Verify Drupal coding standards are enforced.
+      array('id-name-1', 'ID NAME_[1]'),
+      // Verify that a repeated ID is made unique.
+      array('test-unique-id', 'test-unique-id', TRUE),
+      array('test-unique-id--2', 'test-unique-id'),
+      array('test-unique-id--3', 'test-unique-id'),
+    );
+  }
+
+}
