summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/core/media_capture_devices_dispatcher.cpp204
-rw-r--r--src/core/media_capture_devices_dispatcher.h2
-rw-r--r--src/core/web_contents_adapter_client.h2
-rw-r--r--src/webengine/api/qquickwebengineview.cpp20
-rw-r--r--src/webengine/api/qquickwebengineview_p.h4
-rw-r--r--src/webengine/doc/src/webengineview.qdoc5
-rw-r--r--src/webengine/plugin/plugin.cpp1
-rw-r--r--src/webengine/plugin/plugins.qmltypes10
-rw-r--r--src/webenginewidgets/api/qwebenginepage.cpp99
-rw-r--r--src/webenginewidgets/api/qwebenginepage.h4
-rw-r--r--src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc5
-rw-r--r--tests/auto/quick/qmltests/data/tst_getUserMedia.qml201
-rw-r--r--tests/auto/quick/qmltests/qmltests.pro1
-rw-r--r--tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp120
14 files changed, 505 insertions, 173 deletions
diff --git a/src/core/media_capture_devices_dispatcher.cpp b/src/core/media_capture_devices_dispatcher.cpp
index 8bdbaadd2..04d4a1924 100644
--- a/src/core/media_capture_devices_dispatcher.cpp
+++ b/src/core/media_capture_devices_dispatcher.cpp
@@ -121,13 +121,51 @@ std::unique_ptr<content::MediaStreamUI> getDevicesForDesktopCapture(content::Med
return std::move(ui);
}
+content::DesktopMediaID getDefaultScreenId()
+{
+#if BUILDFLAG(ENABLE_WEBRTC)
+ // Source id patterns are different across platforms.
+ // On Linux, the hardcoded value "0" is used.
+ // On Windows, the screens are enumerated consecutively in increasing order from 0.
+ // On macOS the source ids are randomish numbers assigned by the OS.
+
+ // In order to provide a correct screen id, we query for the available screen ids, and
+ // select the first one as the main display id.
+ // The code is based on the file
+ // src/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.cc.
+ webrtc::DesktopCaptureOptions options =
+ webrtc::DesktopCaptureOptions::CreateDefault();
+ options.set_disable_effects(false);
+ std::unique_ptr<webrtc::DesktopCapturer> screen_capturer(
+ webrtc::DesktopCapturer::CreateScreenCapturer(options));
+
+ if (screen_capturer) {
+ webrtc::DesktopCapturer::SourceList screens;
+ if (screen_capturer->GetSourceList(&screens)) {
+ if (screens.size() > 0) {
+ return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, screens[0].id);
+ }
+ }
+ }
+#endif
+
+ return content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 0);
+}
+
WebContentsAdapterClient::MediaRequestFlags mediaRequestFlagsForRequest(const content::MediaStreamRequest &request)
{
WebContentsAdapterClient::MediaRequestFlags requestFlags = WebContentsAdapterClient::MediaNone;
+
if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE)
requestFlags |= WebContentsAdapterClient::MediaAudioCapture;
+ else if (request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE)
+ requestFlags |= WebContentsAdapterClient::MediaDesktopAudioCapture;
+
if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE)
requestFlags |= WebContentsAdapterClient::MediaVideoCapture;
+ else if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE)
+ requestFlags |= WebContentsAdapterClient::MediaDesktopVideoCapture;
+
return requestFlags;
}
@@ -150,6 +188,7 @@ void MediaCaptureDevicesDispatcher::handleMediaAccessPermissionResponse(content:
{
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ std::unique_ptr<content::MediaStreamUI> ui;
content::MediaStreamDevices devices;
std::map<content::WebContents*, RequestsQueue>::iterator it = m_pendingRequests.find(webContents);
@@ -176,16 +215,30 @@ void MediaCaptureDevicesDispatcher::handleMediaAccessPermissionResponse(content:
(request.audio_type && authorizationFlags & WebContentsAdapterClient::MediaAudioCapture);
bool webcamRequested =
(request.video_type && authorizationFlags & WebContentsAdapterClient::MediaVideoCapture);
- if (securityOriginsMatch && (microphoneRequested || webcamRequested)) {
- switch (request.request_type) {
- case content::MEDIA_OPEN_DEVICE_PEPPER_ONLY:
- getDefaultDevices("", "", microphoneRequested, webcamRequested, &devices);
- break;
- case content::MEDIA_DEVICE_ACCESS:
- case content::MEDIA_GENERATE_STREAM:
- getDefaultDevices(request.requested_audio_device_id, request.requested_video_device_id,
- microphoneRequested, webcamRequested, &devices);
- break;
+ bool desktopAudioRequested =
+ (request.audio_type && authorizationFlags & WebContentsAdapterClient::MediaDesktopAudioCapture);
+ bool desktopVideoRequested =
+ (request.video_type && authorizationFlags & WebContentsAdapterClient::MediaDesktopVideoCapture);
+
+ if (securityOriginsMatch) {
+ if (microphoneRequested || webcamRequested) {
+ switch (request.request_type) {
+ case content::MEDIA_OPEN_DEVICE_PEPPER_ONLY:
+ getDefaultDevices("", "", microphoneRequested, webcamRequested, &devices);
+ break;
+ case content::MEDIA_DEVICE_ACCESS:
+ case content::MEDIA_GENERATE_STREAM:
+ getDefaultDevices(request.requested_audio_device_id, request.requested_video_device_id,
+ microphoneRequested, webcamRequested, &devices);
+ break;
+ }
+ } else if (desktopVideoRequested) {
+ ui = getDevicesForDesktopCapture(
+ &devices,
+ getDefaultScreenId(),
+ desktopAudioRequested,
+ /* display_notification: */ false,
+ getContentsUrl(webContents));
}
}
@@ -200,7 +253,7 @@ void MediaCaptureDevicesDispatcher::handleMediaAccessPermissionResponse(content:
BrowserThread::UI, FROM_HERE, base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest, base::Unretained(this), webContents));
}
- callback.Run(devices, devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE : content::MEDIA_DEVICE_OK, std::unique_ptr<content::MediaStreamUI>());
+ callback.Run(devices, devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE : content::MEDIA_DEVICE_OK, std::move(ui));
}
@@ -238,22 +291,34 @@ void MediaCaptureDevicesDispatcher::processMediaAccessRequest(WebContentsAdapter
, const content::MediaStreamRequest &request
, const content::MediaResponseCallback &callback)
{
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- // Let's not support tab capture for now.
- if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE || request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE)
- return;
-
- if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE || request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE)
- // It's still unclear what to make of screen capture. We can rely on existing javascript dialog infrastructure
- // to experiment with this without exposing it through our API yet.
- processDesktopCaptureAccessRequest(webContents, request, callback);
- else {
- enqueueMediaAccessRequest(webContents, request, callback);
- // We might not require this approval for pepper requests.
- adapterClient->runMediaAccessPermissionRequest(toQt(request.security_origin), mediaRequestFlagsForRequest(request));
- }
+ // Let's not support tab capture for now.
+ if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE || request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE) {
+ callback.Run(content::MediaStreamDevices(), content::MEDIA_DEVICE_NOT_SUPPORTED, std::unique_ptr<content::MediaStreamUI>());
+ return;
+ }
+
+ if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE ||
+ request.audio_type == content::MEDIA_DESKTOP_AUDIO_CAPTURE) {
+ const bool screenCaptureEnabled =
+ adapterClient->webEngineSettings()->testAttribute(WebEngineSettings::ScreenCaptureEnabled);
+ const bool originIsSecure = content::IsOriginSecure(request.security_origin);
+ if (!screenCaptureEnabled || !originIsSecure) {
+ callback.Run(content::MediaStreamDevices(), content::MEDIA_DEVICE_INVALID_STATE, std::unique_ptr<content::MediaStreamUI>());
+ return;
+ }
+ if (!request.requested_video_device_id.empty()) {
+ // Non-empty device id from the chooseDesktopMedia() extension API.
+ processDesktopCaptureAccessRequest(webContents, request, callback);
+ return;
+ }
+ }
+
+ enqueueMediaAccessRequest(webContents, request, callback);
+ // We might not require this approval for pepper requests.
+ adapterClient->runMediaAccessPermissionRequest(toQt(request.security_origin), mediaRequestFlagsForRequest(request));
}
void MediaCaptureDevicesDispatcher::processDesktopCaptureAccessRequest(content::WebContents *webContents, const content::MediaStreamRequest &request
@@ -262,19 +327,12 @@ void MediaCaptureDevicesDispatcher::processDesktopCaptureAccessRequest(content::
content::MediaStreamDevices devices;
std::unique_ptr<content::MediaStreamUI> ui;
- if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
+ if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE ||
+ request.requested_video_device_id.empty()) {
callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, std::move(ui));
return;
}
- // If the device id wasn't specified then this is a screen capture request
- // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
- if (request.requested_video_device_id.empty()) {
- processScreenCaptureAccessRequest(
- webContents, request, callback);
- return;
- }
-
content::WebContents* const web_contents_for_stream = content::WebContents::FromRenderFrameHost(
content::RenderFrameHost::FromID(request.render_process_id, request.render_frame_id));
content::RenderFrameHost* const main_frame = web_contents_for_stream ? web_contents_for_stream->GetMainFrame() : NULL;
@@ -307,84 +365,6 @@ void MediaCaptureDevicesDispatcher::processDesktopCaptureAccessRequest(content::
callback.Run(devices, devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE : content::MEDIA_DEVICE_OK, std::move(ui));
}
-void MediaCaptureDevicesDispatcher::processScreenCaptureAccessRequest(content::WebContents *webContents, const content::MediaStreamRequest &request
- ,const content::MediaResponseCallback &callback)
-{
- DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE);
-
- WebContentsAdapterClient *adapterClient = WebContentsViewQt::from(static_cast<content::WebContentsImpl*>(webContents)->GetView())->client();
- const bool screenCaptureEnabled = adapterClient->webEngineSettings()->testAttribute(WebEngineSettings::ScreenCaptureEnabled);
-
- const bool originIsSecure = content::IsOriginSecure(request.security_origin);
-
- if (screenCaptureEnabled && originIsSecure) {
-
- enqueueMediaAccessRequest(webContents, request, callback);
- base::Callback<void(bool, const base::string16&)> dialogCallback = base::Bind(&MediaCaptureDevicesDispatcher::handleScreenCaptureAccessRequest,
- base::Unretained(this), base::Unretained(webContents));
-
- QUrl securityOrigin(toQt(request.security_origin));
- QString message = QCoreApplication::translate("MediaCaptureDevicesDispatcher", "Do you want %1 to share your screen?").arg(securityOrigin.toString());
- QString title = QCoreApplication::translate("MediaCaptureDevicesDispatcher", "%1 Screen Sharing request").arg(securityOrigin.toString());
- JavaScriptDialogManagerQt::GetInstance()->runDialogForContents(webContents, WebContentsAdapterClient::InternalAuthorizationDialog, message
- , QString(), securityOrigin, dialogCallback, title);
- } else
- callback.Run(content::MediaStreamDevices(), content::MEDIA_DEVICE_INVALID_STATE, std::unique_ptr<content::MediaStreamUI>());
-}
-
-void MediaCaptureDevicesDispatcher::handleScreenCaptureAccessRequest(content::WebContents *webContents, bool userAccepted, const base::string16 &)
-{
- content::MediaStreamDevices devices;
- std::unique_ptr<content::MediaStreamUI> ui;
-#if BUILDFLAG(ENABLE_WEBRTC)
- if (userAccepted) {
- // Source id patterns are different across platforms.
- // On Linux, the hardcoded value "0" is used.
- // On Windows, the screens are enumerated consecutively in increasing order from 0.
- // On macOS the source ids are randomish numbers assigned by the OS.
- webrtc::DesktopCapturer::SourceId id = 0;
-
- // In order to provide a correct screen id, we query for the available screen ids, and
- // select the first one as the main display id.
- // The code is based on the file
- // src/chrome/browser/extensions/api/desktop_capture/desktop_capture_base.cc.
- webrtc::DesktopCaptureOptions options =
- webrtc::DesktopCaptureOptions::CreateDefault();
- options.set_disable_effects(false);
- std::unique_ptr<webrtc::DesktopCapturer> screen_capturer(
- webrtc::DesktopCapturer::CreateScreenCapturer(options));
-
- if (screen_capturer) {
- webrtc::DesktopCapturer::SourceList screens;
- if (screen_capturer->GetSourceList(&screens)) {
- if (screens.size() > 0) {
- id = screens[0].id;
- }
- }
- }
-
- content::DesktopMediaID screenId = content::DesktopMediaID(
- content::DesktopMediaID::TYPE_SCREEN, id);
- ui = getDevicesForDesktopCapture(&devices, screenId, false/*capture_audio*/, false/*display_notification*/, getContentsUrl(webContents));
- }
-#endif
- std::map<content::WebContents*, RequestsQueue>::iterator it =
- m_pendingRequests.find(webContents);
- if (it == m_pendingRequests.end()) {
- // WebContents has been destroyed. Don't need to do anything.
- return;
- }
-
- RequestsQueue &queue(it->second);
- if (queue.empty())
- return;
-
- content::MediaResponseCallback callback = queue.front().callback;
- queue.pop_front();
-
- callback.Run(devices, devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE : content::MEDIA_DEVICE_OK, std::move(ui));
-}
-
void MediaCaptureDevicesDispatcher::enqueueMediaAccessRequest(content::WebContents *webContents, const content::MediaStreamRequest &request
,const content::MediaResponseCallback &callback)
{
diff --git a/src/core/media_capture_devices_dispatcher.h b/src/core/media_capture_devices_dispatcher.h
index c378c327e..579d159a4 100644
--- a/src/core/media_capture_devices_dispatcher.h
+++ b/src/core/media_capture_devices_dispatcher.h
@@ -116,8 +116,6 @@ class MediaCaptureDevicesDispatcher : public content::MediaObserver,
// Helpers for ProcessMediaAccessRequest().
void processDesktopCaptureAccessRequest(content::WebContents *, const content::MediaStreamRequest &, const content::MediaResponseCallback &);
- void processScreenCaptureAccessRequest(content::WebContents *,const content::MediaStreamRequest &, const content::MediaResponseCallback &);
- void handleScreenCaptureAccessRequest(content::WebContents *, bool userAccepted, const base::string16 &/*unused callback_input*/);
void enqueueMediaAccessRequest(content::WebContents *, const content::MediaStreamRequest &, const content::MediaResponseCallback &);
void ProcessQueuedAccessRequest(content::WebContents *);
diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h
index 8d75f24b7..8b7365342 100644
--- a/src/core/web_contents_adapter_client.h
+++ b/src/core/web_contents_adapter_client.h
@@ -316,6 +316,8 @@ public:
MediaNone = 0,
MediaAudioCapture = 0x01,
MediaVideoCapture = 0x02,
+ MediaDesktopAudioCapture = 0x04,
+ MediaDesktopVideoCapture = 0x08
};
Q_DECLARE_FLAGS(MediaRequestFlags, MediaRequestFlag)
diff --git a/src/webengine/api/qquickwebengineview.cpp b/src/webengine/api/qquickwebengineview.cpp
index c5c772411..09f22c280 100644
--- a/src/webengine/api/qquickwebengineview.cpp
+++ b/src/webengine/api/qquickwebengineview.cpp
@@ -703,8 +703,13 @@ void QQuickWebEngineViewPrivate::runMediaAccessPermissionRequest(const QUrl &sec
feature = QQuickWebEngineView::MediaAudioVideoCapture;
else if (requestFlags.testFlag(WebContentsAdapterClient::MediaAudioCapture))
feature = QQuickWebEngineView::MediaAudioCapture;
- else // WebContentsAdapterClient::MediaVideoCapture
+ else if (requestFlags.testFlag(WebContentsAdapterClient::MediaVideoCapture))
feature = QQuickWebEngineView::MediaVideoCapture;
+ else if (requestFlags.testFlag(WebContentsAdapterClient::MediaDesktopAudioCapture) &&
+ requestFlags.testFlag(WebContentsAdapterClient::MediaDesktopVideoCapture))
+ feature = QQuickWebEngineView::DesktopAudioVideoCapture;
+ else // if (requestFlags.testFlag(WebContentsAdapterClient::MediaDesktopVideoCapture))
+ feature = QQuickWebEngineView::DesktopVideoCapture;
Q_EMIT q->featurePermissionRequested(securityOrigin, feature);
}
@@ -1431,7 +1436,8 @@ void QQuickWebEngineView::grantFeaturePermission(const QUrl &securityOrigin, QQu
{
if (!d_ptr->adapter)
return;
- if (!granted && feature >= MediaAudioCapture && feature <= MediaAudioVideoCapture) {
+ if (!granted && ((feature >= MediaAudioCapture && feature <= MediaAudioVideoCapture) ||
+ (feature >= DesktopVideoCapture && feature <= DesktopAudioVideoCapture))) {
d_ptr->adapter->grantMediaAccessPermission(securityOrigin, WebContentsAdapterClient::MediaNone);
return;
}
@@ -1449,6 +1455,16 @@ void QQuickWebEngineView::grantFeaturePermission(const QUrl &securityOrigin, QQu
case Geolocation:
d_ptr->adapter->runGeolocationRequestCallback(securityOrigin, granted);
break;
+ case DesktopVideoCapture:
+ d_ptr->adapter->grantMediaAccessPermission(securityOrigin, WebContentsAdapterClient::MediaDesktopVideoCapture);
+ break;
+ case DesktopAudioVideoCapture:
+ d_ptr->adapter->grantMediaAccessPermission(
+ securityOrigin,
+ WebContentsAdapterClient::MediaRequestFlags(
+ WebContentsAdapterClient::MediaDesktopAudioCapture |
+ WebContentsAdapterClient::MediaDesktopVideoCapture));
+ break;
default:
Q_UNREACHABLE();
}
diff --git a/src/webengine/api/qquickwebengineview_p.h b/src/webengine/api/qquickwebengineview_p.h
index 32419329a..ae0523460 100644
--- a/src/webengine/api/qquickwebengineview_p.h
+++ b/src/webengine/api/qquickwebengineview_p.h
@@ -199,7 +199,9 @@ public:
MediaAudioCapture,
MediaVideoCapture,
MediaAudioVideoCapture,
- Geolocation
+ Geolocation,
+ DesktopVideoCapture,
+ DesktopAudioVideoCapture
};
Q_ENUM(Feature)
diff --git a/src/webengine/doc/src/webengineview.qdoc b/src/webengine/doc/src/webengineview.qdoc
index e452746d9..1d1c61f03 100644
--- a/src/webengine/doc/src/webengineview.qdoc
+++ b/src/webengine/doc/src/webengineview.qdoc
@@ -850,6 +850,11 @@
Video devices, such as cameras.
\value WebEngineView.MediaAudioVideoCapture
Both audio and video capture devices.
+ \value DesktopVideoCapture
+ Video output capture, that is, the capture of the user's display.
+ (Added in Qt 5.10)
+ \value DesktopAudioVideoCapture
+ Both audio and video output capture. (Added in Qt 5.10)
\sa featurePermissionRequested(), grantFeaturePermission()
*/
diff --git a/src/webengine/plugin/plugin.cpp b/src/webengine/plugin/plugin.cpp
index e58b2ab61..5ab792699 100644
--- a/src/webengine/plugin/plugin.cpp
+++ b/src/webengine/plugin/plugin.cpp
@@ -85,6 +85,7 @@ public:
qmlRegisterType<QQuickWebEngineView, 3>(uri, 1, 3, "WebEngineView");
qmlRegisterType<QQuickWebEngineView, 4>(uri, 1, 4, "WebEngineView");
qmlRegisterType<QQuickWebEngineView, 5>(uri, 1, 5, "WebEngineView");
+ qmlRegisterType<QQuickWebEngineView, 6>(uri, 1, 6, "WebEngineView");
qmlRegisterType<QQuickWebEngineProfile>(uri, 1, 1, "WebEngineProfile");
qmlRegisterType<QQuickWebEngineProfile, 1>(uri, 1, 2, "WebEngineProfile");
qmlRegisterType<QQuickWebEngineProfile, 2>(uri, 1, 3, "WebEngineProfile");
diff --git a/src/webengine/plugin/plugins.qmltypes b/src/webengine/plugin/plugins.qmltypes
index 0cfa2d25b..321c462b5 100644
--- a/src/webengine/plugin/plugins.qmltypes
+++ b/src/webengine/plugin/plugins.qmltypes
@@ -576,9 +576,10 @@ Module {
"QtWebEngine/WebEngineView 1.2",
"QtWebEngine/WebEngineView 1.3",
"QtWebEngine/WebEngineView 1.4",
- "QtWebEngine/WebEngineView 1.5"
+ "QtWebEngine/WebEngineView 1.5",
+ "QtWebEngine/WebEngineView 1.6"
]
- exportMetaObjectRevisions: [0, 1, 2, 3, 4, 5]
+ exportMetaObjectRevisions: [0, 1, 2, 3, 4, 5, 6]
Enum {
name: "NavigationRequestAction"
values: {
@@ -633,7 +634,9 @@ Module {
"MediaAudioCapture": 0,
"MediaVideoCapture": 1,
"MediaAudioVideoCapture": 2,
- "Geolocation": 3
+ "Geolocation": 3,
+ "DesktopVideoCapture": 4,
+ "DesktopAudioVideoCapture": 5
}
}
Enum {
@@ -889,6 +892,7 @@ Module {
Property { name: "audioMuted"; revision: 3; type: "bool" }
Property { name: "recentlyAudible"; revision: 3; type: "bool"; isReadonly: true }
Property { name: "webChannelWorld"; revision: 3; type: "uint" }
+ Property { name: "testSupport"; type: "QQuickWebEngineTestSupport"; isPointer: true }
Signal {
name: "loadingChanged"
Parameter { name: "loadRequest"; type: "QQuickWebEngineLoadRequest"; isPointer: true }
diff --git a/src/webenginewidgets/api/qwebenginepage.cpp b/src/webenginewidgets/api/qwebenginepage.cpp
index 87af187e3..5b7fa9df2 100644
--- a/src/webenginewidgets/api/qwebenginepage.cpp
+++ b/src/webenginewidgets/api/qwebenginepage.cpp
@@ -560,16 +560,20 @@ void QWebEnginePagePrivate::showColorDialog(QSharedPointer<ColorChooserControlle
void QWebEnginePagePrivate::runMediaAccessPermissionRequest(const QUrl &securityOrigin, WebContentsAdapterClient::MediaRequestFlags requestFlags)
{
Q_Q(QWebEnginePage);
- QWebEnginePage::Feature requestedFeature;
- if (requestFlags.testFlag(WebContentsAdapterClient::MediaAudioCapture) && requestFlags.testFlag(WebContentsAdapterClient::MediaVideoCapture))
- requestedFeature = QWebEnginePage::MediaAudioVideoCapture;
+ QWebEnginePage::Feature feature;
+ if (requestFlags.testFlag(WebContentsAdapterClient::MediaAudioCapture) &&
+ requestFlags.testFlag(WebContentsAdapterClient::MediaVideoCapture))
+ feature = QWebEnginePage::MediaAudioVideoCapture;
else if (requestFlags.testFlag(WebContentsAdapterClient::MediaAudioCapture))
- requestedFeature = QWebEnginePage::MediaAudioCapture;
+ feature = QWebEnginePage::MediaAudioCapture;
else if (requestFlags.testFlag(WebContentsAdapterClient::MediaVideoCapture))
- requestedFeature = QWebEnginePage::MediaVideoCapture;
- else
- return;
- Q_EMIT q->featurePermissionRequested(securityOrigin, requestedFeature);
+ feature = QWebEnginePage::MediaVideoCapture;
+ else if (requestFlags.testFlag(WebContentsAdapterClient::MediaDesktopAudioCapture) &&
+ requestFlags.testFlag(WebContentsAdapterClient::MediaDesktopVideoCapture))
+ feature = QWebEnginePage::DesktopAudioVideoCapture;
+ else // if (requestFlags.testFlag(WebContentsAdapterClient::MediaDesktopVideoCapture))
+ feature = QWebEnginePage::DesktopVideoCapture;
+ Q_EMIT q->featurePermissionRequested(securityOrigin, feature);
}
void QWebEnginePagePrivate::runGeolocationPermissionRequest(const QUrl &securityOrigin)
@@ -1728,37 +1732,58 @@ void QWebEnginePage::setFeaturePermission(const QUrl &securityOrigin, QWebEngine
Q_D(QWebEnginePage);
if (policy == PermissionUnknown)
return;
- WebContentsAdapterClient::MediaRequestFlags flags = WebContentsAdapterClient::MediaNone;
- switch (feature) {
- case MediaAudioVideoCapture:
- case MediaAudioCapture:
- case MediaVideoCapture:
- if (policy != PermissionUnknown) {
- if (policy == PermissionDeniedByUser)
- flags = WebContentsAdapterClient::MediaNone;
- else {
- if (feature == MediaAudioCapture)
- flags = WebContentsAdapterClient::MediaAudioCapture;
- else if (feature == MediaVideoCapture)
- flags = WebContentsAdapterClient::MediaVideoCapture;
- else
- flags = WebContentsAdapterClient::MediaRequestFlags(WebContentsAdapterClient::MediaVideoCapture | WebContentsAdapterClient::MediaAudioCapture);
- }
- d->adapter->grantMediaAccessPermission(securityOrigin, flags);
- }
- d->adapter->grantMediaAccessPermission(securityOrigin, flags);
- break;
- case QWebEnginePage::Geolocation:
- d->adapter->runGeolocationRequestCallback(securityOrigin, (policy == PermissionGrantedByUser) ? true : false);
- break;
- case MouseLock:
- if (policy == PermissionGrantedByUser)
+
+ const WebContentsAdapterClient::MediaRequestFlags audioVideoCaptureFlags(
+ WebContentsAdapterClient::MediaVideoCapture |
+ WebContentsAdapterClient::MediaAudioCapture);
+ const WebContentsAdapterClient::MediaRequestFlags desktopAudioVideoCaptureFlags(
+ WebContentsAdapterClient::MediaDesktopVideoCapture |
+ WebContentsAdapterClient::MediaDesktopAudioCapture);
+
+ if (policy == PermissionGrantedByUser) {
+ switch (feature) {
+ case MediaAudioVideoCapture:
+ d->adapter->grantMediaAccessPermission(securityOrigin, audioVideoCaptureFlags);
+ break;
+ case MediaAudioCapture:
+ d->adapter->grantMediaAccessPermission(securityOrigin, WebContentsAdapterClient::MediaAudioCapture);
+ break;
+ case MediaVideoCapture:
+ d->adapter->grantMediaAccessPermission(securityOrigin, WebContentsAdapterClient::MediaVideoCapture);
+ break;
+ case DesktopAudioVideoCapture:
+ d->adapter->grantMediaAccessPermission(securityOrigin, desktopAudioVideoCaptureFlags);
+ break;
+ case DesktopVideoCapture:
+ d->adapter->grantMediaAccessPermission(securityOrigin, WebContentsAdapterClient::MediaDesktopVideoCapture);
+ break;
+ case Geolocation:
+ d->adapter->runGeolocationRequestCallback(securityOrigin, true);
+ break;
+ case MouseLock:
d->adapter->grantMouseLockPermission(true);
- else
+ break;
+ case Notifications:
+ break;
+ }
+ } else { // if (policy == PermissionDeniedByUser)
+ switch (feature) {
+ case MediaAudioVideoCapture:
+ case MediaAudioCapture:
+ case MediaVideoCapture:
+ case DesktopAudioVideoCapture:
+ case DesktopVideoCapture:
+ d->adapter->grantMediaAccessPermission(securityOrigin, WebContentsAdapterClient::MediaNone);
+ break;
+ case Geolocation:
+ d->adapter->runGeolocationRequestCallback(securityOrigin, false);
+ break;
+ case MouseLock:
d->adapter->grantMouseLockPermission(false);
- break;
- case Notifications:
- break;
+ break;
+ case Notifications:
+ break;
+ }
}
}
diff --git a/src/webenginewidgets/api/qwebenginepage.h b/src/webenginewidgets/api/qwebenginepage.h
index 74ebd0a35..292075827 100644
--- a/src/webenginewidgets/api/qwebenginepage.h
+++ b/src/webenginewidgets/api/qwebenginepage.h
@@ -188,7 +188,9 @@ public:
MediaAudioCapture = 2,
MediaVideoCapture,
MediaAudioVideoCapture,
- MouseLock
+ MouseLock,
+ DesktopVideoCapture,
+ DesktopAudioVideoCapture
};
Q_ENUM(Feature)
diff --git a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc
index 621982951..3b9300a4b 100644
--- a/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc
+++ b/src/webenginewidgets/doc/src/qwebenginepage_lgpl.qdoc
@@ -286,6 +286,11 @@
\value MouseLock
Mouse locking, which locks the mouse pointer to the web view and is typically used in
games.
+ \value DesktopVideoCapture
+ Video output capture, that is, the capture of the user's display,
+ for screen sharing purposes for example. (Added in Qt 5.10)
+ \value DesktopAudioVideoCapture
+ Both audio and video output capture. (Added in Qt 5.10)
\sa featurePermissionRequested(), featurePermissionRequestCanceled(), setFeaturePermission(), PermissionPolicy
diff --git a/tests/auto/quick/qmltests/data/tst_getUserMedia.qml b/tests/auto/quick/qmltests/data/tst_getUserMedia.qml
new file mode 100644
index 000000000..b497542e3
--- /dev/null
+++ b/tests/auto/quick/qmltests/data/tst_getUserMedia.qml
@@ -0,0 +1,201 @@
+/****************************************************************************
+**
+** Copyright (C) 2017 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the QtWebEngine module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL-EXCEPT$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 as published by the Free Software
+** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+import QtQuick 2.2
+import QtTest 1.0
+import QtWebEngine 1.6
+
+TestWebEngineView {
+ id: webEngineView
+
+ settings.screenCaptureEnabled: true
+
+ TestCase {
+ name: "GetUserMedia"
+
+ function init_data() {
+ return [
+ {
+ tag: "device audio",
+ constraints: { audio: true },
+ feature: WebEngineView.MediaAudioCapture,
+ },
+ {
+ tag: "device video",
+ constraints: { video: true },
+ feature: WebEngineView.MediaVideoCapture,
+ },
+ {
+ tag: "device audio+video",
+ constraints: { audio: true, video: true },
+ feature: WebEngineView.MediaAudioVideoCapture,
+ },
+ {
+ tag: "desktop video",
+ constraints: {
+ video: {
+ mandatory: {
+ chromeMediaSource: "desktop"
+ }
+ }
+ },
+ feature: WebEngineView.DesktopVideoCapture,
+ },
+ {
+ tag: "desktop audio+video",
+ constraints: {
+ audio: {
+ mandatory: {
+ chromeMediaSource: "desktop"
+ }
+ },
+ video: {
+ mandatory: {
+ chromeMediaSource: "desktop"
+ }
+ }
+ },
+ feature: WebEngineView.DesktopAudioVideoCapture,
+ }
+ ]
+ }
+
+ function test_getUserMedia(row) {
+ loadSync(Qt.resolvedUrl("test1.html"))
+
+ // 1. Rejecting request on QML side should reject promise on JS side.
+ jsGetUserMedia(row.constraints)
+ tryVerify(function(){ return gotFeatureRequest(row.feature) })
+ rejectPendingRequest()
+ tryVerify(function(){ return !jsPromiseFulfilled() && jsPromiseRejected() })
+
+ // 2. Accepting request on QML side should either fulfill or reject the
+ // Promise on JS side. Due to the potential lack of physical media devices
+ // deeper in the content layer we cannot guarantee that the promise will
+ // always be fulfilled, however in this case an error should be returned to
+ // JS instead of leaving the Promise in limbo.
+ jsGetUserMedia(row.constraints)
+ tryVerify(function(){ return gotFeatureRequest(row.feature) })
+ acceptPendingRequest()
+ tryVerify(function(){ return jsPromiseFulfilled() || jsPromiseRejected() });
+
+ // 3. Media feature permissions are not remembered.
+ jsGetUserMedia(row.constraints);
+ tryVerify(function(){ return gotFeatureRequest(row.feature) })
+ acceptPendingRequest()
+ tryVerify(function(){ return jsPromiseFulfilled() || jsPromiseRejected() });
+ }
+ }
+
+ ////
+ // synchronous loading
+
+ signal loadFinished
+
+ SignalSpy {
+ id: spyOnLoadFinished
+ target: webEngineView
+ signalName: "loadFinished"
+ }
+
+ onLoadingChanged: {
+ if (loadRequest.status == WebEngineLoadRequest.LoadSucceededStatus) {
+ loadFinished()
+ }
+ }
+
+ function loadSync(url) {
+ webEngineView.url = url
+ spyOnLoadFinished.wait()
+ }
+
+ ////
+ // synchronous permission requests
+
+ property variant requestedFeature
+ property variant requestedSecurityOrigin
+
+ onFeaturePermissionRequested: {
+ requestedFeature = feature
+ requestedSecurityOrigin = securityOrigin
+ }
+
+ function gotFeatureRequest(expectedFeature) {
+ return requestedFeature == expectedFeature
+ }
+
+ function acceptPendingRequest() {
+ webEngineView.grantFeaturePermission(requestedSecurityOrigin, requestedFeature, true)
+ requestedFeature = undefined
+ requestedSecurityOrigin = undefined
+ }
+
+ function rejectPendingRequest() {
+ webEngineView.grantFeaturePermission(requestedSecurityOrigin, requestedFeature, false)
+ requestedFeature = undefined
+ requestedSecurityOrigin = undefined
+ }
+
+ ////
+ // synchronous JavaScript evaluation
+
+ signal runJavaScriptFinished(variant result)
+
+ SignalSpy {
+ id: spyOnRunJavaScriptFinished
+ target: webEngineView
+ signalName: "runJavaScriptFinished"
+ }
+
+ function runJavaScriptSync(code) {
+ spyOnRunJavaScriptFinished.clear()
+ runJavaScript(code, runJavaScriptFinished)
+ spyOnRunJavaScriptFinished.wait()
+ return spyOnRunJavaScriptFinished.signalArguments[0][0]
+ }
+
+ ////
+ // JavaScript snippets
+
+ function jsGetUserMedia(constraints) {
+ runJavaScript(
+ "var promiseFulfilled = false;" +
+ "var promiseRejected = false;" +
+ "navigator.mediaDevices.getUserMedia(" + JSON.stringify(constraints) + ")" +
+ ".then(stream => { promiseFulfilled = true})" +
+ ".catch(err => { promiseRejected = true})")
+ }
+
+ function jsPromiseFulfilled() {
+ return runJavaScriptSync("promiseFulfilled")
+ }
+
+ function jsPromiseRejected() {
+ return runJavaScriptSync("promiseRejected")
+ }
+}
diff --git a/tests/auto/quick/qmltests/qmltests.pro b/tests/auto/quick/qmltests/qmltests.pro
index d2c9245bd..39b9d0151 100644
--- a/tests/auto/quick/qmltests/qmltests.pro
+++ b/tests/auto/quick/qmltests/qmltests.pro
@@ -52,6 +52,7 @@ OTHER_FILES += \
$$PWD/data/tst_focusOnNavigation.qml \
$$PWD/data/tst_formValidation.qml \
$$PWD/data/tst_geopermission.qml \
+ $$PWD/data/tst_getUserMedia.qml \
$$PWD/data/tst_inputMethod.qml \
$$PWD/data/tst_javaScriptDialogs.qml \
$$PWD/data/tst_linkHovered.qml \
diff --git a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
index cd80db9a3..231012c65 100644
--- a/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
+++ b/tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp
@@ -125,7 +125,10 @@ private Q_SLOTS:
void userAgentNewlineStripping();
void undoActionHaveCustomText();
void renderWidgetHostViewNotShowTopLevel();
+ void getUserMediaRequest_data();
void getUserMediaRequest();
+ void getUserMediaRequestDesktopAudio();
+ void getUserMediaRequestSettingDisabled();
void savePage();
void crashTests_LazyInitializationOfMainFrame();
@@ -2605,6 +2608,33 @@ public:
: m_gotRequest(false)
{
connect(this, &QWebEnginePage::featurePermissionRequested, this, &GetUserMediaTestPage::onFeaturePermissionRequested);
+
+ // We need to load content from a resource in order for the securityOrigin to be valid.
+ QSignalSpy loadSpy(this, SIGNAL(loadFinished(bool)));
+ load(QUrl("qrc:///resources/content.html"));
+ QTRY_COMPARE(loadSpy.count(), 1);
+ }
+
+ void jsGetUserMedia(const QString & constraints)
+ {
+ runJavaScript(
+ QStringLiteral(
+ "var promiseFulfilled = false;"
+ "var promiseRejected = false;"
+ "navigator.mediaDevices.getUserMedia(%1)"
+ ".then(stream => { promiseFulfilled = true})"
+ ".catch(err => { promiseRejected = true})")
+ .arg(constraints));
+ }
+
+ bool jsPromiseFulfilled()
+ {
+ return evaluateJavaScriptSync(this, QStringLiteral("promiseFulfilled")).toBool();
+ }
+
+ bool jsPromiseRejected()
+ {
+ return evaluateJavaScriptSync(this, QStringLiteral("promiseRejected")).toBool();
}
void rejectPendingRequest()
@@ -2623,6 +2653,11 @@ public:
return m_gotRequest && m_requestedFeature == feature;
}
+ bool gotFeatureRequest() const
+ {
+ return m_gotRequest;
+ }
+
private Q_SLOTS:
void onFeaturePermissionRequested(const QUrl &securityOrigin, QWebEnginePage::Feature feature)
{
@@ -2638,28 +2673,83 @@ private:
};
+void tst_QWebEnginePage::getUserMediaRequest_data()
+{
+ QTest::addColumn<QString>("constraints");
+ QTest::addColumn<QWebEnginePage::Feature>("feature");
+
+ QTest::addRow("device audio")
+ << "{audio: true}" << QWebEnginePage::MediaAudioCapture;
+ QTest::addRow("device video")
+ << "{video: true}" << QWebEnginePage::MediaVideoCapture;
+ QTest::addRow("device audio+video")
+ << "{audio: true, video: true}" << QWebEnginePage::MediaAudioVideoCapture;
+ QTest::addRow("desktop video")
+ << "{video: { mandatory: { chromeMediaSource: 'desktop' }}}"
+ << QWebEnginePage::DesktopVideoCapture;
+ QTest::addRow("desktop audio+video")
+ << "{audio: { mandatory: { chromeMediaSource: 'desktop' }}, video: { mandatory: { chromeMediaSource: 'desktop' }}}"
+ << QWebEnginePage::DesktopAudioVideoCapture;
+}
void tst_QWebEnginePage::getUserMediaRequest()
{
- GetUserMediaTestPage page;
+ QFETCH(QString, constraints);
+ QFETCH(QWebEnginePage::Feature, feature);
- // We need to load content from a resource in order for the securityOrigin to be valid.
- QSignalSpy loadSpy(&page, SIGNAL(loadFinished(bool)));
- page.load(QUrl("qrc:///resources/content.html"));
- QTRY_COMPARE(loadSpy.count(), 1);
+ GetUserMediaTestPage page;
+ page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true);
+
+ // 1. Rejecting request on C++ side should reject promise on JS side.
+ page.jsGetUserMedia(constraints);
+ QTRY_VERIFY(page.gotFeatureRequest(feature));
+ page.rejectPendingRequest();
+ QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected());
+
+ // 2. Accepting request on C++ side should either fulfill or reject the
+ // Promise on JS side. Due to the potential lack of physical media devices
+ // deeper in the content layer we cannot guarantee that the promise will
+ // always be fulfilled, however in this case an error should be returned to
+ // JS instead of leaving the Promise in limbo.
+ page.jsGetUserMedia(constraints);
+ QTRY_VERIFY(page.gotFeatureRequest(feature));
+ page.acceptPendingRequest();
+ QTRY_VERIFY(page.jsPromiseFulfilled() || page.jsPromiseRejected());
- QVERIFY(evaluateJavaScriptSync(&page, QStringLiteral("!!navigator.webkitGetUserMedia")).toBool());
- evaluateJavaScriptSync(&page, QStringLiteral("navigator.webkitGetUserMedia({audio: true}, function() {}, function(){})"));
- QTRY_VERIFY(page.gotFeatureRequest(QWebEnginePage::MediaAudioCapture));
- // Might end up failing due to the lack of physical media devices deeper in the content layer, so the JS callback is not guaranteed to be called,
- // but at least we go through that code path, potentially uncovering failing assertions.
+ // 3. Media feature permissions are not remembered.
+ page.jsGetUserMedia(constraints);
+ QTRY_VERIFY(page.gotFeatureRequest(feature));
page.acceptPendingRequest();
+ QTRY_VERIFY(page.jsPromiseFulfilled() || page.jsPromiseRejected());
+}
+
+void tst_QWebEnginePage::getUserMediaRequestDesktopAudio()
+{
+ GetUserMediaTestPage page;
+ page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, true);
+
+ // Audio-only desktop capture is not supported. JS Promise should be
+ // rejected immediately.
+
+ page.jsGetUserMedia(
+ QStringLiteral("{audio: { mandatory: { chromeMediaSource: 'desktop' }}}"));
+ QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected());
+
+ page.jsGetUserMedia(
+ QStringLiteral("{audio: { mandatory: { chromeMediaSource: 'desktop' }}, video: true}"));
+ QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected());
+}
+
+void tst_QWebEnginePage::getUserMediaRequestSettingDisabled()
+{
+ GetUserMediaTestPage page;
+ page.settings()->setAttribute(QWebEngineSettings::ScreenCaptureEnabled, false);
+
+ // With the setting disabled, the JS Promise should be rejected without
+ // asking for permission first.
- page.runJavaScript(QStringLiteral("errorCallbackCalled = false;"));
- evaluateJavaScriptSync(&page, QStringLiteral("navigator.webkitGetUserMedia({audio: true, video: true}, function() {}, function(){errorCallbackCalled = true;})"));
- QTRY_VERIFY(page.gotFeatureRequest(QWebEnginePage::MediaAudioVideoCapture));
- page.rejectPendingRequest(); // Should always end up calling the error callback in JS.
- QTRY_VERIFY(evaluateJavaScriptSync(&page, QStringLiteral("errorCallbackCalled;")).toBool());
+ page.jsGetUserMedia(QStringLiteral("{video: { mandatory: { chromeMediaSource: 'desktop' }}}"));
+ QTRY_VERIFY(!page.jsPromiseFulfilled() && page.jsPromiseRejected());
}
void tst_QWebEnginePage::savePage()