Skip to content

Commit 602eb7e

Browse files
authored
Merge pull request #6573 from ampproject/bug/4635-disable-mute-when-autoplay-2
Add muted message via direct DOM manipulation
2 parents 8791efa + 9c80afd commit 602eb7e

File tree

6 files changed

+168
-20
lines changed

6 files changed

+168
-20
lines changed

assets/css/src/amp-block-editor.css

+1-4
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,7 @@
4545
margin-bottom: 1em;
4646
}
4747

48-
.components-panel__body.amp-video-autoplay-notice {
48+
.amp-video-autoplay-notice {
4949
font-size: 12px;
50-
font-style: normal;
5150
color: #757575;
52-
border-top: none;
53-
padding-top: 0;
5451
}

assets/src/block-editor/helpers/index.js

+65-16
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ import { isFunction, isObject, isString } from 'lodash';
1111
import { __, sprintf } from '@wordpress/i18n';
1212
import { SelectControl, ToggleControl, Notice, PanelBody } from '@wordpress/components';
1313
import { InspectorControls } from '@wordpress/block-editor';
14-
import { select } from '@wordpress/data';
15-
import { cloneElement, isValidElement } from '@wordpress/element';
14+
import { select, useSelect } from '@wordpress/data';
15+
import { cloneElement, isValidElement, useLayoutEffect, useRef } from '@wordpress/element';
1616

1717
/**
1818
* Internal dependencies
@@ -274,7 +274,7 @@ export const filterBlocksEdit = ( BlockEdit ) => {
274274
}
275275

276276
const EnhancedBlockEdit = function( props ) {
277-
const { attributes: { ampLayout }, name } = props;
277+
const { isSelected, attributes: { ampLayout }, name } = props;
278278

279279
let inspectorControls;
280280

@@ -283,7 +283,7 @@ export const filterBlocksEdit = ( BlockEdit ) => {
283283
} else if ( 'core/image' === name ) {
284284
inspectorControls = setUpImageInspectorControls( props );
285285
} else if ( 'core/video' === name ) {
286-
inspectorControls = setUpVideoInspectorControls( props );
286+
inspectorControls = isSelected ? VideoInspectorControls( props ) : null;
287287
} else if ( 0 === name.indexOf( 'core-embed/' ) ) {
288288
inspectorControls = setUpInspectorControls( props );
289289
}
@@ -354,23 +354,68 @@ export const setImageBlockLayoutAttributes = ( props, layout ) => {
354354
/**
355355
* Show notice if video player have autoplay enabled and mute is disabled.
356356
*
357-
* @param {Object} props Props.
357+
* @param {Object} props Props.
358+
* @param {boolean} props.isSelected Flag indicating if the block selected.
359+
* @param {Object} props.attributes Block attributes.
360+
* @param {boolean} props.attributes.autoplay Flag indicating if the video has autoplay turned on.
361+
* @param {boolean} props.attributes.muted Flag indicating if the video is muted.
358362
* @return {ReactElement} Inspector Controls.
359363
*/
360-
export const setUpVideoInspectorControls = ( props ) => {
361-
const { isSelected, attributes: { autoplay, muted } } = props;
364+
export const VideoInspectorControls = ( props ) => {
365+
const { attributes: { autoplay, muted } } = props;
366+
367+
const message = useRef( null );
368+
const blockTypeDescription = useSelect( ( _select ) => _select( 'core/blocks' ).getBlockType( 'core/video' )?.description, [] );
369+
370+
/**
371+
* Since we are mutating the DOM, it's not the regular `useEffect` hook.
372+
*/
373+
useLayoutEffect( () => {
374+
const showMessage = true === autoplay && true !== muted;
375+
376+
if ( showMessage && ! message.current ) {
377+
/**
378+
* When switching over from another block selection, the message is
379+
* tried to be added before the sidebar content changes. This delay
380+
* prevents this issue.
381+
*/
382+
setTimeout( () => {
383+
/**
384+
* Since there is no actual ID or block type designator exposed
385+
* in the block editor's sidebar DOM, we need to use to a hack.
386+
* In order to determine the video settings DOM container we are
387+
* first searching for a block type description. Once we have it
388+
* we traverse through the DOM tree to finally find out the
389+
* "Muted" toggle container.
390+
*/
391+
const blockInspector = [ ...document.querySelectorAll( '.edit-post-sidebar .block-editor-block-card__description' ) ]
392+
?.find( ( description ) => description.textContent === blockTypeDescription )?.closest( '.block-editor-block-inspector' );
393+
394+
if ( ! blockInspector ) {
395+
return;
396+
}
362397

363-
if ( ! isSelected ) {
364-
return null;
365-
}
398+
const mutedToggleContainerIndex = 3;
399+
const mutedToggleContainer = blockInspector.querySelector( '.editor-video-poster-control' )?.parentElement.children[ mutedToggleContainerIndex ];
400+
401+
if ( ! mutedToggleContainer ) {
402+
return;
403+
}
404+
405+
message.current = document.createElement( 'p' );
406+
message.current.classList.add( 'amp-video-autoplay-notice' );
407+
message.current.textContent = __( 'Autoplay will cause the video to be muted in many browsers to prevent a poor user experience. It will be muted in AMP for this reason as well.', 'amp' );
408+
409+
mutedToggleContainer.append( message.current );
410+
}, 1 );
411+
} else if ( ! showMessage && message.current ) {
412+
message.current.remove();
413+
message.current = null;
414+
}
415+
}, [ autoplay, blockTypeDescription, muted ] );
366416

367417
return (
368418
<InspectorControls>
369-
{ ( true === autoplay && true !== muted ) && (
370-
<PanelBody className="amp-video-autoplay-notice">
371-
{ __( 'Autoplay will mute the video player by default in AMP mode.', 'amp' ) }
372-
</PanelBody>
373-
) }
374419
<PanelBody title={ __( 'AMP Settings', 'amp' ) }>
375420
<AmpLayoutControl { ...props } />
376421
<AmpNoloadingToggle { ...props } />
@@ -379,8 +424,12 @@ export const setUpVideoInspectorControls = ( props ) => {
379424
);
380425
};
381426

382-
setUpVideoInspectorControls.propTypes = {
427+
VideoInspectorControls.propTypes = {
383428
isSelected: PropTypes.bool,
429+
attributes: PropTypes.shape( {
430+
autoplay: PropTypes.bool,
431+
muted: PropTypes.bool,
432+
} ),
384433
};
385434

386435
/**

tests/e2e/assets/sample-video.mp4

17.7 KB
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import {
5+
createNewPost,
6+
ensureSidebarOpened,
7+
insertBlock,
8+
} from '@wordpress/e2e-test-utils';
9+
10+
/**
11+
* Internal dependencies
12+
*/
13+
import {
14+
clickButton,
15+
getBlockEditorSidebarToggle,
16+
uploadMedia,
17+
} from '../../utils';
18+
19+
const sampleVideo = 'sample-video.mp4';
20+
const autoplayNotice = 'Autoplay may cause usability issues for some users.';
21+
const mutedNotice = 'Autoplay will cause the video to be muted in many browsers to prevent a poor user experience. It will be muted in AMP for this reason as well.';
22+
23+
/**
24+
* Tests the notices for the featured image.
25+
*/
26+
describe( 'Video Block Muted Notice', () => {
27+
beforeEach( async () => {
28+
await createNewPost( { postType: 'post' } );
29+
} );
30+
31+
it( 'displays a message only if the autoplay is turned on and the muted option is off', async () => {
32+
await insertBlock( 'Video' );
33+
await page.waitForSelector( '.wp-block-video .block-editor-media-placeholder' );
34+
await clickButton( 'Media Library' );
35+
await uploadMedia( sampleVideo );
36+
await page.waitForSelector( 'button.media-button-select:not([disabled])' );
37+
await expect( page ).toClick( 'button.media-button-select:not([disabled])' );
38+
39+
let [ autoplayContainer, autoplayInput ] = await getBlockEditorSidebarToggle( 'Autoplay' );
40+
let [ mutedContainer, mutedInput ] = await getBlockEditorSidebarToggle( 'Muted' );
41+
42+
// Confirm both autoplay and muted are off.
43+
await expect( await autoplayInput.evaluate( ( node ) => node.checked ) ).toBe( false );
44+
await expect( await mutedInput.evaluate( ( node ) => node.checked ) ).toBe( false );
45+
await expect( await autoplayContainer.evaluate( ( node ) => node.textContent ) ).not.toMatch( autoplayNotice );
46+
await expect( await mutedContainer.evaluate( ( node ) => node.textContent ) ).not.toMatch( mutedNotice );
47+
48+
await autoplayInput.click();
49+
50+
// Check if both notices are displayed after turning autoplay on.
51+
await expect( await autoplayInput.evaluate( ( node ) => node.checked ) ).toBe( true );
52+
await expect( await mutedInput.evaluate( ( node ) => node.checked ) ).toBe( false );
53+
await expect( await autoplayContainer.evaluate( ( node ) => node.textContent ) ).toMatch( autoplayNotice );
54+
await expect( await mutedContainer.evaluate( ( node ) => node.textContent ) ).toMatch( mutedNotice );
55+
56+
// Insert new block so that the sidebar content changes.
57+
await insertBlock( 'Code' );
58+
await page.waitForSelector( '.wp-block-code' );
59+
await ensureSidebarOpened();
60+
61+
// Switch back to the video block and confirm messages have been persisted.
62+
await expect( page ).toClick( '.wp-block-video' );
63+
64+
[ autoplayContainer, autoplayInput ] = await getBlockEditorSidebarToggle( 'Autoplay' );
65+
[ mutedContainer, mutedInput ] = await getBlockEditorSidebarToggle( 'Muted' );
66+
67+
await expect( await autoplayInput.evaluate( ( node ) => node.checked ) ).toBe( true );
68+
await expect( await mutedInput.evaluate( ( node ) => node.checked ) ).toBe( false );
69+
await expect( await autoplayContainer.evaluate( ( node ) => node.textContent ) ).toMatch( autoplayNotice );
70+
await expect( await mutedContainer.evaluate( ( node ) => node.textContent ) ).toMatch( mutedNotice );
71+
72+
await mutedInput.click();
73+
74+
await expect( await autoplayInput.evaluate( ( node ) => node.checked ) ).toBe( true );
75+
await expect( await mutedInput.evaluate( ( node ) => node.checked ) ).toBe( true );
76+
await expect( await autoplayContainer.evaluate( ( node ) => node.textContent ) ).toMatch( autoplayNotice );
77+
await expect( await mutedContainer.evaluate( ( node ) => node.textContent ) ).not.toMatch( mutedNotice );
78+
79+
await autoplayInput.click();
80+
81+
await expect( await autoplayInput.evaluate( ( node ) => node.checked ) ).toBe( false );
82+
await expect( await mutedInput.evaluate( ( node ) => node.checked ) ).toBe( true );
83+
await expect( await autoplayContainer.evaluate( ( node ) => node.textContent ) ).not.toMatch( autoplayNotice );
84+
await expect( await mutedContainer.evaluate( ( node ) => node.textContent ) ).not.toMatch( mutedNotice );
85+
} );
86+
} );
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Waits for and returns a block edtior sidebar toggle input and container handles.
3+
*
4+
* @param {string} label The toggle label.
5+
*/
6+
export async function getBlockEditorSidebarToggle( label ) {
7+
const containerXpath = `div[contains(@class, 'components-toggle-control')][.//label[contains(text(), '${ label }')]]`;
8+
9+
await page.waitForXPath( `//${ containerXpath }` );
10+
11+
const [ containerHandle ] = await page.$x( `//${ containerXpath }` );
12+
const [ inputHandle ] = await page.$x( `//input[./ancestor-or-self::${ containerXpath }]` );
13+
14+
return [ containerHandle, inputHandle ];
15+
}

tests/e2e/utils/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export { clickButton } from './click-button';
2+
export { getBlockEditorSidebarToggle } from './get-block-editor-sidebar-toggle';
23
export { uploadMedia } from './upload-media';

0 commit comments

Comments
 (0)