shaka-player 3.2.13 → 3.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-and-test.yaml +11 -6
- package/.github/workflows/compute-incremental-coverage.py +14 -3
- package/.github/workflows/demo-version-index.yaml +9 -1
- package/.github/workflows/nightly-demo.yaml +1 -1
- package/.github/workflows/release-please.yaml +4 -4
- package/.github/workflows/report-incremental-coverage.yaml +1 -1
- package/.github/workflows/selenium-lab-tests.yaml +8 -6
- package/CHANGELOG.md +12 -0
- package/dist/deps.js +3 -3
- package/dist/locales.js +31 -31
- package/dist/shaka-player.compiled.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.externs.js +1 -0
- package/dist/shaka-player.compiled.debug.js +29 -28
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +1 -0
- package/dist/shaka-player.compiled.js +648 -648
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +1 -0
- package/dist/shaka-player.ui.debug.d.ts +1 -0
- package/dist/shaka-player.ui.debug.externs.js +1 -0
- package/dist/shaka-player.ui.debug.js +142 -141
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +1 -0
- package/dist/shaka-player.ui.js +748 -748
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/welcome.md +2 -2
- package/lib/ads/client_side_ad_manager.js +5 -2
- package/lib/media/drm_engine.js +4 -2
- package/lib/offline/indexeddb/storage_mechanism.js +24 -0
- package/lib/player.js +1 -1
- package/lib/polyfill/media_capabilities.js +1 -0
- package/lib/text/ttml_text_parser.js +69 -93
- package/lib/util/error.js +7 -0
- package/lib/util/periods.js +9 -9
- package/lib/util/xml_utils.js +45 -6
- package/package.json +1 -1
- package/test/offline/storage_integration.js +30 -0
- package/test/util/periods_unit.js +149 -11
- package/test/util/xml_utils_unit.js +64 -0
|
@@ -66,8 +66,8 @@ python build/all.py
|
|
|
66
66
|
|
|
67
67
|
Alternatively you can use a docker container:
|
|
68
68
|
```sh
|
|
69
|
-
|
|
70
|
-
docker
|
|
69
|
+
docker build -t shaka-player-build build/docker
|
|
70
|
+
docker run -v $(pwd):/usr/src --user $(id -u):$(id -g) shaka-player-build
|
|
71
71
|
```
|
|
72
72
|
|
|
73
73
|
The output is:
|
|
@@ -15,7 +15,6 @@ goog.require('goog.asserts');
|
|
|
15
15
|
goog.require('shaka.ads.ClientSideAd');
|
|
16
16
|
goog.require('shaka.util.IReleasable');
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
/**
|
|
20
19
|
* A class responsible for client-side ad interactions.
|
|
21
20
|
* @implements {shaka.util.IReleasable}
|
|
@@ -211,7 +210,11 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
211
210
|
// TODO (ismena): Need a better inderstanding of what this does.
|
|
212
211
|
// The docs say it's called to 'start playing the ads,' but I haven't
|
|
213
212
|
// seen the ads actually play until requestAds() is called.
|
|
214
|
-
|
|
213
|
+
// Note: We listen for a play event to avoid autoplay issues that might
|
|
214
|
+
// crash IMA.
|
|
215
|
+
this.video_.addEventListener('play', () => {
|
|
216
|
+
this.imaAdsManager_.start();
|
|
217
|
+
}, {once: true});
|
|
215
218
|
} catch (adError) {
|
|
216
219
|
// If there was a problem with the VAST response,
|
|
217
220
|
// we we won't be getting an ad. Hide ad UI if we showed it already
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -27,6 +27,7 @@ goog.require('shaka.util.StreamUtils');
|
|
|
27
27
|
goog.require('shaka.util.StringUtils');
|
|
28
28
|
goog.require('shaka.util.Timer');
|
|
29
29
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
30
|
+
goog.require('shaka.util.XmlUtils');
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
/** @implements {shaka.util.IDestroyable} */
|
|
@@ -1439,7 +1440,8 @@ shaka.media.DrmEngine = class {
|
|
|
1439
1440
|
return;
|
|
1440
1441
|
}
|
|
1441
1442
|
shaka.log.debug('Unwrapping PlayReady request.');
|
|
1442
|
-
const dom =
|
|
1443
|
+
const dom = shaka.util.XmlUtils.parseXmlString(xml, 'PlayReadyKeyMessage');
|
|
1444
|
+
goog.asserts.assert(dom, 'Failed to parse PlayReady XML!');
|
|
1443
1445
|
|
|
1444
1446
|
// Set request headers.
|
|
1445
1447
|
const headers = dom.getElementsByTagName('HttpHeader');
|
|
@@ -1523,7 +1525,7 @@ shaka.media.DrmEngine = class {
|
|
|
1523
1525
|
hasExpiredKeys = true;
|
|
1524
1526
|
}
|
|
1525
1527
|
|
|
1526
|
-
const keyIdHex = shaka.util.Uint8ArrayUtils.toHex(keyId);
|
|
1528
|
+
const keyIdHex = shaka.util.Uint8ArrayUtils.toHex(keyId).slice(0, 32);
|
|
1527
1529
|
|
|
1528
1530
|
this.keyStatusByKeyId_.set(keyIdHex, status);
|
|
1529
1531
|
});
|
|
@@ -16,6 +16,7 @@ goog.require('shaka.offline.indexeddb.V5StorageCell');
|
|
|
16
16
|
goog.require('shaka.util.Error');
|
|
17
17
|
goog.require('shaka.util.PublicPromise');
|
|
18
18
|
goog.require('shaka.util.Platform');
|
|
19
|
+
goog.require('shaka.util.Timer');
|
|
19
20
|
|
|
20
21
|
|
|
21
22
|
/**
|
|
@@ -55,8 +56,25 @@ shaka.offline.indexeddb.StorageMechanism = class {
|
|
|
55
56
|
const version = shaka.offline.indexeddb.StorageMechanism.VERSION;
|
|
56
57
|
|
|
57
58
|
const p = new shaka.util.PublicPromise();
|
|
59
|
+
|
|
60
|
+
// Add a timeout mechanism, for the (rare?) case where no callbacks are
|
|
61
|
+
// called at all, so that this method doesn't hang forever.
|
|
62
|
+
let timedOut = false;
|
|
63
|
+
const timeOutTimer = new shaka.util.Timer(() => {
|
|
64
|
+
timedOut = true;
|
|
65
|
+
p.reject(new shaka.util.Error(
|
|
66
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
67
|
+
shaka.util.Error.Category.STORAGE,
|
|
68
|
+
shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
|
|
69
|
+
});
|
|
70
|
+
timeOutTimer.tickAfter(2);
|
|
71
|
+
|
|
58
72
|
const open = window.indexedDB.open(name, version);
|
|
59
73
|
open.onsuccess = (event) => {
|
|
74
|
+
if (timedOut) {
|
|
75
|
+
// Too late, we have already given up on opening the storage mechanism.
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
60
78
|
const db = open.result;
|
|
61
79
|
this.db_ = db;
|
|
62
80
|
this.v1_ = shaka.offline.indexeddb.StorageMechanism.createV1_(db);
|
|
@@ -68,6 +86,7 @@ shaka.offline.indexeddb.StorageMechanism = class {
|
|
|
68
86
|
this.v5_ = shaka.offline.indexeddb.StorageMechanism.createV5_(db);
|
|
69
87
|
this.sessions_ =
|
|
70
88
|
shaka.offline.indexeddb.StorageMechanism.createEmeSessionCell_(db);
|
|
89
|
+
timeOutTimer.stop();
|
|
71
90
|
p.resolve();
|
|
72
91
|
};
|
|
73
92
|
open.onupgradeneeded = (event) => {
|
|
@@ -75,11 +94,16 @@ shaka.offline.indexeddb.StorageMechanism = class {
|
|
|
75
94
|
this.createStores_(open.result);
|
|
76
95
|
};
|
|
77
96
|
open.onerror = (event) => {
|
|
97
|
+
if (timedOut) {
|
|
98
|
+
// Too late, we have already given up on opening the storage mechanism.
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
78
101
|
p.reject(new shaka.util.Error(
|
|
79
102
|
shaka.util.Error.Severity.CRITICAL,
|
|
80
103
|
shaka.util.Error.Category.STORAGE,
|
|
81
104
|
shaka.util.Error.Code.INDEXED_DB_ERROR,
|
|
82
105
|
open.error));
|
|
106
|
+
timeOutTimer.stop();
|
|
83
107
|
|
|
84
108
|
// Firefox will raise an error on the main thread unless we stop it here.
|
|
85
109
|
event.preventDefault();
|
package/lib/player.js
CHANGED
|
@@ -6431,7 +6431,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6431
6431
|
* @define {string} A version number taken from git at compile time.
|
|
6432
6432
|
* @export
|
|
6433
6433
|
*/
|
|
6434
|
-
shaka.Player.version = 'v3.2.
|
|
6434
|
+
shaka.Player.version = 'v3.2.14-uncompiled';
|
|
6435
6435
|
|
|
6436
6436
|
// Initialize the deprecation system using the version string we just set
|
|
6437
6437
|
// on the player.
|
|
@@ -43,6 +43,7 @@ shaka.polyfill.MediaCapabilities = class {
|
|
|
43
43
|
!shaka.util.Platform.isPS5() &&
|
|
44
44
|
!shaka.util.Platform.isWebOS() &&
|
|
45
45
|
!shaka.util.Platform.isTizen() &&
|
|
46
|
+
!shaka.util.Platform.isEOS() &&
|
|
46
47
|
navigator.mediaCapabilities) {
|
|
47
48
|
shaka.log.info(
|
|
48
49
|
'MediaCapabilities: Native mediaCapabilities support found.');
|
|
@@ -41,8 +41,6 @@ shaka.text.TtmlTextParser = class {
|
|
|
41
41
|
const ttsNs = TtmlTextParser.styleNs_;
|
|
42
42
|
const str = shaka.util.StringUtils.fromUTF8(data);
|
|
43
43
|
const cues = [];
|
|
44
|
-
const parser = new DOMParser();
|
|
45
|
-
let xml = null;
|
|
46
44
|
|
|
47
45
|
// dont try to parse empty string as
|
|
48
46
|
// DOMParser will not throw error but return an errored xml
|
|
@@ -50,9 +48,8 @@ shaka.text.TtmlTextParser = class {
|
|
|
50
48
|
return cues;
|
|
51
49
|
}
|
|
52
50
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
} catch (exception) {
|
|
51
|
+
const tt = XmlUtils.parseXmlString(str, 'tt');
|
|
52
|
+
if (!tt) {
|
|
56
53
|
throw new shaka.util.Error(
|
|
57
54
|
shaka.util.Error.Severity.CRITICAL,
|
|
58
55
|
shaka.util.Error.Category.TEXT,
|
|
@@ -60,110 +57,89 @@ shaka.text.TtmlTextParser = class {
|
|
|
60
57
|
'Failed to parse TTML.');
|
|
61
58
|
}
|
|
62
59
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
68
|
-
shaka.util.Error.Category.TEXT,
|
|
69
|
-
shaka.util.Error.Code.INVALID_XML,
|
|
70
|
-
parserError.textContent);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const tt = xml.getElementsByTagName('tt')[0];
|
|
74
|
-
// TTML should always have tt element.
|
|
75
|
-
if (!tt) {
|
|
76
|
-
throw new shaka.util.Error(
|
|
77
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
78
|
-
shaka.util.Error.Category.TEXT,
|
|
79
|
-
shaka.util.Error.Code.INVALID_XML,
|
|
80
|
-
'TTML does not contain <tt> tag.');
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
const body = tt.getElementsByTagName('body')[0];
|
|
84
|
-
if (!body) {
|
|
85
|
-
return [];
|
|
86
|
-
}
|
|
60
|
+
const body = tt.getElementsByTagName('body')[0];
|
|
61
|
+
if (!body) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
87
64
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
65
|
+
// Get the framerate, subFrameRate and frameRateMultiplier if applicable.
|
|
66
|
+
const frameRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRate');
|
|
67
|
+
const subFrameRate = XmlUtils.getAttributeNSList(
|
|
68
|
+
tt, ttpNs, 'subFrameRate');
|
|
69
|
+
const frameRateMultiplier =
|
|
70
|
+
XmlUtils.getAttributeNSList(tt, ttpNs, 'frameRateMultiplier');
|
|
71
|
+
const tickRate = XmlUtils.getAttributeNSList(tt, ttpNs, 'tickRate');
|
|
95
72
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
73
|
+
const cellResolution = XmlUtils.getAttributeNSList(
|
|
74
|
+
tt, ttpNs, 'cellResolution');
|
|
75
|
+
const spaceStyle = tt.getAttribute('xml:space') || 'default';
|
|
76
|
+
const extent = XmlUtils.getAttributeNSList(tt, ttsNs, 'extent');
|
|
100
77
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
78
|
+
if (spaceStyle != 'default' && spaceStyle != 'preserve') {
|
|
79
|
+
throw new shaka.util.Error(
|
|
80
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
81
|
+
shaka.util.Error.Category.TEXT,
|
|
82
|
+
shaka.util.Error.Code.INVALID_XML,
|
|
83
|
+
'Invalid xml:space value: ' + spaceStyle);
|
|
84
|
+
}
|
|
85
|
+
const whitespaceTrim = spaceStyle == 'default';
|
|
109
86
|
|
|
110
|
-
|
|
111
|
-
|
|
87
|
+
const rateInfo = new TtmlTextParser.RateInfo_(
|
|
88
|
+
frameRate, subFrameRate, frameRateMultiplier, tickRate);
|
|
112
89
|
|
|
113
|
-
|
|
114
|
-
|
|
90
|
+
const cellResolutionInfo =
|
|
91
|
+
TtmlTextParser.getCellResolution_(cellResolution);
|
|
115
92
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
93
|
+
const metadata = tt.getElementsByTagName('metadata')[0];
|
|
94
|
+
const metadataElements = metadata ? XmlUtils.getChildren(metadata) : [];
|
|
95
|
+
const styles = Array.from(tt.getElementsByTagName('style'));
|
|
96
|
+
const regionElements = Array.from(tt.getElementsByTagName('region'));
|
|
120
97
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
98
|
+
const cueRegions = [];
|
|
99
|
+
for (const region of regionElements) {
|
|
100
|
+
const cueRegion =
|
|
101
|
+
TtmlTextParser.parseCueRegion_(region, styles, extent);
|
|
102
|
+
if (cueRegion) {
|
|
103
|
+
cueRegions.push(cueRegion);
|
|
128
104
|
}
|
|
105
|
+
}
|
|
129
106
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
107
|
+
// A <body> element should only contain <div> elements, not <p> or <span>
|
|
108
|
+
// elements. We used to allow this, but it is non-compliant, and the
|
|
109
|
+
// loose nature of our previous parser made it difficult to implement TTML
|
|
110
|
+
// nesting more fully.
|
|
111
|
+
if (XmlUtils.findChildren(body, 'p').length) {
|
|
112
|
+
throw new shaka.util.Error(
|
|
113
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
114
|
+
shaka.util.Error.Category.TEXT,
|
|
115
|
+
shaka.util.Error.Code.INVALID_TEXT_CUE,
|
|
116
|
+
'<p> can only be inside <div> in TTML');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
for (const div of XmlUtils.findChildren(body, 'div')) {
|
|
120
|
+
// A <div> element should only contain <p>, not <span>.
|
|
121
|
+
if (XmlUtils.findChildren(div, 'span').length) {
|
|
135
122
|
throw new shaka.util.Error(
|
|
136
123
|
shaka.util.Error.Severity.CRITICAL,
|
|
137
124
|
shaka.util.Error.Category.TEXT,
|
|
138
125
|
shaka.util.Error.Code.INVALID_TEXT_CUE,
|
|
139
|
-
'<
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
for (const div of XmlUtils.findChildren(body, 'div')) {
|
|
143
|
-
// A <div> element should only contain <p>, not <span>.
|
|
144
|
-
if (XmlUtils.findChildren(div, 'span').length) {
|
|
145
|
-
throw new shaka.util.Error(
|
|
146
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
147
|
-
shaka.util.Error.Category.TEXT,
|
|
148
|
-
shaka.util.Error.Code.INVALID_TEXT_CUE,
|
|
149
|
-
'<span> can only be inside <p> in TTML');
|
|
150
|
-
}
|
|
126
|
+
'<span> can only be inside <p> in TTML');
|
|
151
127
|
}
|
|
128
|
+
}
|
|
152
129
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
cues.push(cue);
|
|
130
|
+
const cue = TtmlTextParser.parseCue_(
|
|
131
|
+
body, time.periodStart, rateInfo, metadataElements, styles,
|
|
132
|
+
regionElements, cueRegions, whitespaceTrim,
|
|
133
|
+
cellResolutionInfo, /* parentCueElement= */ null,
|
|
134
|
+
/* isContent= */ false);
|
|
135
|
+
if (cue) {
|
|
136
|
+
// According to the TTML spec, backgrounds default to transparent.
|
|
137
|
+
// So default the background of the top-level element to transparent.
|
|
138
|
+
// Nested elements may override that background color already.
|
|
139
|
+
if (!cue.backgroundColor) {
|
|
140
|
+
cue.backgroundColor = 'transparent';
|
|
166
141
|
}
|
|
142
|
+
cues.push(cue);
|
|
167
143
|
}
|
|
168
144
|
|
|
169
145
|
return cues;
|
package/lib/util/error.js
CHANGED
|
@@ -993,6 +993,13 @@ shaka.util.Error.Code = {
|
|
|
993
993
|
*/
|
|
994
994
|
'DOWNLOAD_SIZE_CALLBACK_ERROR': 9015,
|
|
995
995
|
|
|
996
|
+
/**
|
|
997
|
+
* When attempting to open an indexedDB instance, nothing happened for long
|
|
998
|
+
* enough for us to time out. This keeps the storage mechanism from hanging
|
|
999
|
+
* indefinitely, if neither the success nor error callbacks are called.
|
|
1000
|
+
*/
|
|
1001
|
+
'INDEXED_DB_INIT_TIMED_OUT': 9017,
|
|
1002
|
+
|
|
996
1003
|
/**
|
|
997
1004
|
* CS IMA SDK, required for ad insertion, has not been included on the page.
|
|
998
1005
|
*/
|
package/lib/util/periods.js
CHANGED
|
@@ -1146,15 +1146,6 @@ shaka.util.PeriodCombiner = class {
|
|
|
1146
1146
|
return false;
|
|
1147
1147
|
}
|
|
1148
1148
|
|
|
1149
|
-
// If the language doesn't match, but the candidate is the "primary"
|
|
1150
|
-
// language, then that should be preferred as a fallback.
|
|
1151
|
-
if (!best.primary && candidate.primary) {
|
|
1152
|
-
return true;
|
|
1153
|
-
}
|
|
1154
|
-
if (best.primary && !candidate.primary) {
|
|
1155
|
-
return false;
|
|
1156
|
-
}
|
|
1157
|
-
|
|
1158
1149
|
// If language-based differences haven't decided this, look at roles. If
|
|
1159
1150
|
// the candidate has more roles in common with the output, upgrade to the
|
|
1160
1151
|
// candidate.
|
|
@@ -1188,6 +1179,15 @@ shaka.util.PeriodCombiner = class {
|
|
|
1188
1179
|
return false;
|
|
1189
1180
|
}
|
|
1190
1181
|
|
|
1182
|
+
// If the language doesn't match, but the candidate is the "primary"
|
|
1183
|
+
// language, then that should be preferred as a fallback.
|
|
1184
|
+
if (!best.primary && candidate.primary) {
|
|
1185
|
+
return true;
|
|
1186
|
+
}
|
|
1187
|
+
if (best.primary && !candidate.primary) {
|
|
1188
|
+
return false;
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
1191
|
// If language-based and role-based features are equivalent, take the audio
|
|
1192
1192
|
// with the closes channel count to the output.
|
|
1193
1193
|
const channelsBetterOrWorse =
|
package/lib/util/xml_utils.js
CHANGED
|
@@ -8,6 +8,7 @@ goog.provide('shaka.util.XmlUtils');
|
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
10
|
goog.require('shaka.log');
|
|
11
|
+
goog.require('shaka.util.Lazy');
|
|
11
12
|
goog.require('shaka.util.StringUtils');
|
|
12
13
|
|
|
13
14
|
|
|
@@ -348,19 +349,21 @@ shaka.util.XmlUtils = class {
|
|
|
348
349
|
*/
|
|
349
350
|
static parseXmlString(xmlString, expectedRootElemName) {
|
|
350
351
|
const parser = new DOMParser();
|
|
351
|
-
|
|
352
|
+
const unsafeXmlString =
|
|
353
|
+
shaka.util.XmlUtils.trustedHTMLFromString_.value()(xmlString);
|
|
354
|
+
let unsafeXml = null;
|
|
352
355
|
try {
|
|
353
|
-
|
|
356
|
+
unsafeXml = parser.parseFromString(unsafeXmlString, 'text/xml');
|
|
354
357
|
} catch (exception) {
|
|
355
358
|
shaka.log.error('XML parsing exception:', exception);
|
|
356
359
|
return null;
|
|
357
360
|
}
|
|
358
361
|
|
|
359
362
|
// According to MDN, parseFromString never returns null.
|
|
360
|
-
goog.asserts.assert(
|
|
363
|
+
goog.asserts.assert(unsafeXml, 'Parsed XML document cannot be null!');
|
|
361
364
|
|
|
362
365
|
// Check for empty documents.
|
|
363
|
-
const rootElem =
|
|
366
|
+
const rootElem = unsafeXml.documentElement;
|
|
364
367
|
if (!rootElem) {
|
|
365
368
|
shaka.log.error('XML document was empty!');
|
|
366
369
|
return null;
|
|
@@ -374,13 +377,28 @@ shaka.util.XmlUtils = class {
|
|
|
374
377
|
}
|
|
375
378
|
|
|
376
379
|
// The top-level element in the loaded XML should have the name we expect.
|
|
377
|
-
if (
|
|
380
|
+
if (rootElem.tagName != expectedRootElemName) {
|
|
378
381
|
shaka.log.error(
|
|
379
382
|
`XML tag name does not match expected "${expectedRootElemName}":`,
|
|
380
|
-
|
|
383
|
+
rootElem.tagName);
|
|
381
384
|
return null;
|
|
382
385
|
}
|
|
383
386
|
|
|
387
|
+
// SECURITY: Verify that the document does not contain elements from the
|
|
388
|
+
// HTML or SVG namespaces, which could trigger script execution and XSS.
|
|
389
|
+
const iterator = document.createNodeIterator(
|
|
390
|
+
unsafeXml,
|
|
391
|
+
NodeFilter.SHOW_ALL,
|
|
392
|
+
);
|
|
393
|
+
let currentNode;
|
|
394
|
+
while (currentNode = iterator.nextNode()) {
|
|
395
|
+
if (currentNode instanceof HTMLElement ||
|
|
396
|
+
currentNode instanceof SVGElement) {
|
|
397
|
+
shaka.log.error('XML document embeds unsafe content!');
|
|
398
|
+
return null;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
384
402
|
return rootElem;
|
|
385
403
|
}
|
|
386
404
|
|
|
@@ -402,3 +420,24 @@ shaka.util.XmlUtils = class {
|
|
|
402
420
|
}
|
|
403
421
|
}
|
|
404
422
|
};
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Promote a string to TrustedHTML. This function is security-sensitive and
|
|
426
|
+
* should only be used with security approval where the string is guaranteed not
|
|
427
|
+
* to cause an XSS vulnerability.
|
|
428
|
+
*
|
|
429
|
+
* @private {!shaka.util.Lazy.<function(!string): (!TrustedHTML|!string)>}
|
|
430
|
+
*/
|
|
431
|
+
shaka.util.XmlUtils.trustedHTMLFromString_ = new shaka.util.Lazy(() => {
|
|
432
|
+
if (typeof trustedTypes !== 'undefined') {
|
|
433
|
+
// Create a Trusted Types policy for promoting the string to TrustedHTML.
|
|
434
|
+
// The Lazy wrapper ensures this policy is only created once.
|
|
435
|
+
const policy = trustedTypes.createPolicy('shaka-player#xml', {
|
|
436
|
+
createHTML: (s) => s,
|
|
437
|
+
});
|
|
438
|
+
return (s) => policy.createHTML(s);
|
|
439
|
+
}
|
|
440
|
+
// Fall back to strings in environments that don't support Trusted Types.
|
|
441
|
+
return (s) => s;
|
|
442
|
+
});
|
|
443
|
+
|
package/package.json
CHANGED
|
@@ -990,6 +990,36 @@ filterDescribe('Storage', storageSupport, () => {
|
|
|
990
990
|
}
|
|
991
991
|
});
|
|
992
992
|
|
|
993
|
+
/**
|
|
994
|
+
* In some situations, indexedDB.open() can just hang, and call neither the
|
|
995
|
+
* 'success' nor the 'error' callbacks.
|
|
996
|
+
* I'm not sure what causes it, but it seems to happen consistently between
|
|
997
|
+
* reloads when it does so it might be a browser-based issue.
|
|
998
|
+
* In that case, we should time out with an error, instead of also hanging.
|
|
999
|
+
*/
|
|
1000
|
+
it('throws an error if indexedDB open times out', async () => {
|
|
1001
|
+
const oldOpen = window.indexedDB.open;
|
|
1002
|
+
window.indexedDB.open = () => {
|
|
1003
|
+
// Just return a dummy object.
|
|
1004
|
+
return /** @type {!IDBOpenDBRequest} */ ({
|
|
1005
|
+
onsuccess: (event) => {},
|
|
1006
|
+
onerror: (error) => {},
|
|
1007
|
+
});
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
/** @type {!shaka.offline.StorageMuxer} */
|
|
1011
|
+
const muxer = new shaka.offline.StorageMuxer();
|
|
1012
|
+
const expectedError = shaka.test.Util.jasmineError(new shaka.util.Error(
|
|
1013
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1014
|
+
shaka.util.Error.Category.STORAGE,
|
|
1015
|
+
shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
|
|
1016
|
+
|
|
1017
|
+
await expectAsync(muxer.init())
|
|
1018
|
+
.toBeRejectedWith(expectedError);
|
|
1019
|
+
|
|
1020
|
+
window.indexedDB.open = oldOpen;
|
|
1021
|
+
});
|
|
1022
|
+
|
|
993
1023
|
it('throws an error if the content is a live stream', async () => {
|
|
994
1024
|
const expected = Util.jasmineError(new shaka.util.Error(
|
|
995
1025
|
shaka.util.Error.Severity.CRITICAL,
|