Loading core/modules/navigation/src/NavigationRenderer.php +21 −5 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Block\BlockPluginInterface; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\ContentEntityInterface; Loading @@ -18,6 +19,7 @@ use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Security\Attribute\TrustedCallback; use Drupal\Core\Session\AccountInterface; use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface; use Symfony\Component\HttpFoundation\RequestStack; Loading Loading @@ -100,6 +102,20 @@ public function removeToolbar(array &$page_top): void { * @see hook_page_top() */ public function buildNavigation(array &$page_top): void { $page_top['navigation'] = [ '#cache' => [ 'keys' => ['navigation', 'navigation'], 'max-age' => CacheBackendInterface::CACHE_PERMANENT, ], '#pre_render' => ['navigation.renderer:doBuildNavigation'], ]; } /** * Pre-render callback for ::buildNavigation. */ #[TrustedCallback] public function doBuildNavigation($build): array { $logo_settings = $this->configFactory->get('navigation.settings'); $logo_provider = $logo_settings->get('logo.provider'); Loading @@ -109,7 +125,6 @@ public function buildNavigation(array &$page_top): void { ]; $storage = $this->sectionStorageManager->findByContext($contexts, $cacheability); $build = []; if ($storage) { foreach ($storage->getSections() as $delta => $section) { $build[$delta] = $section->toRenderArray([]); Loading Loading @@ -141,20 +156,21 @@ public function buildNavigation(array &$page_top): void { ], ]; $build[0] = NestedArray::mergeDeepArray([$build[0], $defaults]); $page_top['navigation'] = $build; if ($logo_provider === self::LOGO_PROVIDER_CUSTOM) { $logo_path = $logo_settings->get('logo.path'); if (!empty($logo_path) && is_file($logo_path)) { $logo_managed_url = $this->fileUrlGenerator->generateAbsoluteString($logo_path); $image = $this->imageFactory->get($logo_path); $page_top['navigation'][0]['settings']['logo_path'] = $logo_managed_url; $build[0]['settings']['logo_path'] = $logo_managed_url; if ($image->isValid()) { $page_top['navigation'][0]['settings']['logo_width'] = $image->getWidth(); $page_top['navigation'][0]['settings']['logo_height'] = $image->getHeight(); $build[0]['settings']['logo_width'] = $image->getWidth(); $build[0]['settings']['logo_height'] = $image->getHeight(); } } } $build[0]['#cache']['contexts'] = ['user.permissions', 'theme', 'languages:language_interface']; return $build; } /** Loading core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php 0 → 100644 +91 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Tests\navigation\FunctionalJavascript; use Drupal\FunctionalJavascriptTests\PerformanceTestBase; /** * Tests performance with the navigation toolbar enabled. * * Stark is used as the default theme so that this test is not Olivero specific. * * @todo move this coverage to StandardPerformanceTest when Navigation is * enabled by default. * * @group Common * @group #slow * @requires extension apcu */ class PerformanceTest extends PerformanceTestBase { /** * {@inheritdoc} */ protected $defaultTheme = 'stark'; /** * {@inheritdoc} */ protected $profile = 'standard'; /** * {@inheritdoc} */ protected function setUp(): void { parent::setUp(); // Uninstall the toolbar. \Drupal::service('module_installer')->uninstall(['toolbar']); \Drupal::service('module_installer')->install(['navigation']); } /** * Tests performance of the navigation toolbar. */ public function testLogin(): void { $user = $this->drupalCreateUser(); $user->addRole('administrator'); $user->save(); $this->drupalLogin($user); // Request the front page twice to ensure all cache collectors are fully // warmed. The exact contents of cache collectors depends on the order in // which requests complete so this ensures that the second request completes // after asset aggregates are served. $this->drupalGet(''); sleep(1); $this->drupalGet(''); // Flush the dynamic page cache to simulate visiting a page that is not // already fully cached. \Drupal::cache('dynamic_page_cache')->deleteAll(); $performance_data = $this->collectPerformanceData(function () { $this->drupalGet(''); }, 'navigation'); $expected_queries = [ 'SELECT "session" FROM "sessions" WHERE "sid" = "SESSION_ID" LIMIT 0, 1', 'SELECT * FROM "users_field_data" "u" WHERE "u"."uid" = "2" AND "u"."default_langcode" = 1', 'SELECT "roles_target_id" FROM "user__roles" WHERE "entity_id" = "2"', 'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "theme:stark" ) AND "collection" = "config.entity.key_store.block"', ]; $recorded_queries = $performance_data->getQueries(); $this->assertSame($expected_queries, $recorded_queries); $this->assertSame(4, $performance_data->getQueryCount()); $this->assertSame(60, $performance_data->getCacheGetCount()); $this->assertSame(2, $performance_data->getCacheSetCount()); $this->assertSame(0, $performance_data->getCacheDeleteCount()); $this->assertSame(2, $performance_data->getCacheTagChecksumCount()); $this->assertSame(29, $performance_data->getCacheTagIsValidCount()); $this->assertSame(0, $performance_data->getCacheTagInvalidationCount()); $this->assertSame(1, $performance_data->getStyleSheetCount()); $this->assertSame(2, $performance_data->getScriptCount()); $this->assertLessThan(90000, $performance_data->getStylesheetBytes()); $this->assertLessThan(220000, $performance_data->getScriptBytes()); // Check that the navigation toolbar is cached without any high-cardinality // cache contexts (user, route, query parameters etc.). $this->assertIsObject(\Drupal::cache('render')->get('navigation:navigation:[languages:language_interface]=en:[theme]=stark:[user.permissions]=is-admin')); } } Loading
core/modules/navigation/src/NavigationRenderer.php +21 −5 Original line number Diff line number Diff line Loading @@ -4,6 +4,7 @@ use Drupal\Component\Utility\NestedArray; use Drupal\Core\Block\BlockPluginInterface; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Cache\CacheableMetadata; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\Core\Entity\ContentEntityInterface; Loading @@ -18,6 +19,7 @@ use Drupal\Core\Plugin\Context\Context; use Drupal\Core\Plugin\Context\ContextDefinition; use Drupal\Core\Routing\RouteMatchInterface; use Drupal\Core\Security\Attribute\TrustedCallback; use Drupal\Core\Session\AccountInterface; use Drupal\layout_builder\SectionStorage\SectionStorageManagerInterface; use Symfony\Component\HttpFoundation\RequestStack; Loading Loading @@ -100,6 +102,20 @@ public function removeToolbar(array &$page_top): void { * @see hook_page_top() */ public function buildNavigation(array &$page_top): void { $page_top['navigation'] = [ '#cache' => [ 'keys' => ['navigation', 'navigation'], 'max-age' => CacheBackendInterface::CACHE_PERMANENT, ], '#pre_render' => ['navigation.renderer:doBuildNavigation'], ]; } /** * Pre-render callback for ::buildNavigation. */ #[TrustedCallback] public function doBuildNavigation($build): array { $logo_settings = $this->configFactory->get('navigation.settings'); $logo_provider = $logo_settings->get('logo.provider'); Loading @@ -109,7 +125,6 @@ public function buildNavigation(array &$page_top): void { ]; $storage = $this->sectionStorageManager->findByContext($contexts, $cacheability); $build = []; if ($storage) { foreach ($storage->getSections() as $delta => $section) { $build[$delta] = $section->toRenderArray([]); Loading Loading @@ -141,20 +156,21 @@ public function buildNavigation(array &$page_top): void { ], ]; $build[0] = NestedArray::mergeDeepArray([$build[0], $defaults]); $page_top['navigation'] = $build; if ($logo_provider === self::LOGO_PROVIDER_CUSTOM) { $logo_path = $logo_settings->get('logo.path'); if (!empty($logo_path) && is_file($logo_path)) { $logo_managed_url = $this->fileUrlGenerator->generateAbsoluteString($logo_path); $image = $this->imageFactory->get($logo_path); $page_top['navigation'][0]['settings']['logo_path'] = $logo_managed_url; $build[0]['settings']['logo_path'] = $logo_managed_url; if ($image->isValid()) { $page_top['navigation'][0]['settings']['logo_width'] = $image->getWidth(); $page_top['navigation'][0]['settings']['logo_height'] = $image->getHeight(); $build[0]['settings']['logo_width'] = $image->getWidth(); $build[0]['settings']['logo_height'] = $image->getHeight(); } } } $build[0]['#cache']['contexts'] = ['user.permissions', 'theme', 'languages:language_interface']; return $build; } /** Loading
core/modules/navigation/tests/src/FunctionalJavascript/PerformanceTest.php 0 → 100644 +91 −0 Original line number Diff line number Diff line <?php declare(strict_types=1); namespace Drupal\Tests\navigation\FunctionalJavascript; use Drupal\FunctionalJavascriptTests\PerformanceTestBase; /** * Tests performance with the navigation toolbar enabled. * * Stark is used as the default theme so that this test is not Olivero specific. * * @todo move this coverage to StandardPerformanceTest when Navigation is * enabled by default. * * @group Common * @group #slow * @requires extension apcu */ class PerformanceTest extends PerformanceTestBase { /** * {@inheritdoc} */ protected $defaultTheme = 'stark'; /** * {@inheritdoc} */ protected $profile = 'standard'; /** * {@inheritdoc} */ protected function setUp(): void { parent::setUp(); // Uninstall the toolbar. \Drupal::service('module_installer')->uninstall(['toolbar']); \Drupal::service('module_installer')->install(['navigation']); } /** * Tests performance of the navigation toolbar. */ public function testLogin(): void { $user = $this->drupalCreateUser(); $user->addRole('administrator'); $user->save(); $this->drupalLogin($user); // Request the front page twice to ensure all cache collectors are fully // warmed. The exact contents of cache collectors depends on the order in // which requests complete so this ensures that the second request completes // after asset aggregates are served. $this->drupalGet(''); sleep(1); $this->drupalGet(''); // Flush the dynamic page cache to simulate visiting a page that is not // already fully cached. \Drupal::cache('dynamic_page_cache')->deleteAll(); $performance_data = $this->collectPerformanceData(function () { $this->drupalGet(''); }, 'navigation'); $expected_queries = [ 'SELECT "session" FROM "sessions" WHERE "sid" = "SESSION_ID" LIMIT 0, 1', 'SELECT * FROM "users_field_data" "u" WHERE "u"."uid" = "2" AND "u"."default_langcode" = 1', 'SELECT "roles_target_id" FROM "user__roles" WHERE "entity_id" = "2"', 'SELECT "name", "value" FROM "key_value" WHERE "name" IN ( "theme:stark" ) AND "collection" = "config.entity.key_store.block"', ]; $recorded_queries = $performance_data->getQueries(); $this->assertSame($expected_queries, $recorded_queries); $this->assertSame(4, $performance_data->getQueryCount()); $this->assertSame(60, $performance_data->getCacheGetCount()); $this->assertSame(2, $performance_data->getCacheSetCount()); $this->assertSame(0, $performance_data->getCacheDeleteCount()); $this->assertSame(2, $performance_data->getCacheTagChecksumCount()); $this->assertSame(29, $performance_data->getCacheTagIsValidCount()); $this->assertSame(0, $performance_data->getCacheTagInvalidationCount()); $this->assertSame(1, $performance_data->getStyleSheetCount()); $this->assertSame(2, $performance_data->getScriptCount()); $this->assertLessThan(90000, $performance_data->getStylesheetBytes()); $this->assertLessThan(220000, $performance_data->getScriptBytes()); // Check that the navigation toolbar is cached without any high-cardinality // cache contexts (user, route, query parameters etc.). $this->assertIsObject(\Drupal::cache('render')->get('navigation:navigation:[languages:language_interface]=en:[theme]=stark:[user.permissions]=is-admin')); } }