Update to Drupal 8.1.8. For more information, see https://www.drupal.org/project/drupal/releases/8.1.8

This commit is contained in:
Pantheon Automation 2016-08-03 13:22:33 -07:00 committed by Greg Anderson
parent e9f047ccf8
commit f9f23cdf38
312 changed files with 6751 additions and 1546 deletions

View file

@ -89,7 +89,10 @@
}, interval);
}
var interval = 200;
// The frequency with which to check for newly arrived BigPipe placeholders.
// Hence 50 ms means we check 20 times per second. Setting this to 100 ms or
// more would cause the user to see content appear noticeably slower.
var interval = drupalSettings.bigPipeInterval || 50;
// The internal ID to contain the watcher service.
var timeoutID;

View file

@ -126,9 +126,15 @@ class BigPipe implements BigPipeInterface {
// Reopen it for the duration that we are rendering placeholders.
$this->session->start();
list($pre_body, $post_body) = explode('</body>', $content, 2);
// Find the closing </body> tag and get the strings before and after. But be
// careful to use the latest occurrence of the string "</body>", to ensure
// that strings in inline JavaScript or CDATA sections aren't used instead.
$parts = explode('</body>', $content);
$post_body = array_pop($parts);
$pre_body = implode('', $parts);
$this->sendPreBody($pre_body, $nojs_placeholders, $cumulative_assets);
$this->sendPlaceholders($placeholders, $this->getPlaceholderOrder($pre_body), $cumulative_assets);
$this->sendPlaceholders($placeholders, $this->getPlaceholderOrder($pre_body, $placeholders), $cumulative_assets);
$this->sendPostBody($post_body);
// Close the session again.
@ -528,6 +534,9 @@ EOF;
*
* @param string $html
* HTML markup.
* @param array $placeholders
* Associative array; the BigPipe placeholders. Keys are the BigPipe
* placeholder IDs.
*
* @return array
* Indexed array; the order in which the BigPipe placeholders must be sent.
@ -535,18 +544,45 @@ EOF;
* placeholders are kept: if the same placeholder occurs multiple times, we
* only keep the first occurrence.
*/
protected function getPlaceholderOrder($html) {
protected function getPlaceholderOrder($html, $placeholders) {
$fragments = explode('<div data-big-pipe-placeholder-id="', $html);
array_shift($fragments);
$order = [];
$placeholder_ids = [];
foreach ($fragments as $fragment) {
$t = explode('"></div>', $fragment, 2);
$placeholder = $t[0];
$order[] = $placeholder;
$placeholder_id = $t[0];
$placeholder_ids[] = $placeholder_id;
}
$placeholder_ids = array_unique($placeholder_ids);
// The 'status messages' placeholder needs to be special cased, because it
// depends on global state that can be modified when other placeholders are
// being rendered: any code can add messages to render.
// This violates the principle that each lazy builder must be able to render
// itself in isolation, and therefore in any order. However, we cannot
// change the way drupal_set_message() works in the Drupal 8 cycle. So we
// have to accommodate its special needs.
// Allowing placeholders to be rendered in a particular order (in this case:
// last) would violate this isolation principle. Thus a monopoly is granted
// to this one special case, with this hard-coded solution.
// @see \Drupal\Core\Render\Element\StatusMessages
// @see \Drupal\Core\Render\Renderer::replacePlaceholders()
// @see https://www.drupal.org/node/2712935#comment-11368923
$message_placeholder_ids = [];
foreach ($placeholders as $placeholder_id => $placeholder_element) {
if (isset($placeholder_element['#lazy_builder']) && $placeholder_element['#lazy_builder'][0] === 'Drupal\Core\Render\Element\StatusMessages::renderMessages') {
$message_placeholder_ids[] = $placeholder_id;
}
}
return array_unique($order);
// Return placeholder IDs in DOM order, but with the 'status messages'
// placeholders at the end, if they are present.
$ordered_placeholder_ids = array_merge(
array_diff($placeholder_ids, $message_placeholder_ids),
array_intersect($placeholder_ids, $message_placeholder_ids)
);
return $ordered_placeholder_ids;
}
}

View file

@ -50,7 +50,7 @@ class BigPipePlaceholderTestCases {
// 1. Real-world example of HTML placeholder.
$status_messages = new BigPipePlaceholderTestCase(
[], //['#type' => 'status_messages'],
['#type' => 'status_messages'],
'<drupal-render-placeholder callback="Drupal\Core\Render\Element\StatusMessages::renderMessages" arguments="0" token="a8c34b5e"></drupal-render-placeholder>',
[
'#lazy_builder' => [
@ -110,7 +110,7 @@ class BigPipePlaceholderTestCases {
'command' => 'insert',
'method' => 'replaceWith',
'selector' => '[data-big-pipe-placeholder-id="callback=Drupal%5CCore%5CRender%5CElement%5CStatusMessages%3A%3ArenderMessages&args[0]&token=a8c34b5e"]',
'data' => "\n" . ' <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . ' <h2 class="visually-hidden">Status message</h2>' . "\n" . ' Hello from BigPipe!' . "\n" . ' </div>' . "\n \n",
'data' => "\n" . ' <div role="contentinfo" aria-label="Status message" class="messages messages--status">' . "\n" . ' <h2 class="visually-hidden">Status message</h2>' . "\n" . ' Hello from BigPipe!' . "\n" . ' </div>' . "\n ",
'settings' => NULL,
],
];

View file

@ -170,6 +170,11 @@ class BigPipeTest extends WebTestBase {
$cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId => Json::encode($cases['edge_case__html_non_lazy_builder']->embeddedAjaxResponseCommands),
$cases['exception__lazy_builder']->bigPipePlaceholderId => NULL,
$cases['exception__embedded_response']->bigPipePlaceholderId => NULL,
], [
0 => $cases['edge_case__html_non_lazy_builder']->bigPipePlaceholderId,
// The 'html' case contains the 'status messages' placeholder, which is
// always rendered last.
1 => $cases['html']->bigPipePlaceholderId,
]);
$this->assertRaw('</body>', 'Closing body tag present.');
@ -183,7 +188,7 @@ class BigPipeTest extends WebTestBase {
$records = db_query('SELECT * FROM {watchdog} ORDER BY wid DESC LIMIT 2')->fetchAll();
$this->assertEqual(RfcLogLevel::ERROR, $records[0]->severity);
$this->assertTrue(FALSE !== strpos((string) unserialize($records[0]->variables)['@message'], 'Oh noes!'));
$this->assertEqual(RfcLogLevel::ERROR, $records[0]->severity);
$this->assertEqual(RfcLogLevel::ERROR, $records[1]->severity);
$this->assertTrue(FALSE !== strpos((string) unserialize($records[1]->variables)['@message'], 'You are not allowed to say llamas are not cool!'));
// Verify that 4xx responses work fine. (4xx responses are handled by
@ -250,7 +255,7 @@ class BigPipeTest extends WebTestBase {
$this->assertNoRaw(BigPipe::STOP_SIGNAL, 'BigPipe stop signal absent.');
$this->pass('Verifying BigPipe assets are absent…', 'Debug');
$this->assertFalse(empty($this->getDrupalSettings()), 'drupalSettings and BigPipe asset library absent.');
$this->assertTrue(!isset($this->getDrupalSettings()['bigPipePlaceholderIds']) && empty($this->getDrupalSettings()['ajaxPageState']), 'BigPipe drupalSettings and BigPipe asset library absent.');
$this->assertRaw('</body>', 'Closing body tag present.');
// Verify that 4xx responses work fine. (4xx responses are handled by
@ -336,8 +341,11 @@ class BigPipeTest extends WebTestBase {
*
* @param array $expected_big_pipe_placeholders
* Keys: BigPipe placeholder IDs. Values: expected AJAX response.
* @param array $expected_big_pipe_placeholder_stream_order
* Keys: BigPipe placeholder IDs. Values: expected AJAX response. Keys are
* defined in the order that they are expected to be rendered & streamed.
*/
protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholders) {
protected function assertBigPipePlaceholders(array $expected_big_pipe_placeholders, array $expected_big_pipe_placeholder_stream_order) {
$this->pass('Verifying BigPipe placeholders & replacements…', 'Debug');
$this->assertSetsEqual(array_keys($expected_big_pipe_placeholders), explode(' ', $this->drupalGetHeader('BigPipe-Test-Placeholders')));
$placeholder_positions = [];
@ -364,9 +372,14 @@ class BigPipeTest extends WebTestBase {
}
ksort($placeholder_positions, SORT_NUMERIC);
$this->assertEqual(array_keys($expected_big_pipe_placeholders), array_values($placeholder_positions));
$this->assertEqual(count($expected_big_pipe_placeholders), preg_match_all('/' . preg_quote('<div data-big-pipe-placeholder-id="', '/') . '/', $this->getRawContent()));
$expected_big_pipe_placeholders_with_replacements = array_filter($expected_big_pipe_placeholders);
$this->assertEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
$placeholders = array_map(function(\SimpleXMLElement $element) { return (string) $element['data-big-pipe-placeholder-id']; }, $this->cssSelect('[data-big-pipe-placeholder-id]'));
$this->assertEqual(count($expected_big_pipe_placeholders), count(array_unique($placeholders)));
$expected_big_pipe_placeholders_with_replacements = [];
foreach ($expected_big_pipe_placeholder_stream_order as $big_pipe_placeholder_id) {
$expected_big_pipe_placeholders_with_replacements[$big_pipe_placeholder_id] = $expected_big_pipe_placeholders[$big_pipe_placeholder_id];
}
$this->assertEqual($expected_big_pipe_placeholders_with_replacements, array_filter($expected_big_pipe_placeholders));
$this->assertSetsEqual(array_keys($expected_big_pipe_placeholders_with_replacements), array_values($placeholder_replacement_positions));
$this->assertEqual(count($expected_big_pipe_placeholders_with_replacements), preg_match_all('/' . preg_quote('<script type="application/vnd.drupal-ajax" data-big-pipe-replacement-for-placeholder-with-id="', '/') . '/', $this->getRawContent()));
$this->pass('Verifying BigPipe start/stop signals…', 'Debug');

View file

@ -0,0 +1,6 @@
name: 'BigPipe regression test'
type: module
description: 'Support module for BigPipe regression testing.'
package: Testing
version: VERSION
core: 8.x

View file

@ -0,0 +1,6 @@
big_pipe_regression_test.2678662:
path: '/big_pipe_regression_test/2678662'
defaults:
_controller: '\Drupal\big_pipe_regression_test\BigPipeRegressionTestController::regression2678662'
requirements:
_access: 'TRUE'

View file

@ -0,0 +1,20 @@
<?php
namespace Drupal\big_pipe_regression_test;
use Drupal\big_pipe\Render\BigPipeMarkup;
class BigPipeRegressionTestController {
const MARKER_2678662 = '<script>var hitsTheFloor = "</body>";</script>';
/**
* @see \Drupal\Tests\big_pipe\FunctionalJavascript\BigPipeRegressionTest::testMultipleBodies_2678662()
*/
public function regression2678662() {
return [
'#markup' => BigPipeMarkup::create(self::MARKER_2678662),
];
}
}

View file

@ -2,10 +2,13 @@
namespace Drupal\Tests\big_pipe\FunctionalJavascript;
use Drupal\big_pipe\Render\BigPipe;
use Drupal\big_pipe_regression_test\BigPipeRegressionTestController;
use Drupal\comment\CommentInterface;
use Drupal\comment\Entity\Comment;
use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
use Drupal\comment\Tests\CommentTestTrait;
use Drupal\Core\Url;
use Drupal\editor\Entity\Editor;
use Drupal\filter\Entity\FilterFormat;
use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
@ -27,13 +30,8 @@ class BigPipeRegressionTest extends JavascriptTestBase {
* {@inheritdoc}
*/
public static $modules = [
'node',
'comment',
'big_pipe',
'history',
'editor',
'ckeditor',
'filter',
'big_pipe_regression_test',
];
/**
@ -53,6 +51,8 @@ class BigPipeRegressionTest extends JavascriptTestBase {
* @see https://www.drupal.org/node/2698811
*/
public function testCommentForm_2698811() {
$this->assertTrue($this->container->get('module_installer')->install(['comment', 'history', 'ckeditor'], TRUE), 'Installed modules.');
// Ensure an `article` node type exists.
$this->createContentType(['type' => 'article']);
$this->addDefaultCommentField('node', 'article');
@ -114,4 +114,74 @@ JS;
$this->assertJsCondition($javascript);
}
/**
* Ensure BigPipe works despite inline JS containing the string "</body>".
*
* @see https://www.drupal.org/node/2678662
*/
public function testMultipleClosingBodies_2678662() {
$this->assertTrue($this->container->get('module_installer')->install(['render_placeholder_message_test'], TRUE), 'Installed modules.');
$this->drupalLogin($this->drupalCreateUser());
$this->drupalGet(Url::fromRoute('big_pipe_regression_test.2678662'));
// Confirm that AJAX behaviors were instantiated, if not, this points to a
// JavaScript syntax error.
$javascript = <<<JS
(function(){
return Object.keys(Drupal.ajax.instances).length > 0;
}());
JS;
$this->assertJsCondition($javascript);
// Besides verifying there is no JavaScript syntax error, also verify the
// HTML structure.
$this->assertSession()
->responseContains(BigPipe::STOP_SIGNAL . "\n\n\n</body></html>", 'The BigPipe stop signal is present just before the closing </body> and </html> tags.');
$js_code_until_closing_body_tag = substr(BigPipeRegressionTestController::MARKER_2678662, 0, strpos(BigPipeRegressionTestController::MARKER_2678662, '</body>'));
$this->assertSession()
->responseNotContains($js_code_until_closing_body_tag . "\n" . BigPipe::START_SIGNAL, 'The BigPipe start signal does NOT start at the closing </body> tag string in an inline script.');
}
/**
* Ensure messages set in placeholders always appear.
*
* @see https://www.drupal.org/node/2712935
*/
public function testMessages_2712935() {
$this->assertTrue($this->container->get('module_installer')->install(['render_placeholder_message_test'], TRUE), 'Installed modules.');
$this->drupalLogin($this->drupalCreateUser());
$messages_markup = '<div role="contentinfo" aria-label="Status message"';
$test_routes = [
// Messages placeholder rendered first.
'render_placeholder_message_test.first',
// Messages placeholder rendered after one, before another.
'render_placeholder_message_test.middle',
// Messages placeholder rendered last.
'render_placeholder_message_test.last',
];
$assert = $this->assertSession();
foreach ($test_routes as $route) {
// Verify that we start off with zero messages queued.
$this->drupalGet(Url::fromRoute('render_placeholder_message_test.queued'));
$assert->responseNotContains($messages_markup);
// Verify the test case at this route behaves as expected.
$this->drupalGet(Url::fromRoute($route));
$assert->elementContains('css', 'p.logged-message:nth-of-type(1)', 'Message: P1');
$assert->elementContains('css', 'p.logged-message:nth-of-type(2)', 'Message: P2');
$assert->responseContains($messages_markup);
$assert->elementExists('css', 'div[aria-label="Status message"] ul');
$assert->elementContains('css', 'div[aria-label="Status message"] ul li:nth-of-type(1)', 'P1');
$assert->elementContains('css', 'div[aria-label="Status message"] ul li:nth-of-type(2)', 'P2');
// Verify that we end with all messages printed, hence again zero queued.
$this->drupalGet(Url::fromRoute('render_placeholder_message_test.queued'));
$assert->responseNotContains($messages_markup);
}
}
}