#include "chrome/browser/media/media_capture_devices_dispatcher.h"
+#include "apps/app_window.h"
+#include "apps/app_window_registry.h"
#include "base/command_line.h"
#include "base/logging.h"
+#include "base/metrics/field_trial.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/sha1.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
-#include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
#include "chrome/browser/media/desktop_streams_registry.h"
#include "chrome/browser/media/media_stream_capture_indicator.h"
#include "chrome/browser/media/media_stream_infobar_delegate.h"
#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/screen_capture_notification_ui.h"
#include "chrome/browser/ui/simple_message_box.h"
#include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_version_info.h"
#include "chrome/common/pref_names.h"
-#include "components/user_prefs/pref_registry_syncable.h"
+#include "components/pref_registry/pref_registry_syncable.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/browser/media_capture_devices.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_frame_host.h"
+#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/media_stream_request.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
+#include "extensions/common/permissions/permissions_data.h"
#include "grit/generated_resources.h"
#include "media/audio/audio_manager_base.h"
#include "media/base/media_switches.h"
#include "chrome/browser/media/audio_stream_monitor.h"
#endif // !defined(OS_ANDROID) && !defined(OS_IOS)
+#if defined(ENABLE_EXTENSIONS)
+#include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
+#endif
+
using content::BrowserThread;
using content::MediaCaptureDevices;
using content::MediaStreamDevices;
namespace {
+// A finch experiment to enable the permission bubble for media requests only.
+bool MediaStreamPermissionBubbleExperimentEnabled() {
+ const std::string group =
+ base::FieldTrialList::FindFullName("MediaStreamPermissionBubble");
+ if (group == "enabled")
+ return true;
+
+ return false;
+}
+
// Finds a device in |devices| that has |device_id|, or NULL if not found.
const content::MediaStreamDevice* FindDeviceWithId(
const content::MediaStreamDevices& devices,
}
}
return NULL;
-};
+}
// This is a short-term solution to grant camera and/or microphone access to
// extensions:
extension->id() == "nnckehldicaciogcbchegobnafnjkcne";
}
-// This is a short-term solution to allow testing of the the Screen Capture API
-// with Google Hangouts in M27.
-// TODO(sergeyu): Remove this whitelist as soon as possible.
-bool IsOriginWhitelistedForScreenCapture(const GURL& origin) {
-#if defined(OFFICIAL_BUILD)
- if (// Google Hangouts.
- (origin.SchemeIs("https") &&
- EndsWith(origin.spec(), ".talkgadget.google.com/", true)) ||
- origin.spec() == "https://talkgadget.google.com/" ||
- origin.spec() == "https://plus.google.com/" ||
- origin.spec() == "chrome-extension://pkedcjkdefgpdelpbcmbmeomcjbeemfm/" ||
- origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
- origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
- origin.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/" ||
- origin.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/") {
- return true;
- }
- // Check against hashed origins.
- // TODO(hshi): remove this when trusted tester becomes public.
- const std::string origin_hash = base::SHA1HashString(origin.spec());
- DCHECK_EQ(origin_hash.length(), base::kSHA1Length);
- const std::string hexencoded_origin_hash =
- base::HexEncode(origin_hash.data(), origin_hash.length());
+bool IsBuiltInExtension(const GURL& origin) {
return
- hexencoded_origin_hash == "3C2705BC432E7C51CA8553FDC5BEE873FF2468EE";
-#else
- return false;
-#endif
+ // Feedback Extension.
+ origin.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/";
}
-#if defined(OS_CHROMEOS)
// Returns true of the security origin is associated with casting.
bool IsOriginForCasting(const GURL& origin) {
-#if defined(OFFICIAL_BUILD)
// Whitelisted tab casting extensions.
- if (origin.spec() == "chrome-extension://pkedcjkdefgpdelpbcmbmeomcjbeemfm/" ||
- origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
- origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
- origin.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/") {
- return true;
- }
- // Check against hashed origins.
- // TODO(hshi): remove this when trusted tester becomes public.
- const std::string origin_hash = base::SHA1HashString(origin.spec());
- DCHECK_EQ(origin_hash.length(), base::kSHA1Length);
- const std::string hexencoded_origin_hash =
- base::HexEncode(origin_hash.data(), origin_hash.length());
return
- hexencoded_origin_hash == "3C2705BC432E7C51CA8553FDC5BEE873FF2468EE";
-#else
- return false;
-#endif
+ // Dev
+ origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" ||
+ // Canary
+ origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
+ // Beta (internal)
+ origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
+ // Google Cast Beta
+ origin.spec() == "chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm/" ||
+ // Google Cast Stable
+ origin.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/";
}
-#endif
// Helper to get title of the calling application shown in the screen capture
// notification.
#endif // defined(AUDIO_STREAM_MONITORING)
+#if !defined(OS_ANDROID)
+// Find browser or app window from a given |web_contents|.
+gfx::NativeWindow FindParentWindowForWebContents(
+ content::WebContents* web_contents) {
+ Browser* browser = chrome::FindBrowserWithWebContents(web_contents);
+ if (browser && browser->window())
+ return browser->window()->GetNativeWindow();
+
+ const apps::AppWindowRegistry::AppWindowList& window_list =
+ apps::AppWindowRegistry::Get(
+ web_contents->GetBrowserContext())->app_windows();
+ for (apps::AppWindowRegistry::AppWindowList::const_iterator iter =
+ window_list.begin();
+ iter != window_list.end(); ++iter) {
+ if ((*iter)->web_contents() == web_contents)
+ return (*iter)->GetNativeWindow();
+ }
+
+ return NULL;
+}
+#endif
+
} // namespace
MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
notifications_registrar_.Add(
this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
content::NotificationService::AllSources());
-
- // AVFoundation is used for video/audio device monitoring and video capture in
- // Mac. Experimentally, connect it in Canary and Unkown (developer builds).
-#if defined(OS_MACOSX)
- chrome::VersionInfo::Channel channel = chrome::VersionInfo::GetChannel();
- if (channel == chrome::VersionInfo::CHANNEL_CANARY ||
- channel == chrome::VersionInfo::CHANNEL_UNKNOWN) {
- CommandLine::ForCurrentProcess()->AppendSwitch(
- switches::kEnableAVFoundation);
- }
-#endif
}
MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
// The extension name that the stream is registered with.
std::string original_extension_name;
// Resolve DesktopMediaID for the specified device id.
- content::DesktopMediaID media_id =
- GetDesktopStreamsRegistry()->RequestMediaForStreamId(
- request.requested_video_device_id, request.render_process_id,
- request.render_view_id, request.security_origin,
- &original_extension_name);
+ content::DesktopMediaID media_id;
+ // TODO(miu): Replace "main RenderFrame" IDs with the request's actual
+ // RenderFrame IDs once the desktop capture extension API implementation is
+ // fixed. http://crbug.com/304341
+ 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;
+ if (main_frame) {
+ media_id = GetDesktopStreamsRegistry()->RequestMediaForStreamId(
+ request.requested_video_device_id,
+ main_frame->GetProcess()->GetID(),
+ main_frame->GetRoutingID(),
+ request.security_origin,
+ &original_extension_name);
+ }
// Received invalid device id.
if (media_id.type == content::DesktopMediaID::TYPE_NONE) {
const bool screen_capture_enabled =
CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableUserMediaScreenCapturing) ||
- IsOriginWhitelistedForScreenCapture(request.security_origin);
+ IsOriginForCasting(request.security_origin) ||
+ IsBuiltInExtension(request.security_origin);
const bool origin_is_secure =
request.security_origin.SchemeIsSecure() ||
// is closed. See http://crbug.com/326690.
base::string16 application_title =
GetApplicationTitle(web_contents, extension);
+#if !defined(OS_ANDROID)
+ gfx::NativeWindow parent_window =
+ FindParentWindowForWebContents(web_contents);
+#else
+ gfx::NativeWindow parent_window = NULL;
+#endif
web_contents = NULL;
// For component extensions, bypass message box.
IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT,
application_name);
chrome::MessageBoxResult result = chrome::ShowMessageBox(
- NULL,
+ parent_window,
l10n_util::GetStringFUTF16(
IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name),
confirmation_text,
content::MediaStreamDevices devices;
scoped_ptr<content::MediaStreamUI> ui;
-#if defined(OS_ANDROID)
- // Tab capture is not supported on Android.
- callback.Run(devices, content::MEDIA_DEVICE_TAB_CAPTURE_FAILURE, ui.Pass());
-#else // defined(OS_ANDROID)
+#if defined(ENABLE_EXTENSIONS)
Profile* profile =
Profile::FromBrowserContext(web_contents->GetBrowserContext());
extensions::TabCaptureRegistry* tab_capture_registry =
callback.Run(devices, content::MEDIA_DEVICE_INVALID_STATE, ui.Pass());
return;
}
- bool tab_capture_allowed =
- tab_capture_registry->VerifyRequest(request.render_process_id,
- request.render_view_id);
+ const bool tab_capture_allowed = tab_capture_registry->VerifyRequest(
+ request.render_process_id, request.render_frame_id, extension->id());
if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE &&
tab_capture_allowed &&
- extension->HasAPIPermission(extensions::APIPermission::kTabCapture)) {
+ extension->permissions_data()->HasAPIPermission(
+ extensions::APIPermission::kTabCapture)) {
devices.push_back(content::MediaStreamDevice(
content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string()));
}
if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE &&
tab_capture_allowed &&
- extension->HasAPIPermission(extensions::APIPermission::kTabCapture)) {
+ extension->permissions_data()->HasAPIPermission(
+ extensions::APIPermission::kTabCapture)) {
devices.push_back(content::MediaStreamDevice(
content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string()));
}
devices.empty() ? content::MEDIA_DEVICE_INVALID_STATE :
content::MEDIA_DEVICE_OK,
ui.Pass());
-#endif // !defined(OS_ANDROID)
+#else // defined(ENABLE_EXTENSIONS)
+ callback.Run(devices, content::MEDIA_DEVICE_TAB_CAPTURE_FAILURE, ui.Pass());
+#endif // defined(ENABLE_EXTENSIONS)
}
void MediaCaptureDevicesDispatcher::
const content::MediaStreamRequest& request,
const content::MediaResponseCallback& callback,
const extensions::Extension* extension) {
+
+ // TODO(vrk): This code is largely duplicated in
+ // MediaStreamDevicesController::Accept(). Move this code into a shared method
+ // between the two classes.
+
+ bool audio_allowed =
+ request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE &&
+ extension->permissions_data()->HasAPIPermission(
+ extensions::APIPermission::kAudioCapture);
+ bool video_allowed =
+ request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE &&
+ extension->permissions_data()->HasAPIPermission(
+ extensions::APIPermission::kVideoCapture);
+
+ bool get_default_audio_device = audio_allowed;
+ bool get_default_video_device = video_allowed;
+
content::MediaStreamDevices devices;
- Profile* profile =
- Profile::FromBrowserContext(web_contents->GetBrowserContext());
- if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE &&
- extension->HasAPIPermission(extensions::APIPermission::kAudioCapture)) {
- GetDefaultDevicesForProfile(profile, true, false, &devices);
+ // Get the exact audio or video device if an id is specified.
+ if (audio_allowed && !request.requested_audio_device_id.empty()) {
+ const content::MediaStreamDevice* audio_device =
+ GetRequestedAudioDevice(request.requested_audio_device_id);
+ if (audio_device) {
+ devices.push_back(*audio_device);
+ get_default_audio_device = false;
+ }
+ }
+ if (video_allowed && !request.requested_video_device_id.empty()) {
+ const content::MediaStreamDevice* video_device =
+ GetRequestedVideoDevice(request.requested_video_device_id);
+ if (video_device) {
+ devices.push_back(*video_device);
+ get_default_video_device = false;
+ }
}
- if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE &&
- extension->HasAPIPermission(extensions::APIPermission::kVideoCapture)) {
- GetDefaultDevicesForProfile(profile, false, true, &devices);
+ // If either or both audio and video devices were requested but not
+ // specified by id, get the default devices.
+ if (get_default_audio_device || get_default_video_device) {
+ Profile* profile =
+ Profile::FromBrowserContext(web_contents->GetBrowserContext());
+ GetDefaultDevicesForProfile(profile,
+ get_default_audio_device,
+ get_default_video_device,
+ &devices);
}
scoped_ptr<content::MediaStreamUI> ui;
DCHECK(!it->second.empty());
- if (PermissionBubbleManager::Enabled()) {
+ if (PermissionBubbleManager::Enabled() ||
+ MediaStreamPermissionBubbleExperimentEnabled()) {
scoped_ptr<MediaStreamDevicesController> controller(
new MediaStreamDevicesController(web_contents,
it->second.front().request,
base::Unretained(this), web_contents)));
if (controller->DismissInfoBarAndTakeActionOnSettings())
return;
- PermissionBubbleManager::FromWebContents(web_contents)->
- AddRequest(controller.release());
+ PermissionBubbleManager* bubble_manager =
+ PermissionBubbleManager::FromWebContents(web_contents);
+ if (bubble_manager)
+ bubble_manager->AddRequest(controller.release());
return;
}
void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
int render_process_id,
- int render_view_id,
+ int render_frame_id,
int page_request_id,
const GURL& security_origin,
- const content::MediaStreamDevice& device,
+ content::MediaStreamType stream_type,
content::MediaRequestState state) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(
&MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread,
- base::Unretained(this), render_process_id, render_view_id,
- page_request_id, security_origin, device, state));
+ base::Unretained(this), render_process_id, render_frame_id,
+ page_request_id, security_origin, stream_type, state));
}
void MediaCaptureDevicesDispatcher::OnAudioStreamPlaying(
void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
int render_process_id,
- int render_view_id,
+ int render_frame_id,
int page_request_id,
const GURL& security_origin,
- const content::MediaStreamDevice& device,
+ content::MediaStreamType stream_type,
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 (stream_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE) {
if (state == content::MEDIA_REQUEST_STATE_DONE) {
- DesktopCaptureSession session = { render_process_id, render_view_id,
+ DesktopCaptureSession session = { render_process_id, render_frame_id,
page_request_id };
desktop_capture_sessions_.push_back(session);
} else if (state == content::MEDIA_REQUEST_STATE_CLOSING) {
it != desktop_capture_sessions_.end();
++it) {
if (it->render_process_id == render_process_id &&
- it->render_view_id == render_view_id &&
+ it->render_frame_id == render_frame_id &&
it->page_request_id == page_request_id) {
desktop_capture_sessions_.erase(it);
break;
for (RequestsQueue::iterator it = queue.begin();
it != queue.end(); ++it) {
if (it->request.render_process_id == render_process_id &&
- it->request.render_view_id == render_view_id &&
+ it->request.render_frame_id == render_frame_id &&
it->request.page_request_id == page_request_id) {
queue.erase(it);
found = true;
}
#if defined(OS_CHROMEOS)
- if (IsOriginForCasting(security_origin) && IsVideoMediaType(device.type)) {
+ if (IsOriginForCasting(security_origin) && IsVideoMediaType(stream_type)) {
// Notify ash that casting state has changed.
if (state == content::MEDIA_REQUEST_STATE_DONE) {
ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true);
FOR_EACH_OBSERVER(Observer, observers_,
OnRequestUpdate(render_process_id,
- render_view_id,
- device,
+ render_frame_id,
+ stream_type,
state));
}