From 8a626498dc9694a992e76855781e2ad1cbd4fe9b Mon Sep 17 00:00:00 2001 From: Jey Nandakumar Date: Fri, 10 Jan 2020 15:09:10 +0000 Subject: [PATCH] feat(core): add preload configuration option for media files (#1958) --- doc/API.md | 1 + lib/core/constants.js | 2 +- lib/core/utils/preload-media.js | 45 +++++++++++++++++ lib/core/utils/preload.js | 3 +- test/assets/moon-speech.mp3 | Bin 0 -> 652266 bytes test/assets/video.mp4 | Bin 0 -> 1053651 bytes test/assets/video.webm | Bin 0 -> 1123636 bytes test/core/utils/preload-media.js | 83 +++++++++++++++++++++++++++++++ test/core/utils/preload.js | 4 +- 9 files changed, 134 insertions(+), 4 deletions(-) create mode 100644 lib/core/utils/preload-media.js create mode 100644 test/assets/moon-speech.mp3 create mode 100755 test/assets/video.mp4 create mode 100644 test/assets/video.webm create mode 100644 test/core/utils/preload-media.js diff --git a/doc/API.md b/doc/API.md index 117477e28d..f8890512ec 100644 --- a/doc/API.md +++ b/doc/API.md @@ -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. diff --git a/lib/core/constants.js b/lib/core/constants.js index 02d40619b8..81a73386e6 100644 --- a/lib/core/constants.js +++ b/lib/core/constants.js @@ -36,7 +36,7 @@ /** * array of supported & preload(able) asset types. */ - assets: ['cssom'], + assets: ['cssom', 'media'], /** * timeout value when resolving preload(able) assets */ diff --git a/lib/core/utils/preload-media.js b/lib/core/utils/preload-media.js new file mode 100644 index 0000000000..9a0000d79e --- /dev/null +++ b/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); + }); +} diff --git a/lib/core/utils/preload.js b/lib/core/utils/preload.js index 945e40f788..e4cba60c80 100644 --- a/lib/core/utils/preload.js +++ b/lib/core/utils/preload.js @@ -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); diff --git a/test/assets/moon-speech.mp3 b/test/assets/moon-speech.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..15d74d87fb2dcd19a540d861d59b5e84631b7e03 GIT binary patch literal 652266 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