Skip to content
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

feat(core): add preload configuration option for media files #1958

Merged
merged 18 commits into from
Jan 10, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 4 additions & 4 deletions lib/checks/aria/required-children.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ function missingRequiredChildren(node, childRoles, all, role) {
const textTypeInputs = ['text', 'search', 'email', 'url', 'tel'];
if (
(textboxIndex >= 0 &&
(node.nodeName.toUpperCase() === 'INPUT' &&
textTypeInputs.includes(node.type))) ||
(owns(node, virtualNode, 'searchbox') ||
ariaOwns(ownedElements, 'searchbox'))
node.nodeName.toUpperCase() === 'INPUT' &&
textTypeInputs.includes(node.type)) ||
owns(node, virtualNode, 'searchbox') ||
ariaOwns(ownedElements, 'searchbox')
) {
missing.splice(textboxIndex, 1);
}
Expand Down
2 changes: 1 addition & 1 deletion lib/core/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
/**
* array of supported & preload(able) asset types.
*/
assets: ['cssom'],
assets: ['cssom', 'media'],
/**
* timeout value when resolving preload(able) assets
*/
Expand Down
6 changes: 3 additions & 3 deletions lib/core/utils/is-hidden.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,12 @@ axe.utils.isHidden = function isHidden(el, recursed) {

if (
!style ||
(!el.parentNode ||
(style.getPropertyValue('display') === 'none' ||
!el.parentNode ||
style.getPropertyValue('display') === 'none' ||
(!recursed &&
// visibility is only accurate on the first element
style.getPropertyValue('visibility') === 'hidden') ||
el.getAttribute('aria-hidden') === 'true'))
el.getAttribute('aria-hidden') === 'true'
) {
return true;
}
Expand Down
53 changes: 53 additions & 0 deletions lib/core/utils/preload-media.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* Given a rootNode
* -> get all HTMLMediaElement's and ensure their metadata is loaded
*
* @method preloadMedia
* @memberof axe.utils
* @property {Object} options.treeRoot (optional) the DOM tree to be inspected
*/
axe.utils.preloadMedia = function preloadMedia({ treeRoot = axe._tree[0] }) {
WilcoFiers marked this conversation as resolved.
Show resolved Hide resolved
const mediaVirtualNodes = axe.utils.querySelectorAll(
treeRoot,
'video, audio'
);
if (!mediaVirtualNodes || !mediaVirtualNodes.length) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is statement can be removed. mediaVirtualNodes is will always be an array, and an empty array will work fine with your map function. Promise.all will resolve.

return Promise.resolve([]);
}

return Promise.all(
mediaVirtualNodes.map(({ actualNode }) => isMediaElementReady(actualNode))
);
};

/**
* Ensures a media element's metadata is loaded
* @param {HTMLMediaElement} elm elm
* @returns {Promise}
*/
function isMediaElementReady(elm) {
return new Promise((resolve, reject) => {
/**
* See - https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
*/
if (elm.readyState > 0) {
resolve(elm);
}

function onMediaReady() {
elm.removeEventListener('loadedmetadata', onMediaReady);
if (elm.readyState <= 0) {
reject(
`Preload media element has wrong readyState of HAVE_NOTHING (0)`
WilcoFiers marked this conversation as resolved.
Show resolved Hide resolved
);
}
resolve(elm);
}

/**
* Given media is not ready, wire up listener for `loadedmetadata`
* See - https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/loadedmetadata_event
*/
elm.addEventListener('loadedmetadata', onMediaReady);
});
}
3 changes: 2 additions & 1 deletion lib/core/utils/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ axe.utils.getPreloadConfig = function getPreloadConfig(options) {
*/
axe.utils.preload = function preload(options) {
const preloadFunctionsMap = {
cssom: axe.utils.preloadCssom
cssom: axe.utils.preloadCssom,
media: axe.utils.preloadMedia
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
};

const shouldPreload = axe.utils.shouldPreload(options);
Expand Down
76 changes: 76 additions & 0 deletions test/core/utils/preload-media.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* global Promise */

describe('axe.utils.preloadMedia', function() {
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
'use strict';

var origFn = axe.utils.preloadMedia;
var fixture = document.getElementById('fixture');
var fixtureSetup = axe.testUtils.fixtureSetup;

function rebuildTree() {
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
axe._tree = axe.utils.getFlattenedTree(document);
}

afterEach(function() {
axe.utils.preloadMedia = origFn;
fixture.innerHTML = '';
});

it('invokes utils.preloadMedia and passes the treeRoot property', function(done) {
var isCalled = false;
axe.utils.preloadMedia = function(options) {
assert.isDefined(options.treeRoot);
isCalled = true;
return Promise.resolve();
};

rebuildTree();

axe.utils.preloadMedia({ treeRoot: axe._tree[0] }).then(function() {
assert.ok(isCalled);
done();
});
});

it('returns empty array when there are no media nodes to be preloaded', function(done) {
rebuildTree();

axe.utils.preloadMedia({ treeRoot: axe._tree[0] }).then(function(result) {
assert.equal(result.length, 0);
done();
});
});

it('returns media node (audio) after their metadata has been preloaded', function(done) {
fixtureSetup(
'<audio src="https://act-rules.github.io/test-assets/moon-audio/moon-speech.mp3" autoplay="true" controls></audio>'
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved
);

axe.utils.preloadMedia({ treeRoot: axe._tree[0] }).then(function(result) {
assert.equal(result.length, 1);
assert.isTrue(result[0].readyState > 0);

done();
});
});

it('returns media nodes (audio, video) after their metadata has been preloaded', function(done) {
fixtureSetup(
// 1 audio elm
'<audio src="https://act-rules.github.io/test-assets/moon-audio/moon-speech.mp3"></audio>' +
// 1 video elm
'<video>' +
'<source src="https://act-rules.github.io/test-assets/rabbit-video/video.mp4" type="video/mp4" />' +
'<source src="https://act-rules.github.io/test-assets/rabbit-video/video.webm" type="video/webm" />' +
'</video>'
);

axe.utils.preloadMedia({ treeRoot: axe._tree[0] }).then(function(result) {
assert.equal(result.length, 2);
assert.isTrue(result[0].readyState > 0);
assert.notEqual(result[1].duration, NaN);
jeeyyy marked this conversation as resolved.
Show resolved Hide resolved

done();
});
});
});
4 changes: 2 additions & 2 deletions test/core/utils/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ describe('axe.utils.preload', function() {
describe('axe.utils.getPreloadConfig', function() {
it('should return default assets if preload configuration is not set', function() {
var actual = axe.utils.getPreloadConfig({}).assets;
var expected = ['cssom'];
var expected = ['cssom', 'media'];
assert.deepEqual(actual, expected);
});

it('should return default assets if preload options is set to true', function() {
var actual = axe.utils.getPreloadConfig({}).assets;
var expected = ['cssom'];
var expected = ['cssom', 'media'];
assert.deepEqual(actual, expected);
});

Expand Down