refreshless-8.x-1.x-dev/modules/refreshless_turbo/src/Hooks/Javascript.php
modules/refreshless_turbo/src/Hooks/Javascript.php
<?php
declare(strict_types=1);
namespace Drupal\refreshless_turbo\Hooks;
use Drupal\Core\Access\CsrfTokenGenerator;
use Drupal\Core\Asset\AttachedAssetsInterface;
use Drupal\Core\Extension\ThemeHandlerInterface;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\hux\Attribute\Alter;
use Drupal\hux\Attribute\Hook;
use Drupal\refreshless\Service\PageStateFactoryInterface;
use Drupal\refreshless\Service\RefreshlessKillSwitchInterface;
use Drupal\refreshless\Value\PageStateInterface;
use Drupal\refreshless_turbo\Value\RefreshlessAsset;
use Drupal\refreshless_turbo\Value\RequestWrapper;
use function in_array;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* JavaScript hook implementations.
*/
class Javascript {
/**
* Hook constructor; saves dependencies.
*
* @param \Drupal\Core\Access\CsrfTokenGenerator $csrfTokenGenerator
* The CSRF token generator.
*
* @param \Drupal\refreshless\Service\PageStateFactoryInterface $pageStateFactory
* The RefreshLess page state factory service.
*
* @param \Drupal\refreshless_turbo\Service\RefreshlessKillSwitchInterface $killSwitch
* The RefreshLess kill switch service.
*
* @param \Symfony\Component\HttpFoundation\RequestStack $requestStack
* The request stack.
*
* @param \Drupal\Core\Extension\ThemeHandlerInterface $themeHandler
* The theme handler.
*
* @param \Drupal\Core\Theme\ThemeManagerInterface $themeManager
* The theme manager.
*/
public function __construct(
protected readonly CsrfTokenGenerator $csrfTokenGenerator,
protected readonly PageStateFactoryInterface $pageStateFactory,
protected readonly RefreshlessKillSwitchInterface $killSwitch,
protected readonly RequestStack $requestStack,
protected readonly ThemeHandlerInterface $themeHandler,
protected readonly ThemeManagerInterface $themeManager,
) {}
#[Alter('js')]
/**
* Move all JavaScript to the <head> as recommended for Hotwire Turbo.
*
* JavaScript found in the <body> will be executed each time that page is
* loaded whereas JavaScript linked in the <head> will only be executed on a
* full page load. Since all Drupal JavaScript should be using behaviours,
* they should all correctly re-attach on every Turbo visit.
*
* @see \hook_js_alter()
*/
public function alter(
array &$javascript,
AttachedAssetsInterface $assets, LanguageInterface $language,
): void {
if ($this->killSwitch->triggered()) {
return;
}
foreach ($javascript as $filePath => &$settings) {
// Skip items that are already set to the header as they may need to be
// render blocking rather than deferred.
if ($settings['scope'] === 'header') {
continue;
}
$settings['scope'] = 'header';
if (!isset($settings['attributes']['defer'])) {
$settings['attributes']['defer'] = true;
}
}
}
#[Hook('js_settings_build')]
/**
* Output 'ajaxPageState' to enable additive JavaScript aggregation.
*
* @see https://www.drupal.org/project/refreshless/issues/3414538
* Details regarding additive JavaScript aggregation and why it's needed.
*/
public function outputPageState(
array &$settings, AttachedAssetsInterface $assets,
): void {
// Don't do anything if ajaxPageState is already present or the kill switch
// has been triggered.
if (
isset($settings['ajaxPageState']) ||
$this->killSwitch->triggered()
) {
return;
}
/** @var string The active theme's machine name. */
$activeThemeKey = $this->themeManager->getActiveTheme()->getName();
$settings['ajaxPageState'] = [
// \system_js_settings_build() builds 'libraries' for us if
// 'ajaxPageState' is present.
'theme' => $activeThemeKey,
];
// Only output the theme token if the active theme is different from the
// default theme.
//
// @see \system_js_settings_alter()
if ($activeThemeKey !== $this->themeHandler->getDefault()) {
$settings['ajaxPageState'][
'theme_token'
] = $this->csrfTokenGenerator->get($activeThemeKey);
}
}
#[Hook('js_settings_build')]
/**
* Output reload reason cookie settings.
*/
public function outputReloadReasonCookieSettings(
array &$settings, AttachedAssetsInterface $assets,
): void {
if (!isset($settings['refreshless']['reloadReasonCookie'])) {
return;
}
$cookie = &$settings['refreshless']['reloadReasonCookie'];
$cookie['name'] = RequestWrapper::getReloadReasonCookieName();
// If the path was set to anything other than null, don't auto fill it.
if ($cookie['attributes']['path'] !== null) {
return;
}
// The same thing that system_js_settings_alter() does for
// drupalSettings.path.baseUrl.
$cookie['attributes']['path'] = $this->requestStack->getMainRequest()
->getBaseUrl() . '/';
}
#[Hook('js_settings_build')]
/**
* Output page state cookie settings.
*/
public function outputPageStateCookieSettings(
array &$settings, AttachedAssetsInterface $assets,
): void {
if (!isset($settings['refreshless']['pageStateCookie'])) {
return;
}
$cookie = &$settings['refreshless']['pageStateCookie'];
$cookie['name'] = $this->pageStateFactory->getClass()::getCookieName();
// If the path was set to anything other than null, don't auto fill it.
if ($cookie['attributes']['path'] !== null) {
return;
}
// The same thing that system_js_settings_alter() does for
// drupalSettings.path.baseUrl.
$cookie['attributes']['path'] = $this->requestStack->getMainRequest()
->getBaseUrl() . '/';
}
#[Hook('js_settings_build')]
/**
* Output header settings.
*/
public function outputHeaderSettings(
array &$settings, AttachedAssetsInterface $assets,
): void {
if (
!isset($settings['refreshless']['headerName']) ||
$settings['refreshless']['headerName'] === null
) {
$settings['refreshless'][
'headerName'
] = RequestWrapper::getRequestHeaderName();
}
if (
!isset($settings['refreshless']['prefetchNotifyHeaderName']) ||
$settings['refreshless']['prefetchNotifyHeaderName'] === null
) {
$settings['refreshless'][
'prefetchNotifyHeaderName'
] = RequestWrapper::getPrefetchNotifyHeaderName();
}
}
#[Hook('js_settings_build')]
/**
* Output stylesheet settings.
*/
public function outputStylesheetSettings(
array &$settings, AttachedAssetsInterface $assets,
): void {
if (
isset($settings['refreshless']['stylesheetOrderAttributeName']) &&
$settings['refreshless']['stylesheetOrderAttributeName'] !== null
) {
return;
}
$settings['refreshless'][
'stylesheetOrderAttributeName'
] = RefreshlessAsset::getStylesheetOrderAttributeName();
}
}
