 core/core.services.yml                             |   5 +
 .../EarlyRenderingControllerWrapperSubscriber.php  | 166 ++++++++++++++++++
 .../Core/Render/MainContent/HtmlRenderer.php       |   5 +-
 core/lib/Drupal/Core/Render/RenderContext.php      |  62 +++++++
 core/lib/Drupal/Core/Render/Renderer.php           | 136 ++++++++-------
 core/lib/Drupal/Core/Render/RendererInterface.php  |  59 +++++--
 .../aggregator/src/Tests/Views/IntegrationTest.php |  19 ++-
 .../src/Tests/Views/CommentFieldNameTest.php       |  13 +-
 .../src/Tests/Views/ExtensionViewsFieldTest.php    |  26 ++-
 core/modules/filter/src/Tests/FilterUnitTest.php   |  31 ++--
 .../rest/src/Plugin/views/display/RestExport.php   |  51 +++++-
 core/modules/simpletest/src/AssertContentTrait.php |   8 +-
 .../Tests/Common/EarlyRenderingControllerTest.php  |  92 ++++++++++
 core/modules/system/src/Tests/Common/UrlTest.php   |   9 +-
 .../early_rendering_controller_test.info.yml       |   6 +
 .../early_rendering_controller_test.routing.yml    |  97 +++++++++++
 .../early_rendering_controller_test.services.yml   |   5 +
 .../src/AttachmentsTestDomainObject.php            |  17 ++
 .../src/AttachmentsTestResponse.php                |  18 ++
 .../src/CacheableTestDomainObject.php              |  35 ++++
 .../src/CacheableTestResponse.php                  |  18 ++
 .../src/EarlyRenderingTestController.php           | 128 ++++++++++++++
 .../src/TestDomainObject.php                       |  10 ++
 .../src/TestDomainObjectViewSubscriber.php         |  51 ++++++
 .../src/Tests/Views/TaxonomyFieldTidTest.php       |   8 +-
 .../src/Tests/Views/HandlerFieldUserNameTest.php   |  23 ++-
 .../views/src/Tests/Handler/FieldGroupRowsTest.php |  24 ++-
 .../views/src/Tests/Handler/FieldUnitTest.php      | 189 ++++++++++++++++-----
 .../views/src/Tests/Handler/FieldWebTest.php       | 139 +++++++++++----
 core/modules/views/src/Tests/Plugin/CacheTest.php  |   9 +-
 30 files changed, 1262 insertions(+), 197 deletions(-)

diff --git a/core/core.services.yml b/core/core.services.yml
index 0fe0a7f..a0122c5 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1392,6 +1392,11 @@ services:
   renderer:
     class: Drupal\Core\Render\Renderer
     arguments: ['@controller_resolver', '@theme.manager', '@plugin.manager.element_info', '@render_cache', '%renderer.config%']
+  early_rendering_controller_wrapper_subscriber:
+    class: Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber
+    arguments: ['@controller_resolver', '@renderer']
+    tags:
+      - { name: event_subscriber }
   email.validator:
     class: Egulias\EmailValidator\EmailValidator
 
diff --git a/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php
new file mode 100644
index 0000000..09ea886
--- /dev/null
+++ b/core/lib/Drupal/Core/EventSubscriber/EarlyRenderingControllerWrapperSubscriber.php
@@ -0,0 +1,166 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber.
+ */
+
+namespace Drupal\Core\EventSubscriber;
+
+use Drupal\Core\Cache\CacheableDependencyInterface;
+use Drupal\Core\Cache\CacheableResponseInterface;
+use Drupal\Core\Controller\ControllerResolverInterface;
+use Drupal\Core\Render\AttachmentsInterface;
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Render\RenderContext;
+use Drupal\Core\Render\RendererInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * Subscriber that wraps controllers, to handle early rendering.
+ *
+ * When controllers call drupal_render() (RendererInterface::render()) outside
+ * of a render context, we call that "early rendering". Controllers should
+ * return only render arrays, but we cannot prevent controllers from doing early
+ * rendering. The problem with early rendering is that the bubbleable metadata
+ * (cacheability & attachments) are lost.
+ *
+ * This can lead to broken pages (missing assets), stale pages (missing cache
+ * tags causing a page not to be invalidated) or even security problems (missing
+ * cache contexts causing a cached page not to be varied sufficiently).
+ *
+ * This event subscriber wraps all controller executions in a closure that sets
+ * up a render context. Consequently, any early rendering will have their
+ * bubbleable metadata (assets & cacheability) stored on that render context.
+ *
+ * If the render context is empty, then the controller either did not do any
+ * rendering at all, or used the RendererInterface::renderRoot() or
+ * ::renderPlain() methods. In that case, no bubbleable metadata is lost.
+ *
+ * If the render context is not empty, then the controller did use
+ * drupal_render(), and bubbleable metadata was collected. This bubbleable
+ * metadata is then merged onto the render array.
+ *
+ * In other words: this just exists to ease the transition to Drupal 8: it
+ * allows controllers that return render arrays (the majority) to still do early
+ * rendering. But controllers that return responses are already expected to do
+ * the right thing: if early rendering is detected in such a case, an exception
+ * is thrown.
+ *
+ * @see \Drupal\Core\Render\RendererInterface
+ * @see \Drupal\Core\Render\Renderer
+ *
+ * @todo Remove in Drupal 9.0.0, by disallowing early rendering.
+ */
+class EarlyRenderingControllerWrapperSubscriber implements EventSubscriberInterface {
+
+  /**
+   * The controller resolver.
+   *
+   * @var \Drupal\Core\Controller\ControllerResolverInterface
+   */
+  protected $controllerResolver;
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs a new EarlyRenderingControllerWrapperSubscriber instance.
+   *
+   * @param \Drupal\Core\Controller\ControllerResolverInterface $controller_resolver
+   *   The controller resolver.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer.
+   */
+  public function __construct(ControllerResolverInterface $controller_resolver, RendererInterface $renderer) {
+    $this->controllerResolver = $controller_resolver;
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * Ensures bubbleable metadata from early rendering is not lost.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\FilterControllerEvent $event
+   *   The controller event.
+   */
+  public function onController(FilterControllerEvent $event) {
+    $controller = $event->getController();
+
+    // See \Symfony\Component\HttpKernel\HttpKernel::handleRaw().
+    $arguments = $this->controllerResolver->getArguments($event->getRequest(), $controller);
+
+    $event->setController(function() use ($controller, $arguments) {
+      return $this->wrapControllerExecutionInRenderContext($controller, $arguments);
+    });
+  }
+
+  /**
+   * Wraps a controller execution in a render context.
+   *
+   * @param callable $controller
+   *   The controller to execute.
+   * @param array $arguments
+   *   The arguments to pass to the controller.
+   *
+   * @return mixed
+   *   The return value of the controller.
+   *
+   * @throws \LogicException
+   *   When early rendering has occurred in a controller that returned a
+   *   Response or domain object that cares about attachments or cacheability.
+   *
+   * @see \Symfony\Component\HttpKernel\HttpKernel::handleRaw()
+   */
+  protected function wrapControllerExecutionInRenderContext($controller, array $arguments) {
+    $context = new RenderContext();
+
+    $response = $this->renderer->executeInRenderContext($context, function() use ($controller, $arguments) {
+      // Now call the actual controller, just like HttpKernel does.
+      return call_user_func_array($controller, $arguments);
+    });
+
+    // If early rendering happened, i.e. if code in the controller called
+    // drupal_render() outside of a render context, then the bubbleable metadata
+    // for that is stored in the current render context.
+    if (!$context->isEmpty()) {
+      // If a render array is returned by the controller, merge the "lost"
+      // bubbleable metadata.
+      if (is_array($response)) {
+        $early_rendering_bubbleable_metadata = $context->pop();
+        BubbleableMetadata::createFromRenderArray($response)
+          ->merge($early_rendering_bubbleable_metadata)
+          ->applyTo($response);
+      }
+      // If a Response or domain object is returned, and it cares about
+      // attachments or cacheability, then throw an exception: early rendering
+      // is not permitted in that case. It is the developer's responsibility
+      // to not use early rendering.
+      elseif (is_object($response) && ($response instanceof AttachmentsInterface || $response instanceof CacheableResponseInterface || $response instanceof CacheableDependencyInterface)) {
+        throw new \LogicException(sprintf('Early rendering is not permitted for controllers returning anything else than render arrays. Returned object class: %s.', get_class($response)));
+      }
+      else {
+        // A Response or domain object is returned that does not care about
+        // attachments nor cacheability. E.g. a RedirectResponse. It is safe to
+        // discard any early rendering metadata.
+      }
+    }
+
+    return $response;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[KernelEvents::CONTROLLER][] = ['onController'];
+
+    return $events;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
index cb987f4..10d2ce9 100644
--- a/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
+++ b/core/lib/Drupal/Core/Render/MainContent/HtmlRenderer.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Render\HtmlResponse;
 use Drupal\Core\Render\PageDisplayVariantSelectionEvent;
 use Drupal\Core\Render\RenderCacheInterface;
+use Drupal\Core\Render\RenderContext;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Render\RenderEvents;
 use Drupal\Core\Routing\RouteMatchInterface;
@@ -181,7 +182,9 @@ protected function prepare(array $main_content, Request $request, RouteMatchInte
       // ::renderResponse().
       // @todo Remove this once https://www.drupal.org/node/2359901 lands.
       if (!empty($main_content)) {
-        $this->renderer->render($main_content, FALSE);
+        $this->renderer->executeInRenderContext(new RenderContext(), function() use (&$main_content) {
+          return $this->renderer->render($main_content, FALSE);
+        });
         $main_content = $this->renderCache->getCacheableRenderArray($main_content) + [
           '#title' => isset($main_content['#title']) ? $main_content['#title'] : NULL
         ];
diff --git a/core/lib/Drupal/Core/Render/RenderContext.php b/core/lib/Drupal/Core/Render/RenderContext.php
new file mode 100644
index 0000000..e49c291
--- /dev/null
+++ b/core/lib/Drupal/Core/Render/RenderContext.php
@@ -0,0 +1,62 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\Render\RenderContext.
+ */
+
+namespace Drupal\Core\Render;
+
+/**
+ * The render context: a stack containing bubbleable rendering metadata.
+ *
+ * A stack of \Drupal\Core\Render\BubbleableMetadata objects.
+ *
+ * @see \Drupal\Core\Render\RendererInterface
+ * @see \Drupal\Core\Render\Renderer
+ * @see \Drupal\Core\Render\BubbleableMetadata
+ *
+ * @internal
+ */
+class RenderContext extends \SplStack {
+
+  /**
+   * Updates the current frame of the stack.
+   *
+   * @param array &$element
+   *   The element of the render array that has just been rendered. The stack
+   *   frame for this element will be updated with the bubbleable rendering
+   *   metadata of this element.
+   */
+  public function update(&$element) {
+    // The latest frame represents the bubbleable metadata for the subtree.
+    $frame = $this->pop();
+    // Update the frame, but also update the current element, to ensure it
+    // contains up-to-date information in case it gets render cached.
+    $updated_frame = BubbleableMetadata::createFromRenderArray($element)->merge($frame);
+    $updated_frame->applyTo($element);
+    $this->push($updated_frame);
+  }
+
+  /**
+   * Bubbles the stack.
+   *
+   * Whenever another level in the render array has been rendered, the stack
+   * must be bubbled, to merge its rendering metadata with that of the parent
+   * element.
+   */
+  public function bubble() {
+    // If there's only one frame on the stack, then this is the root call, and
+    // we can't bubble up further. ::renderRoot() will reset the stack, but we
+    // must not reset it here to allow users of ::executeInRenderContext() to
+    // access the stack directly.
+    if ($this->count() === 1) {
+      return;
+    }
+
+    // Merge the current and the parent stack frame.
+    $current = $this->pop();
+    $parent = $this->pop();
+    $this->push($current->merge($parent));
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Renderer.php b/core/lib/Drupal/Core/Render/Renderer.php
index 858501b..d98955d 100644
--- a/core/lib/Drupal/Core/Render/Renderer.php
+++ b/core/lib/Drupal/Core/Render/Renderer.php
@@ -58,11 +58,24 @@ class Renderer implements RendererInterface {
   protected $rendererConfig;
 
   /**
-   * The stack containing bubbleable rendering metadata.
+   * The render context.
    *
-   * @var \SplStack|null
+   * This must be static as long as some controllers rebuild the container
+   * during a request. This causes multiple renderer instances to co-exist
+   * simultaneously, render state getting lost, and therefore causing pages to
+   * fail to render correctly. As soon as it is guaranteed that during a request
+   * the same container is used, it no longer needs to be static.
+   *
+   * @var \Drupal\Core\Render\RenderContext|null
+   */
+  protected static $context;
+
+  /**
+   * Whether we're currently in a ::renderRoot() call.
+   *
+   * @var bool
    */
-  protected static $stack;
+  protected $isRenderingRoot = FALSE;
 
   /**
    * Constructs a new Renderer.
@@ -90,18 +103,29 @@ public function __construct(ControllerResolverInterface $controller_resolver, Th
    * {@inheritdoc}
    */
   public function renderRoot(&$elements) {
-    return $this->render($elements, TRUE);
+    // Disallow calling ::renderRoot() from within another ::renderRoot() call.
+    if ($this->isRenderingRoot) {
+      $this->isRenderingRoot = FALSE;
+      throw new \LogicException('A stray renderRoot() invocation is causing bubbling of attached assets to break.');
+    }
+
+    // Render in its own render context.
+    $this->isRenderingRoot = TRUE;
+    $output = $this->executeInRenderContext(new RenderContext(), function () use (&$elements) {
+      return $this->render($elements, TRUE);
+    });
+    $this->isRenderingRoot = FALSE;
+
+    return $output;
   }
 
   /**
    * {@inheritdoc}
    */
   public function renderPlain(&$elements) {
-    $current_stack = static::$stack;
-    $this->resetStack();
-    $output = $this->renderRoot($elements);
-    static::$stack = $current_stack;
-    return $output;
+    return $this->executeInRenderContext(new RenderContext(), function () use (&$elements) {
+      return $this->render($elements, TRUE);
+    });
   }
 
   /**
@@ -151,16 +175,17 @@ public function render(&$elements, $is_root_call = FALSE) {
     // possible that any of them throw an exception that will cause a different
     // page to be rendered (e.g. throwing
     // \Symfony\Component\HttpKernel\Exception\NotFoundHttpException will cause
-    // the 404 page to be rendered). That page might also use Renderer::render()
-    // but if exceptions aren't caught here, the stack will be left in an
-    // inconsistent state.
-    // Hence, catch all exceptions and reset the stack and re-throw them.
+    // the 404 page to be rendered). That page might also use
+    // Renderer::renderRoot() but if exceptions aren't caught here, it will be
+    // impossible to call Renderer::renderRoot() again.
+    // Hence, catch all exceptions, reset the isRenderingRoot property and
+    // re-throw exceptions.
     try {
       return $this->doRender($elements, $is_root_call);
     }
     catch (\Exception $e) {
-      // Reset stack and re-throw exception.
-      $this->resetStack();
+      // Mark the ::rootRender() call finished due to this exception & re-throw.
+      $this->isRenderingRoot = FALSE;
       throw $e;
     }
   }
@@ -200,10 +225,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
       return '';
     }
 
-    if (!isset(static::$stack)) {
-      static::$stack = new \SplStack();
+    if (!isset(static::$context)) {
+      throw new \LogicException("Render context is empty, because render() was called outside of a renderRoot() or renderPlain() call. Use renderPlain()/renderRoot() or #lazy_builder/#pre_render instead.");
     }
-    static::$stack->push(new BubbleableMetadata());
+    static::$context->push(new BubbleableMetadata());
 
     // Set the bubbleable rendering metadata that has configurable defaults, if:
     // - this is the root call, to ensure that the final render array definitely
@@ -244,10 +269,10 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
         }
         // The render cache item contains all the bubbleable rendering metadata
         // for the subtree.
-        $this->updateStack($elements);
+        static::$context->update($elements);
         // Render cache hit, so rendering is finished, all necessary info
         // collected!
-        $this->bubbleStack();
+        static::$context->bubble();
         return $elements['#markup'];
       }
     }
@@ -345,9 +370,9 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     if (!empty($elements['#printed'])) {
       // The #printed element contains all the bubbleable rendering metadata for
       // the subtree.
-      $this->updateStack($elements);
+      static::$context->update($elements);
       // #printed, so rendering is finished, all necessary info collected!
-      $this->bubbleStack();
+      static::$context->bubble();
       return '';
     }
 
@@ -473,8 +498,8 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
 
     $elements['#markup'] = $prefix . $elements['#children'] . $suffix;
 
-    // We've rendered this element (and its subtree!), now update the stack.
-    $this->updateStack($elements);
+    // We've rendered this element (and its subtree!), now update the context.
+    static::$context->update($elements);
 
     // Cache the processed element if both $pre_bubbling_elements and $elements
     // have the metadata necessary to generate a cache ID.
@@ -496,13 +521,14 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
     // that is handled earlier in Renderer::render().
     if ($is_root_call) {
       $this->replacePlaceholders($elements);
-      if (static::$stack->count() !== 1) {
+      // @todo remove as part of https://www.drupal.org/node/2511330.
+      if (static::$context->count() !== 1) {
         throw new \LogicException('A stray drupal_render() invocation with $is_root_call = TRUE is causing bubbling of attached assets to break.');
       }
     }
 
     // Rendering is finished, all necessary info collected!
-    $this->bubbleStack();
+    static::$context->bubble();
 
     $elements['#printed'] = TRUE;
     $elements['#markup'] = SafeMarkup::set($elements['#markup']);
@@ -510,52 +536,24 @@ protected function doRender(&$elements, $is_root_call = FALSE) {
   }
 
   /**
-   * Resets the renderer service's internal stack (used for bubbling metadata).
-   *
-   * Only necessary in very rare/advanced situations, such as when rendering an
-   * error page if an exception occurred *during* rendering.
-   */
-  protected function resetStack() {
-    static::$stack = NULL;
-  }
-
-  /**
-   * Updates the stack.
-   *
-   * @param array &$element
-   *   The element of the render array that has just been rendered. The stack
-   *   frame for this element will be updated with the bubbleable rendering
-   *   metadata of this element.
-   */
-  protected function updateStack(&$element) {
-    // The latest frame represents the bubbleable metadata for the subtree.
-    $frame = static::$stack->pop();
-    // Update the frame, but also update the current element, to ensure it
-    // contains up-to-date information in case it gets render cached.
-    $updated_frame = BubbleableMetadata::createFromRenderArray($element)->merge($frame);
-    $updated_frame->applyTo($element);
-    static::$stack->push($updated_frame);
-  }
-
-  /**
-   * Bubbles the stack.
-   *
-   * Whenever another level in the render array has been rendered, the stack
-   * must be bubbled, to merge its rendering metadata with that of the parent
-   * element.
+   * {@inheritdoc}
    */
-  protected function bubbleStack() {
-    // If there's only one frame on the stack, then this is the root call, and
-    // we can't bubble up further. Reset the stack for the next root call.
-    if (static::$stack->count() === 1) {
-      $this->resetStack();
-      return;
+  public function executeInRenderContext(RenderContext $context, callable $callable) {
+    // Store the current render context.
+    $current_context = static::$context;
+
+    // Set the provided context and call the callable, it will use that context.
+    static::$context = $context;
+    $result = $callable();
+    // @todo Convert to an assertion in https://www.drupal.org/node/2408013
+    if (static::$context->count() > 1) {
+      throw new \LogicException('Bubbling failed.');
     }
 
-    // Merge the current and the parent stack frame.
-    $current = static::$stack->pop();
-    $parent = static::$stack->pop();
-    static::$stack->push($current->merge($parent));
+    // Restore the original render context.
+    static::$context = $current_context;
+
+    return $result;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Render/RendererInterface.php b/core/lib/Drupal/Core/Render/RendererInterface.php
index 5d59d4d..b55966f 100644
--- a/core/lib/Drupal/Core/Render/RendererInterface.php
+++ b/core/lib/Drupal/Core/Render/RendererInterface.php
@@ -22,6 +22,8 @@
    * - system internals that are responsible for rendering the final HTML
    * - render arrays for non-HTML responses, such as feeds
    *
+   * (Cannot be executed within another render context.)
+   *
    * @param array $elements
    *   The structured array describing the data to be rendered.
    *
@@ -29,6 +31,9 @@
    *   The rendered HTML.
    *
    * @see ::render()
+   *
+   * @throws \LogicException
+   *   When called from inside another renderRoot() call.
    */
   public function renderRoot(&$elements);
 
@@ -45,9 +50,11 @@ public function renderRoot(&$elements);
    * ::renderRoot() call, but that is generally highly problematic (and hence an
    * exception is thrown when a ::renderRoot() call happens within another
    * ::renderRoot() call). However, in this case, we only care about the output,
-   * not about the bubbling. Hence this uses a separate render stack, to not
+   * not about the bubbling. Hence this uses a separate render context, to not
    * affect the parent ::renderRoot() call.
    *
+   * (Can be executed within another render context: it runs in isolation.)
+   *
    * @param array $elements
    *   The structured array describing the data to be rendered.
    *
@@ -88,8 +95,8 @@ public function renderPlain(&$elements);
    *   or configuration that can affect that rendering changes.
    * - Placeholders, with associated self-contained placeholder render arrays,
    *   for executing code to handle dynamic requirements that cannot be cached.
-   * A stack of \Drupal\Core\Render\BubbleableMetadata objects can be used to
-   * perform this bubbling.
+   * A render context (\Drupal\Core\Render\RenderContext) can be used to perform
+   * bubbling; it is a stack of \Drupal\Core\Render\BubbleableMetadata objects.
    *
    * Additionally, whether retrieving from cache or not, it is important to
    * know all of the assets (CSS and JavaScript) required by the rendered HTML,
@@ -103,9 +110,9 @@ public function renderPlain(&$elements);
    *   - If this element has already been printed (#printed = TRUE) or the user
    *     does not have access to it (#access = FALSE), then an empty string is
    *     returned.
-   *   - If no stack data structure has been created yet, it is done now. Next,
+   *   - If no render context is set yet, an exception is thrown. Otherwise,
    *     an empty \Drupal\Core\Render\BubbleableMetadata is pushed onto the
-   *     stack.
+   *     render context.
    *   - If this element has #cache defined then the cached markup for this
    *     element will be returned if it exists in Renderer::render()'s cache. To
    *     use Renderer::render() caching, set the element's #cache property to an
@@ -299,13 +306,12 @@ public function renderPlain(&$elements);
    *   The rendered HTML.
    *
    * @throws \LogicException
-   *   If a root call to ::render() does not result in an empty stack, this
-   *   indicates an erroneous ::render() root call (a root call within a
-   *   root call, which makes no sense). Therefore, a logic exception is thrown.
+   *   When called outside of a render context. (i.e. outside of a renderRoot(),
+   *   renderPlain() or executeInRenderContext() call.)
    * @throws \Exception
-   *   If a #pre_render callback throws an exception, it is caught to reset the
-   *   stack used for bubbling rendering metadata, and then the exception is re-
-   *   thrown.
+   *   If a #pre_render callback throws an exception, it is caught to mark the
+   *   renderer as no longer being in a root render call, if any. Then the
+   *   exception is rethrown.
    *
    * @see \Drupal\Core\Render\ElementInfoManagerInterface::getInfo()
    * @see \Drupal\Core\Theme\ThemeManagerInterface::render()
@@ -316,6 +322,37 @@ public function renderPlain(&$elements);
   public function render(&$elements, $is_root_call = FALSE);
 
   /**
+   * Executes a callable within a render context.
+   *
+   * Only for very advanced use cases. Prefer using ::renderRoot() and
+   * ::renderPlain() instead.
+   *
+   * All rendering must happen within a render context. Within a render context,
+   * all bubbleable metadata is bubbled and hence tracked. Outside of a render
+   * context, it would be lost. This could lead to missing assets, incorrect
+   * cache variations (and thus security issues), insufficient cache
+   * invalidations, and so on.
+   *
+   * Any and all rendering must therefore happen within a render context, and it
+   * is this method that provides that.
+   *
+   * @see \Drupal\Core\Render\BubbleableMetadata
+   *
+   * @param \Drupal\Core\Render\RenderContext $context
+   *   The render context to execute the callable within.
+   * @param callable $callable
+   *   The callable to execute.
+   * @return mixed
+   *   The callable's return value.
+   *
+   * @see \Drupal\Core\Render\RenderContext
+   *
+   * @throws \LogicException
+   *   In case bubbling has failed, can only happen in case of broken code.
+   */
+  public function executeInRenderContext(RenderContext $context, callable $callable);
+
+  /**
    * Merges the bubbleable rendering metadata o/t 2nd render array with the 1st.
    *
    * @param array $a
diff --git a/core/modules/aggregator/src/Tests/Views/IntegrationTest.php b/core/modules/aggregator/src/Tests/Views/IntegrationTest.php
index 1ec27b9..9444808 100644
--- a/core/modules/aggregator/src/Tests/Views/IntegrationTest.php
+++ b/core/modules/aggregator/src/Tests/Views/IntegrationTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\aggregator\Tests\Views;
 
+use Drupal\Core\Render\RenderContext;
 use Drupal\Core\Url;
 use Drupal\views\Views;
 use Drupal\views\Tests\ViewTestData;
@@ -66,6 +67,9 @@ protected function setUp() {
    * Tests basic aggregator_item view.
    */
   public function testAggregatorItemView() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $feed = $this->feedStorage->create(array(
       'title' => $this->randomMachineName(),
       'url' => 'https://www.drupal.org/',
@@ -112,13 +116,22 @@ public function testAggregatorItemView() {
     foreach ($view->result as $row) {
       $iid = $view->field['iid']->getValue($row);
       $expected_link = \Drupal::l($items[$iid]->getTitle(), Url::fromUri($items[$iid]->getLink(), ['absolute' => TRUE]));
-      $this->assertEqual($view->field['title']->advancedRender($row), $expected_link, 'Ensure the right link is generated');
+      $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view, $row) {
+        return $view->field['title']->advancedRender($row);
+      });
+      $this->assertEqual($output, $expected_link, 'Ensure the right link is generated');
 
       $expected_author = aggregator_filter_xss($items[$iid]->getAuthor());
-      $this->assertEqual($view->field['author']->advancedRender($row), $expected_author, 'Ensure the author got filtered');
+      $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view, $row) {
+        return $view->field['author']->advancedRender($row);
+      });
+      $this->assertEqual($output, $expected_author, 'Ensure the author got filtered');
 
       $expected_description = aggregator_filter_xss($items[$iid]->getDescription());
-      $this->assertEqual($view->field['description']->advancedRender($row), $expected_description, 'Ensure the author got filtered');
+      $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view, $row) {
+        return $view->field['description']->advancedRender($row);
+      });
+      $this->assertEqual($output, $expected_description, 'Ensure the author got filtered');
     }
   }
 
diff --git a/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php b/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php
index ab40e6f..2fcf468 100644
--- a/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php
+++ b/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\comment\Tests\Views;
 
 use Drupal\comment\Entity\Comment;
+use Drupal\Core\Render\RenderContext;
 use Drupal\Core\Session\AnonymousUserSession;
 use Drupal\user\RoleInterface;
 use Drupal\views\Views;
@@ -58,6 +59,8 @@ protected function setUp() {
    * Test comment field name.
    */
   public function testCommentFieldName() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
     $view = Views::getView('test_comment_field_name');
     $this->executeView($view);
 
@@ -85,8 +88,14 @@ public function testCommentFieldName() {
     $view = Views::getView('test_comment_field_name');
     $this->executeView($view);
     // Test that data rendered.
-    $this->assertIdentical($this->comment->getFieldName(), $view->field['field_name']->advancedRender($view->result[0]));
-    $this->assertIdentical($this->customComment->getFieldName(), $view->field['field_name']->advancedRender($view->result[1]));
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['field_name']->advancedRender($view->result[0]);
+    });
+    $this->assertIdentical($this->comment->getFieldName(), $output);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['field_name']->advancedRender($view->result[1]);
+    });
+    $this->assertIdentical($this->customComment->getFieldName(), $output);
   }
 
 }
diff --git a/core/modules/file/src/Tests/Views/ExtensionViewsFieldTest.php b/core/modules/file/src/Tests/Views/ExtensionViewsFieldTest.php
index 75623f4..f7d2b3f 100644
--- a/core/modules/file/src/Tests/Views/ExtensionViewsFieldTest.php
+++ b/core/modules/file/src/Tests/Views/ExtensionViewsFieldTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\file\Tests\Views;
 
+use Drupal\Core\Render\RenderContext;
 use Drupal\file\Entity\File;
 use Drupal\views\Views;
 use Drupal\views\Tests\ViewUnitTestBase;
@@ -69,17 +70,22 @@ protected function setUp() {
    * Tests file extension views field handler extension_detect_tar option.
    */
   public function testFileExtensionTarOption() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $view = Views::getView('file_extension_view');
     $view->setDisplay();
     $this->executeView($view);
 
     // Test without the tar option.
-    $this->assertEqual($view->field['extension']->advancedRender($view->result[0]), 'png');
-    $this->assertEqual($view->field['extension']->advancedRender($view->result[1]), 'tar');
-    $this->assertEqual($view->field['extension']->advancedRender($view->result[2]), 'gz');
-    $this->assertEqual($view->field['extension']->advancedRender($view->result[3]), '');
-    // Test with the tar option.
+    $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      $this->assertEqual($view->field['extension']->advancedRender($view->result[0]), 'png');
+      $this->assertEqual($view->field['extension']->advancedRender($view->result[1]), 'tar');
+      $this->assertEqual($view->field['extension']->advancedRender($view->result[2]), 'gz');
+      $this->assertEqual($view->field['extension']->advancedRender($view->result[3]), '');
+    });
 
+    // Test with the tar option.
     $view = Views::getView('file_extension_view');
     $view->setDisplay();
     $view->initHandlers();
@@ -87,10 +93,12 @@ public function testFileExtensionTarOption() {
     $view->field['extension']->options['settings']['extension_detect_tar'] = TRUE;
     $this->executeView($view);
 
-    $this->assertEqual($view->field['extension']->advancedRender($view->result[0]), 'png');
-    $this->assertEqual($view->field['extension']->advancedRender($view->result[1]), 'tar');
-    $this->assertEqual($view->field['extension']->advancedRender($view->result[2]), 'tar.gz');
-    $this->assertEqual($view->field['extension']->advancedRender($view->result[3]), '');
+    $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      $this->assertEqual($view->field['extension']->advancedRender($view->result[0]), 'png');
+      $this->assertEqual($view->field['extension']->advancedRender($view->result[1]), 'tar');
+      $this->assertEqual($view->field['extension']->advancedRender($view->result[2]), 'tar.gz');
+      $this->assertEqual($view->field['extension']->advancedRender($view->result[3]), '');
+    });
   }
 
 }
diff --git a/core/modules/filter/src/Tests/FilterUnitTest.php b/core/modules/filter/src/Tests/FilterUnitTest.php
index 7fdf8f9..8e6c5db 100644
--- a/core/modules/filter/src/Tests/FilterUnitTest.php
+++ b/core/modules/filter/src/Tests/FilterUnitTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Core\Render\RenderContext;
 use Drupal\editor\EditorXssFilter\Standard;
 use Drupal\filter\Entity\FilterFormat;
 use Drupal\filter\FilterPluginCollection;
@@ -101,10 +102,14 @@ function testAlignFilter() {
    * Tests the caption filter.
    */
   function testCaptionFilter() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
     $filter = $this->filters['filter_caption'];
 
-    $test = function($input) use ($filter) {
-      return $filter->process($input, 'und');
+    $test = function($input) use ($filter, $renderer) {
+      return $renderer->executeInRenderContext(new RenderContext(), function () use ($input, $filter) {
+        return $filter->process($input, 'und');
+      });
     };
 
     $attached_library = array(
@@ -190,12 +195,14 @@ function testCaptionFilter() {
         'filter_html_nofollow' => 0,
       )
     ));
-    $test_with_html_filter = function ($input) use ($filter, $html_filter) {
-      // 1. Apply HTML filter's processing step.
-      $output = $html_filter->process($input, 'und');
-      // 2. Apply caption filter's processing step.
-      $output = $filter->process($output, 'und');
-      return $output->getProcessedText();
+    $test_with_html_filter = function ($input) use ($filter, $html_filter, $renderer) {
+      return $renderer->executeInRenderContext(new RenderContext(), function () use ($input, $filter, $html_filter) {
+        // 1. Apply HTML filter's processing step.
+        $output = $html_filter->process($input, 'und');
+        // 2. Apply caption filter's processing step.
+        $output = $filter->process($output, 'und');
+        return $output->getProcessedText();
+      });
     };
     // Editor XSS filter.
     $test_editor_xss_filter = function ($input) {
@@ -252,11 +259,15 @@ function testCaptionFilter() {
    * Tests the combination of the align and caption filters.
    */
   function testAlignAndCaptionFilters() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
     $align_filter = $this->filters['filter_align'];
     $caption_filter = $this->filters['filter_caption'];
 
-    $test = function($input) use ($align_filter, $caption_filter) {
-      return $caption_filter->process($align_filter->process($input, 'und'), 'und');
+    $test = function($input) use ($align_filter, $caption_filter, $renderer) {
+      return $renderer->executeInRenderContext(new RenderContext(), function () use ($input, $align_filter, $caption_filter) {
+        return $caption_filter->process($align_filter->process($input, 'und'), 'und');
+      });
     };
 
     $attached_library = array(
diff --git a/core/modules/rest/src/Plugin/views/display/RestExport.php b/core/modules/rest/src/Plugin/views/display/RestExport.php
index 247b60c..ae5ac7e 100644
--- a/core/modules/rest/src/Plugin/views/display/RestExport.php
+++ b/core/modules/rest/src/Plugin/views/display/RestExport.php
@@ -10,9 +10,14 @@
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Cache\CacheableResponse;
+use Drupal\Core\Render\RenderContext;
+use Drupal\Core\Render\RendererInterface;
+use Drupal\Core\Routing\RouteProviderInterface;
+use Drupal\Core\State\StateInterface;
 use Drupal\views\Plugin\views\display\ResponseDisplayPluginInterface;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\PathPluginBase;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\Routing\RouteCollection;
 
 /**
@@ -71,6 +76,48 @@ class RestExport extends PathPluginBase implements ResponseDisplayPluginInterfac
   protected $mimeType;
 
   /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs a RestExport object.
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param mixed $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
+   *   The route provider.
+   * @param \Drupal\Core\State\StateInterface $state
+   *   The state key value store.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, RendererInterface $renderer) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider, $state);
+
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('router.route_provider'),
+      $container->get('state'),
+      $container->get('renderer')
+    );
+  }
+  /**
    * {@inheritdoc}
    */
   public function initDisplay(ViewExecutable $view, array &$display, array &$options = NULL) {
@@ -263,7 +310,9 @@ public function execute() {
    */
   public function render() {
     $build = array();
-    $build['#markup'] = $this->view->style_plugin->render();
+    $build['#markup'] = $this->renderer->executeInRenderContext(new RenderContext(), function() {
+      return $this->view->style_plugin->render();
+    });
 
     $this->view->element['#content_type'] = $this->getMimeType();
     $this->view->element['#cache_properties'][] = '#content_type';
diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
index b043ea9..3cce705 100644
--- a/core/modules/simpletest/src/AssertContentTrait.php
+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\Xss;
+use Drupal\Core\Render\RenderContext;
 use Symfony\Component\CssSelector\CssSelector;
 
 /**
@@ -808,7 +809,12 @@ protected function assertNoTitle($title, $message = '', $group = 'Other') {
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertThemeOutput($callback, array $variables = array(), $expected = '', $message = '', $group = 'Other') {
-    $output = \Drupal::theme()->render($callback, $variables);
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
+    $output = $renderer->executeInRenderContext(new RenderContext(), function() use ($callback, $variables) {
+      return \Drupal::theme()->render($callback, $variables);
+    });
     $this->verbose(
       '<hr />' . 'Result:' . '<pre>' . SafeMarkup::checkPlain(var_export($output, TRUE)) . '</pre>'
       . '<hr />' . 'Expected:' . '<pre>' . SafeMarkup::checkPlain(var_export($expected, TRUE)) . '</pre>'
diff --git a/core/modules/system/src/Tests/Common/EarlyRenderingControllerTest.php b/core/modules/system/src/Tests/Common/EarlyRenderingControllerTest.php
new file mode 100644
index 0000000..27bc664
--- /dev/null
+++ b/core/modules/system/src/Tests/Common/EarlyRenderingControllerTest.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Common\EarlyRenderingControllerTest.
+ */
+
+namespace Drupal\system\Tests\Common;
+
+use Drupal\Core\Render\Element;
+use Drupal\Core\Url;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Verifies that bubbleable metadata of early rendering is not lost.
+ *
+ * @group Common
+ */
+class EarlyRenderingControllerTest extends WebTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $dumpHeaders = TRUE;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['system', 'early_rendering_controller_test'];
+
+  /**
+   * Tests theme preprocess functions being able to attach assets.
+   */
+  function testEarlyRendering() {
+    // Render array: non-early & early.
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.render_array'));
+    $this->assertResponse(200, 'Hello world!');
+    $this->assertCacheTag('foo');
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.render_array.early'));
+    $this->assertResponse(200, 'Hello world!');
+    $this->assertCacheTag('foo');
+
+    // Basic Response object: non-early & early.
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.response'));
+    $this->assertResponse(200, 'Hello world!');
+    $this->assertNoCacheTag('foo');
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.response.early'));
+    $this->assertResponse(200, 'Hello world!');
+    $this->assertNoCacheTag('foo');
+
+    // Response object with attachments: non-early & early.
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.response-with-attachments'));
+    $this->assertResponse(200, 'Hello world!');
+    $this->assertNoCacheTag('foo');
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.response-with-attachments.early'));
+    $this->assertResponse(500, 'Early rendering is not permitted for controllers returning anything else than render arrays. Returned object class: Drupal\early_rendering_controller_test\AttachmentsTestResponse.');
+
+    // Cacheable Response object: non-early & early.
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-response'));
+    $this->assertResponse(200, 'Hello world!');
+    $this->assertNoCacheTag('foo');
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-response.early'));
+    $this->assertResponse(500, 'Early rendering is not permitted for controllers returning anything else than render arrays. Returned object class: Drupal\early_rendering_controller_test\AttachmentsTestResponse.');
+
+    // Basic domain object: non-early & early.
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object'));
+    $this->assertResponse(200, 'TestDomainObject');
+    $this->assertNoCacheTag('foo');
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object.early'));
+    $this->assertResponse(200, 'TestDomainObject');
+    $this->assertNoCacheTag('foo');
+
+    // Basic domain object with attachments: non-early & early.
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object-with-attachments'));
+    $this->assertResponse(200, 'AttachmentsTestDomainObject');
+    $this->assertNoCacheTag('foo');
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.domain-object-with-attachments.early'));
+    $this->assertResponse(500, 'Early rendering is not permitted for controllers returning anything else than render arrays. Returned object class: Drupal\early_rendering_controller_test\AttachmentsTestDomainObject.');
+
+    // Cacheable Response object: non-early & early.
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-domain-object'));
+    $this->assertResponse(200, 'CacheableTestDomainObject');
+    $this->assertNoCacheTag('foo');
+    $this->drupalGet(Url::fromRoute('early_rendering_controller_test.cacheable-domain-object.early'));
+    $this->assertResponse(500, 'Early rendering is not permitted for controllers returning anything else than render arrays. Returned object class: Drupal\early_rendering_controller_test\CacheableTestDomainObject.');
+
+    // The exceptions are expected. Do not interpret them as a test failure.
+    // Not using File API; a potential error must trigger a PHP warning.
+    unlink(\Drupal::root() . '/' . $this->siteDirectory . '/error.log');
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Common/UrlTest.php b/core/modules/system/src/Tests/Common/UrlTest.php
index 74d9129..1a26232 100644
--- a/core/modules/system/src/Tests/Common/UrlTest.php
+++ b/core/modules/system/src/Tests/Common/UrlTest.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Render\RenderContext;
 use Drupal\Core\Url;
 use Drupal\simpletest\WebTestBase;
 
@@ -168,9 +169,11 @@ function testLinkRenderArrayText() {
     $l = \Drupal::l('foo', Url::fromUri('https://www.drupal.org'));
 
     // Test a renderable array passed to _l().
-    $renderable_text = array('#markup' => 'foo');
-    $l_renderable_text = \Drupal::l($renderable_text, Url::fromUri('https://www.drupal.org'));
-    $this->assertEqual($l_renderable_text, $l);
+    $renderer->executeInRenderContext(new RenderContext(), function() use ($renderer, $l) {
+      $renderable_text = array('#markup' => 'foo');
+      $l_renderable_text = \Drupal::l($renderable_text, Url::fromUri('https://www.drupal.org'));
+      $this->assertEqual($l_renderable_text, $l);
+    });
 
     // Test a themed link with plain text 'text'.
     $type_link_plain_array = array(
diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.info.yml b/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.info.yml
new file mode 100644
index 0000000..44ab452
--- /dev/null
+++ b/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.info.yml
@@ -0,0 +1,6 @@
+name: 'Early rendering controller test'
+type: module
+description: 'Support module for EarlyRenderingControllerTest.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.routing.yml b/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.routing.yml
new file mode 100644
index 0000000..4e050e4
--- /dev/null
+++ b/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.routing.yml
@@ -0,0 +1,97 @@
+# Controller returning a render array.
+early_rendering_controller_test.render_array:
+  path: '/early-rendering-controller-test/render-array'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::renderArray'
+  requirements:
+    _access: 'TRUE'
+early_rendering_controller_test.render_array.early:
+  path: '/early-rendering-controller-test/render-array/early'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::renderArrayEarly'
+  requirements:
+    _access: 'TRUE'
+
+# Controller returning a basic Response object.
+early_rendering_controller_test.response:
+  path: '/early-rendering-controller-test/response'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::response'
+  requirements:
+    _access: 'TRUE'
+early_rendering_controller_test.response.early:
+  path: '/early-rendering-controller-test/response/early'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::responseEarly'
+  requirements:
+    _access: 'TRUE'
+
+# Controller returning a Response object with attachments.
+early_rendering_controller_test.response-with-attachments:
+  path: '/early-rendering-controller-test/response-with-attachments'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::responseWithAttachments'
+  requirements:
+    _access: 'TRUE'
+early_rendering_controller_test.response-with-attachments.early:
+  path: '/early-rendering-controller-test/response-with-attachments/early'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::responseWithAttachmentsEarly'
+  requirements:
+    _access: 'TRUE'
+
+# Controller returning a cacheable Response object.
+early_rendering_controller_test.cacheable-response:
+  path: '/early-rendering-controller-test/cacheable-response'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableResponse'
+  requirements:
+    _access: 'TRUE'
+early_rendering_controller_test.cacheable-response.early:
+  path: '/early-rendering-controller-test/cacheable-response/early'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableResponseEarly'
+  requirements:
+    _access: 'TRUE'
+
+# Controller returning a basic domain object.
+early_rendering_controller_test.domain-object:
+  path: '/early-rendering-controller-test/domain-object'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObject'
+  requirements:
+    _access: 'TRUE'
+early_rendering_controller_test.domain-object.early:
+  path: '/early-rendering-controller-test/domain-object/early'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObjectEarly'
+  requirements:
+    _access: 'TRUE'
+
+# Controller returning a domain object with attachments.
+early_rendering_controller_test.domain-object-with-attachments:
+  path: '/early-rendering-controller-test/domain-object-with-attachments'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObjectWithAttachments'
+  requirements:
+    _access: 'TRUE'
+early_rendering_controller_test.domain-object-with-attachments.early:
+  path: '/early-rendering-controller-test/domain-object-with-attachments/early'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::domainObjectWithAttachmentsEarly'
+  requirements:
+    _access: 'TRUE'
+
+# Controller returning a cacheable domain object.
+early_rendering_controller_test.cacheable-domain-object:
+  path: '/early-rendering-controller-test/cacheable-domain-object'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableDomainObject'
+  requirements:
+    _access: 'TRUE'
+early_rendering_controller_test.cacheable-domain-object.early:
+  path: '/early-rendering-controller-test/cacheable-domain-object/early'
+  defaults:
+    _controller: '\Drupal\early_rendering_controller_test\EarlyRenderingTestController::cacheableDomainObjectEarly'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.services.yml b/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.services.yml
new file mode 100644
index 0000000..8c62f1a
--- /dev/null
+++ b/core/modules/system/tests/modules/early_rendering_controller_test/early_rendering_controller_test.services.yml
@@ -0,0 +1,5 @@
+services:
+  test_domain_object.view_subscriber:
+    class: Drupal\early_rendering_controller_test\TestDomainObjectViewSubscriber
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/src/AttachmentsTestDomainObject.php b/core/modules/system/tests/modules/early_rendering_controller_test/src/AttachmentsTestDomainObject.php
new file mode 100644
index 0000000..e6b6a4f
--- /dev/null
+++ b/core/modules/system/tests/modules/early_rendering_controller_test/src/AttachmentsTestDomainObject.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\early_rendering_controller_test\AttachmentsTestDomainObject.
+ */
+
+namespace Drupal\early_rendering_controller_test;
+
+use Drupal\Core\Render\AttachmentsInterface;
+use Drupal\Core\Render\AttachmentsTrait;
+
+class AttachmentsTestDomainObject extends TestDomainObject implements AttachmentsInterface {
+
+  use AttachmentsTrait;
+
+}
diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/src/AttachmentsTestResponse.php b/core/modules/system/tests/modules/early_rendering_controller_test/src/AttachmentsTestResponse.php
new file mode 100644
index 0000000..2b81f02
--- /dev/null
+++ b/core/modules/system/tests/modules/early_rendering_controller_test/src/AttachmentsTestResponse.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\early_rendering_controller_test\AttachmentsTestResponse.
+ */
+
+namespace Drupal\early_rendering_controller_test;
+
+use Drupal\Core\Render\AttachmentsInterface;
+use Drupal\Core\Render\AttachmentsTrait;
+use Symfony\Component\HttpFoundation\Response;
+
+class AttachmentsTestResponse extends Response implements AttachmentsInterface {
+
+  use AttachmentsTrait;
+
+}
diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/src/CacheableTestDomainObject.php b/core/modules/system/tests/modules/early_rendering_controller_test/src/CacheableTestDomainObject.php
new file mode 100644
index 0000000..4df64e2
--- /dev/null
+++ b/core/modules/system/tests/modules/early_rendering_controller_test/src/CacheableTestDomainObject.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\early_rendering_controller_test\AttachmentsTestDomainObject.
+ */
+
+namespace Drupal\early_rendering_controller_test;
+
+use Drupal\Core\Cache\CacheableDependencyInterface;
+
+class CacheableTestDomainObject extends TestDomainObject implements CacheableDependencyInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheContexts() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheTags() {
+    return [];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCacheMaxAge() {
+    return 0;
+  }
+
+}
diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/src/CacheableTestResponse.php b/core/modules/system/tests/modules/early_rendering_controller_test/src/CacheableTestResponse.php
new file mode 100644
index 0000000..06bf3cc
--- /dev/null
+++ b/core/modules/system/tests/modules/early_rendering_controller_test/src/CacheableTestResponse.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\early_rendering_controller_test\CacheableTestResponse.
+ */
+
+namespace Drupal\early_rendering_controller_test;
+
+use Drupal\Core\Cache\CacheableResponseInterface;
+use Drupal\Core\Cache\CacheableResponseTrait;
+use Symfony\Component\HttpFoundation\Response;
+
+class CacheableTestResponse extends Response implements CacheableResponseInterface {
+
+  use CacheableResponseTrait;
+
+}
diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/src/EarlyRenderingTestController.php b/core/modules/system/tests/modules/early_rendering_controller_test/src/EarlyRenderingTestController.php
new file mode 100644
index 0000000..9c27f77
--- /dev/null
+++ b/core/modules/system/tests/modules/early_rendering_controller_test/src/EarlyRenderingTestController.php
@@ -0,0 +1,128 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\early_rendering_controller_test\EarlyRenderingTestController.
+ */
+
+namespace Drupal\early_rendering_controller_test;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Render\RendererInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Response;
+
+/**
+ * Controller routines for early_rendering_test routes.
+ */
+class EarlyRenderingTestController extends ControllerBase  {
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * Constructs a EarlyRenderingTestController.
+   *
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   */
+  public function __construct(RendererInterface $renderer) {
+    $this->renderer = $renderer;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('renderer')
+    );
+  }
+
+  protected function earlyRenderContent() {
+    return [
+      '#markup' => 'Hello world!',
+      '#cache' => [
+        'tags' => [
+          'foo',
+        ],
+      ],
+    ];
+  }
+
+  public function renderArray() {
+    return [
+      '#pre_render' => [function () {
+        $elements = $this->earlyRenderContent();
+        return $elements;
+      }],
+    ];
+  }
+
+  public function renderArrayEarly() {
+    $render_array = $this->earlyRenderContent();
+    return [
+      '#markup' => $this->renderer->render($render_array),
+    ];
+  }
+
+  public function response() {
+    return new Response('Hello world!');
+  }
+
+  public function responseEarly() {
+    return new Response($this->renderer->render($render_array));
+  }
+
+  public function responseWithAttachments() {
+    return new AttachmentsTestResponse('Hello world!');
+  }
+
+  public function responseWithAttachmentsEarly() {
+    $render_array = $this->earlyRenderContent();
+    return new AttachmentsTestResponse($this->renderer->render($render_array));
+  }
+
+  public function cacheableResponse() {
+    return new CacheableTestResponse('Hello world!');
+  }
+
+  public function cacheableResponseEarly() {
+    $render_array = $this->earlyRenderContent();
+    return new CacheableTestResponse($this->renderer->render($render_array));
+  }
+
+  public function domainObject() {
+    return new TestDomainObject();
+  }
+
+  public function domainObjectEarly() {
+    $render_array = $this->earlyRenderContent();
+    $this->renderer->render($render_array);
+    return new TestDomainObject();
+  }
+
+  public function domainObjectWithAttachments() {
+    return new AttachmentsTestDomainObject();
+  }
+
+  public function domainObjectWithAttachmentsEarly() {
+    $render_array = $this->earlyRenderContent();
+    $this->renderer->render($render_array);
+    return new AttachmentsTestDomainObject();
+  }
+
+  public function cacheableDomainObject() {
+    return new CacheableTestDomainObject();
+  }
+
+  public function cacheableDomainObjectEarly() {
+    $render_array = $this->earlyRenderContent();
+    $this->renderer->render($render_array);
+    return new CacheableTestDomainObject();
+  }
+
+}
diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/src/TestDomainObject.php b/core/modules/system/tests/modules/early_rendering_controller_test/src/TestDomainObject.php
new file mode 100644
index 0000000..b2f8f8b
--- /dev/null
+++ b/core/modules/system/tests/modules/early_rendering_controller_test/src/TestDomainObject.php
@@ -0,0 +1,10 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\early_rendering_controller_test\TestDomainObject.
+ */
+
+namespace Drupal\early_rendering_controller_test;
+
+class TestDomainObject { }
diff --git a/core/modules/system/tests/modules/early_rendering_controller_test/src/TestDomainObjectViewSubscriber.php b/core/modules/system/tests/modules/early_rendering_controller_test/src/TestDomainObjectViewSubscriber.php
new file mode 100644
index 0000000..9c68c3e
--- /dev/null
+++ b/core/modules/system/tests/modules/early_rendering_controller_test/src/TestDomainObjectViewSubscriber.php
@@ -0,0 +1,51 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\early_rendering_controller_test\TestDomainObjectViewSubscriber.
+ */
+
+namespace Drupal\early_rendering_controller_test;
+
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent;
+use Symfony\Component\HttpKernel\KernelEvents;
+
+/**
+ * View subscriber for turning TestDomainObject objects into Response objects.
+ */
+class TestDomainObjectViewSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Sets a response given a TestDomainObject instance.
+   *
+   * @param \Symfony\Component\HttpKernel\Event\GetResponseForControllerResultEvent $event
+   *   The event to process.
+   */
+  public function onViewTestDomainObject(GetResponseForControllerResultEvent $event) {
+    $result = $event->getControllerResult();
+
+    if ($result instanceof TestDomainObject) {
+      if ($result instanceof AttachmentsTestDomainObject) {
+        $event->setResponse(new AttachmentsTestResponse('AttachmentsTestDomainObject'));
+      }
+      elseif ($result instanceof CacheableTestDomainObject) {
+        $event->setResponse(new CacheableTestResponse('CacheableTestDomainObject'));
+      }
+      else {
+        $event->setResponse(new Response('TestDomainObject'));
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  static function getSubscribedEvents() {
+    $events[KernelEvents::VIEW][] = ['onViewTestDomainObject'];
+
+    return $events;
+  }
+
+}
diff --git a/core/modules/taxonomy/src/Tests/Views/TaxonomyFieldTidTest.php b/core/modules/taxonomy/src/Tests/Views/TaxonomyFieldTidTest.php
index 0289049..f02968b 100644
--- a/core/modules/taxonomy/src/Tests/Views/TaxonomyFieldTidTest.php
+++ b/core/modules/taxonomy/src/Tests/Views/TaxonomyFieldTidTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\taxonomy\Tests\Views;
 
+use Drupal\Core\Render\RenderContext;
 use Drupal\views\Views;
 
 /**
@@ -24,10 +25,15 @@ class TaxonomyFieldTidTest extends TaxonomyTestBase {
   public static $testViews = array('test_taxonomy_tid_field');
 
   function testViewsHandlerTidField() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $view = Views::getView('test_taxonomy_tid_field');
     $this->executeView($view);
 
-    $actual = $view->field['name']->advancedRender($view->result[0]);
+    $actual = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $expected = \Drupal::l($this->term1->label(), $this->term1->urlInfo());
 
     $this->assertEqual($expected, $actual);
diff --git a/core/modules/user/src/Tests/Views/HandlerFieldUserNameTest.php b/core/modules/user/src/Tests/Views/HandlerFieldUserNameTest.php
index c2c0c93..5db1e3e 100644
--- a/core/modules/user/src/Tests/Views/HandlerFieldUserNameTest.php
+++ b/core/modules/user/src/Tests/Views/HandlerFieldUserNameTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\user\Tests\Views;
 
+use Drupal\Core\Render\RenderContext;
 use Drupal\views\Views;
 
 /**
@@ -25,6 +26,9 @@ class HandlerFieldUserNameTest extends UserTestBase {
   public static $testViews = array('test_views_handler_field_user_name');
 
   public function testUserName() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $this->drupalLogin($this->drupalCreateUser(array('access user profiles')));
 
     // Set defaults.
@@ -37,13 +41,17 @@ public function testUserName() {
     $this->executeView($view);
 
     $anon_name = $this->config('user.settings')->get('anonymous');
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertTrue(strpos($render, $anon_name) !== FALSE, 'For user 0 it should use the default anonymous name by default.');
 
     $username = $this->randomMachineName();
     $view->result[0]->_entity->setUsername($username);
     $view->result[0]->_entity->uid->value = 1;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertTrue(strpos($render, $username) !== FALSE, 'If link to user is checked the username should be part of the output.');
     $this->assertTrue(strpos($render, 'user/1') !== FALSE, 'If link to user is checked the link to the user should appear as well.');
 
@@ -52,7 +60,9 @@ public function testUserName() {
     $username = $this->randomMachineName();
     $view->result[0]->_entity->setUsername($username);
     $view->result[0]->_entity->uid->value = 1;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $username, 'If the user is not linked the username should be printed out for a normal user.');
 
   }
@@ -61,13 +71,18 @@ public function testUserName() {
    * Tests that the field handler works when no additional fields are added.
    */
   public function testNoAdditionalFields() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $view = Views::getView('test_views_handler_field_user_name');
     $this->executeView($view);
 
     $username = $this->randomMachineName();
     $view->result[0]->_entity->setUsername($username);
     $view->result[0]->_entity->uid->value = 1;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertTrue(strpos($render, $username) !== FALSE, 'If link to user is checked the username should be part of the output.');
   }
 
diff --git a/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php b/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php
index d318a79..9cfd854 100644
--- a/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php
+++ b/core/modules/views/src/Tests/Handler/FieldGroupRowsTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\views\Tests\Handler;
 
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
+use Drupal\Core\Render\RenderContext;
 use Drupal\views\Views;
 
 /**
@@ -67,6 +68,9 @@ protected function setUp() {
    * Testing the "Grouped rows" functionality.
    */
   public function testGroupRows() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $edit = array(
       'title' => $this->randomMachineName(),
       $this->fieldName => array('a', 'b', 'c'),
@@ -77,7 +81,10 @@ public function testGroupRows() {
 
     // Test grouped rows.
     $this->executeView($view);
-    $this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[0]), 'a, b, c');
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field[$this->fieldName]->advancedRender($view->result[0]);
+    });
+    $this->assertEqual($output, 'a, b, c');
 
     // Change the group_rows checkbox to false.
     $view = Views::getView('test_group_rows');
@@ -88,11 +95,20 @@ public function testGroupRows() {
     $view->render();
 
     $view->row_index = 0;
-    $this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[0]), 'a');
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field[$this->fieldName]->advancedRender($view->result[0]);
+    });
+    $this->assertEqual($output, 'a');
     $view->row_index = 1;
-    $this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[1]), 'b');
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field[$this->fieldName]->advancedRender($view->result[1]);
+    });
+    $this->assertEqual($output, 'b');
     $view->row_index = 2;
-    $this->assertEqual($view->field[$this->fieldName]->advancedRender($view->result[2]), 'c');
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field[$this->fieldName]->advancedRender($view->result[2]);
+    });
+    $this->assertEqual($output, 'c');
   }
 
 }
diff --git a/core/modules/views/src/Tests/Handler/FieldUnitTest.php b/core/modules/views/src/Tests/Handler/FieldUnitTest.php
index f76e1ce..5a5d73f 100644
--- a/core/modules/views/src/Tests/Handler/FieldUnitTest.php
+++ b/core/modules/views/src/Tests/Handler/FieldUnitTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests\Handler;
 
+use Drupal\Core\Render\RenderContext;
 use Drupal\views\Tests\ViewUnitTestBase;
 use Drupal\views\Plugin\views\field\FieldPluginBase;
 use Drupal\views\Views;
@@ -52,12 +53,18 @@ protected function viewsData() {
    * Tests that the render function is called.
    */
   public function testRender() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $view = Views::getView('test_field_tokens');
     $this->executeView($view);
 
     $random_text = $this->randomMachineName();
     $view->field['job']->setTestValue($random_text);
-    $this->assertEqual($view->field['job']->theme($view->result[0]), $random_text, 'Make sure the render method rendered the manual set value.');
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['job']->theme($view->result[0]);
+    });
+    $this->assertEqual($output, $random_text, 'Make sure the render method rendered the manual set value.');
   }
 
   /**
@@ -141,6 +148,9 @@ protected function assertNotSubString($haystack, $needle, $message = '', $group
    * Tests general rewriting of the output.
    */
   public function testRewrite() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $view = Views::getView('test_view');
     $view->initHandlers();
     $this->executeView($view);
@@ -149,11 +159,15 @@ public function testRewrite() {
 
     // Don't check the rewrite checkbox, so the text shouldn't appear.
     $id_field->options['alter']['text'] = $random_text = $this->randomMachineName();
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertNotSubString($output, $random_text);
 
     $id_field->options['alter']['alter_text'] = TRUE;
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertSubString($output, $random_text);
   }
 
@@ -161,6 +175,9 @@ public function testRewrite() {
    * Tests the field tokens, row level and field level.
    */
   public function testFieldTokens() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $view = Views::getView('test_field_tokens');
     $this->executeView($view);
     $name_field_0 = $view->field['name'];
@@ -182,19 +199,25 @@ public function testFieldTokens() {
       $expected_output_1 = "$row->views_test_data_name $row->views_test_data_name";
       $expected_output_2 = "$row->views_test_data_name $row->views_test_data_name $row->views_test_data_name";
 
-      $output = $name_field_0->advancedRender($row);
+      $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_0, $row) {
+        return $name_field_0->advancedRender($row);
+      });
       $this->assertEqual($output, $expected_output_0, format_string('Test token replacement: "!token" gave "!output"', [
         '!token' => $name_field_0->options['alter']['text'],
         '!output' => $output,
       ]));
 
-      $output = $name_field_1->advancedRender($row);
+      $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_1, $row) {
+        return $name_field_1->advancedRender($row);
+      });
       $this->assertEqual($output, $expected_output_1, format_string('Test token replacement: "!token" gave "!output"', [
         '!token' => $name_field_1->options['alter']['text'],
         '!output' => $output,
       ]));
 
-      $output = $name_field_2->advancedRender($row);
+      $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field_2, $row) {
+        return $name_field_2->advancedRender($row);
+      });
       $this->assertEqual($output, $expected_output_2, format_string('Test token replacement: "!token" gave "!output"', [
         '!token' => $name_field_2->options['alter']['text'],
         '!output' => $output,
@@ -207,7 +230,9 @@ public function testFieldTokens() {
 
     $random_text = $this->randomMachineName();
     $job_field->setTestValue($random_text);
-    $output = $job_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
+      return $job_field->advancedRender($row);
+    });
     $this->assertSubString($output, $random_text, format_string('Make sure the self token (!token => !value) appears in the output (!output)', [
       '!value' => $random_text,
       '!output' => $output,
@@ -219,7 +244,9 @@ public function testFieldTokens() {
     $job_field->options['alter']['text'] = $old_token;
     $random_text = $this->randomMachineName();
     $job_field->setTestValue($random_text);
-    $output = $job_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($job_field, $row) {
+      return $job_field->advancedRender($row);
+    });
     $this->assertSubString($output, $old_token, format_string('Make sure the old token style (!token => !value) is not changed in the output (!output)', [
       '!value' => $random_text,
       '!output' => $output,
@@ -268,6 +295,9 @@ function testEmpty() {
    * This tests alters the result to get easier and less coupled results.
    */
   function _testHideIfEmpty() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $view = Views::getView('test_view');
     $view->initDisplay();
     $this->executeView($view);
@@ -284,22 +314,30 @@ function _testHideIfEmpty() {
 
     // Test a valid string.
     $view->result[0]->{$column_map_reversed['name']} = $random_name;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $random_name, 'By default, a string should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "", 'By default, "" should not be treated as empty.');
 
     // Test zero as an integer.
     $view->result[0]->{$column_map_reversed['name']} = 0;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, '0', 'By default, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "0", 'By default, "0" should not be treated as empty.');
 
     // Test when results are not rewritten and non-zero empty values are hidden.
@@ -309,22 +347,30 @@ function _testHideIfEmpty() {
 
     // Test a valid string.
     $view->result[0]->{$column_map_reversed['name']} = $random_name;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $random_name, 'If hide_empty is checked, a string should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "", 'If hide_empty is checked, "" should be treated as empty.');
 
     // Test zero as an integer.
     $view->result[0]->{$column_map_reversed['name']} = 0;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, '0', 'If hide_empty is checked, but not empty_zero, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "0", 'If hide_empty is checked, but not empty_zero, "0" should not be treated as empty.');
 
     // Test when results are not rewritten and all empty values are hidden.
@@ -334,12 +380,16 @@ function _testHideIfEmpty() {
 
     // Test zero as an integer.
     $view->result[0]->{$column_map_reversed['name']} = 0;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "", 'If hide_empty and empty_zero are checked, 0 should be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "", 'If hide_empty and empty_zero are checked, "0" should be treated as empty.');
 
     // Test when results are rewritten to a valid string and non-zero empty
@@ -352,22 +402,30 @@ function _testHideIfEmpty() {
 
     // Test a valid string.
     $view->result[0]->{$column_map_reversed['name']} = $random_value;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, it should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, "" should not be treated as empty.');
 
     // Test zero as an integer.
     $view->result[0]->{$column_map_reversed['name']} = 0;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $random_name, 'If the rewritten string is not empty, "0" should not be treated as empty.');
 
     // Test when results are rewritten to an empty string and non-zero empty results are hidden.
@@ -379,22 +437,30 @@ function _testHideIfEmpty() {
 
     // Test a valid string.
     $view->result[0]->{$column_map_reversed['name']} = $random_name;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $random_name, 'If the rewritten string is empty, it should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "", 'If the rewritten string is empty, "" should be treated as empty.');
 
     // Test zero as an integer.
     $view->result[0]->{$column_map_reversed['name']} = 0;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, '0', 'If the rewritten string is empty, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "0", 'If the rewritten string is empty, "0" should not be treated as empty.');
 
     // Test when results are rewritten to zero as a string and non-zero empty
@@ -407,22 +473,30 @@ function _testHideIfEmpty() {
 
     // Test a valid string.
     $view->result[0]->{$column_map_reversed['name']} = $random_name;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, the string rewritten as 0 should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, "" rewritten as 0 should not be treated as empty.');
 
     // Test zero as an integer.
     $view->result[0]->{$column_map_reversed['name']} = 0;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "0", 'If the rewritten string is zero and empty_zero is not checked, "0" should not be treated as empty.');
 
     // Test when results are rewritten to a valid string and non-zero empty
@@ -435,22 +509,30 @@ function _testHideIfEmpty() {
 
     // Test a valid string.
     $view->result[0]->{$column_map_reversed['name']} = $random_name;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, it should not be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "", 'If either the original or rewritten string is invalid, "" should be treated as empty.');
 
     // Test zero as an integer.
     $view->result[0]->{$column_map_reversed['name']} = 0;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $random_value, 'If the original and rewritten strings are valid, "0" should not be treated as empty.');
 
     // Test when results are rewritten to zero as a string and all empty
@@ -463,22 +545,30 @@ function _testHideIfEmpty() {
 
     // Test a valid string.
     $view->result[0]->{$column_map_reversed['name']} = $random_name;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "", 'If the rewritten string is zero, it should be treated as empty.');
 
     // Test an empty string.
     $view->result[0]->{$column_map_reversed['name']} = "";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "", 'If the rewritten string is zero, "" should be treated as empty.');
 
     // Test zero as an integer.
     $view->result[0]->{$column_map_reversed['name']} = 0;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "", 'If the rewritten string is zero, 0 should not be treated as empty.');
 
     // Test zero as a string.
     $view->result[0]->{$column_map_reversed['name']} = "0";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "", 'If the rewritten string is zero, "0" should not be treated as empty.');
   }
 
@@ -486,6 +576,9 @@ function _testHideIfEmpty() {
    * Tests the usage of the empty text.
    */
   function _testEmptyText() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $view = Views::getView('test_view');
     $view->initDisplay();
     $this->executeView($view);
@@ -495,27 +588,37 @@ function _testEmptyText() {
 
     $empty_text = $view->field['name']->options['empty'] = $this->randomMachineName();
     $view->result[0]->{$column_map_reversed['name']} = "";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $empty_text, 'If a field is empty, the empty text should be used for the output.');
 
     $view->result[0]->{$column_map_reversed['name']} = "0";
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, "0", 'If a field is 0 and empty_zero is not checked, the empty text should not be used for the output.');
 
     $view->result[0]->{$column_map_reversed['name']} = "0";
     $view->field['name']->options['empty_zero'] = TRUE;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $empty_text, 'If a field is 0 and empty_zero is checked, the empty text should be used for the output.');
 
     $view->result[0]->{$column_map_reversed['name']} = "";
     $view->field['name']->options['alter']['alter_text'] = TRUE;
     $alter_text = $view->field['name']->options['alter']['text'] = $this->randomMachineName();
     $view->field['name']->options['hide_alter_empty'] = FALSE;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $alter_text, 'If a field is empty, some rewrite text exists, but hide_alter_empty is not checked, render the rewrite text.');
 
     $view->field['name']->options['hide_alter_empty'] = TRUE;
-    $render = $view->field['name']->advancedRender($view->result[0]);
+    $render = $renderer->executeInRenderContext(new RenderContext(), function () use ($view) {
+      return $view->field['name']->advancedRender($view->result[0]);
+    });
     $this->assertIdentical($render, $empty_text, 'If a field is empty, some rewrite text exists, and hide_alter_empty is checked, use the empty text.');
   }
 
diff --git a/core/modules/views/src/Tests/Handler/FieldWebTest.php b/core/modules/views/src/Tests/Handler/FieldWebTest.php
index 45a1617..39e1eb2 100644
--- a/core/modules/views/src/Tests/Handler/FieldWebTest.php
+++ b/core/modules/views/src/Tests/Handler/FieldWebTest.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\Unicode;
 use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Render\RenderContext;
 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
 use Drupal\views\Views;
 
@@ -197,6 +198,9 @@ protected function xpathContent($content, $xpath, array $arguments = array()) {
    * Tests rewriting the output to a link.
    */
   public function testAlterUrl() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $view = Views::getView('test_view');
     $view->setDisplay();
     $view->initHandlers();
@@ -211,13 +215,17 @@ public function testAlterUrl() {
     // Tests that the suffix/prefix appears on the output.
     $id_field->options['alter']['prefix'] = $prefix = $this->randomMachineName();
     $id_field->options['alter']['suffix'] = $suffix = $this->randomMachineName();
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertSubString($output, $prefix);
     $this->assertSubString($output, $suffix);
     unset($id_field->options['alter']['prefix']);
     unset($id_field->options['alter']['suffix']);
 
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertSubString($output, $path, 'Make sure that the path is part of the output');
 
     // Some generic test code adapted from the UrlTest class, which tests
@@ -228,44 +236,60 @@ public function testAlterUrl() {
 
       $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['absolute' => $absolute]);
       $alter['absolute'] = $absolute;
-      $result = $id_field->theme($row);
+      $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+        return $id_field->theme($row);
+      });
       $this->assertSubString($result, $expected_result);
 
       $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['fragment' => 'foo', 'absolute' => $absolute]);
       $alter['path'] = 'node/123#foo';
-      $result = $id_field->theme($row);
+      $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+        return $id_field->theme($row);
+      });
       $this->assertSubString($result, $expected_result);
 
       $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['query' => ['foo' => NULL], 'absolute' => $absolute]);
       $alter['path'] = 'node/123?foo';
-      $result = $id_field->theme($row);
+      $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+        return $id_field->theme($row);
+      });
       $this->assertSubString($result, $expected_result);
 
       $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['query' => ['foo' => 'bar', 'bar' => 'baz'], 'absolute' => $absolute]);
       $alter['path'] = 'node/123?foo=bar&bar=baz';
-      $result = $id_field->theme($row);
+      $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+        return $id_field->theme($row);
+      });
       $this->assertSubString(Html::decodeEntities($result), Html::decodeEntities($expected_result));
 
       // @todo The route-based URL generator strips out NULL attributes.
       // $expected_result = \Drupal::url('entity.node.canonical', ['node' => '123'], ['query' => ['foo' => NULL], 'fragment' => 'bar', 'absolute' => $absolute]);
       $expected_result = \Drupal::urlGenerator()->generateFromPath('node/123', array('query' => array('foo' => NULL), 'fragment' => 'bar', 'absolute' => $absolute));
       $alter['path'] = 'node/123?foo#bar';
-      $result = $id_field->theme($row);
+      $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+        return $id_field->theme($row);
+      });
       $this->assertSubString(Html::decodeEntities($result), Html::decodeEntities($expected_result));
 
       $expected_result = \Drupal::url('<front>', [], ['absolute' => $absolute]);
       $alter['path'] = '<front>';
-      $result = $id_field->theme($row);
+      $result = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+        return $id_field->theme($row);
+      });
       $this->assertSubString($result, $expected_result);
     }
 
     // Tests the replace spaces with dashes feature.
     $id_field->options['alter']['replace_spaces'] = TRUE;
     $id_field->options['alter']['path'] = $path = $this->randomMachineName() . ' ' . $this->randomMachineName();
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertSubString($output, str_replace(' ', '-', $path));
     $id_field->options['alter']['replace_spaces'] = FALSE;
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     // The url has a space in it, so to check we have to decode the url output.
     $this->assertSubString(urldecode($output), $path);
 
@@ -273,44 +297,60 @@ public function testAlterUrl() {
     // Switch on the external flag should output an external url as well.
     $id_field->options['alter']['external'] = TRUE;
     $id_field->options['alter']['path'] = $path = 'www.drupal.org';
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertSubString($output, 'http://www.drupal.org');
 
     // Setup a not external url, which shouldn't lead to an external url.
     $id_field->options['alter']['external'] = FALSE;
     $id_field->options['alter']['path'] = $path = 'www.drupal.org';
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertNotSubString($output, 'http://www.drupal.org');
 
     // Tests the transforming of the case setting.
     $id_field->options['alter']['path'] = $path = $this->randomMachineName();
     $id_field->options['alter']['path_case'] = 'none';
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertSubString($output, $path);
 
     // Switch to uppercase and lowercase.
     $id_field->options['alter']['path_case'] = 'upper';
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertSubString($output, strtoupper($path));
     $id_field->options['alter']['path_case'] = 'lower';
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertSubString($output, strtolower($path));
 
     // Switch to ucfirst and ucwords.
     $id_field->options['alter']['path_case'] = 'ucfirst';
     $id_field->options['alter']['path'] = 'drupal has a great community';
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertSubString($output, UrlHelper::encodePath('Drupal has a great community'));
 
     $id_field->options['alter']['path_case'] = 'ucwords';
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $this->assertSubString($output, UrlHelper::encodePath('Drupal Has A Great Community'));
     unset($id_field->options['alter']['path_case']);
 
     // Tests the linkclass setting and see whether it actually exists in the
     // output.
     $id_field->options['alter']['link_class'] = $class = $this->randomMachineName();
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $elements = $this->xpathContent($output, '//a[contains(@class, :class)]', array(':class' => $class));
     $this->assertTrue($elements);
     // @fixme link_class, alt, rel cannot be unset, which should be fixed.
@@ -318,21 +358,27 @@ public function testAlterUrl() {
 
     // Tests the alt setting.
     $id_field->options['alter']['alt'] = $rel = $this->randomMachineName();
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $elements = $this->xpathContent($output, '//a[contains(@title, :alt)]', array(':alt' => $rel));
     $this->assertTrue($elements);
     $id_field->options['alter']['alt'] = '';
 
     // Tests the rel setting.
     $id_field->options['alter']['rel'] = $rel = $this->randomMachineName();
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $elements = $this->xpathContent($output, '//a[contains(@rel, :rel)]', array(':rel' => $rel));
     $this->assertTrue($elements);
     $id_field->options['alter']['rel'] = '';
 
     // Tests the target setting.
     $id_field->options['alter']['target'] = $target = $this->randomMachineName();
-    $output = $id_field->theme($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($id_field, $row) {
+      return $id_field->theme($row);
+    });
     $elements = $this->xpathContent($output, '//a[contains(@target, :target)]', array(':target' => $target));
     $this->assertTrue($elements);
     unset($id_field->options['alter']['target']);
@@ -453,6 +499,9 @@ public function testFieldClasses() {
    * Tests trimming/read-more/ellipses.
    */
   public function testTextRendering() {
+    /** @var \Drupal\Core\Render\RendererInterface $renderer */
+    $renderer = \Drupal::service('renderer');
+
     $view = Views::getView('test_field_output');
     $view->initHandlers();
     $name_field = $view->field['name'];
@@ -465,18 +514,24 @@ public function testTextRendering() {
     $row = $view->result[0];
 
     $name_field->options['alter']['strip_tags'] = TRUE;
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
     $this->assertSubString($output, $random_text, 'Find text without html if stripping of views field output is enabled.');
     $this->assertNotSubString($output, $html_text, 'Find no text with the html if stripping of views field output is enabled.');
 
     // Tests preserving of html tags.
     $name_field->options['alter']['preserve_tags'] = '<div>';
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
     $this->assertSubString($output, $random_text, 'Find text without html if stripping of views field output is enabled but a div is allowed.');
     $this->assertSubString($output, $html_text, 'Find text with the html if stripping of views field output is enabled but a div is allowed.');
 
     $name_field->options['alter']['strip_tags'] = FALSE;
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
     $this->assertSubString($output, $random_text, 'Find text without html if stripping of views field output is disabled.');
     $this->assertSubString($output, $html_text, 'Find text with the html if stripping of views field output is disabled.');
 
@@ -485,13 +540,17 @@ public function testTextRendering() {
     $views_test_data_name = $row->views_test_data_name;
     $row->views_test_data_name = '  ' . $views_test_data_name . '     ';
     $name_field->options['alter']['trim_whitespace'] = TRUE;
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
 
     $this->assertSubString($output, $views_test_data_name, 'Make sure the trimmed text can be found if trimming is enabled.');
     $this->assertNotSubString($output, $row->views_test_data_name, 'Make sure the untrimmed text can be found if trimming is enabled.');
 
     $name_field->options['alter']['trim_whitespace'] = FALSE;
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
     $this->assertSubString($output, $views_test_data_name, 'Make sure the trimmed text can be found if trimming is disabled.');
     $this->assertSubString($output, $row->views_test_data_name, 'Make sure the untrimmed text can be found  if trimming is disabled.');
 
@@ -504,12 +563,16 @@ public function testTextRendering() {
     $name_field->options['alter']['max_length'] = 5;
     $trimmed_name = Unicode::substr($row->views_test_data_name, 0, 5);
 
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
     $this->assertSubString($output, $trimmed_name, format_string('Make sure the trimmed output (!trimmed) appears in the rendered output (!output).', array('!trimmed' => $trimmed_name, '!output' => $output)));
     $this->assertNotSubString($output, $row->views_test_data_name, format_string("Make sure the untrimmed value (!untrimmed) shouldn't appear in the rendered output (!output).", array('!untrimmed' => $row->views_test_data_name, '!output' => $output)));
 
     $name_field->options['alter']['max_length'] = 9;
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
     $this->assertSubString($output, $trimmed_name, format_string('Make sure the untrimmed (!untrimmed) output appears in the rendered output  (!output).', array('!trimmed' => $trimmed_name, '!output' => $output)));
 
     // Take word_boundary into account for the tests.
@@ -549,7 +612,9 @@ public function testTextRendering() {
 
     foreach ($tuples as $tuple) {
       $row->views_test_data_name = $tuple['value'];
-      $output = $name_field->advancedRender($row);
+      $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+        return $name_field->advancedRender($row);
+      });
 
       if ($tuple['trimmed']) {
         $this->assertNotSubString($output, $tuple['value'], format_string('The untrimmed value (!untrimmed) should not appear in the trimmed output (!output).', array('!untrimmed' => $tuple['value'], '!output' => $output)));
@@ -566,22 +631,30 @@ public function testTextRendering() {
     $name_field->options['alter']['more_link_text'] = $more_text = $this->randomMachineName();
     $name_field->options['alter']['more_link_path'] = $more_path = $this->randomMachineName();
 
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
     $this->assertSubString($output, $more_text, 'Make sure a read more text is displayed if the output got trimmed');
     $this->assertTrue($this->xpathContent($output, '//a[contains(@href, :path)]', array(':path' => $more_path)), 'Make sure the read more link points to the right destination.');
 
     $name_field->options['alter']['more_link'] = FALSE;
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
     $this->assertNotSubString($output, $more_text, 'Make sure no read more text appears.');
     $this->assertFalse($this->xpathContent($output, '//a[contains(@href, :path)]', array(':path' => $more_path)), 'Make sure no read more link appears.');
 
     // Check for the ellipses.
     $row->views_test_data_name = $this->randomMachineName(8);
     $name_field->options['alter']['max_length'] = 5;
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
     $this->assertSubString($output, '…', 'An ellipsis should appear if the output is trimmed');
     $name_field->options['alter']['max_length'] = 10;
-    $output = $name_field->advancedRender($row);
+    $output = $renderer->executeInRenderContext(new RenderContext(), function () use ($name_field, $row) {
+      return $name_field->advancedRender($row);
+    });
     $this->assertNotSubString($output, '…', 'No ellipsis should appear if the output is not trimmed');
   }
 
diff --git a/core/modules/views/src/Tests/Plugin/CacheTest.php b/core/modules/views/src/Tests/Plugin/CacheTest.php
index e628f8e..0d8c907 100644
--- a/core/modules/views/src/Tests/Plugin/CacheTest.php
+++ b/core/modules/views/src/Tests/Plugin/CacheTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\views\Tests\Plugin;
 
+use Drupal\Core\Render\RenderContext;
 use Drupal\node\Entity\Node;
 use Drupal\views\Tests\ViewUnitTestBase;
 use Drupal\views\Views;
@@ -282,14 +283,18 @@ function testHeaderStorage() {
     $output = $view->buildRenderable();
     /** @var \Drupal\Core\Render\RendererInterface $renderer */
     $renderer = \Drupal::service('renderer');
-    $renderer->render($output);
+    $renderer->executeInRenderContext(new RenderContext(), function () use (&$output, $renderer) {
+      return $renderer->render($output);
+    });
 
     unset($view->pre_render_called);
     $view->destroy();
 
     $view->setDisplay();
     $output = $view->buildRenderable();
-    $renderer->render($output);
+    $renderer->executeInRenderContext(new RenderContext(), function () use (&$output, $renderer) {
+      return $renderer->render($output);
+    });
 
     $this->assertTrue(in_array('views_test_data/test', $output['#attached']['library']), 'Make sure libraries are added for cached views.');
     $this->assertEqual(['foo' => 'bar'], $output['#attached']['drupalSettings'], 'Make sure drupalSettings are added for cached views.');
