forked from dequelabs/axe-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpreload-media.js
More file actions
109 lines (99 loc) · 3.39 KB
/
preload-media.js
File metadata and controls
109 lines (99 loc) · 3.39 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import querySelectorAllFilter from './query-selector-all-filter';
/**
* 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
*/
// TODO: es-modules_tree
function preloadMedia({ treeRoot = axe._tree[0] }) {
const mediaVirtualNodes = querySelectorAllFilter(
treeRoot,
/**
* Only concern ourselves with media that autoplays as the no-autoplay-audio rule
* is the only rule that uses this information
*/
'video[autoplay], audio[autoplay]',
({ actualNode }) => {
/**
* Ignore media that won't load no matter how long we wait (i.e. preload=none).
*
* Although the spec says that the autoplay attribute can override the preload
* attribute, it depends on the browser settings (if autoplay is allowed) and
* operating system (e.g. Android does not preload autoplay media even when
* autoplay is allowed).
*
* We can identify preload=none media that won't load if the networkState is
* idle and the readyState is 0. If the browser is currently loading the media
* (networkState) or if the media is already loaded (readyState) that means the
* preload attribute was ignored.
*
* @see https://github.com/dequelabs/axe-core/issues/4665
* @see https://html.spec.whatwg.org/multipage/media.html#attr-media-preload
*/
if (
actualNode.preload === 'none' &&
actualNode.readyState === 0 &&
actualNode.networkState !== actualNode.NETWORK_LOADING
) {
return false;
}
/**
* Ignore media nodes which are `paused` or `muted` as the no-autoplay-audio
* rule matcher ignores them
*/
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
*/
if (actualNode.hasAttribute('src')) {
return !!actualNode.getAttribute('src');
}
/**
* The `src` on <source> element is essential for `audio` and `video` elements
*/
const sourceWithSrc = Array.from(
actualNode.getElementsByTagName('source')
).filter(source => !!source.getAttribute('src'));
if (sourceWithSrc.length <= 0) {
return false;
}
return true;
}
);
return Promise.all(
mediaVirtualNodes.map(({ actualNode }) => isMediaElementReady(actualNode))
);
}
export default preloadMedia;
/**
* 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);
});
}