Commit 8b10bc20 authored by Guido Urdaneta's avatar Guido Urdaneta Committed by Commit Bot

Reland "Do not expose media devices before a successful getUserMedia()"

This reverts commit 69a60019.

chrome-prefixed URLs are exempted from the getUserMedia() requirement.

Original change's description:
> Revert "Do not expose media devices before a successful getUserMedia()"
>
> This reverts commit d64e736a.
>
> Reason for revert: Caused chrome_all_tast_tests / camera.CCAUISanity.fake failures; e.g. https://ci.chromium.org/p/chrome/builders/ci/chromeos-betty-pi-arc-chrome/7680
>
> Original change's description:
> > Do not expose media devices before a successful getUserMedia()
> >
> > This is a spec-compliance change.
> >
> > This CL also adds some improvements to the blink MediaDevices unit test.
> >
> > Bug: 1101860
> > Change-Id: If2e7824df3b3dbd3cdb80f4bcf6b5c417d6f0ef2
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2279808
> > Commit-Queue: Guido Urdaneta <guidou@chromium.org>
> > Reviewed-by: Harald Alvestrand <hta@chromium.org>
> > Cr-Commit-Position: refs/heads/master@{#786253}
>
> TBR=hta@chromium.org,guidou@chromium.org
>
> Change-Id: I41b676c7d3e437c06d23f2b46270453f1cc81670
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Bug: 1101860
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2288282
> Reviewed-by: Mohsen Izadi <mohsen@chromium.org>
> Commit-Queue: Mohsen Izadi <mohsen@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#786615}

TBR=mohsen@chromium.org,hta@chromium.org,guidou@chromium.org

# Not skipping CQ checks because this is a reland.

Bug: 1101860
Change-Id: I3fb6d9a620291cda57eb5fe48ce330b45425a5bc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2289833
Commit-Queue: Guido Urdaneta <guidou@chromium.org>
Reviewed-by: default avatarGuido Urdaneta <guidou@chromium.org>
Reviewed-by: default avatarHarald Alvestrand <hta@chromium.org>
Cr-Commit-Position: refs/heads/master@{#791797}
parent 328bb410
...@@ -149,6 +149,12 @@ bool WebRtcTestBase::GetUserMediaAndAccept( ...@@ -149,6 +149,12 @@ bool WebRtcTestBase::GetUserMediaAndAccept(
tab_contents, kAudioVideoCallConstraints); tab_contents, kAudioVideoCallConstraints);
} }
bool WebRtcTestBase::GetUserMediaAndAcceptIfPrompted(
content::WebContents* tab_contents) const {
return GetUserMediaWithSpecificConstraintsAndAcceptIfPrompted(
tab_contents, kAudioVideoCallConstraints);
}
bool WebRtcTestBase::GetUserMediaWithSpecificConstraintsAndAccept( bool WebRtcTestBase::GetUserMediaWithSpecificConstraintsAndAccept(
content::WebContents* tab_contents, content::WebContents* tab_contents,
const std::string& constraints) const { const std::string& constraints) const {
......
...@@ -73,6 +73,8 @@ class WebRtcTestBase : public InProcessBrowserTest { ...@@ -73,6 +73,8 @@ class WebRtcTestBase : public InProcessBrowserTest {
// prompt will be shown (i.e. the current origin in the tab_contents doesn't // prompt will be shown (i.e. the current origin in the tab_contents doesn't
// have a saved permission). // have a saved permission).
bool GetUserMediaAndAccept(content::WebContents* tab_contents) const; bool GetUserMediaAndAccept(content::WebContents* tab_contents) const;
bool GetUserMediaAndAcceptIfPrompted(
content::WebContents* tab_contents) const;
bool GetUserMediaWithSpecificConstraintsAndAccept( bool GetUserMediaWithSpecificConstraintsAndAccept(
content::WebContents* tab_contents, content::WebContents* tab_contents,
const std::string& constraints) const; const std::string& constraints) const;
......
...@@ -72,7 +72,10 @@ class WebRtcGetMediaDevicesBrowserTest ...@@ -72,7 +72,10 @@ class WebRtcGetMediaDevicesBrowserTest
void EnumerateDevices(content::WebContents* tab, void EnumerateDevices(content::WebContents* tab,
std::vector<MediaDeviceInfo>* devices) { std::vector<MediaDeviceInfo>* devices) {
std::string devices_as_json = ExecuteJavascript("enumerateDevices()", tab); std::string devices_as_json = ExecuteJavascript(
"navigator.mediaDevices.enumerateDevices().then(e => "
"window.domAutomationController.send(JSON.stringify(e)))",
tab);
EXPECT_FALSE(devices_as_json.empty()); EXPECT_FALSE(devices_as_json.empty());
base::JSONReader::ValueWithError parsed_json = base::JSONReader::ValueWithError parsed_json =
...@@ -114,8 +117,6 @@ class WebRtcGetMediaDevicesBrowserTest ...@@ -114,8 +117,6 @@ class WebRtcGetMediaDevicesBrowserTest
} else if (device.kind == kDeviceKindVideoInput) { } else if (device.kind == kDeviceKindVideoInput) {
found_video_input = true; found_video_input = true;
} }
EXPECT_FALSE(device.group_id.empty());
devices->push_back(device); devices->push_back(device);
} }
...@@ -171,6 +172,22 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest, ...@@ -171,6 +172,22 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest,
} }
} }
IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest,
EnumerateDevicesChromePrefix) {
ui_test_utils::NavigateToURL(browser(), GURL("chrome://version"));
content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents();
std::vector<MediaDeviceInfo> devices;
EnumerateDevices(tab, &devices);
// Labels should be not be empty since chrome:// URLs always have
// permission and do not require a previous getUserMedia() call.
for (const auto& device_info : devices) {
EXPECT_FALSE(device_info.label.empty());
}
}
IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest, IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest,
EnumerateDevicesWithAccess) { EnumerateDevicesWithAccess) {
ASSERT_TRUE(embedded_test_server()->Start()); ASSERT_TRUE(embedded_test_server()->Start());
...@@ -197,10 +214,12 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest, ...@@ -197,10 +214,12 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest,
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
content::WebContents* tab = content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents(); browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(GetUserMediaAndAcceptIfPrompted(tab));
std::vector<MediaDeviceInfo> devices; std::vector<MediaDeviceInfo> devices;
EnumerateDevices(tab, &devices); EnumerateDevices(tab, &devices);
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
EXPECT_TRUE(GetUserMediaAndAcceptIfPrompted(tab));
std::vector<MediaDeviceInfo> devices2; std::vector<MediaDeviceInfo> devices2;
EnumerateDevices(tab, &devices2); EnumerateDevices(tab, &devices2);
...@@ -227,6 +246,7 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest, ...@@ -227,6 +246,7 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest,
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
content::WebContents* tab1 = content::WebContents* tab1 =
browser()->tab_strip_model()->GetActiveWebContents(); browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(GetUserMediaAndAcceptIfPrompted(tab1));
std::vector<MediaDeviceInfo> devices; std::vector<MediaDeviceInfo> devices;
EnumerateDevices(tab1, &devices); EnumerateDevices(tab1, &devices);
...@@ -234,6 +254,7 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest, ...@@ -234,6 +254,7 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest,
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
content::WebContents* tab2 = content::WebContents* tab2 =
browser()->tab_strip_model()->GetActiveWebContents(); browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(GetUserMediaAndAcceptIfPrompted(tab2));
std::vector<MediaDeviceInfo> devices2; std::vector<MediaDeviceInfo> devices2;
EnumerateDevices(tab2, &devices2); EnumerateDevices(tab2, &devices2);
...@@ -303,6 +324,7 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest, ...@@ -303,6 +324,7 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest,
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
content::WebContents* tab2 = content::WebContents* tab2 =
browser()->tab_strip_model()->GetActiveWebContents(); browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(GetUserMediaAndAcceptIfPrompted(tab2));
std::vector<MediaDeviceInfo> devices2; std::vector<MediaDeviceInfo> devices2;
EnumerateDevices(tab2, &devices2); EnumerateDevices(tab2, &devices2);
...@@ -320,14 +342,13 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest, ...@@ -320,14 +342,13 @@ IN_PROC_BROWSER_TEST_F(WebRtcGetMediaDevicesBrowserTest,
->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK); ->SetDefaultCookieSetting(CONTENT_SETTING_BLOCK);
content::WebContents* tab = content::WebContents* tab =
browser()->tab_strip_model()->GetActiveWebContents(); browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(GetUserMediaAndAccept(tab)); EXPECT_TRUE(GetUserMediaAndAccept(tab));
std::vector<MediaDeviceInfo> devices; std::vector<MediaDeviceInfo> devices;
EnumerateDevices(tab, &devices); EnumerateDevices(tab, &devices);
ui_test_utils::NavigateToURL(browser(), url); ui_test_utils::NavigateToURL(browser(), url);
tab = browser()->tab_strip_model()->GetActiveWebContents(); tab = browser()->tab_strip_model()->GetActiveWebContents();
EXPECT_TRUE(GetUserMediaAndAcceptIfPrompted(tab));
std::vector<MediaDeviceInfo> devices2; std::vector<MediaDeviceInfo> devices2;
EnumerateDevices(tab, &devices2); EnumerateDevices(tab, &devices2);
......
...@@ -48,6 +48,7 @@ struct TestParams { ...@@ -48,6 +48,7 @@ struct TestParams {
}; };
static const char kVideoCaptureHtmlFile[] = "/media/video_capture_test.html"; static const char kVideoCaptureHtmlFile[] = "/media/video_capture_test.html";
static const char kRequestDevicePermission[] = "requestDevicePermission()";
static const char kEnumerateVideoCaptureDevicesAndVerify[] = static const char kEnumerateVideoCaptureDevicesAndVerify[] =
"enumerateVideoCaptureDevicesAndVerifyCount(%d)"; "enumerateVideoCaptureDevicesAndVerifyCount(%d)";
static const char kRegisterForDeviceChangeEvent[] = static const char kRegisterForDeviceChangeEvent[] =
...@@ -170,6 +171,15 @@ class WebRtcVideoCaptureServiceEnumerationBrowserTest ...@@ -170,6 +171,15 @@ class WebRtcVideoCaptureServiceEnumerationBrowserTest
video_source_provider_.reset(); video_source_provider_.reset();
} }
void RequestDevicePermission() {
const std::string javascript_to_execute =
base::StringPrintf(kRequestDevicePermission);
std::string result;
ASSERT_TRUE(
ExecuteScriptAndExtractString(shell(), javascript_to_execute, &result));
ASSERT_EQ("OK", result);
}
void EnumerateDevicesInRendererAndVerifyDeviceCount( void EnumerateDevicesInRendererAndVerifyDeviceCount(
int expected_device_count) { int expected_device_count) {
const std::string javascript_to_execute = base::StringPrintf( const std::string javascript_to_execute = base::StringPrintf(
...@@ -249,6 +259,7 @@ IN_PROC_BROWSER_TEST_P(WebRtcVideoCaptureServiceEnumerationBrowserTest, ...@@ -249,6 +259,7 @@ IN_PROC_BROWSER_TEST_P(WebRtcVideoCaptureServiceEnumerationBrowserTest,
ConnectToService(); ConnectToService();
// Exercise // Exercise
EnumerateDevicesInRendererAndVerifyDeviceCount(0);
AddVirtualDevice("test"); AddVirtualDevice("test");
EnumerateDevicesInRendererAndVerifyDeviceCount(1); EnumerateDevicesInRendererAndVerifyDeviceCount(1);
...@@ -263,6 +274,9 @@ IN_PROC_BROWSER_TEST_P(WebRtcVideoCaptureServiceEnumerationBrowserTest, ...@@ -263,6 +274,9 @@ IN_PROC_BROWSER_TEST_P(WebRtcVideoCaptureServiceEnumerationBrowserTest,
ConnectToService(); ConnectToService();
AddVirtualDevice("test_1"); AddVirtualDevice("test_1");
// Request permission using getUserMedia() after adding the first device.
// Otherwise, getUserMedia() will fail with NotFoundError.
RequestDevicePermission();
AddVirtualDevice("test_2"); AddVirtualDevice("test_2");
EnumerateDevicesInRendererAndVerifyDeviceCount(2); EnumerateDevicesInRendererAndVerifyDeviceCount(2);
RemoveVirtualDevice("test_1"); RemoveVirtualDevice("test_1");
......
...@@ -21,14 +21,18 @@ ...@@ -21,14 +21,18 @@
// run here because they require --use-fake-device-for-media-capture. // run here because they require --use-fake-device-for-media-capture.
function getDepthStreamAndCallCreateImageBitmap() { function getDepthStreamAndCallCreateImageBitmap() {
console.log('Calling getDepthStreamAndCallCreateImageBitmap'); console.log('Calling getDepthStreamAndCallCreateImageBitmap');
getFake16bitStream().then((stream) => { // Call getUserMedia() to allow device enumerations.
detectVideoInLocalView1(stream).then(() => { navigator.mediaDevices.getUserMedia({video:true}).then(stream => {
testVideoToImageBitmap('local-view-1', function() { stream.getTracks().forEach(track => track.stop());
stream.getVideoTracks()[0].stop(); getFake16bitStream().then(stream => {
detectVideoStopped('local-view-1') detectVideoInLocalView1(stream).then(() => {
.then(reportTestSuccess); testVideoToImageBitmap('local-view-1', function() {
}, failedCallback); stream.getVideoTracks()[0].stop();
}); detectVideoStopped('local-view-1')
.then(reportTestSuccess);
}, failedCallback);
});
}, failedCallback);
}, failedCallback); }, failedCallback);
} }
...@@ -421,12 +425,16 @@ ...@@ -421,12 +425,16 @@
var query = /query=(.*)/.exec(window.location.href); var query = /query=(.*)/.exec(window.location.href);
if (!query) if (!query)
return; return;
if (query[1] == "RGBAUint8") // Call getUserMedia() first to allow device enumerations.
depthStreamToRGBAUint8Texture(); navigator.mediaDevices.getUserMedia({video: true}).then(stream => {
else if (query[1] == "RGBAFloat") stream.getTracks().forEach(track => track.stop());
depthStreamToRGBAFloatTexture(); if (query[1] == "RGBAUint8")
else if (query[1] == "R32Float") depthStreamToRGBAUint8Texture();
depthStreamToR32FloatTexture(); else if (query[1] == "RGBAFloat")
depthStreamToRGBAFloatTexture();
else if (query[1] == "R32Float")
depthStreamToR32FloatTexture();
}, failedCallback);
} }
</script> </script>
</head> </head>
......
...@@ -18,11 +18,16 @@ ...@@ -18,11 +18,16 @@
window.addEventListener('click', runFunctionOnClick); window.addEventListener('click', runFunctionOnClick);
}); });
function getSources() { async function getSources() {
navigator.mediaDevices.enumerateDevices().then(function(devices) { try {
// Call getUserMedia() to allow exposing devices in enumerateDevices.
let stream = await navigator.mediaDevices.getUserMedia(
{audio:true, video:true});
stream.getTracks().forEach(track => track.stop());
let devices = await navigator.mediaDevices.enumerateDevices();
document.title = 'Media devices available'; document.title = 'Media devices available';
var results = []; let results = [];
for (var device, i = 0; device = devices[i]; ++i) { for (let device, i = 0; device = devices[i]; ++i) {
if (device.kind != "audioinput" && device.kind != "videoinput") if (device.kind != "audioinput" && device.kind != "videoinput")
continue; continue;
results.push({ results.push({
...@@ -33,7 +38,9 @@ ...@@ -33,7 +38,9 @@
}); });
} }
sendValueToTest(JSON.stringify(results)); sendValueToTest(JSON.stringify(results));
}); } catch (e) {
failTest();
}
} }
// Creates a MediaStream and renders it locally. When the video is detected to // Creates a MediaStream and renders it locally. When the video is detected to
...@@ -446,19 +453,18 @@ ...@@ -446,19 +453,18 @@
var detectorInterval = setInterval(detectorFunction, 50); var detectorInterval = setInterval(detectorFunction, 50);
} }
function getAudioSettingsDefault() { async function getAudioSettingsDefault() {
navigator.mediaDevices.getUserMedia({audio:true}) try {
.then(stream => { let stream = await navigator.mediaDevices.getUserMedia({audio:true});
assertEquals(stream.getAudioTracks().length, 1); assertEquals(stream.getAudioTracks().length, 1);
var settings = stream.getAudioTracks()[0].getSettings(); let settings = stream.getAudioTracks()[0].getSettings();
assertEquals(settings.deviceId, 'default'); assertEquals(settings.deviceId, 'default');
assertTrue(settings.echoCancellation); assertTrue(settings.echoCancellation);
stream.getAudioTracks()[0].stop(); stream.getAudioTracks()[0].stop();
reportTestSuccess(); reportTestSuccess();
}) } catch (e) {
.catch(_ => { failTest("Error: " + e);
failTest("getUserMedia failed") }
});
} }
function getAudioSettingsNoEchoCancellation() { function getAudioSettingsNoEchoCancellation() {
...@@ -476,28 +482,30 @@ ...@@ -476,28 +482,30 @@
}); });
} }
function getAudioSettingsDeviceId() { async function getAudioSettingsDeviceId() {
navigator.mediaDevices.enumerateDevices() try {
.then(devices => { // Plain getUserMedia() call to enable device enumeration.
var last_device_id; let stream = await navigator.mediaDevices.getUserMedia({audio:true});
for (var device, i = 0; device = devices[i]; ++i) { stream.getAudioTracks()[0].stop();
if (device.kind != "audioinput") let devices = await navigator.mediaDevices.enumerateDevices();
continue; let last_device_id;
last_device_id = device.deviceId; for (let device, i = 0; device = devices[i]; ++i) {
} if (device.kind != "audioinput")
navigator.mediaDevices.getUserMedia( continue;
{audio:{deviceId: {exact: last_device_id}}}) last_device_id = device.deviceId;
.then(stream => { }
assertEquals(stream.getAudioTracks().length, 1); stream = await navigator.mediaDevices.getUserMedia(
var settings = stream.getAudioTracks()[0].getSettings(); {audio:{deviceId: {exact: last_device_id}}});
assertEquals(settings.deviceId, last_device_id); assertEquals(stream.getAudioTracks().length, 1);
assertNotEquals(settings.deviceId, 'default'); var settings = stream.getAudioTracks()[0].getSettings();
assertTrue(settings.echoCancellation); assertEquals(settings.deviceId, last_device_id);
stream.getAudioTracks()[0].stop(); assertNotEquals(settings.deviceId, 'default');
reportTestSuccess(); assertTrue(settings.echoCancellation);
}) stream.getAudioTracks()[0].stop();
}) reportTestSuccess();
.catch(failTest); } catch (e) {
failTest('Error: ' + e);
}
} }
function srcObjectAddVideoTrack() { function srcObjectAddVideoTrack() {
......
...@@ -10,7 +10,6 @@ var hasReceivedTrackEndedEvent = false; ...@@ -10,7 +10,6 @@ var hasReceivedTrackEndedEvent = false;
var hasReceivedDeviceChangeEventReceived = false; var hasReceivedDeviceChangeEventReceived = false;
function startVideoCaptureAndVerifySize(video_width, video_height) { function startVideoCaptureAndVerifySize(video_width, video_height) {
console.log('Calling getUserMediaAndWaitForVideoRendering.');
var constraints = { var constraints = {
video: { video: {
width: {exact: video_width}, width: {exact: video_width},
...@@ -27,47 +26,56 @@ function startVideoCaptureAndVerifySize(video_width, video_height) { ...@@ -27,47 +26,56 @@ function startVideoCaptureAndVerifySize(video_width, video_height) {
function startVideoCaptureFromVirtualDeviceAndVerifyUniformColorVideoWithSize( function startVideoCaptureFromVirtualDeviceAndVerifyUniformColorVideoWithSize(
video_width, video_height) { video_width, video_height) {
console.log('Trying to find device named "Virtual Device".'); navigator.mediaDevices.getUserMedia({video: true}).then(stream => {
navigator.mediaDevices.enumerateDevices().then(function(devices) { stream.getTracks().forEach(track => track.stop());
var target_device; navigator.mediaDevices.enumerateDevices().then(function(devices) {
devices.forEach(function(device) { var target_device;
if (device.kind == 'videoinput') { devices.forEach(function(device) {
console.log('Found videoinput device with label ' + device.label); if (device.kind == 'videoinput') {
if (device.label == 'Virtual Device') { if (device.label == 'Virtual Device') {
target_device = device; target_device = device;
}
} }
});
if (target_device == null) {
failTest(
'No video input device was found with label = Virtual ' +
'Device');
return;
} }
var device_specific_constraints = {
video: {
width: {exact: video_width},
height: {exact: video_height},
deviceId: {exact: target_device.deviceId}
}
};
navigator.mediaDevices.getUserMedia(device_specific_constraints)
.then(function(stream) {
waitForVideoStreamToSatisfyRequirementFunction(
stream, detectUniformColorVideoWithDimensionPlaying,
video_width, video_height);
})
.catch(failedCallback);
}); });
if (target_device == null) { }).catch(failedCallback);
failTest( }
'No video input device was found with label = Virtual ' +
'Device'); function requestDevicePermission() {
return; navigator.mediaDevices.getUserMedia({video:true}).then(stream => {
} stream.getTracks().forEach(track => track.stop());
var device_specific_constraints = { reportTestSuccess();
video: { },
width: {exact: video_width}, e => {
height: {exact: video_height}, failTest(e);
deviceId: {exact: target_device.deviceId}
}
};
navigator.mediaDevices.getUserMedia(device_specific_constraints)
.then(function(stream) {
waitForVideoStreamToSatisfyRequirementFunction(
stream, detectUniformColorVideoWithDimensionPlaying, video_width,
video_height);
})
.catch(failedCallback);
}); });
} }
function enumerateVideoCaptureDevicesAndVerifyCount(expected_count) { function enumerateVideoCaptureDevicesAndVerifyCount(expected_count) {
console.log('Enumerating devices and verifying count.');
navigator.mediaDevices.enumerateDevices().then(function(devices) { navigator.mediaDevices.enumerateDevices().then(function(devices) {
var actual_count = 0; var actual_count = 0;
devices.forEach(function(device) { devices.forEach(function(device) {
if (device.kind == 'videoinput') { if (device.kind == 'videoinput') {
console.log('Found videoinput device with label ' + device.label);
actual_count = actual_count + 1; actual_count = actual_count + 1;
} }
}); });
......
...@@ -27,10 +27,14 @@ ...@@ -27,10 +27,14 @@
#include "third_party/blink/renderer/platform/bindings/script_state.h" #include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/mediastream/webrtc_uma_histograms.h" #include "third_party/blink/renderer/platform/mediastream/webrtc_uma_histograms.h"
#include "third_party/blink/renderer/platform/weborigin/scheme_registry.h"
#include "third_party/blink/renderer/platform/wtf/functional.h" #include "third_party/blink/renderer/platform/wtf/functional.h"
namespace blink { namespace blink {
const base::Feature kEnumerateRequiresGetUserMedia{
"EnumerateRequiresGetUserMedia", base::FEATURE_ENABLED_BY_DEFAULT};
namespace { namespace {
class PromiseResolverCallbacks final : public UserMediaRequest::Callbacks { class PromiseResolverCallbacks final : public UserMediaRequest::Callbacks {
...@@ -115,12 +119,17 @@ ScriptPromise MediaDevices::SendUserMediaRequest( ...@@ -115,12 +119,17 @@ ScriptPromise MediaDevices::SendUserMediaRequest(
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
auto* callbacks = MakeGarbageCollected<PromiseResolverCallbacks>(resolver); auto* callbacks = MakeGarbageCollected<PromiseResolverCallbacks>(resolver);
base::OnceClosure success_update_callback;
if (media_type == UserMediaRequest::MediaType::kUserMedia) {
success_update_callback = WTF::Bind(
&MediaDevices::SetEnumerateCanExposeDevices, WrapWeakPersistent(this));
}
LocalDOMWindow* window = LocalDOMWindow::From(script_state); LocalDOMWindow* window = LocalDOMWindow::From(script_state);
UserMediaController* user_media = UserMediaController::From(window); UserMediaController* user_media = UserMediaController::From(window);
MediaErrorState error_state; MediaErrorState error_state;
UserMediaRequest* request = UserMediaRequest::Create( UserMediaRequest* request = UserMediaRequest::Create(
window, user_media, media_type, options, callbacks, error_state); window, user_media, media_type, options, callbacks,
std::move(success_update_callback), error_state);
if (!request) { if (!request) {
DCHECK(error_state.HadException()); DCHECK(error_state.HadException());
if (error_state.CanGenerateException()) { if (error_state.CanGenerateException()) {
...@@ -287,6 +296,12 @@ void MediaDevices::DevicesEnumerated( ...@@ -287,6 +296,12 @@ void MediaDevices::DevicesEnumerated(
audio_input_capabilities.size()); audio_input_capabilities.size());
} }
String scheme =
resolver->GetExecutionContext()->GetSecurityOrigin()->Protocol();
// |is_isolated_display_scheme| is true only for chrome:// and similar
// schemes.
bool is_isolated_display_scheme =
SchemeRegistry::ShouldTreatURLSchemeAsDisplayIsolated(scheme);
MediaDeviceInfoVector media_devices; MediaDeviceInfoVector media_devices;
for (wtf_size_t i = 0; for (wtf_size_t i = 0;
i < static_cast<wtf_size_t>( i < static_cast<wtf_size_t>(
...@@ -295,6 +310,13 @@ void MediaDevices::DevicesEnumerated( ...@@ -295,6 +310,13 @@ void MediaDevices::DevicesEnumerated(
for (wtf_size_t j = 0; j < enumeration[i].size(); ++j) { for (wtf_size_t j = 0; j < enumeration[i].size(); ++j) {
mojom::blink::MediaDeviceType device_type = mojom::blink::MediaDeviceType device_type =
static_cast<mojom::blink::MediaDeviceType>(i); static_cast<mojom::blink::MediaDeviceType>(i);
if (!is_isolated_display_scheme &&
base::FeatureList::IsEnabled(kEnumerateRequiresGetUserMedia) &&
!enumerate_can_expose_devices_) {
media_devices.push_back(MakeGarbageCollected<MediaDeviceInfo>(
String(), String(), String(), device_type));
break;
}
WebMediaDeviceInfo device_info = enumeration[i][j]; WebMediaDeviceInfo device_info = enumeration[i][j];
if (device_type == mojom::blink::MediaDeviceType::MEDIA_AUDIO_INPUT || if (device_type == mojom::blink::MediaDeviceType::MEDIA_AUDIO_INPUT ||
device_type == mojom::blink::MediaDeviceType::MEDIA_VIDEO_INPUT) { device_type == mojom::blink::MediaDeviceType::MEDIA_VIDEO_INPUT) {
...@@ -363,6 +385,10 @@ void MediaDevices::SetDispatcherHostForTesting( ...@@ -363,6 +385,10 @@ void MediaDevices::SetDispatcherHostForTesting(
WrapWeakPersistent(this))); WrapWeakPersistent(this)));
} }
void MediaDevices::SetEnumerateCanExposeDevices() {
enumerate_can_expose_devices_ = true;
}
void MediaDevices::Trace(Visitor* visitor) const { void MediaDevices::Trace(Visitor* visitor) const {
visitor->Trace(receiver_); visitor->Trace(receiver_);
visitor->Trace(scheduled_events_); visitor->Trace(scheduled_events_);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_MEDIA_DEVICES_H_ #define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASTREAM_MEDIA_DEVICES_H_
#include "base/callback.h" #include "base/callback.h"
#include "base/feature_list.h"
#include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/mojom/mediastream/media_devices.mojom-blink.h" #include "third_party/blink/public/mojom/mediastream/media_devices.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h" #include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
...@@ -29,6 +30,8 @@ class ScriptPromise; ...@@ -29,6 +30,8 @@ class ScriptPromise;
class ScriptPromiseResolver; class ScriptPromiseResolver;
class ScriptState; class ScriptState;
extern const base::Feature kEnumerateRequiresGetUserMedia;
class MODULES_EXPORT MediaDevices final class MODULES_EXPORT MediaDevices final
: public EventTargetWithInlineData, : public EventTargetWithInlineData,
public ActiveScriptWrappable<MediaDevices>, public ActiveScriptWrappable<MediaDevices>,
...@@ -69,6 +72,8 @@ class MODULES_EXPORT MediaDevices final ...@@ -69,6 +72,8 @@ class MODULES_EXPORT MediaDevices final
void OnDevicesChanged(MediaDeviceType, void OnDevicesChanged(MediaDeviceType,
const Vector<WebMediaDeviceInfo>&) override; const Vector<WebMediaDeviceInfo>&) override;
void SetEnumerateCanExposeDevices();
// Callback for testing only. // Callback for testing only.
using EnumerateDevicesTestCallback = using EnumerateDevicesTestCallback =
base::OnceCallback<void(const MediaDeviceInfoVector&)>; base::OnceCallback<void(const MediaDeviceInfoVector&)>;
...@@ -126,6 +131,7 @@ class MODULES_EXPORT MediaDevices final ...@@ -126,6 +131,7 @@ class MODULES_EXPORT MediaDevices final
EnumerateDevicesTestCallback enumerate_devices_test_callback_; EnumerateDevicesTestCallback enumerate_devices_test_callback_;
base::OnceClosure connection_error_test_callback_; base::OnceClosure connection_error_test_callback_;
base::OnceClosure device_change_test_callback_; base::OnceClosure device_change_test_callback_;
bool enumerate_can_expose_devices_ = false;
}; };
} // namespace blink } // namespace blink
......
...@@ -11,9 +11,13 @@ ...@@ -11,9 +11,13 @@
#include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/native_value_traits.h"
#include "third_party/blink/renderer/bindings/core/v8/script_function.h" #include "third_party/blink/renderer/bindings/core/v8/script_function.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_tester.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h" #include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_device_info.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_media_stream_constraints.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_media_stream_constraints.h"
#include "third_party/blink/renderer/core/testing/null_execution_context.h" #include "third_party/blink/renderer/core/testing/null_execution_context.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h"
...@@ -24,6 +28,8 @@ using blink::mojom::blink::MediaDeviceInfoPtr; ...@@ -24,6 +28,8 @@ using blink::mojom::blink::MediaDeviceInfoPtr;
namespace blink { namespace blink {
namespace {
const char kFakeAudioInputDeviceId1[] = "fake_audio_input 1"; const char kFakeAudioInputDeviceId1[] = "fake_audio_input 1";
const char kFakeAudioInputDeviceId2[] = "fake_audio_input 2"; const char kFakeAudioInputDeviceId2[] = "fake_audio_input 2";
const char kFakeVideoInputDeviceId1[] = "fake_video_input 1"; const char kFakeVideoInputDeviceId1[] = "fake_video_input 1";
...@@ -158,11 +164,29 @@ class MockMediaDevicesDispatcherHost ...@@ -158,11 +164,29 @@ class MockMediaDevicesDispatcherHost
mojo::Receiver<mojom::blink::MediaDevicesDispatcherHost> receiver_{this}; mojo::Receiver<mojom::blink::MediaDevicesDispatcherHost> receiver_{this};
}; };
MediaDeviceInfoVector ToMediaDeviceInfoVector(
const v8::Local<v8::Value>& value) {
EXPECT_TRUE(value->IsArray());
v8::Array& array = *v8::Array::Cast(*value);
v8::Local<v8::Context> context =
v8::Isolate::GetCurrent()->GetCurrentContext();
MediaDeviceInfoVector device_infos;
for (uint32_t i = 0; i < array.Length(); ++i) {
MediaDeviceInfo* device_info = V8MediaDeviceInfo::ToImplWithTypeCheck(
context->GetIsolate(), array.Get(context, i).ToLocalChecked());
device_infos.push_back(device_info);
}
return device_infos;
}
} // namespace
class MediaDevicesTest : public testing::Test { class MediaDevicesTest : public testing::Test {
public: public:
using MediaDeviceInfos = HeapVector<Member<MediaDeviceInfo>>; using MediaDeviceInfos = HeapVector<Member<MediaDeviceInfo>>;
MediaDevicesTest() : device_infos_(MakeGarbageCollected<MediaDeviceInfos>()) { MediaDevicesTest() {
dispatcher_host_ = std::make_unique<MockMediaDevicesDispatcherHost>(); dispatcher_host_ = std::make_unique<MockMediaDevicesDispatcherHost>();
} }
...@@ -183,15 +207,6 @@ class MediaDevicesTest : public testing::Test { ...@@ -183,15 +207,6 @@ class MediaDevicesTest : public testing::Test {
Vector<WebMediaDeviceInfo>()); Vector<WebMediaDeviceInfo>());
} }
void DevicesEnumerated(const MediaDeviceInfoVector& device_infos) {
devices_enumerated_ = true;
for (wtf_size_t i = 0; i < device_infos.size(); i++) {
device_infos_->push_back(MakeGarbageCollected<MediaDeviceInfo>(
device_infos[i]->deviceId(), device_infos[i]->label(),
device_infos[i]->groupId(), device_infos[i]->DeviceType()));
}
}
void OnDispatcherHostConnectionError() { void OnDispatcherHostConnectionError() {
dispatcher_host_connection_error_ = true; dispatcher_host_connection_error_ = true;
} }
...@@ -209,10 +224,6 @@ class MediaDevicesTest : public testing::Test { ...@@ -209,10 +224,6 @@ class MediaDevicesTest : public testing::Test {
bool listener_connection_error() const { return listener_connection_error_; } bool listener_connection_error() const { return listener_connection_error_; }
const MediaDeviceInfos& device_infos() const { return *device_infos_; }
bool devices_enumerated() const { return devices_enumerated_; }
bool dispatcher_host_connection_error() const { bool dispatcher_host_connection_error() const {
return dispatcher_host_connection_error_; return dispatcher_host_connection_error_;
} }
...@@ -226,8 +237,6 @@ class MediaDevicesTest : public testing::Test { ...@@ -226,8 +237,6 @@ class MediaDevicesTest : public testing::Test {
private: private:
ScopedTestingPlatformSupport<TestingPlatformSupport> platform_; ScopedTestingPlatformSupport<TestingPlatformSupport> platform_;
std::unique_ptr<MockMediaDevicesDispatcherHost> dispatcher_host_; std::unique_ptr<MockMediaDevicesDispatcherHost> dispatcher_host_;
Persistent<MediaDeviceInfos> device_infos_;
bool devices_enumerated_ = false;
bool dispatcher_host_connection_error_ = false; bool dispatcher_host_connection_error_ = false;
bool device_changed_ = false; bool device_changed_ = false;
bool listener_connection_error_ = false; bool listener_connection_error_ = false;
...@@ -251,61 +260,76 @@ TEST_F(MediaDevicesTest, GetUserMediaCanBeCalled) { ...@@ -251,61 +260,76 @@ TEST_F(MediaDevicesTest, GetUserMediaCanBeCalled) {
TEST_F(MediaDevicesTest, EnumerateDevices) { TEST_F(MediaDevicesTest, EnumerateDevices) {
V8TestingScope scope; V8TestingScope scope;
auto* media_devices = GetMediaDevices(scope.GetExecutionContext()); auto* media_devices = GetMediaDevices(scope.GetExecutionContext());
media_devices->SetEnumerateDevicesCallbackForTesting(
WTF::Bind(&MediaDevicesTest::DevicesEnumerated, WTF::Unretained(this)));
ScriptPromise promise = media_devices->enumerateDevices( ScriptPromise promise = media_devices->enumerateDevices(
scope.GetScriptState(), scope.GetExceptionState()); scope.GetScriptState(), scope.GetExceptionState());
platform()->RunUntilIdle(); ScriptPromiseTester tester(scope.GetScriptState(), promise);
ASSERT_FALSE(promise.IsEmpty()); tester.WaitUntilSettled();
ASSERT_TRUE(tester.IsFulfilled());
MediaDeviceInfoVector device_infos =
ToMediaDeviceInfoVector(tester.Value().V8Value());
// One empty device per kind, since enumerateDevices() cannot expose device
// info by default.
EXPECT_EQ(device_infos.size(), 3u);
for (auto device : device_infos) {
EXPECT_TRUE(device->deviceId().IsEmpty());
EXPECT_TRUE(device->label().IsEmpty());
}
EXPECT_TRUE(devices_enumerated()); // Authorize enumerateDevices() to expose device info.
EXPECT_EQ(5u, device_infos().size()); media_devices->SetEnumerateCanExposeDevices();
promise = media_devices->enumerateDevices(scope.GetScriptState(),
scope.GetExceptionState());
ScriptPromiseTester tester2(scope.GetScriptState(), promise);
tester2.WaitUntilSettled();
ASSERT_TRUE(tester2.IsFulfilled());
device_infos = ToMediaDeviceInfoVector(tester2.Value().V8Value());
EXPECT_EQ(device_infos.size(), 5u);
// Audio input device with matched output ID. // Audio input device with matched output ID.
Member<MediaDeviceInfo> device = device_infos()[0]; MediaDeviceInfo* device = device_infos[0];
EXPECT_FALSE(device->deviceId().IsEmpty()); EXPECT_FALSE(device->deviceId().IsEmpty());
EXPECT_EQ("audioinput", device->kind()); EXPECT_EQ("audioinput", device->kind());
EXPECT_FALSE(device->label().IsEmpty()); EXPECT_FALSE(device->label().IsEmpty());
EXPECT_FALSE(device->groupId().IsEmpty()); EXPECT_FALSE(device->groupId().IsEmpty());
// Audio input device without matched output ID. // Audio input device without matched output ID.
device = device_infos()[1]; device = device_infos[1];
EXPECT_FALSE(device->deviceId().IsEmpty()); EXPECT_FALSE(device->deviceId().IsEmpty());
EXPECT_EQ("audioinput", device->kind()); EXPECT_EQ("audioinput", device->kind());
EXPECT_FALSE(device->label().IsEmpty()); EXPECT_FALSE(device->label().IsEmpty());
EXPECT_FALSE(device->groupId().IsEmpty()); EXPECT_FALSE(device->groupId().IsEmpty());
// Video input devices. // Video input devices.
device = device_infos()[2]; device = device_infos[2];
EXPECT_FALSE(device->deviceId().IsEmpty()); EXPECT_FALSE(device->deviceId().IsEmpty());
EXPECT_EQ("videoinput", device->kind()); EXPECT_EQ("videoinput", device->kind());
EXPECT_FALSE(device->label().IsEmpty()); EXPECT_FALSE(device->label().IsEmpty());
EXPECT_FALSE(device->groupId().IsEmpty()); EXPECT_FALSE(device->groupId().IsEmpty());
device = device_infos()[3]; device = device_infos[3];
EXPECT_FALSE(device->deviceId().IsEmpty()); EXPECT_FALSE(device->deviceId().IsEmpty());
EXPECT_EQ("videoinput", device->kind()); EXPECT_EQ("videoinput", device->kind());
EXPECT_FALSE(device->label().IsEmpty()); EXPECT_FALSE(device->label().IsEmpty());
EXPECT_FALSE(device->groupId().IsEmpty()); EXPECT_FALSE(device->groupId().IsEmpty());
// Audio output device. // Audio output device.
device = device_infos()[4]; device = device_infos[4];
EXPECT_FALSE(device->deviceId().IsEmpty()); EXPECT_FALSE(device->deviceId().IsEmpty());
EXPECT_EQ("audiooutput", device->kind()); EXPECT_EQ("audiooutput", device->kind());
EXPECT_FALSE(device->label().IsEmpty()); EXPECT_FALSE(device->label().IsEmpty());
EXPECT_FALSE(device->groupId().IsEmpty()); EXPECT_FALSE(device->groupId().IsEmpty());
// Verify group IDs. // Verify group IDs.
EXPECT_EQ(device_infos()[0]->groupId(), device_infos()[2]->groupId()); EXPECT_EQ(device_infos[0]->groupId(), device_infos[2]->groupId());
EXPECT_EQ(device_infos()[0]->groupId(), device_infos()[4]->groupId()); EXPECT_EQ(device_infos[0]->groupId(), device_infos[4]->groupId());
EXPECT_NE(device_infos()[1]->groupId(), device_infos()[4]->groupId()); EXPECT_NE(device_infos[1]->groupId(), device_infos[4]->groupId());
} }
TEST_F(MediaDevicesTest, EnumerateDevicesAfterConnectionError) { TEST_F(MediaDevicesTest, EnumerateDevicesAfterConnectionError) {
V8TestingScope scope; V8TestingScope scope;
auto* media_devices = GetMediaDevices(scope.GetExecutionContext()); auto* media_devices = GetMediaDevices(scope.GetExecutionContext());
media_devices->SetEnumerateDevicesCallbackForTesting(
WTF::Bind(&MediaDevicesTest::DevicesEnumerated, WTF::Unretained(this)));
media_devices->SetConnectionErrorCallbackForTesting( media_devices->SetConnectionErrorCallbackForTesting(
WTF::Bind(&MediaDevicesTest::OnDispatcherHostConnectionError, WTF::Bind(&MediaDevicesTest::OnDispatcherHostConnectionError,
WTF::Unretained(this))); WTF::Unretained(this)));
...@@ -317,17 +341,16 @@ TEST_F(MediaDevicesTest, EnumerateDevicesAfterConnectionError) { ...@@ -317,17 +341,16 @@ TEST_F(MediaDevicesTest, EnumerateDevicesAfterConnectionError) {
ScriptPromise promise = media_devices->enumerateDevices( ScriptPromise promise = media_devices->enumerateDevices(
scope.GetScriptState(), scope.GetExceptionState()); scope.GetScriptState(), scope.GetExceptionState());
platform()->RunUntilIdle(); ScriptPromiseTester tester(scope.GetScriptState(), promise);
ASSERT_FALSE(promise.IsEmpty()); tester.WaitUntilSettled();
EXPECT_TRUE(tester.IsRejected());
EXPECT_FALSE(promise.IsEmpty());
EXPECT_TRUE(dispatcher_host_connection_error()); EXPECT_TRUE(dispatcher_host_connection_error());
EXPECT_FALSE(devices_enumerated());
} }
TEST_F(MediaDevicesTest, EnumerateDevicesBeforeConnectionError) { TEST_F(MediaDevicesTest, EnumerateDevicesBeforeConnectionError) {
V8TestingScope scope; V8TestingScope scope;
auto* media_devices = GetMediaDevices(scope.GetExecutionContext()); auto* media_devices = GetMediaDevices(scope.GetExecutionContext());
media_devices->SetEnumerateDevicesCallbackForTesting(
WTF::Bind(&MediaDevicesTest::DevicesEnumerated, WTF::Unretained(this)));
media_devices->SetConnectionErrorCallbackForTesting( media_devices->SetConnectionErrorCallbackForTesting(
WTF::Bind(&MediaDevicesTest::OnDispatcherHostConnectionError, WTF::Bind(&MediaDevicesTest::OnDispatcherHostConnectionError,
WTF::Unretained(this))); WTF::Unretained(this)));
...@@ -335,14 +358,15 @@ TEST_F(MediaDevicesTest, EnumerateDevicesBeforeConnectionError) { ...@@ -335,14 +358,15 @@ TEST_F(MediaDevicesTest, EnumerateDevicesBeforeConnectionError) {
ScriptPromise promise = media_devices->enumerateDevices( ScriptPromise promise = media_devices->enumerateDevices(
scope.GetScriptState(), scope.GetExceptionState()); scope.GetScriptState(), scope.GetExceptionState());
platform()->RunUntilIdle(); ScriptPromiseTester tester(scope.GetScriptState(), promise);
tester.WaitUntilSettled();
EXPECT_TRUE(tester.IsFulfilled());
ASSERT_FALSE(promise.IsEmpty()); ASSERT_FALSE(promise.IsEmpty());
// Simulate a connection error by closing the binding. // Simulate a connection error by closing the binding.
CloseBinding(); CloseBinding();
platform()->RunUntilIdle(); platform()->RunUntilIdle();
EXPECT_TRUE(dispatcher_host_connection_error()); EXPECT_TRUE(dispatcher_host_connection_error());
EXPECT_TRUE(devices_enumerated());
} }
TEST_F(MediaDevicesTest, ObserveDeviceChangeEvent) { TEST_F(MediaDevicesTest, ObserveDeviceChangeEvent) {
......
...@@ -31,7 +31,9 @@ ...@@ -31,7 +31,9 @@
#include "third_party/blink/renderer/core/frame/navigator.h" #include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/core/frame/settings.h" #include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/page/page.h" #include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/modules/mediastream/media_devices.h"
#include "third_party/blink/renderer/modules/mediastream/media_error_state.h" #include "third_party/blink/renderer/modules/mediastream/media_error_state.h"
#include "third_party/blink/renderer/modules/mediastream/navigator_user_media.h"
#include "third_party/blink/renderer/modules/mediastream/user_media_controller.h" #include "third_party/blink/renderer/modules/mediastream/user_media_controller.h"
#include "third_party/blink/renderer/modules/mediastream/user_media_request.h" #include "third_party/blink/renderer/modules/mediastream/user_media_request.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h" #include "third_party/blink/renderer/platform/bindings/exception_state.h"
...@@ -56,10 +58,14 @@ void NavigatorMediaStream::getUserMedia( ...@@ -56,10 +58,14 @@ void NavigatorMediaStream::getUserMedia(
UserMediaController* user_media = UserMediaController* user_media =
UserMediaController::From(navigator.DomWindow()); UserMediaController::From(navigator.DomWindow());
MediaDevices* media_devices = NavigatorUserMedia::mediaDevices(navigator);
MediaErrorState error_state; MediaErrorState error_state;
UserMediaRequest* request = UserMediaRequest* request = UserMediaRequest::Create(
UserMediaRequest::Create(navigator.DomWindow(), user_media, options, navigator.DomWindow(), user_media, options, success_callback,
success_callback, error_callback, error_state); error_callback,
WTF::Bind(&MediaDevices::SetEnumerateCanExposeDevices,
WrapWeakPersistent(media_devices)),
error_state);
if (!request) { if (!request) {
DCHECK(error_state.HadException()); DCHECK(error_state.HadException());
if (error_state.CanGenerateException()) { if (error_state.CanGenerateException()) {
......
...@@ -330,6 +330,7 @@ UserMediaRequest* UserMediaRequest::Create( ...@@ -330,6 +330,7 @@ UserMediaRequest* UserMediaRequest::Create(
UserMediaRequest::MediaType media_type, UserMediaRequest::MediaType media_type,
const MediaStreamConstraints* options, const MediaStreamConstraints* options,
Callbacks* callbacks, Callbacks* callbacks,
base::OnceClosure success_update_callback,
MediaErrorState& error_state) { MediaErrorState& error_state) {
MediaConstraints audio = ParseOptions(context, options->audio(), error_state); MediaConstraints audio = ParseOptions(context, options->audio(), error_state);
if (error_state.HadException()) if (error_state.HadException())
...@@ -394,8 +395,9 @@ UserMediaRequest* UserMediaRequest::Create( ...@@ -394,8 +395,9 @@ UserMediaRequest* UserMediaRequest::Create(
if (!video.IsNull()) if (!video.IsNull())
CountVideoConstraintUses(context, video); CountVideoConstraintUses(context, video);
return MakeGarbageCollected<UserMediaRequest>(context, controller, media_type, return MakeGarbageCollected<UserMediaRequest>(
audio, video, callbacks); context, controller, media_type, audio, video, callbacks,
std::move(success_update_callback));
} }
UserMediaRequest* UserMediaRequest::Create( UserMediaRequest* UserMediaRequest::Create(
...@@ -404,11 +406,12 @@ UserMediaRequest* UserMediaRequest::Create( ...@@ -404,11 +406,12 @@ UserMediaRequest* UserMediaRequest::Create(
const MediaStreamConstraints* options, const MediaStreamConstraints* options,
V8NavigatorUserMediaSuccessCallback* success_callback, V8NavigatorUserMediaSuccessCallback* success_callback,
V8NavigatorUserMediaErrorCallback* error_callback, V8NavigatorUserMediaErrorCallback* error_callback,
base::OnceClosure success_update_callback,
MediaErrorState& error_state) { MediaErrorState& error_state) {
return Create( return Create(
context, controller, UserMediaRequest::MediaType::kUserMedia, options, context, controller, UserMediaRequest::MediaType::kUserMedia, options,
MakeGarbageCollected<V8Callbacks>(success_callback, error_callback), MakeGarbageCollected<V8Callbacks>(success_callback, error_callback),
error_state); std::move(success_update_callback), error_state);
} }
UserMediaRequest* UserMediaRequest::CreateForTesting( UserMediaRequest* UserMediaRequest::CreateForTesting(
...@@ -416,7 +419,7 @@ UserMediaRequest* UserMediaRequest::CreateForTesting( ...@@ -416,7 +419,7 @@ UserMediaRequest* UserMediaRequest::CreateForTesting(
const MediaConstraints& video) { const MediaConstraints& video) {
return MakeGarbageCollected<UserMediaRequest>( return MakeGarbageCollected<UserMediaRequest>(
nullptr, nullptr, UserMediaRequest::MediaType::kUserMedia, audio, video, nullptr, nullptr, UserMediaRequest::MediaType::kUserMedia, audio, video,
nullptr); nullptr, base::OnceClosure());
} }
UserMediaRequest::UserMediaRequest(ExecutionContext* context, UserMediaRequest::UserMediaRequest(ExecutionContext* context,
...@@ -424,7 +427,8 @@ UserMediaRequest::UserMediaRequest(ExecutionContext* context, ...@@ -424,7 +427,8 @@ UserMediaRequest::UserMediaRequest(ExecutionContext* context,
UserMediaRequest::MediaType media_type, UserMediaRequest::MediaType media_type,
MediaConstraints audio, MediaConstraints audio,
MediaConstraints video, MediaConstraints video,
Callbacks* callbacks) Callbacks* callbacks,
base::OnceClosure success_update_callback)
: ExecutionContextLifecycleObserver(context), : ExecutionContextLifecycleObserver(context),
media_type_(media_type), media_type_(media_type),
audio_(audio), audio_(audio),
...@@ -433,7 +437,8 @@ UserMediaRequest::UserMediaRequest(ExecutionContext* context, ...@@ -433,7 +437,8 @@ UserMediaRequest::UserMediaRequest(ExecutionContext* context,
RuntimeEnabledFeatures::DisableHardwareNoiseSuppressionEnabled( RuntimeEnabledFeatures::DisableHardwareNoiseSuppressionEnabled(
context)), context)),
controller_(controller), controller_(controller),
callbacks_(callbacks) { callbacks_(callbacks),
success_update_callback_(std::move(success_update_callback)) {
if (should_disable_hardware_noise_suppression_) { if (should_disable_hardware_noise_suppression_) {
UseCounter::Count(context, UseCounter::Count(context,
WebFeature::kUserMediaDisableHardwareNoiseSuppression); WebFeature::kUserMediaDisableHardwareNoiseSuppression);
...@@ -533,6 +538,8 @@ void UserMediaRequest::OnMediaStreamInitialized(MediaStream* stream) { ...@@ -533,6 +538,8 @@ void UserMediaRequest::OnMediaStreamInitialized(MediaStream* stream) {
for (const auto& video_track : video_tracks) for (const auto& video_track : video_tracks)
video_track->SetConstraints(video_); video_track->SetConstraints(video_);
if (success_update_callback_)
std::move(success_update_callback_).Run();
callbacks_->OnSuccess(nullptr, stream); callbacks_->OnSuccess(nullptr, stream);
is_resolved_ = true; is_resolved_ = true;
} }
......
...@@ -94,12 +94,14 @@ class MODULES_EXPORT UserMediaRequest final ...@@ -94,12 +94,14 @@ class MODULES_EXPORT UserMediaRequest final
MediaType media_type, MediaType media_type,
const MediaStreamConstraints* options, const MediaStreamConstraints* options,
Callbacks*, Callbacks*,
base::OnceClosure success_update_callback,
MediaErrorState&); MediaErrorState&);
static UserMediaRequest* Create(ExecutionContext*, static UserMediaRequest* Create(ExecutionContext*,
UserMediaController*, UserMediaController*,
const MediaStreamConstraints* options, const MediaStreamConstraints* options,
V8NavigatorUserMediaSuccessCallback*, V8NavigatorUserMediaSuccessCallback*,
V8NavigatorUserMediaErrorCallback*, V8NavigatorUserMediaErrorCallback*,
base::OnceClosure success_update_callback,
MediaErrorState&); MediaErrorState&);
static UserMediaRequest* CreateForTesting(const MediaConstraints& audio, static UserMediaRequest* CreateForTesting(const MediaConstraints& audio,
const MediaConstraints& video); const MediaConstraints& video);
...@@ -109,7 +111,8 @@ class MODULES_EXPORT UserMediaRequest final ...@@ -109,7 +111,8 @@ class MODULES_EXPORT UserMediaRequest final
MediaType media_type, MediaType media_type,
MediaConstraints audio, MediaConstraints audio,
MediaConstraints video, MediaConstraints video,
Callbacks*); Callbacks*,
base::OnceClosure success_update_callback);
virtual ~UserMediaRequest(); virtual ~UserMediaRequest();
LocalDOMWindow* GetWindow(); LocalDOMWindow* GetWindow();
...@@ -161,6 +164,7 @@ class MODULES_EXPORT UserMediaRequest final ...@@ -161,6 +164,7 @@ class MODULES_EXPORT UserMediaRequest final
Member<UserMediaController> controller_; Member<UserMediaController> controller_;
Member<Callbacks> callbacks_; Member<Callbacks> callbacks_;
base::OnceClosure success_update_callback_;
bool is_resolved_ = false; bool is_resolved_ = false;
}; };
......
...@@ -17,6 +17,8 @@ promise_test(t => promise_rejects_dom(t, "NotFoundError", audio.setSinkId("nonex ...@@ -17,6 +17,8 @@ promise_test(t => promise_rejects_dom(t, "NotFoundError", audio.setSinkId("nonex
"setSinkId fails with NotFoundError on made up deviceid"); "setSinkId fails with NotFoundError on made up deviceid");
promise_test(async t => { promise_test(async t => {
const stream = await navigator.mediaDevices.getUserMedia({audio:true});
stream.getAudioTracks()[0].stop();
const list = await navigator.mediaDevices.enumerateDevices(); const list = await navigator.mediaDevices.enumerateDevices();
const outputDevicesList = list.filter(({kind}) => kind == "audiooutput"); const outputDevicesList = list.filter(({kind}) => kind == "audiooutput");
assert_not_equals(outputDevicesList.length, 0, assert_not_equals(outputDevicesList.length, 0,
......
This is a testharness.js-based test.
FAIL mediaDevices.enumerateDevices() is present and working - before capture assert_equals: mediaInfo's deviceId should exist and be empty if getUserMedia was never called successfully. expected "" but got "default"
PASS mediaDevices.enumerateDevices() is present and working - after capture
PASS InputDeviceInfo is supported
Harness: the test ran to completion.
...@@ -40,6 +40,8 @@ test(function () { ...@@ -40,6 +40,8 @@ test(function () {
promise_test(async t => { promise_test(async t => {
assert_true(navigator.mediaDevices.getSupportedConstraints()["groupId"], assert_true(navigator.mediaDevices.getSupportedConstraints()["groupId"],
"groupId should be supported"); "groupId should be supported");
const stream = await navigator.mediaDevices.getUserMedia({video:true});
stream.getVideoTracks()[0].stop();
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
for (const device of devices) { for (const device of devices) {
await navigator.mediaDevices.getUserMedia( await navigator.mediaDevices.getUserMedia(
...@@ -63,6 +65,8 @@ promise_test(async t => { ...@@ -63,6 +65,8 @@ promise_test(async t => {
promise_test(async t => { promise_test(async t => {
assert_true(navigator.mediaDevices.getSupportedConstraints()["groupId"], assert_true(navigator.mediaDevices.getSupportedConstraints()["groupId"],
"groupId should be supported"); "groupId should be supported");
const stream = await navigator.mediaDevices.getUserMedia({audio:true});
stream.getAudioTracks()[0].stop();
const devices = await navigator.mediaDevices.enumerateDevices(); const devices = await navigator.mediaDevices.enumerateDevices();
for (const device of devices) { for (const device of devices) {
await navigator.mediaDevices.getUserMedia( await navigator.mediaDevices.getUserMedia(
......
...@@ -13,6 +13,8 @@ idl_test( ...@@ -13,6 +13,8 @@ idl_test(
const inputDevices = []; const inputDevices = [];
const outputDevices = []; const outputDevices = [];
try { try {
const stream = await navigator.mediaDevices.getUserMedia({audio:true, video:true});
stream.getTracks().forEach(track => track.stop());
const list = await navigator.mediaDevices.enumerateDevices(); const list = await navigator.mediaDevices.enumerateDevices();
for (const device of list) { for (const device of list) {
if (device.kind in self) { if (device.kind in self) {
......
...@@ -16,20 +16,23 @@ ...@@ -16,20 +16,23 @@
var t = async_test( var t = async_test(
"Result of enumerateDevices() on an iframe matches that of the main frame", "Result of enumerateDevices() on an iframe matches that of the main frame",
{timeout: 4000}); {timeout: 4000});
var iframe = document.createElement('iframe'); let iframe = document.createElement('iframe');
window.onmessage = t.step_func(event => { window.onmessage = t.step_func(event => {
if (event.source != iframe.contentWindow) if (event.source != iframe.contentWindow)
return; return;
var iframe_devices = JSON.parse(event.data); let iframe_devices = JSON.parse(event.data);
navigator.mediaDevices.enumerateDevices().then(main_devices => { navigator.mediaDevices.getUserMedia({audio:true, video:true}).then(stream => {
assert_equals(main_devices.length, iframe_devices.length); stream.getTracks().forEach(track => track.stop());
for (var i = 0; i < main_devices.length; i++) { navigator.mediaDevices.enumerateDevices().then(main_devices => {
assert_not_equals(main_devices[i].groupId, iframe_devices[i].groupId); assert_equals(main_devices.length, iframe_devices.length);
assert_equals(main_devices[i].kind, iframe_devices[i].kind); for (let i = 0; i < main_devices.length; i++) {
assert_equals(main_devices[i].label, iframe_devices[i].label); assert_not_equals(main_devices[i].groupId, iframe_devices[i].groupId);
} assert_equals(main_devices[i].kind, iframe_devices[i].kind);
t.done(); assert_equals(main_devices[i].label, iframe_devices[i].label);
}
t.done();
});
}); });
}); });
......
<script> <script>
navigator.mediaDevices.enumerateDevices().then(devices => { navigator.mediaDevices.getUserMedia({audio:true, video:true}).then(stream => {
window.parent.postMessage(JSON.stringify(devices), "*"); stream.getTracks().forEach(track => track.stop());
navigator.mediaDevices.enumerateDevices().then(devices => {
window.parent.postMessage(JSON.stringify(devices), "*");
});
}); });
</script> </script>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment