diff -u b/core/misc/ajax.js b/core/misc/ajax.js --- b/core/misc/ajax.js +++ b/core/misc/ajax.js @@ -1032,26 +1032,38 @@ // will be wrapped with a element. This also gives themers the // possibility to style the response. var $newContent; - var onlyElementNodes; + // We use the trimmed data to be able to detect cases when the response + // has only top-level elements or comment nodes with extra whitespace + // around. In that case whitespaces are removed and the response + // should not be wrapped. + // For exemple: + // '
content
' is equivalent to '
content
' and + // no extra wrapper will be added. var trimedData = response.data.trim(); + // List of DOM nodes contained in the response for processing. + var responseDataNodes; - // Tranform the response data in an array of DOM Nodes. - var responseDataNodes = $.parseHTML(trimedData, true); - // When response.data is empty (to empty an element for exemple) - // parseHTML returns null, we need to cheat a little. - if (responseDataNodes === null && trimedData === '') { - onlyElementNodes = false; - responseDataNodes = ['']; + // When 'data' is empty or is only whitespace, manually create the node + // array because parseHTML would return null. + if (trimedData === '') { + responseDataNodes = [document.createTextNode(response.data)]; } else { - onlyElementNodes = responseDataNodes.every(function (element) { - return element.nodeType === Node.ELEMENT_NODE || element.nodeType === Node.COMMENT_NODE; - }); + responseDataNodes = $.parseHTML(trimedData, true); } + // Check every node for it's type to decide if wrapping is necessary. + var onlyElementNodes = responseDataNodes.every(function (element) { + return element.nodeType === Node.ELEMENT_NODE || element.nodeType === Node.COMMENT_NODE; + }); - // If only NODE_ELEMENT or COMMENT_NODE elements are returned - // skip wrap processing. - if (!onlyElementNodes) { + // When there are only element and/or comment nodes in the reponse, no + // extra wrapping necessary. + if (onlyElementNodes) { + $newContent = $(trimedData); + } + // When there are other types of top-level nodes, the response need to be + // wrapped. + else { // If response.data contains only one TEXT_ELEMENT if the target element // is not styled as a block, response.data will be wrapped with SPAN. if (responseDataNodes.length === 1 || ($wrapper.css('display') !== 'block')) { @@ -1060,10 +1072,8 @@ else { $newContent = $('
'); } - $newContent.html(responseDataNodes); - } - else { - $newContent = $(responseDataNodes); + // Use reponse.data to keep whitespace as-is. + $newContent.html(response.data); } // If removing content from the wrapper, detach behaviors first. diff -u b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php --- b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php +++ b/core/modules/system/tests/modules/ajax_test/src/Controller/AjaxTestController.php @@ -291,6 +291,8 @@ 'comment-not-wrapped' => '
comment-not-wrapped
', 'mixed' => ' foo foo bar

some string

additional not wrapped strings,

final string

', 'top-level-only' => '
element #1
element #2
', + 'top-level-only-pre-whitespace' => '
element #1
element #2
', + 'top-level-only-middle-whitespace' => '
element #1
element #2
', 'inline' => 'inline
BLOCK LEVEL
', 'empty' => '', ]; diff -u b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php --- b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php +++ b/core/tests/Drupal/FunctionalJavascriptTests/Ajax/AjaxTest.php @@ -136,7 +136,7 @@ [ 'method' => 'html', 'render_type' => 'mixed', - 'expected' => '
foo foo bar

some string

additional not wrapped strings,

final string

', + 'expected' => '
foo foo bar

some string

additional not wrapped strings,

final string

', ], // Test that when the response has only top-level node elements, they // are processed properly without extra wrapping. @@ -145,6 +145,20 @@ 'render_type' => 'top-level-only', 'expected' => '
element #1
element #2
', ], + // Test that whitespaces at start or end don't wrap the response when + // there are multiple top-level nodes. + [ + 'method' => 'html', + 'render_type' => 'top-level-only-pre-whitespace', + 'expected' => '
element #1
element #2
', + ], + // Test that when there are whitespaces between top-level nodes, the + // response is wrapped. + [ + 'method' => 'html', + 'render_type' => 'top-level-only-middle-whitespace', + 'expected' => '
element #1
element #2
', + ], // Test that inline response data. [ 'render_type' => 'inline',