Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
-
CI results over the last 21 days
- Manifest: dom/media/test/browser/browser.toml
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This test verifies that main thread GMP queries and a pending decodingInfo
// query, from the page or from a worker, all settle in a content process that
// has no GMP service yet.
"use strict";
// Main thread calls that ask for GMP support, with a check of their result.
const MAIN_CALLS = {
RTCPeerConnection: state =>
is(state, "stable", "RTCPeerConnection was created"),
"RTCRtpSender.getCapabilities": codecs =>
Assert.greater(codecs, 0, "getCapabilities returned codecs"),
"webrtc decodingInfo": supported =>
ok(supported, "webrtc decodingInfo resolved as supported"),
requestMediaKeySystemAccess: keySystem =>
is(keySystem, "org.w3.clearkey", "requestMediaKeySystemAccess resolved"),
canPlayType: canPlay =>
ok(
["", "maybe", "probably"].includes(canPlay),
`canPlayType returned a valid value "${canPlay}"`
),
};
// Starts a decodingInfo query in a fresh content process, blocks the main
// thread, then runs a main thread call that asks for GMP support. Asserts that
// the query resolved and returns the result of the main thread call.
// - `inWorker`: if true, the query runs in a dedicated worker with a WebRTC
// config. Otherwise it runs on the page with a media-source config.
// - `mainCall`: the main thread call, a key of MAIN_CALLS.
async function raceInFreshProcess({ inWorker, mainCall }) {
const PAGE_URL =
const WEBRTC_CONFIG = {
type: "webrtc",
video: {
contentType: "video/VP8",
width: 640,
height: 480,
bitrate: 1000000,
framerate: 30,
},
};
let result;
// forceNewProcess gives a content process that has not created the GMP
// service yet.
await BrowserTestUtils.withNewTab(
{ gBrowser, url: PAGE_URL, forceNewProcess: true },
async browser => {
result = await SpecialPowers.spawn(
browser,
[inWorker, mainCall, WEBRTC_CONFIG],
async (inWorker, mainCall, webrtcConfig) => {
let worker;
let pending;
if (inWorker) {
const workerSource = `
onmessage = async ({ data: config }) => {
try {
const info = await navigator.mediaCapabilities.decodingInfo(config);
postMessage({ status: "fulfilled", supported: info.supported });
} catch (e) {
postMessage({ status: "rejected", error: String(e) });
}
};
postMessage("ready");
`;
worker = new content.Worker(
content.URL.createObjectURL(
new content.Blob([workerSource], { type: "text/javascript" })
)
);
const nextMessage = () =>
new Promise((resolve, reject) => {
worker.onmessage = e => resolve(e.data);
worker.onerror = e => reject(new Error(e.message));
});
await nextMessage();
pending = nextMessage().then(data =>
data.status == "fulfilled"
? data.supported
: Promise.reject(new Error(data.error))
);
worker.postMessage(webrtcConfig);
} else {
pending = content.navigator.mediaCapabilities
.decodingInfo({
type: "media-source",
video: {
contentType: 'video/mp4; codecs="avc1.4D4016"',
width: 160,
height: 90,
bitrate: 100000,
framerate: 24,
},
})
.then(info => info.supported);
}
const start = content.performance.now();
while (content.performance.now() - start < 1000) {
// Spin instead of awaiting a timer: the main thread must not run
// events, so that the background query runs before the main thread
// call.
}
try {
let mainResult;
switch (mainCall) {
case "RTCPeerConnection": {
const pc = new content.RTCPeerConnection();
mainResult = pc.signalingState;
pc.close();
break;
}
case "RTCRtpSender.getCapabilities":
mainResult =
content.RTCRtpSender.getCapabilities("video").codecs.length;
break;
case "webrtc decodingInfo":
mainResult = (
await content.navigator.mediaCapabilities.decodingInfo(
webrtcConfig
)
).supported;
break;
case "requestMediaKeySystemAccess":
mainResult = (
await content.navigator.requestMediaKeySystemAccess(
"org.w3.clearkey",
[
{
initDataTypes: ["webm"],
videoCapabilities: [
{ contentType: 'video/webm; codecs="vp9"' },
],
},
]
)
).keySystem;
break;
case "canPlayType":
mainResult = content.document
.createElement("video")
.canPlayType('video/mp4; codecs="avc1.42E01E"');
break;
default:
throw new Error(`Unknown main thread call ${mainCall}`);
}
const [queryResult] = await Promise.allSettled([pending]);
return {
queryStatus: queryResult.status,
queryError: String(queryResult.reason ?? ""),
mainResult,
};
} finally {
worker?.terminate();
}
}
);
}
);
is(
result.queryStatus,
"fulfilled",
`decodingInfo resolved ${inWorker ? "in the worker" : "on the page"} ${result.queryError}`
);
return result.mainResult;
}
for (const inWorker of [false, true]) {
for (const [mainCall, check] of Object.entries(MAIN_CALLS)) {
// On the page, the pref that canPlayType needs makes decodingInfo create
// the GMP service on the main thread first, so there is no race.
if (!inWorker && mainCall == "canPlayType") {
continue;
}
add_task(async function testDecodingInfoThenMainThreadCall() {
info(
`decodingInfo ${inWorker ? "in a worker" : "on the page"}, then ${mainCall}`
);
if (mainCall == "canPlayType") {
// Make canPlayType ask the GMP decoder module first.
await SpecialPowers.pushPrefEnv({
set: [["media.gmp.decoder.preferred", true]],
});
}
check(await raceInFreshProcess({ inWorker, mainCall }));
});
}
}