diff options
-rw-r--r-- | src/core/chrome_qt.gyp | 18 | ||||
-rw-r--r-- | src/core/content_browser_client_qt.cpp | 7 | ||||
-rw-r--r-- | src/core/content_browser_client_qt.h | 1 | ||||
-rw-r--r-- | src/core/core_gyp_generator.pro | 2 | ||||
-rw-r--r-- | src/core/media_capture_devices_dispatcher.cpp | 511 | ||||
-rw-r--r-- | src/core/media_capture_devices_dispatcher.h | 159 | ||||
-rw-r--r-- | src/core/qtwebengine.gypi | 1 | ||||
-rw-r--r-- | src/core/web_contents_adapter.cpp | 7 | ||||
-rw-r--r-- | src/core/web_contents_adapter.h | 1 | ||||
-rw-r--r-- | src/core/web_contents_adapter_client.h | 9 | ||||
-rw-r--r-- | src/core/web_contents_delegate_qt.cpp | 6 | ||||
-rw-r--r-- | src/core/web_contents_delegate_qt.h | 1 | ||||
-rw-r--r-- | src/webengine/api/qquickwebengineview_p_p.h | 1 | ||||
-rw-r--r-- | src/webenginewidgets/api/qwebenginepage_p.h | 1 |
14 files changed, 725 insertions, 0 deletions
diff --git a/src/core/chrome_qt.gyp b/src/core/chrome_qt.gyp new file mode 100644 index 000000000..a38b4a9bf --- /dev/null +++ b/src/core/chrome_qt.gyp @@ -0,0 +1,18 @@ +{ + 'targets': [ + { + 'target_name': 'chrome_qt', + 'type': 'static_library', + 'include_dirs': [ + '<(chromium_src_dir)', + '<(chromium_src_dir)/skia/config', + ], + 'sources': [ + '<(chromium_src_dir)/chrome/browser/media/desktop_streams_registry.h', + '<(chromium_src_dir)/chrome/browser/media/desktop_streams_registry.cc', + '<(chromium_src_dir)/chrome/browser/media/desktop_media_list.h', + ], + } + ], +} + diff --git a/src/core/content_browser_client_qt.cpp b/src/core/content_browser_client_qt.cpp index 86ff11efb..8945ad0e5 100644 --- a/src/core/content_browser_client_qt.cpp +++ b/src/core/content_browser_client_qt.cpp @@ -45,6 +45,7 @@ #include "base/threading/thread_restrictions.h" #include "content/public/browser/browser_main_parts.h" #include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/media_observer.h" #include "content/public/browser/resource_dispatcher_host.h" #include "content/public/common/main_function_params.h" #include "content/public/common/url_constants.h" @@ -54,6 +55,7 @@ #include "browser_context_qt.h" #include "dev_tools_http_handler_delegate_qt.h" +#include "media_capture_devices_dispatcher.h" #include "resource_dispatcher_host_delegate_qt.h" #include "web_contents_view_qt.h" @@ -310,6 +312,11 @@ gfx::GLShareGroup *ContentBrowserClientQt::GetInProcessGpuShareGroup() return m_shareGroupQtQuick.get(); } +content::MediaObserver *ContentBrowserClientQt::GetMediaObserver() +{ + return MediaCaptureDevicesDispatcher::GetInstance(); +} + BrowserContextQt* ContentBrowserClientQt::browser_context() { Q_ASSERT(m_browserMainParts); return static_cast<BrowserMainPartsQt*>(m_browserMainParts)->browser_context(); diff --git a/src/core/content_browser_client_qt.h b/src/core/content_browser_client_qt.h index c8afc633a..f511eb934 100644 --- a/src/core/content_browser_client_qt.h +++ b/src/core/content_browser_client_qt.h @@ -82,6 +82,7 @@ public: virtual void RenderProcessHostCreated(content::RenderProcessHost* host) Q_DECL_OVERRIDE; virtual void ResourceDispatcherHostCreated() Q_DECL_OVERRIDE; virtual gfx::GLShareGroup* GetInProcessGpuShareGroup() Q_DECL_OVERRIDE; + virtual content::MediaObserver* GetMediaObserver(); BrowserContextQt* browser_context(); diff --git a/src/core/core_gyp_generator.pro b/src/core/core_gyp_generator.pro index be9bf87c0..c76c707a5 100644 --- a/src/core/core_gyp_generator.pro +++ b/src/core/core_gyp_generator.pro @@ -45,6 +45,7 @@ SOURCES = \ gl_context_qt.cpp \ javascript_dialog_controller.cpp \ javascript_dialog_manager_qt.cpp \ + media_capture_devices_dispatcher.cpp \ process_main.cpp \ qt_render_view_observer_host.cpp \ render_widget_host_view_qt.cpp \ @@ -85,6 +86,7 @@ HEADERS = \ javascript_dialog_controller_p.h \ javascript_dialog_controller.h \ javascript_dialog_manager_qt.h \ + media_capture_devices_dispatcher.h \ process_main.h \ qt_render_view_observer_host.h \ render_widget_host_view_qt.h \ diff --git a/src/core/media_capture_devices_dispatcher.cpp b/src/core/media_capture_devices_dispatcher.cpp new file mode 100644 index 000000000..2997576ff --- /dev/null +++ b/src/core/media_capture_devices_dispatcher.cpp @@ -0,0 +1,511 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +#include "media_capture_devices_dispatcher.h" + +#include "javascript_dialog_manager_qt.h" +#include "type_conversion.h" +#include "web_contents_view_qt.h" + +#include "base/strings/utf_string_conversions.h" +#include "chrome/browser/media/desktop_streams_registry.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/desktop_media_id.h" +#include "content/public/browser/media_devices_monitor.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/media_stream_request.h" +#include "media/audio/audio_manager_base.h" +#include "ui/base/l10n/l10n_util.h" + +using content::BrowserThread; +using content::MediaStreamDevices; + +namespace { + +const content::MediaStreamDevice *findDeviceWithId(const content::MediaStreamDevices &devices, const std::string &deviceId) +{ + content::MediaStreamDevices::const_iterator iter = devices.begin(); + for (; iter != devices.end(); ++iter) { + if (iter->id == deviceId) { + return &(*iter); + } + } + return 0; +} + +base::string16 getContentsUrl(content::WebContents *webContents) +{ + return UTF8ToUTF16(webContents->GetURL().GetOrigin().spec()); +} + +scoped_ptr<content::MediaStreamUI> getDevicesForDesktopCapture(content::MediaStreamDevices &devices, content::DesktopMediaID mediaId + , bool captureAudio, bool /*display_notification*/, base::string16 /*application_title*/) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + scoped_ptr<content::MediaStreamUI> ui; + + // Add selected desktop source to the list. + devices.push_back(content::MediaStreamDevice( + content::MEDIA_DESKTOP_VIDEO_CAPTURE, mediaId.ToString(), "Screen")); + if (captureAudio) { + // Use the special loopback device ID for system audio capture. + devices.push_back(content::MediaStreamDevice( + content::MEDIA_LOOPBACK_AUDIO_CAPTURE, + media::AudioManagerBase::kLoopbackInputDeviceId, "System Audio")); + } + + return ui.Pass(); +} + +WebContentsAdapterClient::MediaRequestFlags mediaRequestFlagsForRequest(const content::MediaStreamRequest &request) +{ + WebContentsAdapterClient::MediaRequestFlags requestFlags; + if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) + requestFlags |= WebContentsAdapterClient::MediaAudioCapture; + if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) + requestFlags |= WebContentsAdapterClient::MediaVideoCapture; + return requestFlags; +} + +} // namespace + +MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(const content::MediaStreamRequest &request + , const content::MediaResponseCallback &callback) + : request(request) + , callback(callback) +{ +} + +MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() +{ +} + + +void MediaCaptureDevicesDispatcher::handleMediaAccessPermissionResponse(content::WebContents *webContents, const QUrl &securityOrigin + , WebContentsAdapterClient::MediaRequestFlags authorizationFlags) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + content::MediaStreamDevices devices; + 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::MediaStreamRequest &request = queue.front().request; + + const QUrl requestSecurityOrigin(toQt(request.security_origin)); + bool securityOriginsMatch = (requestSecurityOrigin.host() == securityOrigin.host() + && requestSecurityOrigin.scheme() == securityOrigin.scheme() + && requestSecurityOrigin.port() == securityOrigin.port()); + if (!securityOriginsMatch) + qWarning("Security origin mismatch for media access permission: %s requested and %s provided\n", qPrintable(requestSecurityOrigin.toString()) + , qPrintable(securityOrigin.toString())); + + + bool microphoneRequested = + (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: + Q_UNREACHABLE(); // only speculative as this is for Pepper + getDefaultDevices("", "", microphoneRequested, webcamRequested, &devices); + break; + case content::MEDIA_DEVICE_ACCESS: + case content::MEDIA_GENERATE_STREAM: + case content::MEDIA_ENUMERATE_DEVICES: + getDefaultDevices(request.requested_audio_device_id, request.requested_video_device_id, + microphoneRequested, webcamRequested, &devices); + break; + } + } + + content::MediaResponseCallback callback = queue.front().callback; + queue.pop_front(); + + if (!queue.empty()) { + // Post a task to process next queued request. It has to be done + // asynchronously to make sure that calling infobar is not destroyed until + // after this function returns. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest, base::Unretained(this), webContents)); + } + + callback.Run(devices, scoped_ptr<content::MediaStreamUI>()); +} + + + +MediaCaptureDevicesDispatcher *MediaCaptureDevicesDispatcher::GetInstance() +{ + return Singleton<MediaCaptureDevicesDispatcher>::get(); +} + +MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher() + : m_devicesEnumerated(false) +{ + // MediaCaptureDevicesDispatcher is a singleton. It should be created on + // UI thread. Otherwise, it will not receive + // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in + // possible use after free. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + m_notificationsRegistrar.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, + content::NotificationService::AllSources()); +} + +MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() +{ +} + +const MediaStreamDevices &MediaCaptureDevicesDispatcher::getAudioCaptureDevices() +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!m_devicesEnumerated) { + content::EnsureMonitorCaptureDevices(); + m_devicesEnumerated = true; + } + return m_audioDevices; +} + +const MediaStreamDevices &MediaCaptureDevicesDispatcher::getVideoCaptureDevices() +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!m_devicesEnumerated) { + content::EnsureMonitorCaptureDevices(); + m_devicesEnumerated = true; + } + return m_videoDevices; +} + +void MediaCaptureDevicesDispatcher::Observe(int type, const content::NotificationSource &source, const content::NotificationDetails &details) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { + content::WebContents *webContents = content::Source<content::WebContents>(source).ptr(); + m_pendingRequests.erase(webContents); + } +} + +void MediaCaptureDevicesDispatcher::processMediaAccessRequest(WebContentsAdapterClient *adapterClient, content::WebContents *webContents + , const content::MediaStreamRequest &request + , const content::MediaResponseCallback &callback) +{ + 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_LOOPBACK_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)); + } + +} + +void MediaCaptureDevicesDispatcher::processDesktopCaptureAccessRequest(content::WebContents *webContents, const content::MediaStreamRequest &request + , const content::MediaResponseCallback &callback) +{ + content::MediaStreamDevices devices; + scoped_ptr<content::MediaStreamUI> ui; + + if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) { + callback.Run(devices, ui.Pass()); + 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; + } + + // Resolve DesktopMediaID for the specified device id. + content::DesktopMediaID mediaId = + getDesktopStreamsRegistry()->RequestMediaForStreamId( + request.requested_video_device_id, request.render_process_id, + request.render_view_id, request.security_origin); + + // Received invalid device id. + if (mediaId.type == content::DesktopMediaID::TYPE_NONE) { + callback.Run(devices, ui.Pass()); + return; + } + + // Audio is only supported for screen capture streams. + bool capture_audio = (mediaId.type == content::DesktopMediaID::TYPE_SCREEN && + request.audio_type == content::MEDIA_LOOPBACK_AUDIO_CAPTURE); + + ui = getDevicesForDesktopCapture( + devices, mediaId, capture_audio, true, + getContentsUrl(webContents)); + + callback.Run(devices, ui.Pass()); +} + +void MediaCaptureDevicesDispatcher::processScreenCaptureAccessRequest(content::WebContents *webContents, const content::MediaStreamRequest &request + ,const content::MediaResponseCallback &callback) +{ + DCHECK_EQ(request.video_type, content::MEDIA_DESKTOP_VIDEO_CAPTURE); + + // FIXME: expose through the settings once we have them + const bool screenCaptureEnabled = !qgetenv("QT_WEBENGINE_USE_EXPERIMENTAL_SCREEN_CAPTURE").isNull(); + + const bool originIsSecure = request.security_origin.SchemeIsSecure(); + + 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 = QObject::tr("Do you want %1 to share your screen?").arg(securityOrigin.toString()); + QString title = QObject::tr("%1 Screen Sharing request").arg(securityOrigin.toString()); + JavaScriptDialogManagerQt::GetInstance()->runDialogForContents(webContents, WebContentsAdapterClient::InternalAuthorizationDialog, message + , QString(), securityOrigin, dialogCallback, title); + } else + callback.Run(content::MediaStreamDevices(), scoped_ptr<content::MediaStreamUI>()); +} + +void MediaCaptureDevicesDispatcher::handleScreenCaptureAccessRequest(content::WebContents *webContents, bool userAccepted, const base::string16 &) +{ + content::MediaStreamDevices devices; + scoped_ptr<content::MediaStreamUI> ui; + if (userAccepted) { + content::DesktopMediaID screenId = content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 0); + ui = getDevicesForDesktopCapture(devices, screenId, false/*capture_audio*/, false/*display_notification*/, getContentsUrl(webContents)); + } + 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, ui.Pass()); +} + +void MediaCaptureDevicesDispatcher::enqueueMediaAccessRequest(content::WebContents *webContents, const content::MediaStreamRequest &request + ,const content::MediaResponseCallback &callback) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + RequestsQueue &queue = m_pendingRequests[webContents]; + queue.push_back(PendingAccessRequest(request, callback)); +} + +void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest(content::WebContents *webContents) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + std::map<content::WebContents*, RequestsQueue>::iterator it = + m_pendingRequests.find(webContents); + + if (it == m_pendingRequests.end() || it->second.empty()) + return; + + RequestsQueue &queue(it->second); + if (queue.empty()) + return; + + content::MediaStreamRequest &request = queue.front().request; + + DCHECK(!it->second.empty()); + WebContentsAdapterClient *adapterClient = WebContentsViewQt::from(webContents->GetView())->client(); + adapterClient->runMediaAccessPermissionRequest(toQt(request.security_origin), mediaRequestFlagsForRequest(request)); +} + +void MediaCaptureDevicesDispatcher::getDefaultDevices(const std::string &audioDeviceId, const std::string &videoDeviceId, bool audio, bool video + , content::MediaStreamDevices *devices) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(audio || video); + + if (audio) { + const content::MediaStreamDevices &audioDevices = getAudioCaptureDevices(); + const content::MediaStreamDevice *device = findDeviceWithId(audioDevices, audioDeviceId); + if (!device && !audioDevices.empty()) + device = &(*audioDevices.begin()); + if (device) + devices->push_back(*device); + } + + if (video) { + const content::MediaStreamDevices &videoDevices = getVideoCaptureDevices(); + const content::MediaStreamDevice *device = findDeviceWithId(videoDevices, videoDeviceId); + if (!device && !videoDevices.empty()) + device = &(*videoDevices.begin()); + if (device) + devices->push_back(*device); + } +} + +DesktopStreamsRegistry *MediaCaptureDevicesDispatcher::getDesktopStreamsRegistry() +{ + if (!m_desktopStreamsRegistry) + m_desktopStreamsRegistry.reset(new DesktopStreamsRegistry()); + return m_desktopStreamsRegistry.get(); +} + +void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged(const content::MediaStreamDevices &devices) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&MediaCaptureDevicesDispatcher::updateAudioDevicesOnUIThread, + base::Unretained(this), devices)); +} + +void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged(const content::MediaStreamDevices &devices) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&MediaCaptureDevicesDispatcher::updateVideoDevicesOnUIThread, + base::Unretained(this), devices)); +} + +void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(int renderProcessId, int renderViewId, int pageRequestId + , const content::MediaStreamDevice &device, content::MediaRequestState state) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind( + &MediaCaptureDevicesDispatcher::updateMediaRequestStateOnUIThread, + base::Unretained(this), renderProcessId, renderViewId, + pageRequestId, device, state)); +} + +void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(int /*renderProcessId*/, int /*renderViewId*/) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); +} + +void MediaCaptureDevicesDispatcher::updateAudioDevicesOnUIThread(const content::MediaStreamDevices &devices) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + m_devicesEnumerated = true; + m_audioDevices = devices; +} + +void MediaCaptureDevicesDispatcher::updateVideoDevicesOnUIThread(const content::MediaStreamDevices &devices) +{ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + m_devicesEnumerated = true; + m_videoDevices = devices; +} + +void MediaCaptureDevicesDispatcher::updateMediaRequestStateOnUIThread(int renderProcessId, int renderViewId, int pageRequestId + , const content::MediaStreamDevice &device, content::MediaRequestState state) +{ + // Track desktop capture sessions. Tracking is necessary to avoid unbalanced + // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE, + // but they will all reach MEDIA_REQUEST_STATE_CLOSING. + if (device.type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) { + if (state == content::MEDIA_REQUEST_STATE_DONE) { + DesktopCaptureSession session = { renderProcessId, renderViewId, + pageRequestId }; + m_desktopCaptureSessions.push_back(session); + } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) { + for (DesktopCaptureSessions::iterator it = + m_desktopCaptureSessions.begin(); + it != m_desktopCaptureSessions.end(); + ++it) { + if (it->render_process_id == renderProcessId && + it->render_view_id == renderViewId && + it->page_request_id == pageRequestId) { + m_desktopCaptureSessions.erase(it); + break; + } + } + } + } + + // Cancel the request. + if (state == content::MEDIA_REQUEST_STATE_CLOSING) { + bool found = false; + for (RequestsQueues::iterator rqs_it = m_pendingRequests.begin(); + rqs_it != m_pendingRequests.end(); ++rqs_it) { + RequestsQueue &queue = rqs_it->second; + for (RequestsQueue::iterator it = queue.begin(); + it != queue.end(); ++it) { + if (it->request.render_process_id == renderProcessId && + it->request.render_view_id == renderViewId && + it->request.page_request_id == pageRequestId) { + queue.erase(it); + found = true; + break; + } + } + if (found) + break; + } + } +} diff --git a/src/core/media_capture_devices_dispatcher.h b/src/core/media_capture_devices_dispatcher.h new file mode 100644 index 000000000..38d7b1c19 --- /dev/null +++ b/src/core/media_capture_devices_dispatcher.h @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** Copyright (c) 2012 The Chromium Authors. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtWebEngine module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MEDIA_CAPTURE_DEVICES_DISPATCHER_H +#define MEDIA_CAPTURE_DEVICES_DISPATCHER_H + +#include <deque> +#include <list> +#include <map> +#include <QtCore/qcompilerdetection.h> + +#include "web_contents_adapter_client.h" + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/observer_list.h" +#include "content/public/browser/media_observer.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/common/media_stream_request.h" + +class AudioStreamIndicator; +class DesktopStreamsRegistry; +class MediaStreamCaptureIndicator; + +// This singleton is used to receive updates about media events from the content +// layer. Based on Chrome's implementation. +class MediaCaptureDevicesDispatcher : public content::MediaObserver, + public content::NotificationObserver { + public: + + static MediaCaptureDevicesDispatcher *GetInstance(); + + void processMediaAccessRequest(WebContentsAdapterClient *, content::WebContents *, const content::MediaStreamRequest &, const content::MediaResponseCallback &); + + // Called back from our WebContentsAdapter to grant the requested permission. + void handleMediaAccessPermissionResponse(content::WebContents *, const QUrl &securityOrigin, WebContentsAdapterClient::MediaRequestFlags); + + void getDefaultDevices(const std::string &audioDeviceId, const std::string &videoDeviceId, bool audio, bool video, content::MediaStreamDevices *); + + // Overridden from content::MediaObserver: + virtual void OnAudioCaptureDevicesChanged(const content::MediaStreamDevices &) Q_DECL_OVERRIDE; + virtual void OnVideoCaptureDevicesChanged(const content::MediaStreamDevices &) Q_DECL_OVERRIDE; + virtual void OnMediaRequestStateChanged(int renderProcessId, int renderViewId, int pageRequestId, const content::MediaStreamDevice &device + , content::MediaRequestState state) Q_DECL_OVERRIDE; + virtual void OnAudioStreamPlayingChanged(int /*render_process_id*/, int /*render_view_id*/, int /*stream_id*/ + , bool /*is_playing*/, float /*power_dBFS*/, bool /*clipped*/) Q_DECL_OVERRIDE {} + virtual void OnCreatingAudioStream(int renderProcessId, int renderViewId) Q_DECL_OVERRIDE; + + DesktopStreamsRegistry *getDesktopStreamsRegistry(); + + private: + friend struct DefaultSingletonTraits<MediaCaptureDevicesDispatcher>; + + struct PendingAccessRequest { + PendingAccessRequest(const content::MediaStreamRequest &request, + const content::MediaResponseCallback &callback); + ~PendingAccessRequest(); + + content::MediaStreamRequest request; + content::MediaResponseCallback callback; + }; + typedef std::deque<PendingAccessRequest> RequestsQueue; + typedef std::map<content::WebContents *, RequestsQueue> RequestsQueues; + + MediaCaptureDevicesDispatcher(); + virtual ~MediaCaptureDevicesDispatcher(); + + const content::MediaStreamDevices &getAudioCaptureDevices(); + const content::MediaStreamDevices &getVideoCaptureDevices(); + + // content::NotificationObserver implementation. + virtual void Observe(int type, const content::NotificationSource &source, const content::NotificationDetails &details) Q_DECL_OVERRIDE; + + // 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 *); + + // Called by the MediaObserver() functions, executed on UI thread. + void updateAudioDevicesOnUIThread(const content::MediaStreamDevices &); + void updateVideoDevicesOnUIThread(const content::MediaStreamDevices &); + void updateMediaRequestStateOnUIThread(int renderProcessId, int renderViewId, int pageRequestId, const content::MediaStreamDevice & + , content::MediaRequestState); + + // A list of cached audio capture devices. + content::MediaStreamDevices m_audioDevices; + + // A list of cached video capture devices. + content::MediaStreamDevices m_videoDevices; + + // Flag to indicate if device enumeration has been done/doing. + // Only accessed on UI thread. + bool m_devicesEnumerated; + + RequestsQueues m_pendingRequests; + + scoped_ptr<DesktopStreamsRegistry> m_desktopStreamsRegistry; + + content::NotificationRegistrar m_notificationsRegistrar; + + // Tracks MEDIA_DESKTOP_VIDEO_CAPTURE sessions which reach the + // MEDIA_REQUEST_STATE_DONE state. Sessions are remove when + // MEDIA_REQUEST_STATE_CLOSING is encountered. + struct DesktopCaptureSession { + int render_process_id; + int render_view_id; + int page_request_id; + }; + typedef std::list<DesktopCaptureSession> DesktopCaptureSessions; + DesktopCaptureSessions m_desktopCaptureSessions; + + DISALLOW_COPY_AND_ASSIGN(MediaCaptureDevicesDispatcher); +}; + +#endif // MEDIA_CAPTURE_DEVICES_DISPATCHER_H diff --git a/src/core/qtwebengine.gypi b/src/core/qtwebengine.gypi index fee9d37dd..c01998c59 100644 --- a/src/core/qtwebengine.gypi +++ b/src/core/qtwebengine.gypi @@ -28,6 +28,7 @@ '<(chromium_src_dir)/v8/tools/gyp/v8.gyp:v8', '<(chromium_src_dir)/webkit/glue/webkit_glue.gyp:*', '<(chromium_src_dir)/third_party/WebKit/Source/web/web.gyp:webkit', + 'chrome_qt.gyp:*', ], 'include_dirs': [ '<(chromium_src_dir)', diff --git a/src/core/web_contents_adapter.cpp b/src/core/web_contents_adapter.cpp index ff0b9a83d..11b5f7cc2 100644 --- a/src/core/web_contents_adapter.cpp +++ b/src/core/web_contents_adapter.cpp @@ -48,6 +48,7 @@ #include "browser_context_qt.h" #include "content_browser_client_qt.h" #include "javascript_dialog_manager_qt.h" +#include "media_capture_devices_dispatcher.h" #include "qt_render_view_observer_host.h" #include "type_conversion.h" #include "web_contents_adapter_client.h" @@ -676,6 +677,12 @@ void WebContentsAdapter::wasHidden() d->webContents->WasHidden(); } +void WebContentsAdapter::grantMediaAccessPermission(const QUrl &securityOrigin, WebContentsAdapterClient::MediaRequestFlags flags) +{ + Q_D(WebContentsAdapter); + MediaCaptureDevicesDispatcher::GetInstance()->handleMediaAccessPermissionResponse(d->webContents.get(), securityOrigin, flags); +} + void WebContentsAdapter::dpiScaleChanged() { Q_D(WebContentsAdapter); diff --git a/src/core/web_contents_adapter.h b/src/core/web_contents_adapter.h index fc6ea99fb..b6984933e 100644 --- a/src/core/web_contents_adapter.h +++ b/src/core/web_contents_adapter.h @@ -106,6 +106,7 @@ public: void wasShown(); void wasHidden(); + void grantMediaAccessPermission(const QUrl &securityOrigin, WebContentsAdapterClient::MediaRequestFlags flags); void dpiScaleChanged(); diff --git a/src/core/web_contents_adapter_client.h b/src/core/web_contents_adapter_client.h index 64b5fca38..44668b539 100644 --- a/src/core/web_contents_adapter_client.h +++ b/src/core/web_contents_adapter_client.h @@ -43,6 +43,7 @@ #include "qtwebenginecoreglobal.h" +#include <QFlags> #include <QRect> #include <QSharedPointer> #include <QString> @@ -115,6 +116,13 @@ public: Error }; + enum MediaRequestFlag { + MediaNone = 0, + MediaAudioCapture = 0x01, + MediaVideoCapture = 0x02, + }; + Q_DECLARE_FLAGS(MediaRequestFlags, MediaRequestFlag) + virtual ~WebContentsAdapterClient() { } virtual RenderWidgetHostViewQtDelegate* CreateRenderWidgetHostViewQtDelegate(RenderWidgetHostViewQtDelegateClient *client) = 0; @@ -145,6 +153,7 @@ public: virtual void passOnFocus(bool reverse) = 0; virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID) = 0; virtual void authenticationRequired(const QUrl &requestUrl, const QString &realm, bool isProxy, const QString &challengingHost, QString *outUser, QString *outPassword) = 0; + virtual void runMediaAccessPermissionRequest(const QUrl &securityOrigin, MediaRequestFlags requestFlags) = 0; }; #endif // WEB_CONTENTS_ADAPTER_CLIENT_H diff --git a/src/core/web_contents_delegate_qt.cpp b/src/core/web_contents_delegate_qt.cpp index 6e19fd9ab..756646be6 100644 --- a/src/core/web_contents_delegate_qt.cpp +++ b/src/core/web_contents_delegate_qt.cpp @@ -45,6 +45,7 @@ #include "web_contents_delegate_qt.h" +#include "media_capture_devices_dispatcher.h" #include "type_conversion.h" #include "web_contents_adapter.h" #include "web_contents_adapter_client.h" @@ -235,3 +236,8 @@ void WebContentsDelegateQt::FindReply(content::WebContents *source, int request_ if (final_update) m_viewClient->didFindText(request_id, number_of_matches); } + +void WebContentsDelegateQt::RequestMediaAccessPermission(content::WebContents *web_contents, const content::MediaStreamRequest &request, const content::MediaResponseCallback &callback) +{ + MediaCaptureDevicesDispatcher::GetInstance()->processMediaAccessRequest(m_viewClient, web_contents, request, callback); +} diff --git a/src/core/web_contents_delegate_qt.h b/src/core/web_contents_delegate_qt.h index fe36088eb..255428778 100644 --- a/src/core/web_contents_delegate_qt.h +++ b/src/core/web_contents_delegate_qt.h @@ -80,6 +80,7 @@ public: virtual void RunFileChooser(content::WebContents *, const content::FileChooserParams ¶ms) Q_DECL_OVERRIDE; virtual bool AddMessageToConsole(content::WebContents* source, int32 level, const base::string16& message, int32 line_no, const base::string16& source_id) Q_DECL_OVERRIDE; virtual void FindReply(content::WebContents *source, int request_id, int number_of_matches, const gfx::Rect& selection_rect, int active_match_ordinal, bool final_update) Q_DECL_OVERRIDE; + virtual void RequestMediaAccessPermission(content::WebContents* web_contents, const content::MediaStreamRequest& request, const content::MediaResponseCallback& callback) Q_DECL_OVERRIDE; private: WebContentsAdapterClient *m_viewClient; diff --git a/src/webengine/api/qquickwebengineview_p_p.h b/src/webengine/api/qquickwebengineview_p_p.h index 4cbca6594..cde8d297d 100644 --- a/src/webengine/api/qquickwebengineview_p_p.h +++ b/src/webengine/api/qquickwebengineview_p_p.h @@ -153,6 +153,7 @@ public: virtual void passOnFocus(bool reverse) Q_DECL_OVERRIDE; virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID) Q_DECL_OVERRIDE; virtual void authenticationRequired(const QUrl&, const QString&, bool, const QString&, QString*, QString*) Q_DECL_OVERRIDE { } + virtual void runMediaAccessPermissionRequest(const QUrl &securityOrigin, MediaRequestFlags requestFlags) Q_DECL_OVERRIDE { } void setDevicePixelRatio(qreal); void adoptWebContents(WebContentsAdapter *webContents); diff --git a/src/webenginewidgets/api/qwebenginepage_p.h b/src/webenginewidgets/api/qwebenginepage_p.h index ddc7c51e7..0c4d3b850 100644 --- a/src/webenginewidgets/api/qwebenginepage_p.h +++ b/src/webenginewidgets/api/qwebenginepage_p.h @@ -136,6 +136,7 @@ public: virtual void passOnFocus(bool reverse) Q_DECL_OVERRIDE { Q_UNUSED(reverse); }; virtual void javaScriptConsoleMessage(JavaScriptConsoleMessageLevel level, const QString& message, int lineNumber, const QString& sourceID) Q_DECL_OVERRIDE; virtual void authenticationRequired(const QUrl &requestUrl, const QString &realm, bool isProxy, const QString &challengingHost, QString *outUser, QString *outPassword) Q_DECL_OVERRIDE; + virtual void runMediaAccessPermissionRequest(const QUrl &securityOrigin, MediaRequestFlags requestFlags) Q_DECL_OVERRIDE {}; void updateAction(QWebEnginePage::WebAction) const; void updateNavigationActions(); |