diff options
author | Jüri Valdmann <juri.valdmann@qt.io> | 2017-05-26 16:27:11 +0200 |
---|---|---|
committer | Kai Koehne <kai.koehne@qt.io> | 2017-07-17 06:29:56 +0000 |
commit | 69b7942bff3b2fce06aee5ada793fbe46f82bb8c (patch) | |
tree | 76e12557b57ecf7a863faf82283a43607e68c415 /tests | |
parent | 4fafb2c04b671573bd6aabbf293ced84f14f9745 (diff) |
Cleanup support for desktop capture
- Use feature permissions system instead of hard-coded dialog.
- Add QML test for getUserMedia() and extend existing widgets test.
Task-number: QTBUG-60832
Change-Id: I533bed5021b3b0ee199b8abc6ddbd516cbd14ff6
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'tests')
-rw-r--r-- | tests/auto/quick/qmltests/data/tst_getUserMedia.qml | 201 | ||||
-rw-r--r-- | tests/auto/quick/qmltests/qmltests.pro | 1 | ||||
-rw-r--r-- | tests/auto/widgets/qwebenginepage/tst_qwebenginepage.cpp | 120 |
3 files changed, 307 insertions, 15 deletions
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() |