diff --git a/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
index 74ed87b75a229704803e004f416a15863f96c4e6..c58c233a6d7e3b22e6c723d347565f68376991d5 100644
--- a/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
+++ b/core/lib/Drupal/Core/Page/DefaultHtmlFragmentRenderer.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Page;
 
+use Drupal\Core\Cache\CacheableInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Language\LanguageManager;
 
@@ -35,15 +36,18 @@ public function __construct(LanguageManager $language_manager) {
   /**
    * {@inheritdoc}
    */
-  public function render(HtmlFragment $fragment, $status_code = 200) {
+  public function render(HtmlFragmentInterface $fragment, $status_code = 200) {
     // Converts the given HTML fragment which represents the main content region
     // of the page into a render array.
     $page_content['main'] = array(
       '#markup' => $fragment->getContent(),
-      '#cache' => array('tags' => $fragment->getCacheTags()),
     );
     $page_content['#title'] = $fragment->getTitle();
 
+    if ($fragment instanceof CacheableInterface) {
+      $page_content['main']['#cache']['tags'] = $fragment->getCacheTags();
+    }
+
     // Build the full page array by calling drupal_prepare_page(), which invokes
     // hook_page_build(). This adds the other regions to the page.
     $page_array = drupal_prepare_page($page_content);
@@ -54,14 +58,17 @@ public function render(HtmlFragment $fragment, $status_code = 200) {
     $page->setBodyTop(drupal_render($page_array['page_top']));
     $page->setBodyBottom(drupal_render($page_array['page_bottom']));
     $page->setContent(drupal_render($page_array));
-    // Collect cache tags for all the content in all the regions on the page.
-    $tags = $page_array['#cache']['tags'];
-    // Enforce the generic "content" cache tag on all pages.
-    // @todo Remove the "content" cache tag. @see https://drupal.org/node/2124957
-    $tags['content'] = TRUE;
-    $page->setCacheTags($tags);
     $page->setStatusCode($status_code);
 
+    if ($fragment instanceof CacheableInterface) {
+      // Collect cache tags for all the content in all the regions on the page.
+      $tags = $page_array['#cache']['tags'];
+      // Enforce the generic "content" cache tag on all pages.
+      // @todo Remove the "content" cache tag. @see https://drupal.org/node/2124957
+      $tags['content'] = TRUE;
+      $page->setCacheTags($tags);
+    }
+
     return $page;
   }
 
diff --git a/core/lib/Drupal/Core/Page/HtmlFragment.php b/core/lib/Drupal/Core/Page/HtmlFragment.php
index 18f587a9ef3223d6c0e7424ec0420381a6f52b1f..b3c55d88c740e0880c8ad81a0dff213966d4554e 100644
--- a/core/lib/Drupal/Core/Page/HtmlFragment.php
+++ b/core/lib/Drupal/Core/Page/HtmlFragment.php
@@ -13,13 +13,9 @@
 use Drupal\Core\Utility\Title;
 
 /**
- * Response object that contains variables for injection into the html template.
- *
- * @todo Should we have this conform to an interface?
- *   https://drupal.org/node/1871596#comment-7134686
- * @todo Add method replacements for *all* data sourced by html.tpl.php.
+ * Basic mutable implementation of an HTML fragment.
  */
-class HtmlFragment implements CacheableInterface {
+class HtmlFragment implements CacheableInterface, HtmlFragmentInterface {
 
   /**
    * An array of Link elements.
@@ -89,18 +85,14 @@ public function addLinkElement(LinkElement $link) {
   }
 
   /**
-   * Returns an array of all enqueued links.
-   *
-   * @return \Drupal\Core\Page\LinkElement[]
+   * {@inheritdoc}
    */
   public function &getLinkElements() {
     return $this->links;
   }
 
   /**
-   * Returns all feed link elements.
-   *
-   * @return \Drupal\Core\Page\FeedLinkElement[]
+   * {@inheritdoc}
    */
   public function getFeedLinkElements() {
     $feed_links = array();
@@ -126,9 +118,7 @@ public function addMetaElement(MetaElement $meta) {
   }
 
   /**
-   * Returns an array of all enqueued meta elements.
-   *
-   * @return \Drupal\Core\Page\MetaElement[]
+   * {@inheritdoc}
    */
   public function &getMetaElements() {
     return $this->metatags;
@@ -154,10 +144,7 @@ public function setContent($content) {
   }
 
   /**
-   * Gets the main content of this HtmlFragment.
-   *
-   * @return string
-   *   The content for this fragment.
+   * {@inheritdoc}
    */
   public function getContent() {
     return $this->content;
@@ -197,19 +184,14 @@ public function setTitle($title, $output = Title::CHECK_PLAIN) {
   }
 
   /**
-   * Indicates whether or not this HtmlFragment has a title.
-   *
-   * @return bool
+   * {@inheritdoc}
    */
   public function hasTitle() {
     return !empty($this->title);
   }
 
   /**
-   * Gets the title for this HtmlFragment, if any.
-   *
-   * @return string
-   *   The title.
+   * {@inheritdoc}
    */
   public function getTitle() {
     return $this->title;
diff --git a/core/lib/Drupal/Core/Page/HtmlFragmentInterface.php b/core/lib/Drupal/Core/Page/HtmlFragmentInterface.php
new file mode 100644
index 0000000000000000000000000000000000000000..687dacb835aa7eaa4e1db11cd500f7899430a4d6
--- /dev/null
+++ b/core/lib/Drupal/Core/Page/HtmlFragmentInterface.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Page\HtmlFragmentInterface.
+ */
+
+namespace Drupal\Core\Page;
+
+/**
+ * A domain object for a portion of an HTML page, including related data.
+ *
+ * Related data includes any additional information relevant to a fragment of
+ * HTML that would not be part of the HTML string itself. That includes, for
+ * example, required CSS files, Javascript files, link tags, meta tags, and the
+ * title of a page or page section.
+ */
+interface HtmlFragmentInterface {
+
+  /**
+   * Indicates whether or not this HtmlFragment has a title.
+   *
+   * @return bool
+   */
+  public function hasTitle();
+
+  /**
+   * Gets the title for this HtmlFragment, if any.
+   *
+   * @return string
+   *   The title.
+   */
+  public function getTitle();
+
+  /**
+   * Gets the main content of this HtmlFragment.
+   *
+   * @return string
+   *   The content for this fragment.
+   */
+  public function getContent();
+
+  /**
+   * Returns an array of all enqueued links.
+   *
+   * @return \Drupal\Core\Page\LinkElement[]
+   */
+  public function getLinkElements();
+
+  /**
+   * Returns all feed link elements.
+   *
+   * @return \Drupal\Core\Page\FeedLinkElement[]
+   */
+  public function getFeedLinkElements();
+
+  /**
+   * Returns an array of all enqueued meta elements.
+   *
+   * @return \Drupal\Core\Page\MetaElement[]
+   */
+  public function getMetaElements();
+
+}
diff --git a/core/lib/Drupal/Core/Page/HtmlFragmentRendererInterface.php b/core/lib/Drupal/Core/Page/HtmlFragmentRendererInterface.php
index bb82d9db2e593c2634e643bede39bddf2ed2510a..32e0076c5681fb58c94b088951a2b94a609b88bc 100644
--- a/core/lib/Drupal/Core/Page/HtmlFragmentRendererInterface.php
+++ b/core/lib/Drupal/Core/Page/HtmlFragmentRendererInterface.php
@@ -24,7 +24,7 @@ interface HtmlFragmentRendererInterface {
    * metadata from the fragment and using the body of the fragment as the main
    * content region of the page.
    *
-   * @param \Drupal\Core\Page\HtmlFragment $fragment
+   * @param \Drupal\Core\Page\HtmlFragmentInterface $fragment
    *   The HTML fragment object to convert up to a page.
    * @param int $status_code
    *   (optional) The status code of the page. May be any legal HTTP response
@@ -33,6 +33,6 @@ interface HtmlFragmentRendererInterface {
    * @return \Drupal\Core\Page\HtmlPage
    *   An HtmlPage object derived from the provided fragment.
    */
-  public function render(HtmlFragment $fragment, $status_code = 200);
+  public function render(HtmlFragmentInterface $fragment, $status_code = 200);
 
 }