diff --git a/core/includes/form.inc b/core/includes/form.inc
index 5c0c5cf..8f7260e 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -2131,6 +2131,7 @@ function form_pre_render_vertical_tabs($element) {
 function template_preprocess_vertical_tabs(&$variables) {
   $element = $variables['element'];
   $variables['children'] = (!empty($element['#children'])) ? $element['#children'] : '';
+  $variables['attributes'] = new Attribute($element['#attributes']);
 }
 
 /**
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 3dd8449..ad82a67 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -715,20 +715,6 @@ function theme($hook, $variables = array()) {
       template_preprocess($default_template_variables, $hook, $info);
       $variables += $default_template_variables;
     }
-    if (!isset($default_attributes)) {
-      $default_attributes = new Attribute();
-    }
-    foreach (array('attributes', 'title_attributes', 'content_attributes') as $key) {
-      if (isset($variables[$key]) && !($variables[$key] instanceof Attribute)) {
-        if ($variables[$key]) {
-          $variables[$key] = new Attribute($variables[$key]);
-        }
-        else {
-          // Create empty attributes.
-          $variables[$key] = clone $default_attributes;
-        }
-      }
-    }
 
     // Render the output using the template file.
     $template_file = $info['template'] . $extension;
@@ -1884,6 +1870,7 @@ function template_preprocess_feed_icon(&$variables) {
   $variables['attributes']['class'] = array('feed-icon');
   // Stripping tags because that's what l() used to do.
   $variables['attributes']['title'] = strip_tags($text);
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
@@ -2035,8 +2022,6 @@ function _template_preprocess_default_variables() {
   // Variables that don't depend on a database connection.
   $variables = array(
     'attributes' => array(),
-    'title_attributes' => array(),
-    'content_attributes' => array(),
     'title_prefix' => array(),
     'title_suffix' => array(),
     'db_is_active' => !defined('MAINTENANCE_MODE'),
@@ -2102,6 +2087,8 @@ function template_preprocess_html(&$variables) {
     }
   }
 
+  $variables['attributes'] = new Attribute($variables['attributes']);
+
   // Initializes attributes which are specific to the html and body elements.
   $variables['html_attributes'] = new Attribute;
 
@@ -2514,6 +2501,8 @@ function template_preprocess_maintenance_page(&$variables) {
     $variables['attributes']['class'][] = 'sidebar-' . $variables['layout'];
   }
 
+  $variables['attributes'] = new Attribute($variables['attributes']);
+
   $variables['head'] = drupal_get_html_head();
 
   // While this code is used in the installer, the language module may not be
@@ -2578,6 +2567,8 @@ function template_preprocess_region(&$variables) {
 
   $variables['attributes']['class'][] = 'region';
   $variables['attributes']['class'][] = drupal_html_class('region-' . $variables['region']);
+
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php
index 473b31a..1ebdd9f 100644
--- a/core/lib/Drupal/Core/Template/Attribute.php
+++ b/core/lib/Drupal/Core/Template/Attribute.php
@@ -83,6 +83,9 @@ protected function createAttributeValue($name, $value) {
     if (is_array($value)) {
       $value = new AttributeArray($name, $value);
     }
+    elseif ($name == 'class') {
+      $value = new AttributeArray($name, array($value));
+    }
     elseif (is_bool($value)) {
       $value = new AttributeBoolean($name, $value);
     }
diff --git a/core/modules/aggregator/aggregator.pages.inc b/core/modules/aggregator/aggregator.pages.inc
index 010a9d7..b08b6f0 100644
--- a/core/modules/aggregator/aggregator.pages.inc
+++ b/core/modules/aggregator/aggregator.pages.inc
@@ -73,6 +73,7 @@ function template_preprocess_aggregator_item(&$variables) {
   }
 
   $variables['attributes']['class'][] = 'feed-item';
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
@@ -205,4 +206,5 @@ function template_preprocess_aggregator_feed_source(&$variables) {
   }
 
   $variables['attributes']['class'][] = 'feed-source';
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index daea82c..34bc6f6 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -7,6 +7,7 @@
 
 use Drupal\block\BlockInterface;
 use Drupal\Component\Plugin\Exception\PluginException;
+use Drupal\Core\Template\Attribute;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
 
 /**
@@ -483,6 +484,19 @@ function template_preprocess_block(&$variables) {
   $variables['attributes']['class'][] = 'block';
   $variables['attributes']['class'][] = drupal_html_class('block-' . $variables['configuration']['module']);
 
+  // The block template provides a wrapping element for the content. Render the
+  // #attributes of the content on this wrapping element rather than passing
+  // them through to the content's #theme function/template. This allows the
+  // content to not require a function/template at all, or if it does use one,
+  // to not require it to output an extra wrapping element.
+  if(!isset($variables['content_attributes'])) {
+    $variables['content_attributes'] = array();
+  }
+  if (isset($variables['content']['#attributes'])) {
+    $variables['content_attributes'] = NestedArray::mergeDeep($variables['content_attributes'], $variables['content']['#attributes']);
+    unset($variables['content']['#attributes']);
+  }
+
   // Add default class for block content.
   $variables['content_attributes']['class'][] = 'content';
 
@@ -490,6 +504,16 @@ function template_preprocess_block(&$variables) {
   if ($id = $variables['elements']['#block']->id()) {
     $variables['attributes']['id'] = drupal_html_id('block-' . $id);
   }
+
+  static $default_attributes;
+  if (!isset($default_attributes)) {
+    $default_attributes = new Attribute;
+  }
+
+  // For best performance, we only instantiate Attribute objects when needed.
+  $variables['attributes'] = isset($variables['attributes']) ? new Attribute($variables['attributes']) : clone $default_attributes;
+  $variables['title_attributes'] = isset($variables['title_attributes']) ? new Attribute($variables['title_attributes']) : clone $default_attributes;
+  $variables['content_attributes'] = isset($variables['content_attributes']) ? new Attribute($variables['content_attributes']) : clone $default_attributes;
 }
 
 /**
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 01c51da..6f8f0c8 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -13,6 +13,7 @@
 use Drupal\Component\Utility\Number;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityChangedInterface;
+use Drupal\Core\Template\Attribute;
 use Drupal\comment\CommentInterface;
 use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
 use Drupal\field\FieldInstanceInterface;
@@ -1505,6 +1506,16 @@ function template_preprocess_comment(&$variables) {
   $variables['attributes']['data-comment-user-id'] = $comment->getOwnerId();
 
   $variables['content_attributes']['class'][] = 'content';
+
+  static $default_attributes;
+  if (!isset($default_attributes)) {
+    $default_attributes = new Attribute;
+  }
+
+  // For best performance, we only instantiate Attribute objects when needed.
+  $variables['attributes'] = isset($variables['attributes']) ? new Attribute($variables['attributes']) : clone $default_attributes;
+  $variables['title_attributes'] = isset($variables['title_attributes']) ? new Attribute($variables['title_attributes']) : clone $default_attributes;
+  $variables['content_attributes'] = isset($variables['content_attributes']) ? new Attribute($variables['content_attributes']) : clone $default_attributes;
 }
 
 /**
@@ -1529,6 +1540,7 @@ function template_preprocess_comment_wrapper(&$variables) {
 
   // Add a comment wrapper class.
   $variables['attributes']['class'][] = 'comment-wrapper';
+  $variables['attributes'] = new Attribute($variables['attributes']);
 
   // Create separate variables for the comments and comment form.
   $variables['comments'] = $variables['content']['comments'];
diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module
index 3fd2cb9..129befd 100644
--- a/core/modules/contextual/contextual.module
+++ b/core/modules/contextual/contextual.module
@@ -207,6 +207,8 @@ function contextual_preprocess(&$variables, $hook, $info) {
       '#id' => _contextual_links_to_id($element['#contextual_links']),
     );
   }
+
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
diff --git a/core/modules/datetime/datetime.module b/core/modules/datetime/datetime.module
index ccf1a0c..8ff7db0 100644
--- a/core/modules/datetime/datetime.module
+++ b/core/modules/datetime/datetime.module
@@ -200,7 +200,6 @@ function datetime_date_default_time($date) {
 function template_preprocess_datetime_form(&$variables) {
   $element = $variables['element'];
 
-  $variables['attributes'] = array();
   if (isset($element['#id'])) {
     $variables['attributes']['id'] = $element['#id'];
   }
@@ -209,6 +208,7 @@ function template_preprocess_datetime_form(&$variables) {
   }
   $variables['attributes']['class'][] = 'container-inline';
 
+  $variables['attributes'] = new Attribute($variables['attributes']);
   $variables['content'] = $element;
 }
 
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 0aaffb5..6b394df 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -556,16 +556,11 @@ function template_preprocess_field(&$variables, $hook) {
   if (!isset($default_attributes)) {
     $default_attributes = new Attribute;
   }
-  // The default theme implementation for fields is a function.
-  // template_preprocess() (which initializes the attributes, title_attributes,
-  // and content_attributes arrays) does not run for theme function
-  // implementations. Additionally, Attribute objects for the three variables
-  // below only get instantiated for template file implementations, and we need
-  // Attribute objects for printing in both theme functions and template files.
+
   // For best performance, we only instantiate Attribute objects when needed.
   $variables['attributes'] = isset($variables['attributes']) ? new Attribute($variables['attributes']) : clone $default_attributes;
-  $variables['title_attributes'] = isset($variables['title_attributes']) ? new Attribute($variables['title_attributes']) : clone($default_attributes);
-  $variables['content_attributes'] = isset($variables['content_attributes']) ? new Attribute($variables['content_attributes']) : clone($default_attributes);
+  $variables['title_attributes'] = isset($variables['title_attributes']) ? new Attribute($variables['title_attributes']) : clone $default_attributes;
+  $variables['content_attributes'] = isset($variables['content_attributes']) ? new Attribute($variables['content_attributes']) : clone $default_attributes;
 
   // Modules (e.g., rdf.module) can add field item attributes (to
   // $item->_attributes) within hook_entity_prepare_view(). Some field
@@ -575,7 +570,7 @@ function template_preprocess_field(&$variables, $hook) {
   // formatters leave them within $element['#items'][$delta]['_attributes'] to
   // be rendered on the item wrappers provided by theme_field().
   foreach ($variables['items'] as $delta => $item) {
-    $variables['item_attributes'][$delta] = !empty($element['#items'][$delta]->_attributes) ? new Attribute($element['#items'][$delta]->_attributes) : clone($default_attributes);
+    $variables['item_attributes'][$delta] = !empty($element['#items'][$delta]->_attributes) ? new Attribute($element['#items'][$delta]->_attributes) : clone $default_attributes;
   }
 }
 
diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module
index 710c59f..67c3990 100644
--- a/core/modules/filter/filter.module
+++ b/core/modules/filter/filter.module
@@ -832,6 +832,8 @@ function template_preprocess_filter_guidelines(&$variables) {
     '#theme' => 'filter_tips',
     '#tips' => _filter_tips($format->format, FALSE),
   );
+
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 2bb3e43..f49d3b4 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -861,6 +861,7 @@ function template_preprocess_forum_icon(&$variables) {
   $variables['attributes']['class'][] = 'icon';
   $variables['attributes']['class'][] = 'topic-status-' . $icon_status_class;
   $variables['attributes']['title'] = $variables['icon_title'];
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index c22abe3..5bbed87 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -707,6 +707,17 @@ function template_preprocess_node(&$variables) {
     $variables['attributes']['class'][] = 'preview';
   }
   $variables['content_attributes']['class'][] = 'content';
+
+  static $default_attributes;
+  if (!isset($default_attributes)) {
+    $default_attributes = new Attribute;
+  }
+
+  // For best performance, we only instantiate Attribute objects when needed.
+  $variables['attributes'] = isset($variables['attributes']) ? new Attribute($variables['attributes']) : clone $default_attributes;
+  $variables['title_attributes'] = isset($variables['title_attributes']) ? new Attribute($variables['title_attributes']) : clone $default_attributes;
+  $variables['content_attributes'] = isset($variables['content_attributes']) ? new Attribute($variables['content_attributes']) : clone $default_attributes;
+
 }
 
 /**
diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module
index 057af3e..4919cac 100644
--- a/core/modules/rdf/rdf.module
+++ b/core/modules/rdf/rdf.module
@@ -276,6 +276,7 @@ function rdf_preprocess_node(&$variables) {
   $bundle_mapping = $mapping->getPreparedBundleMapping('node', $bundle);
   $variables['attributes']['about'] = empty($variables['node_url']) ? NULL: $variables['node_url'];
   $variables['attributes']['typeof'] = empty($bundle_mapping['types']) ? NULL : $bundle_mapping['types'];
+  $variables['attributes'] = new Attribute($variables['attributes']);
 
   // Adds RDFa markup for the node title as metadata because wrapping the title
   // with markup is not reliable and the title output is different depdending on
@@ -344,6 +345,7 @@ function rdf_preprocess_user(&$variables) {
   if (!empty($bundle_mapping['types'])) {
     $variables['attributes']['typeof'] = $bundle_mapping['types'];
     $variables['attributes']['about'] = $account->url();
+    $variables['attributes'] = new Attribute($variables['attributes']);
   }
   // If we are on the user account page, add the relationship between the
   // sioc:UserAccount and the foaf:Person who holds the account.
@@ -421,6 +423,7 @@ function rdf_preprocess_username(&$variables) {
   }
   else {
     $variables['attributes'] = array_merge_recursive($variables['attributes'], $attributes);
+    $variables['attributes'] = new Attribute($variables['attributes']);
   }
 }
 
diff --git a/core/modules/search/search.pages.inc b/core/modules/search/search.pages.inc
index 023a825..b1ef5d9 100644
--- a/core/modules/search/search.pages.inc
+++ b/core/modules/search/search.pages.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Language\Language;
+use Drupal\Core\Template\Attribute;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 
 /**
@@ -88,5 +89,15 @@ function template_preprocess_search_result(&$variables) {
   // Provide separated and grouped meta information..
   $variables['info_split'] = $info;
   $variables['info'] = implode(' - ', $info);
+
+  static $default_attributes;
+  if (!isset($default_attributes)) {
+    $default_attributes = new Attribute;
+  }
+
+  // For best performance, we only instantiate Attribute objects when needed.
+  $variables['attributes'] = isset($variables['attributes']) ? new Attribute($variables['attributes']) : clone $default_attributes;
+  $variables['title_attributes'] = isset($variables['title_attributes']) ? new Attribute($variables['title_attributes']) : clone $default_attributes;
+  $variables['content_attributes'] = isset($variables['content_attributes']) ? new Attribute($variables['content_attributes']) : clone $default_attributes;
 }
 
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module
index bdb1404..b2a05bd 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -126,6 +126,7 @@ function theme_theme_test_function_template_override($variables) {
  */
 function template_preprocess_theme_test_render_element(&$variables) {
   $variables['attributes']['data-variables-are-preprocessed'] = TRUE;
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index f2fe6fe..e1dee83 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -416,6 +416,7 @@ function template_preprocess_taxonomy_term(&$variables) {
   $variables['attributes']['class'][] = 'taxonomy-term';
   $vocabulary_name_css = str_replace('_', '-', $term->bundle());
   $variables['attributes']['class'][] = 'vocabulary-' . $vocabulary_name_css;
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index d91527b..10715a4 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -221,7 +221,7 @@ function template_preprocess_toolbar(&$variables) {
   $element = $variables['element'];
 
   // Prepare the toolbar attributes.
-  $variables['attributes'] = $element['#attributes'];
+  $variables['attributes'] = new Attribute($element['#attributes']);
   $variables['toolbar_attributes'] = new Attribute($element['#bar']['#attributes']);
   $variables['toolbar_heading'] = $element['#bar']['#heading'];
 
diff --git a/core/modules/user/user.pages.inc b/core/modules/user/user.pages.inc
index f9b5a91..2d9a969 100644
--- a/core/modules/user/user.pages.inc
+++ b/core/modules/user/user.pages.inc
@@ -10,6 +10,7 @@
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
 use Symfony\Component\HttpKernel\HttpKernelInterface;
 use Drupal\Component\Utility\Crypt;
+use Drupal\Core\Template\Attribute;
 
 /**
  * Menu callback; process one time login link and redirects to the user page on success.
@@ -115,6 +116,7 @@ function template_preprocess_user(&$variables) {
 
   // Set up attributes.
   $variables['attributes']['class'][] = 'profile';
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
diff --git a/core/modules/views/views.theme.inc b/core/modules/views/views.theme.inc
index 6c6cd0a..f21b295 100644
--- a/core/modules/views/views.theme.inc
+++ b/core/modules/views/views.theme.inc
@@ -151,6 +151,8 @@ function template_preprocess_views_view(&$variables) {
     }
     $variables['rows'] = $form;
   }
+
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
@@ -750,6 +752,8 @@ function template_preprocess_views_view_table(&$variables) {
     // This is needed to target tables constructed by this function.
     $variables['attributes']['class'][] = 'responsive-enabled';
   }
+
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
@@ -873,6 +877,7 @@ function template_preprocess_views_view_grid(&$variables) {
 
   // Add items to the variables array.
   $variables['items'] = $items;
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
@@ -919,6 +924,7 @@ function template_preprocess_views_view_unformatted(&$variables) {
     }
     $variables['rows'][$id]['attributes'] = new Attribute($variables['rows'][$id]['attributes']);
   }
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
diff --git a/core/modules/views_ui/views_ui.theme.inc b/core/modules/views_ui/views_ui.theme.inc
index db4f3e8..776e49f 100644
--- a/core/modules/views_ui/views_ui.theme.inc
+++ b/core/modules/views_ui/views_ui.theme.inc
@@ -46,6 +46,8 @@ function template_preprocess_views_ui_display_tab_setting(&$variables) {
   if ($variables['description'] && $variables['description_separator']) {
     $variables['description'] .= t(':');
   }
+
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
@@ -74,6 +76,8 @@ function template_preprocess_views_ui_display_tab_bucket(&$variables) {
   $variables['content'] = $element['#children'];
   $variables['title'] = $element['#title'];
   $variables['actions'] = !empty($element['#actions']) ? $element['#actions'] : array();
+
+  $variables['attributes'] = new Attribute($variables['attributes']);
 }
 
 /**
