Unverified Commit ef68ad6f authored by lauriii's avatar lauriii
Browse files

Issue #2973921 by tedbow, bnjmnm, yogeshmpawar, Kristen Pol, lauriii,...

Issue #2973921 by tedbow, bnjmnm, yogeshmpawar, Kristen Pol, lauriii, alwaysworking, xjm, andrewmacpherson: Interactive controls inside preview block in the Layout Builder form should be disabled
parent 72e0a4d8
......@@ -51,10 +51,6 @@
text-align: center;
}
.layout-section .layout-builder--layout__region .block {
padding: 1.5em;
}
.layout-section .remove-section {
position: relative;
background: url(../../../misc/icons/bebebe/ex.svg) #fff center center / 16px 16px no-repeat;
......@@ -75,6 +71,14 @@
background-image: url(../../../misc/icons/787878/ex.svg);
}
.layout-builder-block {
padding: 1.5em;
}
.layout-builder-block [tabindex="-1"] {
pointer-events: none;
}
#drupal-off-canvas .layout-selection li {
display: block;
padding-bottom: 1em;
......
......@@ -160,4 +160,39 @@
});
},
};
/**
* Disables interactive elements in previewed blocks.
*
* @type {Drupal~behavior}
*
* @prop {Drupal~behaviorAttach} attach
* Attach disabling interactive elements behavior to the Layout Builder UI.
*/
behaviors.layoutBuilderDisableInteractiveElements = {
attach() {
// Disable interactive elements inside preview blocks.
const $blocks = $('#layout-builder [data-layout-block-uuid]');
$blocks.find('input, textarea, select').prop('disabled', true);
$blocks.find('a').on('click mouseup touchstart', e => {
e.preventDefault();
e.stopPropagation();
});
/*
* In preview blocks, remove from the tabbing order all input elements
* and elements specifically assigned a tab index, other than those
* related to contextual links.
*/
$blocks
.find(
'button, [href], input, select, textarea, iframe, [tabindex]:not([tabindex="-1"]):not(.tabbable)',
)
.not(
(index, element) =>
$(element).closest('[data-contextual-id]').length > 0,
)
.attr('tabindex', -1);
},
};
})(jQuery, Drupal);
......@@ -76,4 +76,19 @@
});
}
};
behaviors.layoutBuilderDisableInteractiveElements = {
attach: function attach() {
var $blocks = $('#layout-builder [data-layout-block-uuid]');
$blocks.find('input, textarea, select').prop('disabled', true);
$blocks.find('a').on('click mouseup touchstart', function (e) {
e.preventDefault();
e.stopPropagation();
});
$blocks.find('button, [href], input, select, textarea, iframe, [tabindex]:not([tabindex="-1"]):not(.tabbable)').not(function (index, element) {
return $(element).closest('[data-contextual-id]').length > 0;
}).attr('tabindex', -1);
}
};
})(jQuery, Drupal);
\ No newline at end of file
......@@ -40,6 +40,7 @@ function layout_builder_help($route_name, RouteMatchInterface $route_match) {
else {
$output .= '<p>' . t('To manage other areas of the page, use the block administration page.') . '</p>';
}
$output .= '<p>' . t('Forms and links inside the content of the layout builder tool have been disabled.') . '</p>';
return $output;
}
......
......@@ -106,6 +106,7 @@ public function onBuildRender(SectionComponentBuildRenderArrayEvent $event) {
'#base_plugin_id' => $block->getBaseId(),
'#derivative_plugin_id' => $block->getDerivativeId(),
'#weight' => $event->getComponent()->getWeight(),
'#attributes' => ['class' => ['layout-builder-block']],
'content' => $content,
];
if ($is_content_empty && $is_placeholder_ready) {
......
/**
* Remove all transitions for testing.
*/
* {
/* CSS transitions. */
transition: none !important;
}
# @todo Remove this module & its usages in https://www.drupal.org/node/2901792.
name: 'Layout Builder Test Disable Animations'
type: module
description: 'Disables CSS animations for tests '
package: Testing
version: VERSION
core: 8.x
layout_builder.disable_css_transitions:
css:
component:
css/layout_builder_test_css_transitions.test.css: {}
<?php
/**
* @file
* Attaches CSS to disable animations.
*
* CSS animations cause intermittent errors in some tests.
*/
/**
* Implements hook_page_attachments().
*/
function layout_builder_test_css_transitions_page_attachments(array &$attachments) {
// Unconditionally attach an asset to the page.
$attachments['#attached']['library'][] = 'layout_builder_test_css_transitions/layout_builder.disable_css_transitions';
}
<?php
namespace Drupal\Tests\layout_builder\FunctionalJavascript;
use Behat\Mink\Element\NodeElement;
use Drupal\block_content\Entity\BlockContent;
use Drupal\block_content\Entity\BlockContentType;
use Drupal\Component\Render\FormattableMarkup;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
use WebDriver\Exception\UnknownError;
/**
* Tests the Layout Builder disables interactions of rendered blocks.
*
* @group layout_builder
*/
class LayoutBuilderDisableInteractionsTest extends WebDriverTestBase {
use ContextualLinkClickTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'block_content',
'filter',
'filter_test',
'layout_builder',
'node',
'search',
'contextual',
'layout_builder_test_css_transitions',
];
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
// @todo The Layout Builder UI relies on local tasks; fix in
// https://www.drupal.org/project/drupal/issues/2917777.
$this->drupalPlaceBlock('local_tasks_block');
$this->createContentType(['type' => 'bundle_with_section_field']);
$this->createNode([
'type' => 'bundle_with_section_field',
'title' => 'The first node title',
'body' => [
[
'value' => 'Node body',
],
],
]);
$bundle = BlockContentType::create([
'id' => 'basic',
'label' => 'Basic block',
'revision' => 1,
]);
$bundle->save();
block_content_add_body_field($bundle->id());
BlockContent::create([
'type' => 'basic',
'info' => 'Block with link',
'body' => [
// Create a link that should be disabled in Layout Builder preview.
'value' => '<a id="link-that-should-be-disabled" href="/search/node">Take me away</a>',
'format' => 'full_html',
],
])->save();
BlockContent::create([
'type' => 'basic',
'info' => 'Block with iframe',
'body' => [
// Add iframe that should be non-interactive in Layout Builder preview.
'value' => '<iframe id="iframe-that-should-be-disabled" width="560" height="315" src="https://www.youtube.com/embed/gODZzSOelss" frameborder="0"></iframe>',
'format' => 'full_html',
],
])->save();
}
/**
* Tests that forms and links are disabled in the Layout Builder preview.
*/
public function testFormsLinksDisabled() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->drupalLogin($this->drupalCreateUser([
'configure any layout',
'administer node display',
'administer node fields',
'search content',
'access contextual links',
]));
$field_ui_prefix = 'admin/structure/types/manage/bundle_with_section_field';
$this->drupalPostForm("$field_ui_prefix/display", ['layout[enabled]' => TRUE], 'Save');
$assert_session->linkExists('Manage layout');
$this->clickLink('Manage layout');
// Add a block with a form, another with a link, and one with an iframe.
$this->addBlock('Search form', '#layout-builder .search-block-form');
$this->addBlock('Block with link', '#link-that-should-be-disabled');
$this->addBlock('Block with iframe', '#iframe-that-should-be-disabled');
// Ensure the links and forms are disabled using the defaults before the
// layout is saved.
$this->assertLinksFormIframeNotInteractive();
$page->pressButton('Save layout');
$this->clickLink('Manage layout');
// Ensure the links and forms are disabled using the defaults.
$this->assertLinksFormIframeNotInteractive();
// Ensure contextual links were not disabled.
$this->assertContextualLinksClickable();
$this->drupalPostForm("$field_ui_prefix/display/default", ['layout[allow_custom]' => TRUE], 'Save');
$this->drupalGet('node/1/layout');
// Ensure the links and forms are also disabled in using the override.
$this->assertLinksFormIframeNotInteractive();
// Ensure contextual links were not disabled.
$this->assertContextualLinksClickable();
}
/**
* Adds a block in the Layout Builder.
*
* @param string $block_link_text
* The link text to add the block.
* @param string $rendered_locator
* The CSS locator to confirm the block was rendered.
*/
protected function addBlock($block_link_text, $rendered_locator) {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
// Add a new block.
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#layout-builder a:contains(\'Add Block\')'));
$this->clickLink('Add Block');
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas'));
$assert_session->assertWaitOnAjaxRequest();
$assert_session->linkExists($block_link_text);
$this->clickLink($block_link_text);
// Wait for off-canvas dialog to reopen with block form.
$this->assertNotEmpty($assert_session->waitForElementVisible('css', ".layout-builder-add-block"));
$assert_session->assertWaitOnAjaxRequest();
$page->pressButton('Add Block');
// Wait for block form to be rendered in the Layout Builder.
$this->assertNotEmpty($assert_session->waitForElement('css', $rendered_locator));
}
/**
* Checks if element is unclickable.
*
* @param \Behat\Mink\Element\NodeElement $element
* Element being checked for.
*/
protected function assertElementUnclickable(NodeElement $element) {
try {
$element->click();
$tag_name = $element->getTagName();
$this->fail(new FormattableMarkup("@tag_name was clickable when it shouldn't have been", ['@tag_name' => $tag_name]));
}
catch (UnknownError $e) {
$this->assertContains('is not clickable at point', $e->getMessage());
}
}
/**
* Asserts that forms, links, and iframes in preview are non-interactive.
*/
protected function assertLinksFormIframeNotInteractive() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->assertNotEmpty($assert_session->waitForElement('css', '.block-search'));
$searchButton = $assert_session->buttonExists('Search');
$this->assertElementUnclickable($searchButton);
$assert_session->linkExists('Take me away');
$this->assertElementUnclickable($page->findLink('Take me away'));
$iframe = $assert_session->elementExists('css', '#iframe-that-should-be-disabled');
$this->assertElementUnclickable($iframe);
}
/**
* Confirms that Layout Builder contextual links remain active.
*/
protected function assertContextualLinksClickable() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$this->drupalGet($this->getUrl());
$this->clickContextualLink('.block-field-blocknodebundle-with-section-fieldbody [data-contextual-id^="layout_builder_block"]', 'Configure');
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '.ui-dialog-titlebar [title="Close"]'));
$page->pressButton('Close');
$this->assertNoElementAfterWait('#drupal-off-canvas');
// Run the steps a second time after closing dialog, which reverses the
// order that behaviors.layoutBuilderDisableInteractiveElements and
// contextual link initialization occurs.
$this->clickContextualLink('.block-field-blocknodebundle-with-section-fieldbody [data-contextual-id^="layout_builder_block"]', 'Configure');
$this->assertNotEmpty($assert_session->waitForElementVisible('css', '#drupal-off-canvas'));
}
/**
* Waits for an element to be removed from the page.
*
* @param string $selector
* CSS selector.
* @param int $timeout
* (optional) Timeout in milliseconds, defaults to 10000.
* @param string $message
* (optional) Custom message to display with the assertion.
*
* @todo: Remove after https://www.drupal.org/project/drupal/issues/2892440
*/
public function assertNoElementAfterWait($selector, $timeout = 10000, $message = '') {
$page = $this->getSession()->getPage();
if ($message === '') {
$message = "Element '$selector' was not on the page after wait.";
}
$this->assertTrue($page->waitFor($timeout / 1000, function () use ($page, $selector) {
return empty($page->find('css', $selector));
}), $message);
}
}
......@@ -112,6 +112,7 @@ public function testOnBuildRender($refinable_dependent_access) {
'#base_plugin_id' => 'block_plugin_id',
'#derivative_plugin_id' => NULL,
'content' => $block_content,
'#attributes' => ['class' => ['layout-builder-block']],
];
$expected_cache = $expected_build + [
......@@ -236,6 +237,7 @@ public function testOnBuildRenderInPreview($refinable_dependent_access) {
'#base_plugin_id' => 'block_plugin_id',
'#derivative_plugin_id' => NULL,
'content' => $block_content,
'#attributes' => ['class' => ['layout-builder-block']],
];
$expected_cache = $expected_build + [
......@@ -287,6 +289,7 @@ public function testOnBuildRenderInPreviewEmptyBuild() {
'#base_plugin_id' => 'block_plugin_id',
'#derivative_plugin_id' => NULL,
'content' => $block_content,
'#attributes' => ['class' => ['layout-builder-block']],
];
$expected_build['content']['#markup'] = $placeholder_string;
......
......@@ -108,6 +108,7 @@ public function testToRenderArray() {
'#base_plugin_id' => 'block_plugin_id',
'#derivative_plugin_id' => NULL,
'content' => $block_content,
'#attributes' => ['class' => ['layout-builder-block']],
'#cache' => [
'contexts' => [],
'tags' => [],
......@@ -186,6 +187,7 @@ public function testToRenderArrayPreview() {
'#base_plugin_id' => 'block_plugin_id',
'#derivative_plugin_id' => NULL,
'content' => $block_content,
'#attributes' => ['class' => ['layout-builder-block']],
'#cache' => [
'contexts' => [],
'tags' => [],
......@@ -240,6 +242,7 @@ public function testContextAwareBlock() {
'#base_plugin_id' => 'block_plugin_id',
'#derivative_plugin_id' => NULL,
'content' => $block_content,
'#attributes' => ['class' => ['layout-builder-block']],
'#cache' => [
'contexts' => [],
'tags' => [],
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment