Commit 98e2b931 authored by catch's avatar catch

Issue #2552013 by Wim Leers: Follow-up for #2481453: ensure the...

Issue #2552013 by Wim Leers: Follow-up for #2481453: ensure the 'url.query_args': MainContentViewSubscriber::WRAPPER_FORMAT cache context is set
parent 3aa2cbb2
<?php
/**
* @file
* Contains \Drupal\Core\Cache\CacheableResponse.
*/
namespace Drupal\Core\Cache;
use Symfony\Component\HttpFoundation\JsonResponse;
/**
* A JsonResponse that contains and can expose cacheability metadata.
*
* Supports Drupal's caching concepts: cache tags for invalidation and cache
* contexts for variations.
*
* @see \Drupal\Core\Cache\Cache
* @see \Drupal\Core\Cache\CacheableMetadata
* @see \Drupal\Core\Cache\CacheableResponseTrait
*/
class CacheableJsonResponse extends JsonResponse implements CacheableResponseInterface {
use CacheableResponseTrait;
}
......@@ -7,6 +7,8 @@
namespace Drupal\Core\EventSubscriber;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Cache\CacheableResponseInterface;
use Drupal\Core\DependencyInjection\ClassResolverInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
......@@ -90,7 +92,14 @@ public function onViewRenderArray(GetResponseForControllerResultEvent $event) {
$wrapper = isset($this->mainContentRenderers[$wrapper]) ? $wrapper : 'html';
$renderer = $this->classResolver->getInstanceFromDefinition($this->mainContentRenderers[$wrapper]);
$event->setResponse($renderer->renderResponse($result, $request, $this->routeMatch));
$response = $renderer->renderResponse($result, $request, $this->routeMatch);
// The main content render array is rendered into a different Response
// object, depending on the specified wrapper format.
if ($response instanceof CacheableResponseInterface) {
$main_content_view_subscriber_cacheability = (new CacheableMetadata())->setCacheContexts(['url.query_args:' . static::WRAPPER_FORMAT]);
$response->addCacheableDependency($main_content_view_subscriber_cacheability);
}
$event->setResponse($response);
}
}
......
......@@ -39,6 +39,7 @@ class CommentTranslationUITest extends ContentTranslationUITestBase {
'languages:language_interface',
'theme',
'timezone',
'url.query_args:_wrapper_format',
'url.query_args.pagers:0',
'user'
];
......
......@@ -52,7 +52,7 @@ abstract class ContentTranslationUITestBase extends ContentTranslationTestBase {
*
* @var string[]
*/
protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'user.permissions'];
protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'url.query_args:_wrapper_format', 'user.permissions'];
/**
* Tests the basic translation UI.
......
......@@ -20,7 +20,7 @@ class MenuLinkContentTranslationUITest extends ContentTranslationUITestBase {
/**
* {inheritdoc}
*/
protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'user.permissions', 'user.roles:authenticated'];
protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'url.query_args:_wrapper_format', 'user.permissions', 'user.roles:authenticated'];
/**
* Modules to enable.
......
......@@ -8,6 +8,7 @@
namespace Drupal\node\Tests;
use Drupal\block\Entity\Block;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
use Drupal\user\RoleInterface;
......@@ -119,7 +120,7 @@ public function testRecentNodeBlock() {
$this->assertText($node3->label(), 'Node found in block.');
$this->assertText($node4->label(), 'Node found in block.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'user']);
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user']);
// Enable the "Powered by Drupal" block only on article nodes.
$edit = [
......@@ -144,16 +145,16 @@ public function testRecentNodeBlock() {
$this->drupalGet('');
$label = $block->label();
$this->assertNoText($label, 'Block was not displayed on the front page.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'user', 'route']);
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route']);
$this->drupalGet('node/add/article');
$this->assertText($label, 'Block was displayed on the node/add/article page.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'user', 'route']);
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route']);
$this->drupalGet('node/' . $node1->id());
$this->assertText($label, 'Block was displayed on the node/N when node is of type article.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'user', 'route', 'timezone']);
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route', 'timezone']);
$this->drupalGet('node/' . $node5->id());
$this->assertNoText($label, 'Block was not displayed on nodes of type page.');
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'user', 'route', 'timezone']);
$this->assertCacheContexts(['languages:language_content', 'languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user', 'route', 'timezone']);
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/structure/block');
......
......@@ -21,7 +21,7 @@ class ShortcutTranslationUITest extends ContentTranslationUITestBase {
/**
* {inheritdoc}
*/
protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'user', 'url.site'];
protected $defaultCacheContexts = ['languages:language_interface', 'theme', 'user', 'url.query_args:_wrapper_format', 'url.site'];
/**
* Modules to enable.
......
......@@ -7,6 +7,8 @@
namespace Drupal\system\Tests\Common;
use Drupal\Component\Serialization\Json;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Url;
use Drupal\simpletest\WebTestBase;
......@@ -24,6 +26,24 @@ class RenderWebTest extends WebTestBase {
*/
public static $modules = array('common_test');
/**
* Asserts the cache context for the wrapper format is always present.
*/
function testWrapperFormatCacheContext() {
$this->drupalGet('');
$this->assertIdentical(0, strpos($this->getRawContent(), "<!DOCTYPE html>\n<html"));
$this->assertIdentical('text/html; charset=UTF-8', $this->drupalGetHeader('Content-Type'));
$this->assertTitle('Log in | Drupal');
$this->assertCacheContext('url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT);
$this->drupalGet('', ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'json']]);
$this->assertIdentical('application/json', $this->drupalGetHeader('Content-Type'));
$json = Json::decode($this->getRawContent());
$this->assertEqual(['content', 'title'], array_keys($json));
$this->assertIdentical('Log in', $json['title']);
$this->assertCacheContext('url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT);
}
/**
* Tests rendering form elements without passing through
* \Drupal::formBuilder()->doBuildForm().
......
......@@ -9,6 +9,7 @@
use Drupal\Core\Cache\Cache;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Url;
......@@ -337,6 +338,7 @@ public function testReferencedEntity() {
// The default cache contexts for rendered entities.
$default_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
$entity_cache_contexts = $default_cache_contexts;
$page_cache_contexts = Cache::mergeContexts($default_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]);
// Cache tags present on every rendered page.
// 'user.permissions' is a required cache context, and responses that vary
......@@ -428,7 +430,7 @@ public function testReferencedEntity() {
$this->verifyPageCache($empty_entity_listing_url, 'HIT', $empty_entity_listing_cache_tags);
// Verify the entity type's list cache contexts are present.
$contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
$this->assertEqual(Cache::mergeContexts($default_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this->pass("Test listing containing referenced entity.", 'Debug');
......@@ -438,7 +440,7 @@ public function testReferencedEntity() {
$this->verifyPageCache($nonempty_entity_listing_url, 'HIT', $nonempty_entity_listing_cache_tags);
// Verify the entity type's list cache contexts are present.
$contexts_in_header = $this->drupalGetHeader('X-Drupal-Cache-Contexts');
$this->assertEqual(Cache::mergeContexts($default_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
$this->assertEqual(Cache::mergeContexts($page_cache_contexts, $this->getAdditionalCacheContextsForEntityListing()), empty($contexts_in_header) ? [] : explode(' ', $contexts_in_header));
// Verify that after modifying the referenced entity, there is a cache miss
......
......@@ -8,6 +8,7 @@
namespace Drupal\system\Tests\Routing;
use Drupal\Core\Cache\Cache;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Language\LanguageInterface;
use Drupal\simpletest\WebTestBase;
use Symfony\Component\HttpFoundation\Request;
......@@ -32,6 +33,7 @@ class RouterTest extends WebTestBase {
*/
public function testFinishResponseSubscriber() {
$renderer_required_cache_contexts = ['languages:' . LanguageInterface::TYPE_INTERFACE, 'theme', 'user.permissions'];
$expected_cache_contexts = Cache::mergeContexts($renderer_required_cache_contexts, ['url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT]);
// Confirm that the router can get to a controller.
$this->drupalGet('router_test/test1');
......@@ -47,7 +49,7 @@ public function testFinishResponseSubscriber() {
$this->assertRaw('test2', 'The correct string was returned because the route was successful.');
// Check expected headers from FinishResponseSubscriber.
$headers = $this->drupalGetHeaders();
$this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', $renderer_required_cache_contexts));
$this->assertEqual($headers['x-drupal-cache-contexts'], implode(' ', $expected_cache_contexts));
$this->assertEqual($headers['x-drupal-cache-tags'], 'config:user.role.anonymous rendered');
// Confirm that the page wrapping is being added, so we're not getting a
// raw body returned.
......
......@@ -7,6 +7,7 @@
namespace Drupal\system\Tests\System;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\simpletest\WebTestBase;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
......@@ -35,12 +36,12 @@ public function testTokens() {
$this->drupalGet('token-test/' . $node->id());
$this->assertText("Tokens: {$node->id()} {$account->id()}");
$this->assertCacheTags(['node:1', 'rendered', 'user:2']);
$this->assertCacheContexts(['languages:language_interface', 'theme', 'user']);
$this->assertCacheContexts(['languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user']);
$this->drupalGet('token-test-without-bubleable-metadata/' . $node->id());
$this->assertText("Tokens: {$node->id()} {$account->id()}");
$this->assertCacheTags(['node:1', 'rendered', 'user:2']);
$this->assertCacheContexts(['languages:language_interface', 'theme', 'user']);
$this->assertCacheContexts(['languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'user']);
}
}
services:
main_content_renderer.json:
class: Drupal\common_test\Render\MainContent\JsonRenderer
arguments: ['@title_resolver', '@renderer']
tags:
- { name: render.main_content_renderer, format: json }
<?php
/**
* @file
* Contains \Drupal\common_test\Render\MainContent\JsonRenderer.
*/
namespace Drupal\common_test\Render\MainContent;
use Drupal\Core\Cache\CacheableJsonResponse;
use Drupal\Core\Cache\CacheableMetadata;
use Drupal\Core\Controller\TitleResolverInterface;
use Drupal\Core\Render\MainContent\MainContentRendererInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Routing\RouteMatchInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Default main content renderer for JSON requests.
*/
class JsonRenderer implements MainContentRendererInterface {
/**
* The title resolver.
*
* @var \Drupal\Core\Controller\TitleResolverInterface
*/
protected $titleResolver;
/**
* The renderer service.
*
* @var \Drupal\Core\Render\RendererInterface
*/
protected $renderer;
/**
* Constructs a new JsonRenderer.
*
* @param \Drupal\Core\Controller\TitleResolverInterface $title_resolver
* The title resolver.
* @param \Drupal\Core\Render\RendererInterface $renderer
* The renderer service.
*/
public function __construct(TitleResolverInterface $title_resolver, RendererInterface $renderer) {
$this->titleResolver = $title_resolver;
$this->renderer = $renderer;
}
/**
* {@inheritdoc}
*/
public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) {
$json = [];
$json['content'] = (string) $this->renderer->renderRoot($main_content);
if (!empty($main_content['#title'])) {
$json['title'] = (string) $main_content['#title'];
}
else {
$json['title'] = (string) $this->titleResolver->getTitle($request, $route_match->getRouteObject());
}
$response = new CacheableJsonResponse($json, 200);
$response->addCacheableDependency(CacheableMetadata::createFromRenderArray($main_content));
return $response;
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\toolbar\Tests;
use Drupal\Core\Cache\Cache;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\simpletest\WebTestBase;
use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
......@@ -109,6 +110,7 @@ protected function assertToolbarCacheContexts(array $cache_contexts, $message =
$default_cache_contexts = [
'languages:language_interface',
'theme',
'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT,
];
$cache_contexts = Cache::mergeContexts($default_cache_contexts, $cache_contexts);
......
......@@ -10,6 +10,7 @@
use Drupal\comment\CommentInterface;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Core\Cache\Cache;
use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
use Drupal\Core\Session\AccountInterface;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\node\Entity\Node;
......@@ -83,8 +84,7 @@ function testTrackerAll() {
$this->assertLink(t('My recent content'), 0, 'User tab shows up on the global tracker page.');
// Assert cache contexts, specifically the pager and node access contexts.
$this->assertCacheContexts(['languages:language_interface', 'theme', 'url.query_args.pagers:0', 'user.node_grants:view', 'user.permissions']);
// Assert cache tags for the visible node and node list cache tag.
$this->assertCacheContexts(['languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.query_args.pagers:0', 'user.node_grants:view', 'user.permissions']);
$expected_tags = Cache::mergeTags($published->getCacheTags(), $published->getOwner()->getCacheTags());
$expected_tags = Cache::mergeTags($expected_tags, ['node_list', 'rendered']);
$this->assertCacheTags($expected_tags);
......@@ -149,7 +149,7 @@ function testTrackerUser() {
$this->assertText($other_published_my_comment->label(), "Nodes that the user has commented on appear in the user's tracker listing.");
// Assert cache contexts.
$this->assertCacheContexts(['languages:language_interface', 'theme', 'url.query_args.pagers:0', 'user', 'user.node_grants:view']);
$this->assertCacheContexts(['languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.query_args.pagers:0', 'user', 'user.node_grants:view']);
// Assert cache tags for the visible nodes (including owners) and node list
// cache tag.
$expected_tags = Cache::mergeTags($my_published->getCacheTags(), $my_published->getOwner()->getCacheTags());
......@@ -158,7 +158,7 @@ function testTrackerUser() {
$expected_tags = Cache::mergeTags($expected_tags, ['node_list', 'rendered']);
$this->assertCacheTags($expected_tags);
$this->assertCacheContexts(['languages:language_interface', 'theme', 'url.query_args.pagers:0', 'user', 'user.node_grants:view']);
$this->assertCacheContexts(['languages:language_interface', 'theme', 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, 'url.query_args.pagers:0', 'user', 'user.node_grants:view']);
$this->assertLink($my_published->label());
$this->assertNoLink($unpublished->label());
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment