From b96dd2b64654f9d9a80664faf093983312121fcf Mon Sep 17 00:00:00 2001 From: Steven Lambert <2433219+straker@users.noreply.github.com> Date: Tue, 28 Jan 2025 15:14:35 -0700 Subject: [PATCH] fix(no-autoplay-audio): don't timeout for preload=none media elements --- .../media/no-autoplay-audio-evaluate.js | 14 +- lib/core/utils/preload-media.js | 21 ++ test/checks/media/no-autoplay-audio.js | 183 +++++++++--------- test/core/utils/preload-media.js | 95 +++++---- .../no-autoplay-audio/no-autoplay-audio.html | 38 +++- .../no-autoplay-audio/no-autoplay-audio.js | 12 +- 6 files changed, 226 insertions(+), 137 deletions(-) diff --git a/lib/checks/media/no-autoplay-audio-evaluate.js b/lib/checks/media/no-autoplay-audio-evaluate.js index dc378bb864..c2a4ebf245 100644 --- a/lib/checks/media/no-autoplay-audio-evaluate.js +++ b/lib/checks/media/no-autoplay-audio-evaluate.js @@ -1,4 +1,14 @@ function noAutoplayAudioEvaluate(node, options) { + const hasControls = node.hasAttribute('controls'); + + /** + * if the media loops then we only need to know if it has controls, regardless + * of the duration + */ + if (node.hasAttribute('loop')) { + return hasControls; + } + /** * if duration cannot be read, this means `preloadMedia` has failed */ @@ -12,7 +22,7 @@ function noAutoplayAudioEvaluate(node, options) { */ const { allowedDuration = 3 } = options; const playableDuration = getPlayableDuration(node); - if (playableDuration <= allowedDuration && !node.hasAttribute('loop')) { + if (playableDuration <= allowedDuration) { return true; } @@ -20,7 +30,7 @@ function noAutoplayAudioEvaluate(node, options) { * if media element does not provide controls mechanism * -> fail */ - if (!node.hasAttribute('controls')) { + if (!hasControls) { return false; } diff --git a/lib/core/utils/preload-media.js b/lib/core/utils/preload-media.js index 72c8a9e9f2..199ded0caf 100644 --- a/lib/core/utils/preload-media.js +++ b/lib/core/utils/preload-media.js @@ -14,6 +14,27 @@ function preloadMedia({ treeRoot = axe._tree[0] }) { treeRoot, 'video, audio', ({ actualNode }) => { + /** + * Ignore media that won't load no matter how long we wait + * @see https://github.com/dequelabs/axe-core/issues/4665 + */ + if ( + actualNode.preload === 'none' && + actualNode.networkState !== actualNode.NETWORK_LOADING + ) { + return false; + } + + /** + * Ignore media nodes which are `paused` or `muted` + */ + if ( + actualNode.hasAttribute('paused') || + actualNode.hasAttribute('muted') + ) { + return false; + } + /** * this is to safe-gaurd against empty `src` values which can get resolved `window.location`, thus never preloading as the URL is not a media asset */ diff --git a/test/checks/media/no-autoplay-audio.js b/test/checks/media/no-autoplay-audio.js index 24277ce4da..d0b24475c2 100644 --- a/test/checks/media/no-autoplay-audio.js +++ b/test/checks/media/no-autoplay-audio.js @@ -1,119 +1,126 @@ -describe('no-autoplay-audio', function () { - 'use strict'; +describe('no-autoplay-audio', () => { + const check = checks['no-autoplay-audio']; + const checkSetup = axe.testUtils.checkSetup; + const checkContext = axe.testUtils.MockCheckContext(); + const preloadOptions = { preload: { assets: ['media'] } }; - var check; - var fixture = document.getElementById('fixture'); - var checkSetup = axe.testUtils.checkSetup; - var checkContext = axe.testUtils.MockCheckContext(); - var preloadOptions = { preload: { assets: ['media'] } }; - - before(function () { - check = checks['no-autoplay-audio']; - }); - - afterEach(function () { - fixture.innerHTML = ''; - axe._tree = undefined; + afterEach(() => { checkContext.reset(); }); - it('returns undefined when