-
Notifications
You must be signed in to change notification settings - Fork 795
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix(no-autoplay-audio): don't timeout for preload=none media elements #4684
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this comment explain why it's doing this, as opposed to what it's doing? The what is already clear from the code. |
||
*/ | ||
if ( | ||
actualNode.hasAttribute('paused') || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no-autoplay-audio also omits cases without an There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm just realizing now on a fresh reading of the spec that if we do restrict to only waiting for autoplay cases, we actually might want to not add the new logic for
|
||
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 | ||
*/ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <audio> has no source (duration cannot be interpreted)', function (done) { | ||
var checkArgs = checkSetup('<audio id="target"></audio>'); | ||
axe.utils.preload(preloadOptions).then(function () { | ||
assert.isUndefined(check.evaluate.apply(checkContext, checkArgs)); | ||
done(); | ||
}); | ||
it('returns undefined when <audio> has no source (duration cannot be interpreted)', async () => { | ||
const checkArgs = checkSetup('<audio id="target"></audio>'); | ||
await axe.utils.preload(preloadOptions); | ||
assert.isUndefined(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns undefined when <video> has no source (duration cannot be interpreted)', function (done) { | ||
var checkArgs = checkSetup('<video id="target"><source src=""/></video>'); | ||
axe.utils.preload(preloadOptions).then(function () { | ||
assert.isUndefined(check.evaluate.apply(checkContext, checkArgs)); | ||
done(); | ||
}); | ||
it('returns undefined when <video> has no source (duration cannot be interpreted)', async () => { | ||
const checkArgs = checkSetup('<video id="target"><source src=""/></video>'); | ||
await axe.utils.preload(preloadOptions); | ||
assert.isUndefined(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns false when <audio> can autoplay and has no controls mechanism', function (done) { | ||
var checkArgs = checkSetup( | ||
it('returns false when <audio> can autoplay and has no controls mechanism', async () => { | ||
const checkArgs = checkSetup( | ||
'<audio id="target" src="/test/assets/moon-speech.mp3" autoplay="true"></audio>' | ||
); | ||
axe.utils.preload(preloadOptions).then(function () { | ||
assert.isFalse(check.evaluate.apply(checkContext, checkArgs)); | ||
done(); | ||
}); | ||
await axe.utils.preload(preloadOptions); | ||
assert.isFalse(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns false when <video> can autoplay and has no controls mechanism', function (done) { | ||
var checkArgs = checkSetup( | ||
'<video id="target" autoplay="true">' + | ||
'<source src="/test/assets/video.webm" type="video/webm" />' + | ||
'<source src="/test/assets/video.mp4" type="video/mp4" />' + | ||
'</video>' | ||
); | ||
axe.utils.preload(preloadOptions).then(function () { | ||
assert.isFalse(check.evaluate.apply(checkContext, checkArgs)); | ||
done(); | ||
}); | ||
it('returns false when <video> can autoplay and has no controls mechanism', async () => { | ||
const checkArgs = checkSetup(` | ||
<video id="target" autoplay="true"> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are we explicitly specifying the value of the autoplay? it's it just a boolean attribute? E.g. |
||
<source src="/test/assets/video.webm" type="video/webm" /> | ||
<source src="/test/assets/video.mp4" type="video/mp4" /> | ||
</video> | ||
`); | ||
await axe.utils.preload(preloadOptions); | ||
assert.isFalse(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns false when <audio> plays less than allowed dutation but loops', function (done) { | ||
var checkArgs = checkSetup( | ||
it('returns false when <audio> plays less than allowed dutation but loops', async () => { | ||
const checkArgs = checkSetup( | ||
'<audio id="target" src="/test/assets/moon-speech.mp3#t=2,4" autoplay="true" loop="true"></audio>' | ||
); | ||
axe.utils.preload(preloadOptions).then(function () { | ||
assert.isFalse(check.evaluate.apply(checkContext, checkArgs)); | ||
done(); | ||
}); | ||
await axe.utils.preload(preloadOptions); | ||
assert.isFalse(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns true when <video> can autoplay and duration is below allowed duration (by passing options)', function (done) { | ||
var checkArgs = checkSetup( | ||
'<video id="target" autoplay="true">' + | ||
'<source src="/test/assets/video.webm" type="video/webm" />' + | ||
'<source src="/test/assets/video.mp4" type="video/mp4" />' + | ||
'</video>', | ||
{ allowedDuration: 15 } | ||
it('returns false when <video> loops and has no controls mechanism when duration is unknown', () => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the duration actually unknown for this (and all the other test cases meant to exercise unknown duration paths?) It looks like this is using the same media sources as the other test cases for known durations |
||
const checkArgs = checkSetup(` | ||
<video id="target" loop> | ||
<source src="/test/assets/video.webm#t=7,9" type="video/webm" /> | ||
<source src="/test/assets/video.mp4#t=7,9" type="video/mp4" /> | ||
</video> | ||
`); | ||
assert.isFalse(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns false when <audio> loops and has no controls mechanism when duration is unknown', () => { | ||
const checkArgs = checkSetup( | ||
'<audio id="target" src="/test/assets/moon-speech.mp3#t=2,4" loop="true"></audio>' | ||
); | ||
axe.utils.preload(preloadOptions).then(function () { | ||
assert.isTrue(check.evaluate.apply(checkContext, checkArgs)); | ||
done(); | ||
}); | ||
assert.isFalse(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns true when <video> can autoplay and duration is below allowed duration (by setting playback range)', function (done) { | ||
var checkArgs = checkSetup( | ||
'<video id="target" autoplay="true">' + | ||
'<source src="/test/assets/video.webm#t=7,9" type="video/webm" />' + | ||
'<source src="/test/assets/video.mp4#t=7,9" type="video/mp4" />' + | ||
'</video>' | ||
// Note: default allowed duration is 3s | ||
it('returns true when <video> can autoplay and duration is below allowed duration (by passing options)', async () => { | ||
const checkArgs = checkSetup( | ||
` | ||
<video id="target" autoplay="true"> | ||
<source src="/test/assets/video.webm" type="video/webm" /> | ||
<source src="/test/assets/video.mp4" type="video/mp4" /> | ||
</video>`, | ||
{ allowedDuration: 15 } | ||
); | ||
axe.utils.preload(preloadOptions).then(function () { | ||
assert.isTrue(check.evaluate.apply(checkContext, checkArgs)); | ||
done(); | ||
}); | ||
await axe.utils.preload(preloadOptions); | ||
assert.isTrue(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns true when <video> can autoplay and duration is below allowed duration (by setting playback range)', async () => { | ||
const checkArgs = checkSetup(` | ||
<video id="target" autoplay="true"> | ||
<source src="/test/assets/video.webm#t=7,9" type="video/webm" /> | ||
<source src="/test/assets/video.mp4#t=7,9" type="video/mp4" /> | ||
</video>`); | ||
// Note: default allowed duration is 3s | ||
await axe.utils.preload(preloadOptions); | ||
assert.isTrue(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns true when <audio> can autoplay but has controls mechanism', function (done) { | ||
var checkArgs = checkSetup( | ||
it('returns true when <audio> can autoplay but has controls mechanism', async () => { | ||
const checkArgs = checkSetup( | ||
'<audio id="target" src="/test/assets/moon-speech.mp3" autoplay="true" controls></audio>' | ||
); | ||
axe.utils.preload(preloadOptions).then(function () { | ||
assert.isTrue(check.evaluate.apply(checkContext, checkArgs)); | ||
done(); | ||
}); | ||
await axe.utils.preload(preloadOptions); | ||
assert.isTrue(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns true when <video> can autoplay and has controls mechanism', async () => { | ||
const checkArgs = checkSetup(` | ||
<video id="target" autoplay="true" controls> | ||
<source src="/test/assets/video.webm" type="video/webm" /> | ||
<source src="/test/assets/video.mp4" type="video/mp4" /> | ||
</video> | ||
`); | ||
await axe.utils.preload(preloadOptions); | ||
assert.isTrue(check.evaluate.apply(null, checkArgs)); | ||
}); | ||
|
||
it('returns true when <video> loops and has controls mechanism when duration is unknown', () => { | ||
const checkArgs = checkSetup(` | ||
<video id="target" loop controls> | ||
<source src="/test/assets/video.webm#t=7,9" type="video/webm" /> | ||
<source src="/test/assets/video.mp4#t=7,9" type="video/mp4" /> | ||
</video> | ||
`); | ||
assert.isTrue(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
|
||
it('returns true when <video> can autoplay and has controls mechanism', function (done) { | ||
var checkArgs = checkSetup( | ||
'<video id="target" autoplay="true" controls>' + | ||
'<source src="/test/assets/video.webm" type="video/webm" />' + | ||
'<source src="/test/assets/video.mp4" type="video/mp4" />' + | ||
'</video>' | ||
it('returns true when <audio> loops and has controls mechanism when duration is unknown', () => { | ||
const checkArgs = checkSetup( | ||
'<audio id="target" src="/test/assets/moon-speech.mp3#t=2,4" controls="true" loop="true"></audio>' | ||
); | ||
axe.utils.preload(preloadOptions).then(function () { | ||
assert.isTrue(check.evaluate.apply(null, checkArgs)); | ||
done(); | ||
}); | ||
assert.isTrue(check.evaluate.apply(checkContext, checkArgs)); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we ignore the
preload="metadata"
too? it seems like it doesn't put the media in a ready to play state eitherThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need to wait for the media to be playable, we just need the metadata so we can identify the duration (that's why
isMediaElementReady
waits for any non-zero ready state, rather than specifically waiting for a contentful ready state)