Skip to content

Commit 1b0c44d

Browse files
authored
Firefox should not call requestVideoFrameCallback to ensure videos are uploadable. (#3721)
It seems to hang sometimes, maybe due to visibility testing. Fortunately, we believe `await video.play()` is enough in Firefox. In diagnosing and fixing this, did clean-up refactor of tex-image-and-sub-image-*d-with-video.js to use async+await.
1 parent a33f03c commit 1b0c44d

File tree

3 files changed

+186
-119
lines changed

3 files changed

+186
-119
lines changed

sdk/tests/js/tests/tex-image-and-sub-image-2d-with-video.js

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -210,79 +210,80 @@ function generateTest(internalFormat, pixelFormat, pixelType, prologue, resource
210210
cases = tiu.crossProductTestCasesWithUnpackColorSpaces(
211211
cases, tiu.unpackColorSpacesToTest(gl));
212212

213-
function runTexImageTest(bindingTarget) {
213+
async function runTexImageTest(bindingTarget) {
214214
var program;
215215
if (bindingTarget == gl.TEXTURE_2D) {
216216
program = tiu.setupTexturedQuad(gl, internalFormat);
217217
} else {
218218
program = tiu.setupTexturedQuadWithCubeMap(gl, internalFormat);
219219
}
220220

221-
return new Promise(function(resolve, reject) {
222-
var videoNdx = 0;
223-
var video;
224-
function runNextVideo() {
225-
if (video) {
226-
video.pause();
227-
}
228-
229-
if (videoNdx == videos.length) {
230-
resolve("SUCCESS");
231-
return;
232-
}
233-
234-
var info = videos[videoNdx++];
235-
debug("");
236-
debug("testing: " + info.type);
237-
video = document.createElement("video");
238-
video.muted = true;
239-
var canPlay = true;
240-
if (!video.canPlayType) {
241-
testFailed("video.canPlayType required method missing");
242-
runNextVideo();
243-
return;
244-
}
221+
for (const info of videos) {
222+
debug("");
223+
debug("testing: " + JSON.stringify({
224+
type: info.type,
225+
bindingTarget: wtu.glEnumToString(gl, bindingTarget),
226+
}));
245227

246-
if(!video.canPlayType(info.type).replace(/no/, '')) {
247-
debug(info.type + " unsupported");
248-
runNextVideo();
249-
return;
250-
};
228+
const video = await loadVideo(info);
229+
if (!video) continue;
251230

231+
try {
252232
document.body.appendChild(video);
253233
video.type = info.type;
254234
video.src = info.src;
255-
wtu.startPlayingAndWaitForVideo(video, runTest);
235+
236+
await wtu.waitVideoUploadable(video);
237+
238+
await testVideo(video);
239+
} finally {
240+
video.pause();
241+
}
242+
}
243+
244+
async function loadVideo(info) {
245+
const video = document.createElement("video");
246+
video.muted = true;
247+
if (!video.canPlayType) {
248+
testFailed("video.canPlayType required method missing");
249+
return null;
256250
}
257-
function runTest() {
258-
for (var i in cases) {
259-
if (bindingTarget == gl.TEXTURE_CUBE_MAP) {
260-
// Cube map texture must be square but video is not square.
261-
if (!cases[i].sub) {
262-
break;
263-
}
264-
// Skip sub-rectangle tests for cube map textures for the moment.
265-
if (cases[i].sourceSubRectangle) {
266-
break;
267-
}
251+
252+
if(!video.canPlayType(info.type).replace(/no/, '')) {
253+
debug(info.type + " unsupported");
254+
return null;
255+
}
256+
257+
return video;
258+
}
259+
260+
async function testVideo(video) {
261+
await wtu.dispatchPromise();
262+
for (var i in cases) {
263+
if (bindingTarget == gl.TEXTURE_CUBE_MAP) {
264+
// Cube map texture must be square but video is not square.
265+
if (!cases[i].sub) {
266+
break;
267+
}
268+
// Skip sub-rectangle tests for cube map textures for the moment.
269+
if (cases[i].sourceSubRectangle) {
270+
break;
268271
}
269-
runOneIteration(video, cases[i].unpackColorSpace, cases[i].sub, cases[i].flipY,
270-
cases[i].topColor,
271-
cases[i].bottomColor,
272-
cases[i].sourceSubRectangle,
273-
program, bindingTarget);
274272
}
275-
runNextVideo();
273+
runOneIteration(video, cases[i].unpackColorSpace, cases[i].sub, cases[i].flipY,
274+
cases[i].topColor,
275+
cases[i].bottomColor,
276+
cases[i].sourceSubRectangle,
277+
program, bindingTarget);
276278
}
277-
runNextVideo();
278-
});
279+
}
279280
}
280281

281-
runTexImageTest(gl.TEXTURE_2D).then(function(val) {
282-
runTexImageTest(gl.TEXTURE_CUBE_MAP).then(function(val) {
283-
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
284-
finishTest();
285-
});
282+
call(async () => {
283+
await runTexImageTest(gl.TEXTURE_2D);
284+
await runTexImageTest(gl.TEXTURE_CUBE_MAP);
285+
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
286+
finishTest();
286287
});
287288
}
288289

sdk/tests/js/tests/tex-image-and-sub-image-3d-with-video.js

Lines changed: 56 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -166,76 +166,77 @@ function generateTest(internalFormat, pixelFormat, pixelType, prologue, resource
166166
topColor: greenColor, bottomColor: greenColor },
167167
];
168168

169-
function runTexImageTest(bindingTarget) {
169+
async function runTexImageTest(bindingTarget) {
170170
var program;
171171
if (bindingTarget == gl.TEXTURE_3D) {
172172
program = tiu.setupTexturedQuadWith3D(gl, internalFormat);
173173
} else {
174174
program = tiu.setupTexturedQuadWith2DArray(gl, internalFormat);
175175
}
176176

177-
return new Promise(function(resolve, reject) {
178-
var videoNdx = 0;
179-
var video;
180-
function runNextVideo() {
181-
if (video) {
182-
video.pause();
183-
}
184-
185-
if (videoNdx == videos.length) {
186-
resolve("SUCCESS");
187-
return;
188-
}
189-
190-
var info = videos[videoNdx++];
191-
debug("");
192-
debug("testing: " + info.type);
193-
video = document.createElement("video");
194-
video.muted = true;
195-
var canPlay = true;
196-
if (!video.canPlayType) {
197-
testFailed("video.canPlayType required method missing");
198-
runNextVideo();
199-
return;
200-
}
201-
202-
if(!video.canPlayType(info.type).replace(/no/, '')) {
203-
debug(info.type + " unsupported");
204-
runNextVideo();
205-
return;
206-
};
177+
for (const info of videos) {
178+
debug("");
179+
debug("testing: " + JSON.stringify({
180+
type: info.type,
181+
bindingTarget: wtu.glEnumToString(gl, bindingTarget),
182+
}));
207183

184+
const video = await loadVideo(info);
185+
if (!video) continue;
186+
187+
try {
208188
document.body.appendChild(video);
209189
video.type = info.type;
210190
video.src = info.src;
211-
wtu.startPlayingAndWaitForVideo(video, runTest);
191+
192+
await wtu.waitVideoUploadable(video);
193+
194+
await testVideo(video);
195+
} finally {
196+
video.pause();
212197
}
213-
function runTest() {
214-
for (var i in cases) {
215-
runOneIteration(video, cases[i].flipY, false,
216-
cases[i].topColor, cases[i].bottomColor,
217-
program, bindingTarget, cases[i].depth,
218-
cases[i].sourceSubRectangle,
219-
cases[i].unpackImageHeight,
220-
cases[i].rTextureCoord);
221-
runOneIteration(video, cases[i].flipY, true,
222-
cases[i].topColor, cases[i].bottomColor,
223-
program, bindingTarget, cases[i].depth,
224-
cases[i].sourceSubRectangle,
225-
cases[i].unpackImageHeight,
226-
cases[i].rTextureCoord);
227-
}
228-
runNextVideo();
198+
}
199+
200+
async function loadVideo(info) {
201+
const video = document.createElement("video");
202+
video.muted = true;
203+
if (!video.canPlayType) {
204+
testFailed("video.canPlayType required method missing");
205+
return null;
206+
}
207+
208+
if(!video.canPlayType(info.type).replace(/no/, '')) {
209+
debug(info.type + " unsupported");
210+
return null;
229211
}
230-
runNextVideo();
231-
});
212+
213+
return video;
214+
}
215+
216+
async function testVideo(video) {
217+
await wtu.dispatchPromise();
218+
for (var i in cases) {
219+
runOneIteration(video, cases[i].flipY, false,
220+
cases[i].topColor, cases[i].bottomColor,
221+
program, bindingTarget, cases[i].depth,
222+
cases[i].sourceSubRectangle,
223+
cases[i].unpackImageHeight,
224+
cases[i].rTextureCoord);
225+
runOneIteration(video, cases[i].flipY, true,
226+
cases[i].topColor, cases[i].bottomColor,
227+
program, bindingTarget, cases[i].depth,
228+
cases[i].sourceSubRectangle,
229+
cases[i].unpackImageHeight,
230+
cases[i].rTextureCoord);
231+
}
232+
}
232233
}
233234

234-
runTexImageTest(gl.TEXTURE_3D).then(function(val) {
235-
runTexImageTest(gl.TEXTURE_2D_ARRAY).then(function(val) {
236-
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
237-
finishTest();
238-
});
235+
call(async () => {
236+
await runTexImageTest(gl.TEXTURE_3D);
237+
await runTexImageTest(gl.TEXTURE_2D_ARRAY);
238+
wtu.glErrorShouldBe(gl, gl.NO_ERROR, "should be no errors");
239+
finishTest();
239240
});
240241
}
241242

sdk/tests/js/webgl-test-utils.js

Lines changed: 74 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3214,25 +3214,87 @@ async function startPlayingAndWaitForVideo(video, callback) {
32143214
return;
32153215
}
32163216

3217+
try {
3218+
await waitVideoUploadable(video);
3219+
} catch (e) {
3220+
testFailed('waitVideoUploadable/video.play failed: ' + e);
3221+
return;
3222+
}
3223+
3224+
callback(video);
3225+
}
3226+
3227+
/**
3228+
*
3229+
* @returns {Map<string,{name:string,version:string}>}
3230+
*/
3231+
function userAgentEngineClaims() {
3232+
const RE_NAME_SLASH_VERSION = /([^ ]+)[/]([^ ]+)/g;
3233+
const claimsByName = new Map();
3234+
for (const m of navigator.userAgent.matchAll(RE_NAME_SLASH_VERSION)) {
3235+
if (m[0] == 'Mozilla/5.0') continue; // Deprecated and frozen.
3236+
if (m[0] == 'Gecko/20100101') continue; // Deprecated and frozen.
3237+
const claim = {name: m[1], version: m[2]};
3238+
console.log(m[0], claim)
3239+
claimsByName.set(claim.name, claim);
3240+
}
3241+
return claimsByName;
3242+
}
3243+
3244+
const CACHED = {};
3245+
3246+
/**
3247+
*
3248+
* @returns {{name:string,version:string}}
3249+
*/
3250+
function userAgentEngine() {
3251+
return CACHED.USER_AGENT_ENGINE = CACHED.USER_AGENT_ENGINE || call(() => {
3252+
const claimsByName = userAgentEngineClaims();
3253+
3254+
// Chrome on desktop claims 'Chrome' and 'Safari'.
3255+
// Firefox on iOS claims 'FxiOS' and 'Safari'. (and not 'Firefox')
3256+
// (Amusingly, FxiOS also claims 'Mobile/15E148', which wins for highest version!)
3257+
// Chrome on iOS claims 'CriOS' and 'Safari'. (and not 'Chrome')
3258+
// Firefox on desktop and Android and Safari on desktop claim only themselves.
3259+
const CLAIM_CHECK_ORDER = [
3260+
'Chrome',
3261+
'Firefox', // Not FxiOS!
3262+
'Safari', // Safari, Chrome desktop, and FxiOS/CriOS.
3263+
];
3264+
for (const check of CLAIM_CHECK_ORDER) {
3265+
const ret = claimsByName.get(check);
3266+
if (ret) return ret;
3267+
}
3268+
return {name: '', version: '0.0'};
3269+
});
3270+
}
3271+
3272+
/**
3273+
* Awaits a video that is playing and reliably uploadable through webgl.
3274+
* @param {!HTMLVideoElement} video An HTML5 Video element.
3275+
* @throws `video.play()`
3276+
*/
3277+
async function waitVideoUploadable(video) {
32173278
video.loop = true;
32183279
video.muted = true;
32193280
// See whether setting the preload flag de-flakes video-related tests.
32203281
video.preload = 'auto';
32213282

3222-
try {
3223-
await video.play();
3224-
} catch (e) {
3225-
testFailed('video.play failed: ' + e);
3226-
return;
3227-
}
3283+
await video.play();
32283284

3229-
if (video.requestVideoFrameCallback) {
3285+
// Chrome says they need to wait for requestVideoFrameCallback.
3286+
// But Firefox doesn't, and also can get awaiting here. (Occlusion testing?)
3287+
let useRvfc = video.requestVideoFrameCallback;
3288+
if (userAgentEngine().name == 'Firefox') {
3289+
useRvfc = false;
3290+
}
3291+
if (useRvfc) {
32303292
await new Promise(go => video.requestVideoFrameCallback(go));
32313293
}
3232-
3233-
callback(video);
32343294
}
32353295

3296+
// -
3297+
32363298
var getHost = function(url) {
32373299
url = url.replace("\\", "/");
32383300
var pos = url.indexOf("://");
@@ -3679,7 +3741,10 @@ var API = {
36793741
replaceParams: replaceParams,
36803742
requestAnimFrame: requestAnimFrame,
36813743
runSteps: runSteps,
3744+
userAgentEngine,
3745+
userAgentEngineClaims,
36823746
waitForComposite: waitForComposite,
3747+
waitVideoUploadable,
36833748

36843749
// fullscreen api
36853750
setupFullscreen: setupFullscreen,

0 commit comments

Comments
 (0)