Skip to content

Commit a60995d

Browse files
committed
Playlist: Disable waveform player auto-init
1 parent 3c78efc commit a60995d

9 files changed

Lines changed: 240 additions & 154 deletions

File tree

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/block-library/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
### Bug Fixes
1919

2020
- Footnotes: Treat unreadable `footnotes` post meta as no footnotes instead of throwing, so the block shows its placeholder rather than the block crash warning ([#81201](https://github.com/WordPress/gutenberg/pull/81201)).
21+
- Playlist: Disable waveform player auto-initialization before loading the dependency, and sanitize custom SVG icons passed through the Playlist waveform utility.
2122
- Playlist: Improve handling of declarative waveform player configuration ([#81342](https://github.com/WordPress/gutenberg/pull/81342)).
2223
- Cover: Pass `'full'` instead of `null` as the featured image size for parallax and repeated backgrounds, so a null array offset is no longer reached on PHP 8.5 ([#81444](https://github.com/WordPress/gutenberg/pull/81444)).
2324
- Icon: Apply only padding to the inner SVG in the editor, so margin is no longer applied twice compared to the front end ([#81292](https://github.com/WordPress/gutenberg/pull/81292)).

packages/block-library/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
"build-module/*/init.mjs"
9393
],
9494
"dependencies": {
95-
"@arraypress/waveform-player": "^1.23.0",
95+
"@arraypress/waveform-player": "^1.26.0",
9696
"@wordpress/a11y": "file:../a11y",
9797
"@wordpress/api-fetch": "file:../api-fetch",
9898
"@wordpress/autop": "file:../autop",
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const AUTOINIT_ATTRIBUTE = 'data-waveform-autoinit';
2+
3+
let documentElement;
4+
let hadAutoinitAttribute = false;
5+
let previousAutoinitValue;
6+
7+
if ( typeof document !== 'undefined' ) {
8+
documentElement = document.documentElement;
9+
hadAutoinitAttribute = documentElement.hasAttribute( AUTOINIT_ATTRIBUTE );
10+
previousAutoinitValue = documentElement.getAttribute( AUTOINIT_ATTRIBUTE );
11+
12+
// The waveform player checks this during module evaluation, before callers
13+
// can run code just before constructing an instance.
14+
documentElement.setAttribute( AUTOINIT_ATTRIBUTE, 'false' );
15+
}
16+
17+
export function restoreWaveformAutoinitAttribute() {
18+
if ( ! documentElement ) {
19+
return;
20+
}
21+
22+
if ( hadAutoinitAttribute ) {
23+
documentElement.setAttribute(
24+
AUTOINIT_ATTRIBUTE,
25+
previousAutoinitValue
26+
);
27+
} else {
28+
documentElement.removeAttribute( AUTOINIT_ATTRIBUTE );
29+
}
30+
}

packages/block-library/src/utils/test/waveform-player-dependency.js

Lines changed: 26 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,5 @@
11
import '@testing-library/jest-dom';
22

3-
const DEFAULT_PLAYBACK_RATES = [
4-
'0.5x',
5-
'0.75x',
6-
'1x',
7-
'1.25x',
8-
'1.5x',
9-
'1.75x',
10-
'2x',
11-
];
123
const FIXTURE_ATTRIBUTE = 'data-player-fixture';
134

145
function createDeclarativePlayer( attributes = {} ) {
@@ -25,11 +16,28 @@ function createDeclarativePlayer( attributes = {} ) {
2516

2617
function loadWaveformPlayer() {
2718
let WaveformPlayer;
19+
const documentElement = document.documentElement;
20+
const hadAutoinitAttribute = documentElement.hasAttribute(
21+
'data-waveform-autoinit'
22+
);
23+
const previousAutoinitValue = documentElement.getAttribute(
24+
'data-waveform-autoinit'
25+
);
2826

2927
jest.isolateModules( () => {
28+
documentElement.setAttribute( 'data-waveform-autoinit', 'false' );
3029
WaveformPlayer = require( '@arraypress/waveform-player' ).default;
3130
} );
3231

32+
if ( hadAutoinitAttribute ) {
33+
documentElement.setAttribute(
34+
'data-waveform-autoinit',
35+
previousAutoinitValue
36+
);
37+
} else {
38+
documentElement.removeAttribute( 'data-waveform-autoinit' );
39+
}
40+
3341
return WaveformPlayer;
3442
}
3543

@@ -81,27 +89,6 @@ describe( 'Waveform Player dependency', () => {
8189
}
8290
} );
8391

84-
it( 'uses the default control icons when declarative icon values are unsupported', () => {
85-
const iconValue = `<span ${ FIXTURE_ATTRIBUTE }></span>`;
86-
const element = createDeclarativePlayer( {
87-
'data-play-icon': iconValue,
88-
'data-pause-icon': iconValue,
89-
} );
90-
91-
WaveformPlayer = loadWaveformPlayer();
92-
WaveformPlayer.init();
93-
94-
expect(
95-
element.querySelector( `[${ FIXTURE_ATTRIBUTE }]` )
96-
).toBeNull();
97-
expect(
98-
element.querySelector( '.waveform-icon-play svg' )
99-
).not.toBeNull();
100-
expect(
101-
element.querySelector( '.waveform-icon-pause svg' )
102-
).not.toBeNull();
103-
} );
104-
10592
it( 'uses the default alignment when a declarative alignment value is unsupported', () => {
10693
const element = createDeclarativePlayer( {
10794
'data-button-align': getFragmentedAttributeValue( 'center' ),
@@ -113,9 +100,13 @@ describe( 'Waveform Player dependency', () => {
113100
const track = element.querySelector( '.waveform-track' );
114101
expect( track ).toHaveClass( 'waveform-align-center' );
115102
expect( track ).not.toHaveAttribute( FIXTURE_ATTRIBUTE );
103+
expect( console ).toHaveWarnedWith(
104+
'[WaveformPlayer] Invalid buttonAlign option, using default:',
105+
getFragmentedAttributeValue( 'center' )
106+
);
116107
} );
117108

118-
it( 'uses the default playback rates when a declarative rate list is unsupported', () => {
109+
it( 'uses supported playback rates from declarative rate lists', () => {
119110
const element = createDeclarativePlayer( {
120111
'data-show-playback-speed': 'true',
121112
'data-playback-rates': JSON.stringify( [
@@ -129,9 +120,10 @@ describe( 'Waveform Player dependency', () => {
129120
WaveformPlayer.init();
130121

131122
const options = [ ...element.querySelectorAll( '.speed-option' ) ];
132-
expect( options.map( ( option ) => option.textContent ) ).toEqual(
133-
DEFAULT_PLAYBACK_RATES
134-
);
123+
expect( options.map( ( option ) => option.textContent ) ).toEqual( [
124+
'1x',
125+
'1.5x',
126+
] );
135127
expect(
136128
options.some( ( option ) =>
137129
option.hasAttribute( FIXTURE_ATTRIBUTE )

packages/block-library/src/utils/test/waveform-utils.js

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,88 @@ describe( 'Waveform utilities', () => {
734734

735735
player.destroy();
736736
} );
737+
738+
it( 'uses default control icons when custom icon markup is unsupported', () => {
739+
const element = document.createElement( 'div' );
740+
document.body.appendChild( element );
741+
742+
const player = initWaveformPlayer( element, {
743+
src: 'https://cdn.example.com/track.mp3',
744+
title: 'Test track',
745+
labels: { seek: 'Seek' },
746+
playIcon: '<span data-player-fixture="icon"></span>',
747+
pauseIcon: '<span data-player-fixture="icon"></span>',
748+
} );
749+
750+
expect(
751+
player.container.querySelector( '[data-player-fixture]' )
752+
).toBeNull();
753+
expect(
754+
player.container.querySelector( '.waveform-icon-play svg' )
755+
).not.toBeNull();
756+
expect(
757+
player.container.querySelector( '.waveform-icon-pause svg' )
758+
).not.toBeNull();
759+
760+
player.destroy();
761+
} );
762+
763+
it( 'sanitizes custom SVG icon markup', () => {
764+
const element = document.createElement( 'div' );
765+
document.body.appendChild( element );
766+
767+
const player = initWaveformPlayer( element, {
768+
src: 'https://cdn.example.com/track.mp3',
769+
title: 'Test track',
770+
labels: { seek: 'Seek' },
771+
playIcon:
772+
'<svg viewBox="0 0 24 24" onclick="alert( \'x\' )" data-player-fixture="svg"><script>alert( \'x\' )</script><path d="M8 5v14l11-7z" onload="alert( \'x\' )" data-player-fixture="path"/></svg>',
773+
} );
774+
775+
const icon = player.container.querySelector(
776+
'.waveform-icon-play svg'
777+
);
778+
expect( icon ).not.toBeNull();
779+
expect( icon ).toHaveAttribute( 'viewBox', '0 0 24 24' );
780+
expect( icon ).not.toHaveAttribute( 'onclick' );
781+
expect( icon.querySelector( 'path' ) ).not.toHaveAttribute(
782+
'onload'
783+
);
784+
expect( icon.querySelector( 'script' ) ).toBeNull();
785+
expect(
786+
player.container.querySelector( '[data-player-fixture]' )
787+
).toBeNull();
788+
789+
player.destroy();
790+
} );
791+
792+
it( 'supports safe custom SVG icon markup', () => {
793+
const element = document.createElement( 'div' );
794+
document.body.appendChild( element );
795+
796+
const player = initWaveformPlayer( element, {
797+
src: 'https://cdn.example.com/track.mp3',
798+
title: 'Test track',
799+
labels: { seek: 'Seek' },
800+
playIcon:
801+
'<svg viewBox="0 0 24 24" aria-hidden="true" fill="none"><path d="M4 12h16" stroke="currentColor" stroke-width="2"/></svg>',
802+
} );
803+
804+
const icon = player.container.querySelector(
805+
'.waveform-icon-play svg'
806+
);
807+
expect( icon ).toHaveAttribute( 'aria-hidden', 'true' );
808+
expect( icon.querySelector( 'path' ) ).toHaveAttribute(
809+
'stroke',
810+
'currentColor'
811+
);
812+
expect( icon.querySelector( 'path' ) ).toHaveAttribute(
813+
'stroke-width',
814+
'2'
815+
);
816+
817+
player.destroy();
818+
} );
737819
} );
738820

739821
describe( 'logPlayError', () => {

0 commit comments

Comments
 (0)