Skip to content

Commit

Permalink
feat(core): add preload configuration option for media files (#1958)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeeyyy committed Jan 10, 2020
1 parent 9491e09 commit 8a62649
Show file tree
Hide file tree
Showing 9 changed files with 134 additions and 4 deletions.
1 change: 1 addition & 0 deletions doc/API.md
Expand Up @@ -585,6 +585,7 @@ The `assets` attribute expects an array of preload(able) constraints to be fetch
| Asset Type | Description |
| :--------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cssom` | This asset type preloads all CSS Stylesheets rulesets specified in the page. The stylesheets can be an external cross-domain resource, a relative stylesheet or an inline style with in the head tag of the document. If the stylesheet is an external cross-domain a network request is made. An object representing the CSS Rules from each stylesheet is made available to the checks evaluate function as `preloadedAssets` at run-time |
| `media` | This asset type preloads metadata information of any HTMLMediaElement in the specified document |

The `timeout` attribute in the object configuration is `optional` and has a fallback default value (10000ms). The `timeout` is essential for any network dependent assets that are preloaded, where-in if a given request takes longer than the specified/ default value, the operation is aborted.

Expand Down
2 changes: 1 addition & 1 deletion lib/core/constants.js
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
45 changes: 45 additions & 0 deletions lib/core/utils/preload-media.js
@@ -0,0 +1,45 @@
/**
* 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] }) {
const mediaVirtualNodes = axe.utils.querySelectorAll(
treeRoot,
'video, audio'
);

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 => {
/**
* See - https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
*/
if (elm.readyState > 0) {
resolve(elm);
}

function onMediaReady() {
elm.removeEventListener('loadedmetadata', onMediaReady);
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
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
};

const shouldPreload = axe.utils.shouldPreload(options);
Expand Down
Binary file added test/assets/moon-speech.mp3
Binary file not shown.
Binary file added test/assets/video.mp4
Binary file not shown.
Binary file added test/assets/video.webm
Binary file not shown.
83 changes: 83 additions & 0 deletions test/core/utils/preload-media.js
@@ -0,0 +1,83 @@
/* global Promise */

describe('axe.utils.preloadMedia', function() {
'use strict';

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

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();
};

axe._tree = axe.utils.getFlattenedTree(document);

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) {
axe._tree = axe.utils.getFlattenedTree(document);

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

(isIE11 ? it.skip : it)(
'returns media node (audio) after their metadata has been preloaded',
function(done) {
fixtureSetup(
'<audio src="/test/assets/moon-speech.mp3" autoplay="true" controls></audio>'
);

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

done();
});
}
);

(isIE11 ? it.skip : it)(
'returns media nodes (audio, video) after their metadata has been preloaded',
function(done) {
fixtureSetup(
// 1 audio elm
'<audio src="/test/assets/moon-speech.mp3"></audio>' +
// 1 video elm
'<video>' +
'<source src="/test/assets/video.mp4" type="video/mp4" />' +
'<source src="/test/assets/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.equal(Math.round(result[0].duration), 27);

assert.isTrue(result[1].readyState > 0);
assert.equal(Math.round(result[1].duration), 14);

done();
});
}
);
});
4 changes: 2 additions & 2 deletions test/core/utils/preload.js
Expand Up @@ -83,13 +83,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

0 comments on commit 8a62649

Please sign in to comment.