Skip to content
Permalink
Browse files
Enforce user gesture for getUserMedia in case a previous getUserMedia…
… call was denied

https://bugs.webkit.org/show_bug.cgi?id=203362
Source/WebCore:

Reviewed by Eric Carlson.

Compute whether a media request is user priviledged or not.
It is priviledged if it is created as part of a user gesture and no request of the same type
has been previously created for the same user gesture.
If getDisplayMedia is called twice as part of a single user gesture, getDisplaMedia will reject for the second call.

Remove the internal ability to disable user gesture check.
Instead use internals API to simulate a user gesture.

Test: fast/mediastream/getUserMedia-deny-persistency5.html and updated test.

* Modules/mediastream/MediaDevices.cpp:
(WebCore::MediaDevices::computeUserGesturePriviledge):
(WebCore::MediaDevices::getUserMedia):
(WebCore::MediaDevices::getDisplayMedia):
(WebCore::MediaDevices::getUserMedia const): Deleted.
(WebCore::MediaDevices::getDisplayMedia const): Deleted.
* Modules/mediastream/MediaDevices.h:
* platform/mediastream/MediaStreamRequest.h:
(WebCore::MediaStreamRequest::encode const):
(WebCore::MediaStreamRequest::decode):
* testing/Internals.cpp:
(WebCore::Internals::setMediaStreamSourceInterrupted):
(WebCore::Internals::setDisableGetDisplayMediaUserGestureConstraint): Deleted.
* testing/Internals.h:
* testing/Internals.idl:

Source/WebKit:

Reviewed by Eric Carlson.

In case the request has user gesture priviledge, do not look at denied request history.

* UIProcess/UserMediaPermissionRequestManagerProxy.cpp:
(WebKit::UserMediaPermissionRequestManagerProxy::getRequestAction):
* UIProcess/UserMediaPermissionRequestProxy.h:
(WebKit::UserMediaPermissionRequestProxy::isUserGesturePriviledged const):

Tools:

Reviewed by Eric Carlson.

* TestWebKitAPI/Tests/WebKitCocoa/GetDisplayMedia.mm:
(TestWebKitAPI::TEST_F):
Update test to take into account the ability to ask again for permission.
* TestWebKitAPI/Tests/WebKit/getDisplayMedia.html:
Update to make sure we notify test if internals is not available.

LayoutTests:

<rdar://problem/56648232>

Reviewed by Eric Carlson.

* fast/mediastream/constraint-intrinsic-size.html:
* fast/mediastream/get-display-media-muted.html:
* fast/mediastream/getUserMedia-deny-persistency5-expected.txt:
* fast/mediastream/getUserMedia-deny-persistency5.html:
* fast/mediastream/media-stream-page-muted.html:
Use user gesture simulation instead of disabling user gesture check.
* fast/mediastream/screencapture-user-gesture.html:
* fast/mediastream/screencapture-user-gesture-expected.txt:
* http/tests/media/media-stream/get-display-media-iframe-allow-attribute-expected.txt:
* http/tests/media/media-stream/get-display-media-prompt.html:
* http/tests/media/media-stream/resources/get-display-media-devices-iframe.html:
* resources/testharnessreport.js:


Canonical link: https://commits.webkit.org/217202@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@252046 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
youennf committed Nov 5, 2019
1 parent 24ea0fe commit 72931ff069025bf622bcf5e3c717226891927fd6
Showing 25 changed files with 273 additions and 68 deletions.
@@ -1,3 +1,24 @@
2019-11-05 youenn fablet <youenn@apple.com>

Enforce user gesture for getUserMedia in case a previous getUserMedia call was denied
https://bugs.webkit.org/show_bug.cgi?id=203362
<rdar://problem/56648232>

Reviewed by Eric Carlson.

* fast/mediastream/constraint-intrinsic-size.html:
* fast/mediastream/get-display-media-muted.html:
* fast/mediastream/getUserMedia-deny-persistency5-expected.txt:
* fast/mediastream/getUserMedia-deny-persistency5.html:
* fast/mediastream/media-stream-page-muted.html:
Use user gesture simulation instead of disabling user gesture check.
* fast/mediastream/screencapture-user-gesture.html:
* fast/mediastream/screencapture-user-gesture-expected.txt:
* http/tests/media/media-stream/get-display-media-iframe-allow-attribute-expected.txt:
* http/tests/media/media-stream/get-display-media-prompt.html:
* http/tests/media/media-stream/resources/get-display-media-devices-iframe.html:
* resources/testharnessreport.js:

2019-11-05 youenn fablet <youenn@apple.com>

LayoutTest webrtc/captureCanvas-webrtc.html is flaky
@@ -16,17 +16,26 @@
window.internals.settings.setScreenCaptureEnabled(true);
}

function callGetDisplayMedia(options)
{
let promise;
window.internals.withUserGesture(() => {
promise = navigator.mediaDevices.getDisplayMedia(options);
});
return promise;
}

let defaultWidth;
let defaultHeight;
promise_test(async () => {
stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
stream = await callGetDisplayMedia({ video: true });
let settings = stream.getVideoTracks()[0].getSettings();
defaultWidth = settings.width;
defaultHeight = settings.height;
}, "setup");

promise_test((test) => {
return navigator.mediaDevices.getDisplayMedia({ video: { width: {ideal: 640} } })
return callGetDisplayMedia({ video: { width: {ideal: 640} } })
.then((stream) => {
let settings = stream.getVideoTracks()[0].getSettings()
assert_equals(settings.height, Math.floor(640 * (defaultHeight / defaultWidth)));
@@ -35,7 +44,7 @@

promise_test((test) => {

return navigator.mediaDevices.getDisplayMedia({ video: { height: {ideal: 240} } })
return callGetDisplayMedia({ video: { height: {ideal: 240} } })
.then((stream) => {
let settings = stream.getVideoTracks()[0].getSettings()
assert_equals(settings.width, Math.floor(240 * (defaultWidth / defaultHeight)));
@@ -44,4 +53,4 @@

</script>
</body>
</html>
</html>
@@ -10,6 +10,15 @@
if (window.testRunner)
testRunner.setUserMediaPermission(true);

function callGetDisplayMedia(options)
{
let promise;
window.internals.withUserGesture(() => {
promise = navigator.mediaDevices.getDisplayMedia(options);
});
return promise;
}

async function waitForPageStateChange(numberOfTries, originalState)
{
return new Promise(async (resolve) => {
@@ -29,7 +38,7 @@

promise_test(async (test) => {
await new Promise(async (resolve, reject) => {
let stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
let stream = await callGetDisplayMedia({ video: true });
let pageMediaState = internals.pageMediaState();

assert_false(pageMediaState.includes('HasMutedDisplayCaptureDevice'), 'page state does not include HasMutedDisplayCaptureDevice');
@@ -0,0 +1,3 @@

PASS Testing getUserMedia with and without user gesture after user denied access

@@ -0,0 +1,45 @@
<!DOCTYPE HTML>
<html>
<head>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
</head>
<body>
<script>
promise_test(async (test) => {
if (window.testRunner)
testRunner.setUserMediaPermission(false);

await navigator.mediaDevices.getUserMedia({audio:false, video:true}).then(assert_unreached, (e) => { });
await navigator.mediaDevices.getUserMedia({audio:true, video:false}).then(assert_unreached, (e) => { });
await navigator.mediaDevices.getUserMedia({audio:true, video:true}).then(assert_unreached, (e) => { });

if (window.testRunner)
testRunner.setUserMediaPermission(true);

await navigator.mediaDevices.getUserMedia({audio:false, video:true}).then(assert_unreached, (e) => {
assert_equals(e.name, "NotAllowedError");
});

let promise;
internals.withUserGesture(() => {
promise = navigator.mediaDevices.getUserMedia({audio:false, video:true});
});
await promise;
internals.withUserGesture(() => {
promise = navigator.mediaDevices.getUserMedia({audio:true, video:false});
});
await promise;

internals.withUserGesture(() => {
promise = navigator.mediaDevices.getUserMedia({audio:true, video:true});
});
await promise;

await navigator.mediaDevices.getUserMedia({audio:false, video:true}).then(assert_unreached, (e) => { });
await navigator.mediaDevices.getUserMedia({audio:true, video:false}).then(assert_unreached, (e) => { });
await navigator.mediaDevices.getUserMedia({audio:true, video:true}).then(assert_unreached, (e) => { });
}, "Testing getUserMedia with and without user gesture after user denied access");
</script>
</body>
</html>
@@ -4,9 +4,6 @@
<title>mediastream page muted</title>
<script src="../../resources/js-test-pre.js"></script>
<script>
if (window.internals)
internals.setDisableGetDisplayMediaUserGestureConstraint(true);

async function checkPageState(activeState, inactiveState) {
await new Promise((resolve, reject) => {
let retryCount = 0;
@@ -69,14 +66,27 @@
});
}

async function createScreenCaptureStream() {
function createScreenCaptureStream() {
let resolveCallback, rejectCallback;
let promise = new Promise((resolve, reject) => {
resolveCallback = resolve;
rejectCallback = reject;
});
debug("<br>*** Creating screen capture stream");
displayStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
shouldBeType("displayStream", "Object");
shouldBe("displayStream.getTracks().length", "1");
shouldBe("displayStream.getVideoTracks().length", "1");
screenCaptureTrack = displayStream.getVideoTracks()[0];
shouldBeFalse("screenCaptureTrack.muted");
window.internals.withUserGesture(async () => {
try {
displayStream = await navigator.mediaDevices.getDisplayMedia({ video: true });
shouldBeType("displayStream", "Object");
shouldBe("displayStream.getTracks().length", "1");
shouldBe("displayStream.getVideoTracks().length", "1");
screenCaptureTrack = displayStream.getVideoTracks()[0];
shouldBeFalse("screenCaptureTrack.muted");
resolveCallback();
} catch(e) {
rejectCallback(e);
}
});
return promise;
}

async function createCameraMicrophoneStream() {
@@ -1,4 +1,5 @@

PASS Allow getDisplayMedia call in case of user gesture
PASS Disallow getDisplayMedia calls in case of user gesture if not the first call
PASS Deny getDisplayMedia call if no user gesture

@@ -1,9 +1,6 @@
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script>
if (window.internals)
internals.setDisableGetDisplayMediaUserGestureConstraint(false);

promise_test(() => {
let promise;
internals.withUserGesture(() => {
@@ -12,6 +9,21 @@
return promise;
}, "Allow getDisplayMedia call in case of user gesture");

promise_test(() => {
let promise;
internals.withUserGesture(() => {
const promise1 = navigator.mediaDevices.getDisplayMedia({video : true});
const promise2 = navigator.mediaDevices.getDisplayMedia({video : true}).then(() => {
return Promise.reject("Second promise should reject");
}, () => {
return "Second promise rejected";
});
promise = Promise.all([promise1, promise2]);
});
return promise;
}, "Disallow getDisplayMedia calls in case of user gesture if not the first call");


promise_test((test) => {
return promise_rejects(test, "InvalidAccessError", navigator.mediaDevices.getDisplayMedia({video : true}));
}, "Deny getDisplayMedia call if no user gesture");
@@ -1,6 +1,6 @@
CONSOLE MESSAGE: line 8: Trying to call getDisplayMedia from a frame without correct 'allow' attribute.
CONSOLE MESSAGE: line 8: Trying to call getDisplayMedia from a frame without correct 'allow' attribute.
CONSOLE MESSAGE: line 8: Trying to call getDisplayMedia from a frame without correct 'allow' attribute.
CONSOLE MESSAGE: line 6: Trying to call getDisplayMedia from a frame without correct 'allow' attribute.
CONSOLE MESSAGE: line 6: Trying to call getDisplayMedia from a frame without correct 'allow' attribute.
CONSOLE MESSAGE: line 6: Trying to call getDisplayMedia from a frame without correct 'allow' attribute.


PASS: <iframe allow=''> got "deny"
@@ -9,19 +9,25 @@
<div id="console"></div>

<script>
if (window.internals)
internals.setDisableGetDisplayMediaUserGestureConstraint(true);

let stream;
let err;

function callGetDisplayMedia(options)
{
let promise;
window.internals.withUserGesture(() => {
promise = navigator.mediaDevices.getDisplayMedia(options);
});
return promise;
}

function numberOfTimesGetUserMediaPromptHasBeenCalled() {
return testRunner.userMediaPermissionRequestCountForOrigin(document.location.href, document.location.href);
}

async function promptForAudioOnly() {
debug("<br>** Request an audio-only stream, the user should not be prompted **");
stream = await navigator.mediaDevices.getDisplayMedia({ audio: true })
stream = await callGetDisplayMedia({ audio: true })
.catch((e) => { err = e; });
shouldBe("numberOfTimesGetUserMediaPromptHasBeenCalled()", "0");
shouldBeUndefined("stream");
@@ -31,15 +37,15 @@

async function promptForVideoOnly() {
debug("<br>** Request an video-only stream, the user should be prompted **");
stream = await navigator.mediaDevices.getDisplayMedia({ video: true });
stream = await callGetDisplayMedia({ video: true });
shouldBe("numberOfTimesGetUserMediaPromptHasBeenCalled()", "1");
shouldBe("stream.getAudioTracks().length", "0");
shouldBe("stream.getVideoTracks().length", "1");
}

async function promptForAudioAndVideo() {
debug("<br>** Request a stream with audio and video, the user should be prompted but no audio track should be created **");
stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: true });
stream = await callGetDisplayMedia({ video: true, audio: true });
shouldBe("numberOfTimesGetUserMediaPromptHasBeenCalled()", "2");
shouldBe("stream.getAudioTracks().length", "0");
shouldBe("stream.getVideoTracks().length", "1");
@@ -49,7 +55,7 @@
debug("<br>** Request a stream with 'max' constraints, the user should not be prompted **");

stream = null;
stream = await navigator.mediaDevices.getDisplayMedia({ video: {width: {exact: 640}, height: {exact: 480}} })
stream = await callGetDisplayMedia({ video: {width: {exact: 640}, height: {exact: 480}} })
.catch((e) => { err = e; });

shouldBe("numberOfTimesGetUserMediaPromptHasBeenCalled()", "2");
@@ -62,7 +68,7 @@
debug("<br>** Request a stream with 'min' constraints, the user should not be prompted **");

stream = null;
stream = await navigator.mediaDevices.getDisplayMedia({ video: {width: {min: 640}, height: {min: 480}} })
stream = await callGetDisplayMedia({ video: {width: {min: 640}, height: {min: 480}} })
.catch((e) => { err = e; });
shouldBe("numberOfTimesGetUserMediaPromptHasBeenCalled()", "2");
shouldBeUndefined("stream");
@@ -74,7 +80,7 @@
debug("<br>** Request a stream with 'advanced' constraints, the user should not be prompted **");

stream = null;
stream = await navigator.mediaDevices.getDisplayMedia({ video: { width: 640, height: 480, advanced: [ { width: 1920, height: 1280 } ] } })
stream = await callGetDisplayMedia({ video: { width: 640, height: 480, advanced: [ { width: 1920, height: 1280 } ] } })
.catch((e) => { err = e; });
shouldBe("numberOfTimesGetUserMediaPromptHasBeenCalled()", "2");
shouldBeUndefined("stream");
@@ -86,7 +92,7 @@
debug("<br>** Request a stream with valid constraints, the user should be prompted **");

stream = null;
stream = await navigator.mediaDevices.getDisplayMedia({ video: {width: 640, height: 480} })
stream = await callGetDisplayMedia({ video: {width: 640, height: 480} })
.catch((e) => { err = e; });

shouldBe("numberOfTimesGetUserMediaPromptHasBeenCalled()", "3");
@@ -98,7 +104,7 @@
debug("<br>** Request a stream with an exact audio constraint, it should be ignored **");

stream = null;
stream = await navigator.mediaDevices.getDisplayMedia({ video: true, audio: { volume: { exact: 0.5 } } });
stream = await callGetDisplayMedia({ video: true, audio: { volume: { exact: 0.5 } } });
shouldBe("numberOfTimesGetUserMediaPromptHasBeenCalled()", "4");
shouldBe("stream.getAudioTracks().length", "0");
shouldBe("stream.getVideoTracks().length", "1");
@@ -1,11 +1,17 @@
<script>
if (window.internals)
internals.setDisableGetDisplayMediaUserGestureConstraint(true);
function callGetDisplayMedia(options)
{
let promise;
window.internals.withUserGesture(() => {
promise = navigator.mediaDevices.getDisplayMedia(options);
});
return promise;
}

async function enumerate(event)
{
let result;
await navigator.mediaDevices.getDisplayMedia({video: true})
await callGetDisplayMedia({video: true})
.then((s) => result = "allow")
.catch((e) => result = "deny");
parent.postMessage(`${event.data}:${result}`, '*');
@@ -32,9 +32,6 @@ if (self.testRunner) {
testRunner.setStatisticsShouldDowngradeReferrer(false, function() { });
}

if (self.internals && internals.setDisableGetDisplayMediaUserGestureConstraint)
internals.setDisableGetDisplayMediaUserGestureConstraint(true);

if (self.internals && internals.setICECandidateFiltering)
internals.setICECandidateFiltering(false);

@@ -1,3 +1,36 @@
2019-11-05 youenn fablet <youenn@apple.com>

Enforce user gesture for getUserMedia in case a previous getUserMedia call was denied
https://bugs.webkit.org/show_bug.cgi?id=203362

Reviewed by Eric Carlson.

Compute whether a media request is user priviledged or not.
It is priviledged if it is created as part of a user gesture and no request of the same type
has been previously created for the same user gesture.
If getDisplayMedia is called twice as part of a single user gesture, getDisplaMedia will reject for the second call.

Remove the internal ability to disable user gesture check.
Instead use internals API to simulate a user gesture.

Test: fast/mediastream/getUserMedia-deny-persistency5.html and updated test.

* Modules/mediastream/MediaDevices.cpp:
(WebCore::MediaDevices::computeUserGesturePriviledge):
(WebCore::MediaDevices::getUserMedia):
(WebCore::MediaDevices::getDisplayMedia):
(WebCore::MediaDevices::getUserMedia const): Deleted.
(WebCore::MediaDevices::getDisplayMedia const): Deleted.
* Modules/mediastream/MediaDevices.h:
* platform/mediastream/MediaStreamRequest.h:
(WebCore::MediaStreamRequest::encode const):
(WebCore::MediaStreamRequest::decode):
* testing/Internals.cpp:
(WebCore::Internals::setMediaStreamSourceInterrupted):
(WebCore::Internals::setDisableGetDisplayMediaUserGestureConstraint): Deleted.
* testing/Internals.h:
* testing/Internals.idl:

2019-11-05 Carlos Garcia Campos <cgarcia@igalia.com>

[FreeType] Too slow running encoding/legacy-mb-korean/euc-kr WPT tests

0 comments on commit 72931ff

Please sign in to comment.