diff --git a/core/core.services.yml b/core/core.services.yml index 0b093c4669..0305f891e2 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -886,6 +886,10 @@ services: argument_resolver.default: class: Symfony\Component\HttpKernel\Controller\ArgumentResolver\DefaultValueResolver public: false + http_middleware.ajax_page_state: + class: Drupal\Core\StackMiddleware\AjaxPageState + tags: + - { name: http_middleware, priority: 500 } http_middleware.negotiation: class: Drupal\Core\StackMiddleware\NegotiationMiddleware tags: diff --git a/core/lib/Drupal/Core/Ajax/SettingsCommand.php b/core/lib/Drupal/Core/Ajax/SettingsCommand.php index ca41720dcb..5dbcaa600c 100644 --- a/core/lib/Drupal/Core/Ajax/SettingsCommand.php +++ b/core/lib/Drupal/Core/Ajax/SettingsCommand.php @@ -2,6 +2,8 @@ namespace Drupal\Core\Ajax; +use Drupal\Component\Utility\UrlHelper; + /** * AJAX command for adjusting Drupal's JavaScript settings. * @@ -53,6 +55,15 @@ public function __construct(array $settings, $merge = FALSE) { * Implements Drupal\Core\Ajax\CommandInterface:render(). */ public function render() { + if (isset($this->settings['ajax_page_state'])) { + // Get the current ajax_page_state that was passed to this request. + $ajax_page_state = \Drupal::requestStack()->getCurrentRequest()->get('ajax_page_state'); + + foreach ($this->settings as $key => $setting) { + $ajax_page_state[$key] = $setting; + } + $this->settings['ajax_page_state'] = UrlHelper::compressQueryParameter(json_encode($ajax_page_state)); + } return [ 'command' => 'settings', diff --git a/core/lib/Drupal/Core/Asset/AssetResolver.php b/core/lib/Drupal/Core/Asset/AssetResolver.php index 04a5186368..37b5b34125 100644 --- a/core/lib/Drupal/Core/Asset/AssetResolver.php +++ b/core/lib/Drupal/Core/Asset/AssetResolver.php @@ -4,6 +4,7 @@ use Drupal\Component\Utility\Crypt; use Drupal\Component\Utility\NestedArray; +use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Cache\CacheBackendInterface; use Drupal\Core\Extension\ModuleHandlerInterface; use Drupal\Core\Language\LanguageManagerInterface; @@ -335,6 +336,11 @@ public function getJsAssets(AttachedAssetsInterface $assets, $optimize, Language // Update the $assets object accordingly, so that it reflects the final // settings. $assets->setSettings($settings); + // Convert ajaxPageState to a compressed string from an array, since it is + // used by ajax.js to pass to AJAX requests as a query parameter. + if (isset($settings['ajaxPageState'])) { + $settings['ajaxPageState'] = UrlHelper::compressQueryParameter(json_encode($settings['ajaxPageState'])); + } $settings_as_inline_javascript = [ 'type' => 'setting', 'group' => JS_SETTING, diff --git a/core/lib/Drupal/Core/StackMiddleware/AjaxPageState.php b/core/lib/Drupal/Core/StackMiddleware/AjaxPageState.php new file mode 100644 index 0000000000..17ead34fa8 --- /dev/null +++ b/core/lib/Drupal/Core/StackMiddleware/AjaxPageState.php @@ -0,0 +1,42 @@ +request->has('ajax_page_state')) { + $ajax_page_state = $request->request->get('ajax_page_state'); + $ajax_page_state = json_decode(UrlHelper::uncompressQueryParameter($ajax_page_state), TRUE); + $request->request->set('ajax_page_state', $ajax_page_state); + } + if ($request->query->has('ajax_page_state')) { + $ajax_page_state = $request->query->get('ajax_page_state'); + $ajax_page_state = json_decode(UrlHelper::uncompressQueryParameter($ajax_page_state), TRUE); + $request->query->set('ajax_page_state', $ajax_page_state); + $request->request->set('ajax_page_state', $ajax_page_state); + } + return $this->httpKernel->handle($request, $type, $catch); + } + +} diff --git a/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php b/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php index ff50dc3eb0..80025daa6a 100644 --- a/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php +++ b/core/lib/Drupal/Core/Theme/AjaxBasePageNegotiator.php @@ -66,7 +66,7 @@ public function __construct(CsrfTokenGenerator $token_generator, ConfigFactoryIn */ public function applies(RouteMatchInterface $route_match) { $ajax_page_state = $this->requestStack->getCurrentRequest()->get('ajax_page_state'); - return !empty($ajax_page_state['theme']) && isset($ajax_page_state['theme_token']); + return !empty($ajax_page_state['theme']); } /** diff --git a/core/misc/ajax.js b/core/misc/ajax.js index 307fd81163..a601cba41d 100644 --- a/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -824,13 +824,11 @@ // Allow Drupal to return new JavaScript and CSS files to load without // returning the ones already loaded. + // @see \Drupal\Core\StackMiddleWare\AjaxPageState // @see \Drupal\Core\Theme\AjaxBasePageNegotiator // @see \Drupal\Core\Asset\LibraryDependencyResolverInterface::getMinimalRepresentativeSubset() // @see system_js_settings_alter() - const pageState = drupalSettings.ajaxPageState; - options.data['ajax_page_state[theme]'] = pageState.theme; - options.data['ajax_page_state[theme_token]'] = pageState.theme_token; - options.data['ajax_page_state[libraries]'] = pageState.libraries; + options.data.ajax_page_state = drupalSettings.ajaxPageState; }; /** diff --git a/core/modules/system/tests/src/Functional/Render/AjaxPageStateTest.php b/core/modules/system/tests/src/Functional/Render/AjaxPageStateTest.php index 005769d38f..9dbf04b6d1 100644 --- a/core/modules/system/tests/src/Functional/Render/AjaxPageStateTest.php +++ b/core/modules/system/tests/src/Functional/Render/AjaxPageStateTest.php @@ -2,6 +2,7 @@ namespace Drupal\Tests\system\Functional\Render; +use Drupal\Component\Utility\UrlHelper; use Drupal\Tests\BrowserTestBase; /** @@ -69,9 +70,7 @@ public function testDrupalSettingsIsNotLoaded() { [ "query" => [ - 'ajax_page_state' => [ - 'libraries' => 'core/drupalSettings', - ], + 'ajax_page_state' => UrlHelper::compressQueryParameter(json_encode(['libraries' => 'core/drupalSettings'])), ], ] ); @@ -90,7 +89,7 @@ public function testDrupalSettingsIsNotLoaded() { */ public function testMultipleLibrariesAreNotLoaded() { $this->drupalGet('node', - ['query' => ['ajax_page_state' => ['libraries' => 'core/drupal,core/drupalSettings']]] + ['query' => ['ajax_page_state' => UrlHelper::compressQueryParameter(json_encode(['libraries' => 'core/drupal,core/drupalSettings']))]] ); $this->assertSession()->statusCodeEquals(200); // The drupal library from core should be excluded from loading. diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php index be8dae2657..49de1f4c37 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php @@ -2,6 +2,7 @@ namespace Drupal\FunctionalJavascriptTests\Ajax; +use Drupal\Component\Utility\UrlHelper; use Drupal\FunctionalJavascriptTests\WebDriverTestBase; /** @@ -37,7 +38,7 @@ public function testAjaxWithAdminRoute() { $assert = $this->assertSession(); $assert->pageTextContains('Current theme: claro'); - // Now click the modal, which should also use the admin theme. + // Now click the modal, which should use the front-end theme. $this->drupalGet('ajax-test/dialog'); $assert->pageTextNotContains('Current theme: stable9'); $this->clickLink('Link 8 (ajax)'); @@ -59,32 +60,40 @@ public function testDrupalSettingsCachingRegression() { // Insert a fake library into the already loaded library settings. $fake_library = 'fakeLibrary/fakeLibrary'; - $session->evaluateScript("drupalSettings.ajaxPageState.libraries = drupalSettings.ajaxPageState.libraries + ',$fake_library';"); - - $libraries = $session->evaluateScript('drupalSettings.ajaxPageState.libraries'); + $ajax_page_state = $session->evaluateScript("drupalSettings.ajaxPageState"); + $ajax_page_state = json_decode(UrlHelper::uncompressQueryParameter($ajax_page_state), 1); + $ajax_page_state['libraries'] = $ajax_page_state['libraries'] . $fake_library; + $ajax_page_state = UrlHelper::compressQueryParameter(json_encode($ajax_page_state)); + $session->evaluateScript("drupalSettings.ajaxPageState = '$ajax_page_state';"); + $ajax_page_state = $session->evaluateScript("drupalSettings.ajaxPageState"); + $ajax_page_state = json_decode(UrlHelper::uncompressQueryParameter($ajax_page_state), 1); // Test that the fake library is set. - $this->assertStringContainsString($fake_library, $libraries); + $this->assertStringContainsString($fake_library, $ajax_page_state['libraries']); // Click on the AJAX link. $this->clickLink('Link 8 (ajax)'); $assert->assertWaitOnAjaxRequest(); // Test that the fake library is still set after the AJAX call. - $libraries = $session->evaluateScript('drupalSettings.ajaxPageState.libraries'); - $this->assertStringContainsString($fake_library, $libraries); + $ajax_page_state = $session->evaluateScript("drupalSettings.ajaxPageState"); + $ajax_page_state = json_decode(UrlHelper::uncompressQueryParameter($ajax_page_state), 1); + // Test that the fake library is set. + $this->assertStringContainsString($fake_library, $ajax_page_state['libraries']); // Reload the page, this should reset the loaded libraries and remove the // fake library. $this->drupalGet('ajax-test/dialog'); - $libraries = $session->evaluateScript('drupalSettings.ajaxPageState.libraries'); - $this->assertStringNotContainsString($fake_library, $libraries); + $ajax_page_state = $session->evaluateScript("drupalSettings.ajaxPageState"); + $ajax_page_state = json_decode(UrlHelper::uncompressQueryParameter($ajax_page_state), 1); + $this->assertStringNotContainsString($fake_library, $ajax_page_state['libraries']); // Click on the AJAX link again, and the libraries should still not contain // the fake library. $this->clickLink('Link 8 (ajax)'); $assert->assertWaitOnAjaxRequest(); - $libraries = $session->evaluateScript('drupalSettings.ajaxPageState.libraries'); - $this->assertStringNotContainsString($fake_library, $libraries); + $ajax_page_state = $session->evaluateScript("drupalSettings.ajaxPageState"); + $ajax_page_state = json_decode(UrlHelper::uncompressQueryParameter($ajax_page_state), 1); + $this->assertStringNotContainsString($fake_library, $ajax_page_state['libraries']); } /** diff --git a/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php b/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php index e3f011c554..91d69bc544 100644 --- a/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/WebDriverTestBase.php @@ -3,6 +3,7 @@ namespace Drupal\FunctionalJavascriptTests; use Behat\Mink\Exception\DriverException; +use Drupal\Component\Utility\UrlHelper; use Drupal\Tests\BrowserTestBase; use PHPUnit\Runner\BaseTestRunner; use Symfony\Component\DependencyInjection\ContainerInterface; @@ -204,12 +205,15 @@ public function assertSession($name = NULL) { * current values of drupalSettings, capturing all changes made via javascript * after the page was loaded. * + * @param bool $uncompress_ajax_page_state + * Whether to uncompress ajax_page_state, defaults to TRUE. + * * @return array * The Drupal javascript settings array. * * @see \Drupal\Tests\BrowserTestBase::getDrupalSettings() */ - protected function getDrupalSettings() { + protected function getDrupalSettings(bool $uncompress_ajax_page_state = TRUE) { $script = <<getSession()->evaluateScript($script) ?: []; + $settings = $this->getSession()->evaluateScript($script) ?: []; + if (isset($settings['ajaxPageState']) && $uncompress_ajax_page_state) { + $settings['ajaxPageState'] = json_decode(UrlHelper::uncompressQueryParameter($settings['ajaxPageState']), 1); + } + return $settings; } /** diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php index 3d3e41c51d..86bf74010f 100644 --- a/core/tests/Drupal/Tests/BrowserTestBase.php +++ b/core/tests/Drupal/Tests/BrowserTestBase.php @@ -8,6 +8,7 @@ use Behat\Mink\Selector\SelectorsHandler; use Behat\Mink\Session; use Drupal\Component\Serialization\Json; +use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Database\Database; use Drupal\Core\Test\FunctionalTestSetupTrait; use Drupal\Core\Test\TestSetupTrait; @@ -626,13 +627,20 @@ protected function config($name) { /** * Gets the JavaScript drupalSettings variable for the currently-loaded page. * + * @param bool $uncompress_ajax_page_state + * Whether to uncompress ajax_page_state, defaults to TRUE. + * * @return array * The JSON decoded drupalSettings value from the current page. */ - protected function getDrupalSettings() { + protected function getDrupalSettings(bool $uncompress_ajax_page_state = TRUE) { $html = $this->getSession()->getPage()->getContent(); if (preg_match('@@', $html, $matches)) { - return Json::decode($matches[1]); + $settings = Json::decode($matches[1]); + if (isset($settings['ajaxPageState']) && $uncompress_ajax_page_state) { + $settings['ajaxPageState'] = json_decode(UrlHelper::uncompressQueryParameter($settings['ajaxPageState']), 1); + } + return $settings; } return []; }