Bug 1627999 - part7 : add test. r=bryce

This patch will do :
- add test cases
- introduce the test-only notification `media-displayed-metadata-changed` when the event source updates its metadata

The advantage of doing so :
- increase test coverage

Differential Revision: https://phabricator.services.mozilla.com/D72501
This commit is contained in:
alwu 2020-05-15 21:50:12 +00:00
Родитель fa7fe1ff9d
Коммит eac28634c9
7 изменённых файлов: 457 добавлений и 27 удалений

Просмотреть файл

@ -126,6 +126,12 @@ void MediaControlKeysManager::SetMediaMetadata(
NS_ConvertUTF16toUTF8(mMetadata.mTitle).get(),
NS_ConvertUTF16toUTF8(mMetadata.mArtist).get(),
NS_ConvertUTF16toUTF8(mMetadata.mAlbum).get());
if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
if (nsCOMPtr<nsIObserverService> obs = services::GetObserverService()) {
obs->NotifyObservers(nullptr, "media-displayed-metadata-changed",
nullptr);
}
}
}
} // namespace dom

Просмотреть файл

@ -2,6 +2,8 @@
tags = mediacontrol
support-files =
file_autoplay.html
file_main_frame_with_multiple_child_session_frames.html
file_media_session_page.html
file_muted_autoplay.html
file_non_autoplay.html
file_non_eligible_media.html
@ -13,6 +15,7 @@ support-files =
../../../../toolkit/content/tests/browser/silentAudioTrack.webm
[browser_audio_focus_management.js]
[browser_media_control_audio_focus_within_a_page.js]
[browser_media_control_captured_audio.js]
[browser_media_control_metadata.js]
[browser_media_control_keys_event.js]

Просмотреть файл

@ -0,0 +1,357 @@
/* eslint-disable no-undef */
const mainPageURL =
"https://example.com/browser/dom/media/mediacontrol/tests/file_main_frame_with_multiple_child_session_frames.html";
const frameURL =
"https://example.com/browser/dom/media/mediacontrol/tests/file_media_session_page.html";
const frame1 = "frame1";
const frame2 = "frame2";
add_task(async function setupTestingPref() {
await SpecialPowers.pushPrefEnv({
set: [
["media.mediacontrol.testingevents.enabled", true],
["dom.media.mediasession.enabled", true],
],
});
});
/**
* This test is used to check the behaviors when we play media from different
* frames. When a page contains multiple frames, if those frames are using the
* media session and set the metadata, then we have to know which frame owns the
* audio focus that would be the last tab playing media. When the frame owns
* audio focus, it means its metadata would be displayed on the virtual control
* interface if it has a media session.
*/
add_task(async function testAudioFocusChangesAmongMultipleFrames() {
/**
* Play the media from the main frame, so it would own the audio focus and
* its metadata should be shown on the virtual control interface. As the main
* frame doesn't use media session, the current metadata would be the default
* metadata.
*/
const tab = await createTabAndLoad(mainPageURL);
await playAndWaitUntilMetadataChanged(tab);
await isUsingDefaultMetadata(tab);
/**
* Play media for frame1, so the audio focus switches to frame1 because it's
* the last tab playing media and frame1's metadata should be displayed.
*/
await loadPageForFrame(tab, frame1, frameURL);
let metadata = await setMetadataAndGetReturnResult(tab, frame1);
await playAndWaitUntilMetadataChanged(tab, frame1);
isCurrentMetadataEqualTo(metadata);
/**
* Play media for frame2, so the audio focus switches to frame2 because it's
* the last tab playing media and frame2's metadata should be displayed.
*/
await loadPageForFrame(tab, frame2, frameURL);
metadata = await setMetadataAndGetReturnResult(tab, frame2);
await playAndWaitUntilMetadataChanged(tab, frame2);
isCurrentMetadataEqualTo(metadata);
/**
* Remove tab and end test.
*/
await BrowserTestUtils.removeTab(tab);
});
add_task(async function testAudioFocusChangesAfterPausingAudioFocusOwner() {
/**
* Play the media from the main frame, so it would own the audio focus and
* its metadata should be shown on the virtual control interface. As the main
* frame doesn't use media session, the current metadata would be the default
* metadata.
*/
const tab = await createTabAndLoad(mainPageURL);
await playAndWaitUntilMetadataChanged(tab);
await isUsingDefaultMetadata(tab);
/**
* Play media for frame1, so the audio focus switches to frame1 because it's
* the last tab playing media and frame1's metadata should be displayed.
*/
await loadPageForFrame(tab, frame1, frameURL);
let metadata = await setMetadataAndGetReturnResult(tab, frame1);
await playAndWaitUntilMetadataChanged(tab, frame1);
isCurrentMetadataEqualTo(metadata);
/**
* Pause media for frame1, so the audio focus switches back to the main frame
* which is still playing media.
*/
await pauseAndWaitUntilMetadataChangedFrom(tab, frame1);
await isUsingDefaultMetadata(tab);
/**
* Remove tab and end test.
*/
await BrowserTestUtils.removeTab(tab);
});
add_task(async function testAudioFocusUnchangesAfterPausingAudioFocusOwner() {
/**
* Play the media from the main frame, so it would own the audio focus and
* its metadata should be shown on the virtual control interface. As the main
* frame doesn't use media session, the current metadata would be the default
* metadata.
*/
const tab = await createTabAndLoad(mainPageURL);
await playAndWaitUntilMetadataChanged(tab);
await isUsingDefaultMetadata(tab);
/**
* Play media for frame1, so the audio focus switches to frame1 because it's
* the last tab playing media and frame1's metadata should be displayed.
*/
await loadPageForFrame(tab, frame1, frameURL);
let metadata = await setMetadataAndGetReturnResult(tab, frame1);
await playAndWaitUntilMetadataChanged(tab, frame1);
isCurrentMetadataEqualTo(metadata);
/**
* Pause main frame's media first. When pausing frame1's media, there are not
* other frames playing media, so frame1 still owns the audio focus and its
* metadata should be displayed.
*/
await pauseMediaFrom(tab);
isCurrentMetadataEqualTo(metadata);
/**
* Remove tab and end test.
*/
await BrowserTestUtils.removeTab(tab);
});
add_task(
async function testSwitchAudioFocusToMainFrameAfterRemovingAudioFocusOwner() {
/**
* Play the media from the main frame, so it would own the audio focus and
* its metadata should be displayed on the virtual control interface. As the
* main frame doesn't use media session, the current metadata would be the
* default metadata.
*/
const tab = await createTabAndLoad(mainPageURL);
await playAndWaitUntilMetadataChanged(tab);
await isUsingDefaultMetadata(tab);
/**
* Play media for frame1, so the audio focus switches to frame1 because it's
* the last tab playing media and frame1's metadata should be displayed.
*/
await loadPageForFrame(tab, frame1, frameURL);
let metadata = await setMetadataAndGetReturnResult(tab, frame1);
await playAndWaitUntilMetadataChanged(tab, frame1);
isCurrentMetadataEqualTo(metadata);
/**
* Remove frame1, the audio focus would switch to the main frame which
* metadata should be displayed.
*/
await Promise.all([
waitUntilDisplayedMetadataChanged(),
removeFrame(tab, frame1),
]);
await isUsingDefaultMetadata(tab);
/**
* Remove tab and end test.
*/
await BrowserTestUtils.removeTab(tab);
}
);
add_task(
async function testSwitchAudioFocusToIframeAfterRemovingAudioFocusOwner() {
/**
* Play media for frame1, so frame1 owns the audio focus and frame1's metadata
* should be displayed.
*/
const tab = await createTabAndLoad(mainPageURL);
await loadPageForFrame(tab, frame1, frameURL);
let metadataFrame1 = await setMetadataAndGetReturnResult(tab, frame1);
await playAndWaitUntilMetadataChanged(tab, frame1);
isCurrentMetadataEqualTo(metadataFrame1);
/**
* Play media for frame2, so the audio focus switches to frame2 because it's
* the last tab playing media and frame2's metadata should be displayed.
*/
await loadPageForFrame(tab, frame2, frameURL);
let metadataFrame2 = await setMetadataAndGetReturnResult(tab, frame2);
await playAndWaitUntilMetadataChanged(tab, frame2);
isCurrentMetadataEqualTo(metadataFrame2);
/**
* Remove frame2, the audio focus would switch to frame1 which metadata should
* be displayed.
*/
await Promise.all([
waitUntilDisplayedMetadataChanged(),
removeFrame(tab, frame2),
]);
isCurrentMetadataEqualTo(metadataFrame1);
/**
* Remove tab and end test.
*/
await BrowserTestUtils.removeTab(tab);
}
);
add_task(async function testNoAudioFocusAfterRemovingAudioFocusOwner() {
/**
* Play the media from the main frame, so it would own the audio focus and
* its metadata should be shown on the virtual control interface. As the main
* frame doesn't use media session, the current metadata would be the default
* metadata.
*/
const tab = await createTabAndLoad(mainPageURL);
await playAndWaitUntilMetadataChanged(tab);
await isUsingDefaultMetadata(tab);
/**
* Play media for frame1, so the audio focus switches to frame1 because it's
* the last tab playing media and frame1's metadata should be displayed.
*/
await loadPageForFrame(tab, frame1, frameURL);
let metadata = await setMetadataAndGetReturnResult(tab, frame1);
await playAndWaitUntilMetadataChanged(tab, frame1);
isCurrentMetadataEqualTo(metadata);
/**
* Pause media in main frame and then remove frame1. As the frame which owns
* the audio focus is removed and no frame is still playing media, the current
* metadata would be the default metadata.
*/
await pauseMediaFrom(tab);
await Promise.all([
waitUntilDisplayedMetadataChanged(),
removeFrame(tab, frame1),
]);
await isUsingDefaultMetadata(tab);
/**
* Remove tab and end test.
*/
await BrowserTestUtils.removeTab(tab);
});
/**
* The following are helper functions.
*/
function loadPageForFrame(tab, frameId, pageUrl) {
info(`start to load page for ${frameId}`);
return SpecialPowers.spawn(
tab.linkedBrowser,
[frameId, pageUrl],
async (id, url) => {
const iframe = content.document.getElementById(id);
if (!iframe) {
ok(false, `can not get iframe '${id}'`);
}
iframe.src = url;
await new Promise(r => (iframe.onload = r));
// Set the document title that would be used as the value for properties
// in frame's medadata.
iframe.contentDocument.title = id;
}
);
}
function playMediaFrom(tab, frameId = undefined) {
return SpecialPowers.spawn(tab.linkedBrowser, [frameId], id => {
if (id == undefined) {
info(`start to play media from main frame`);
const video = content.document.getElementById("video");
if (!video) {
ok(false, `can't get the media element!`);
}
return video.play();
}
info(`start to play media from ${id}`);
const iframe = content.document.getElementById(id);
if (!iframe) {
ok(false, `can not get ${id}`);
}
iframe.contentWindow.postMessage("play", "*");
return new Promise(r => {
content.onmessage = event => {
is(event.data, "played", `media started playing in ${id}`);
r();
};
});
});
}
function playAndWaitUntilMetadataChanged(tab, frameId = undefined) {
const metadataChanged = waitUntilDisplayedMetadataChanged();
return Promise.all([metadataChanged, playMediaFrom(tab, frameId)]);
}
function pauseMediaFrom(tab, frameId = undefined) {
return SpecialPowers.spawn(tab.linkedBrowser, [frameId], id => {
if (id == undefined) {
info(`start to pause media from in frame`);
const video = content.document.getElementById("video");
if (!video) {
ok(false, `can't get the media element!`);
}
return video.pause();
}
info(`start to pause media in ${id}`);
const iframe = content.document.getElementById(id);
if (!iframe) {
ok(false, `can not get ${id}`);
}
iframe.contentWindow.postMessage("pause", "*");
return new Promise(r => {
content.onmessage = event => {
is(event.data, "paused", `media paused in ${id}`);
r();
};
});
});
}
function pauseAndWaitUntilMetadataChangedFrom(tab, frameId = undefined) {
const metadataChanged = waitUntilDisplayedMetadataChanged();
return Promise.all([metadataChanged, pauseMediaFrom(tab, frameId)]);
}
function setMetadataAndGetReturnResult(tab, frameId) {
info(`start to set metadata for ${frameId}`);
return SpecialPowers.spawn(tab.linkedBrowser, [frameId], id => {
const iframe = content.document.getElementById(id);
if (!iframe) {
ok(false, `can not get ${id}`);
}
iframe.contentWindow.postMessage("setMetadata", "*");
info(`wait until we get metadata for ${id}`);
return new Promise(r => {
content.onmessage = event => {
ok(
event.data.title && event.data.artist && event.data.album,
"correct return format"
);
r(event.data);
};
});
});
}
function removeFrame(tab, frameId) {
info(`remove ${frameId}`);
return SpecialPowers.spawn(tab.linkedBrowser, [frameId], id => {
const iframe = content.document.getElementById(id);
if (!iframe) {
ok(false, `can not get ${id}`);
}
content.document.body.removeChild(iframe);
});
}

Просмотреть файл

@ -335,30 +335,6 @@ add_task(async function testMetadataAfterTabNavigation() {
/**
* The following are helper functions.
*/
async function isUsingDefaultMetadata(tab, options = {}) {
let metadata = ChromeUtils.getCurrentActiveMediaMetadata();
if (options.isPrivateBrowsing) {
is(
metadata.title,
"Firefox is playing media",
"Using generic title to not expose sensitive information"
);
} else {
await SpecialPowers.spawn(tab.linkedBrowser, [metadata.title], title => {
is(
title,
content.document.title,
"Using website title as a default title"
);
});
}
is(metadata.artwork.length, 1, "Default metada contains one artwork");
ok(
metadata.artwork[0].src.includes(defaultFaviconName),
"Using default favicon as a default art work"
);
}
function setMediaMetadata(tab, metadata) {
const promise = SpecialPowers.spawn(tab.linkedBrowser, [metadata], data => {
content.navigator.mediaSession.metadata = new content.MediaMetadata(data);

Просмотреть файл

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>Media control page with multiple iframes which contain media session</title>
</head>
<body>
<video id="video" src="gizmo.mp4" loop></video>
<iframe id="frame1"></iframe>
<iframe id="frame2"></iframe>
</body>
</html>

Просмотреть файл

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<video id="video" src="gizmo.mp4" loop></video>
<script type="text/javascript">
const video = document.getElementById("video");
const w = window.opener || window.parent;
window.onmessage = async event => {
if (event.data == "play") {
await video.play();
w.postMessage("played", "*");
} else if (event.data == "pause") {
video.pause();
w.postMessage("paused", "*");
} else if (event.data == "setMetadata") {
const metadata = {
title: document.title,
artist: document.title,
album: document.title,
artwork: [{ src: document.title, sizes: "128x128", type: "image/jpeg" }],
};
navigator.mediaSession.metadata = new window.MediaMetadata(metadata);
w.postMessage(metadata, "*");
}
}
</script>
</body>
</html>

Просмотреть файл

@ -214,6 +214,35 @@ function isCurrentMetadataEqualTo(metadata) {
}
}
/**
* Check if the given tab is using the default metadata. If the tab is being
* used in the private browsing mode, `isPrivateBrowsing` should be definded in
* the `options`.
*/
async function isUsingDefaultMetadata(tab, options = {}) {
let metadata = ChromeUtils.getCurrentActiveMediaMetadata();
if (options.isPrivateBrowsing) {
is(
metadata.title,
"Firefox is playing media",
"Using generic title to not expose sensitive information"
);
} else {
await SpecialPowers.spawn(tab.linkedBrowser, [metadata.title], title => {
is(
title,
content.document.title,
"Using website title as a default title"
);
});
}
is(metadata.artwork.length, 1, "Default metada contains one artwork");
ok(
metadata.artwork[0].src.includes("defaultFavicon.svg"),
"Using default favicon as a default art work"
);
}
/**
* Wait until the main media controller changes its playback state, we would
* observe that by listening for `media-displayed-playback-changed`
@ -226,6 +255,18 @@ function waitUntilDisplayedPlaybackChanged() {
return BrowserUtils.promiseObserved("media-displayed-playback-changed");
}
/**
* Wait until the metadata that would be displayed on the virtual control
* interface changes. we would observe that by listening for
* `media-displayed-metadata-changed` notification.
*
* @return {Promise}
* Resolve when observing `media-displayed-metadata-changed`
*/
function waitUntilDisplayedMetadataChanged() {
return BrowserUtils.promiseObserved("media-displayed-metadata-changed");
}
/**
* Wait until the main media controller has been changed, we would observe that
* by listening for the `main-media-controller-changed` notification.
@ -238,9 +279,12 @@ function waitUntilMainMediaControllerChanged() {
}
/**
* Wait until the main session controller changes its metadata, we would observe
* that by listening for `media-session-controller-metadata-changed`
* notification.
* Wait until any media controller updates its metadata even if it's not the
* main controller. The difference between this function and
* `waitUntilDisplayedMetadataChanged()` is that the changed metadata might come
* from non-main controller so it won't be show on the virtual control
* interface. we would observe that by listening for
* `media-session-controller-metadata-changed` notification.
*
* @return {Promise}
* Resolve when observing `media-session-controller-metadata-changed`