// 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. // Implements the Chrome Extensions Tab Capture API. #include "chrome/browser/extensions/api/tab_capture/tab_capture_api.h" #include #include #include #include "base/command_line.h" #include "base/strings/stringprintf.h" #include "base/values.h" #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h" #include "chrome/browser/extensions/extension_renderer_state.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sessions/session_id.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" #include "extensions/common/features/feature.h" #include "extensions/common/features/feature_provider.h" #include "extensions/common/features/simple_feature.h" #include "extensions/common/permissions/permissions_data.h" #include "extensions/common/switches.h" using extensions::api::tab_capture::MediaStreamConstraint; namespace TabCapture = extensions::api::tab_capture; namespace GetCapturedTabs = TabCapture::GetCapturedTabs; namespace extensions { namespace { const char kCapturingSameTab[] = "Cannot capture a tab with an active stream."; const char kFindingTabError[] = "Error finding tab to capture."; const char kNoAudioOrVideo[] = "Capture failed. No audio or video requested."; const char kGrantError[] = "Extension has not been invoked for the current page (see activeTab " "permission). Chrome pages cannot be captured."; // Keys/values for media stream constraints. const char kMediaStreamSource[] = "chromeMediaSource"; const char kMediaStreamSourceId[] = "chromeMediaSourceId"; const char kMediaStreamSourceTab[] = "tab"; // Whitelisted extensions that do not check for a browser action grant because // they provide API's. const char* whitelisted_extensions[] = { "enhhojjnijigcajfphajepfemndkmdlo", // Dev "pkedcjkdefgpdelpbcmbmeomcjbeemfm", // Trusted Tester "fmfcbgogabcbclcofgocippekhfcmgfj", // Staging "hfaagokkkhdbgiakmmlclaapfelnkoah", // Canary "F155646B5D1CA545F7E1E4E20D573DFDD44C2540", // Trusted Tester (public) "16CA7A47AAE4BE49B1E75A6B960C3875E945B264" // Release }; } // namespace bool TabCaptureCaptureFunction::RunSync() { scoped_ptr params = TabCapture::Capture::Params::Create(*args_); EXTENSION_FUNCTION_VALIDATE(params.get()); // Figure out the active WebContents and retrieve the needed ids. Browser* target_browser = chrome::FindAnyBrowser( GetProfile(), include_incognito(), chrome::GetActiveDesktop()); if (!target_browser) { error_ = kFindingTabError; return false; } content::WebContents* target_contents = target_browser->tab_strip_model()->GetActiveWebContents(); if (!target_contents) { error_ = kFindingTabError; return false; } const std::string& extension_id = extension()->id(); // Make sure either we have been granted permission to capture through an // extension icon click or our extension is whitelisted. if (!extension()->permissions_data()->HasAPIPermissionForTab( SessionID::IdForTab(target_contents), APIPermission::kTabCaptureForTab) && CommandLine::ForCurrentProcess()->GetSwitchValueASCII( switches::kWhitelistedExtensionID) != extension_id && !SimpleFeature::IsIdInList( extension_id, std::set( whitelisted_extensions, whitelisted_extensions + arraysize(whitelisted_extensions)))) { error_ = kGrantError; return false; } // Create a constraints vector. We will modify all the constraints in this // vector to append our chrome specific constraints. std::vector constraints; bool has_audio = params->options.audio.get() && *params->options.audio.get(); bool has_video = params->options.video.get() && *params->options.video.get(); if (!has_audio && !has_video) { error_ = kNoAudioOrVideo; return false; } if (has_audio) { if (!params->options.audio_constraints.get()) params->options.audio_constraints.reset(new MediaStreamConstraint); constraints.push_back(params->options.audio_constraints.get()); } if (has_video) { if (!params->options.video_constraints.get()) params->options.video_constraints.reset(new MediaStreamConstraint); constraints.push_back(params->options.video_constraints.get()); } // Device id we use for Tab Capture. content::RenderFrameHost* const main_frame = target_contents->GetMainFrame(); // TODO(miu): We should instead use a "randomly generated device ID" scheme, // like that employed by the desktop capture API. http://crbug.com/163100 const std::string device_id = base::StringPrintf( "web-contents-media-stream://%i:%i", main_frame->GetProcess()->GetID(), main_frame->GetRoutingID()); // Append chrome specific tab constraints. for (std::vector::iterator it = constraints.begin(); it != constraints.end(); ++it) { base::DictionaryValue* constraint = &(*it)->mandatory.additional_properties; constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab); constraint->SetString(kMediaStreamSourceId, device_id); } extensions::TabCaptureRegistry* registry = extensions::TabCaptureRegistry::Get(GetProfile()); if (!registry->AddRequest(target_contents, extension_id)) { error_ = kCapturingSameTab; return false; } // Copy the result from our modified input parameters. This will be // intercepted by custom bindings which will build and send the special // WebRTC user media request. base::DictionaryValue* result = new base::DictionaryValue(); result->MergeDictionary(params->options.ToValue().get()); SetResult(result); return true; } bool TabCaptureGetCapturedTabsFunction::RunSync() { extensions::TabCaptureRegistry* registry = extensions::TabCaptureRegistry::Get(GetProfile()); base::ListValue* const list = new base::ListValue(); if (registry) registry->GetCapturedTabs(extension()->id(), list); SetResult(list); return true; } } // namespace extensions