// Copyright 2015 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 "extensions/renderer/display_source_custom_bindings.h" #include #include "base/bind.h" #include "content/public/child/v8_value_converter.h" #include "extensions/renderer/script_context.h" #include "third_party/WebKit/public/platform/WebMediaStream.h" #include "third_party/WebKit/public/platform/WebMediaStreamTrack.h" #include "third_party/WebKit/public/web/WebDOMMediaStreamTrack.h" #include "v8/include/v8.h" namespace extensions { using content::V8ValueConverter; namespace { const char kErrorNotSupported[] = "Not supported"; const char kInvalidStreamArgs[] = "Invalid stream arguments"; const char kSessionAlreadyStarted[] = "The session has been already started"; const char kSessionAlreadyTerminating[] = "The session is already terminating"; const char kSessionNotFound[] = "Session not found"; } // namespace DisplaySourceCustomBindings::DisplaySourceCustomBindings(ScriptContext* context) : ObjectBackedNativeHandler(context), weak_factory_(this) { RouteFunction("StartSession", base::Bind(&DisplaySourceCustomBindings::StartSession, weak_factory_.GetWeakPtr())); RouteFunction("TerminateSession", base::Bind(&DisplaySourceCustomBindings::TerminateSession, weak_factory_.GetWeakPtr())); } DisplaySourceCustomBindings::~DisplaySourceCustomBindings() { } void DisplaySourceCustomBindings::Invalidate() { session_map_.clear(); weak_factory_.InvalidateWeakPtrs(); ObjectBackedNativeHandler::Invalidate(); } namespace { v8::Local GetChildValue(v8::Local value, const std::string& key_name, v8::Isolate* isolate) { v8::Local property_names(value->GetOwnPropertyNames()); for (uint32_t i = 0; i < property_names->Length(); ++i) { v8::Local key(property_names->Get(i)); if (key_name == *v8::String::Utf8Value(key)) { v8::TryCatch try_catch(isolate); v8::Local child_v8 = value->Get(key); if (try_catch.HasCaught()) { return v8::Null(isolate); } return child_v8; } } return v8::Null(isolate); } } // namespace void DisplaySourceCustomBindings::StartSession( const v8::FunctionCallbackInfo& args) { CHECK_EQ(2, args.Length()); CHECK(args[0]->IsObject()); CHECK(args[1]->IsFunction()); v8::Isolate* isolate = context()->isolate(); v8::Local start_info = args[0].As(); v8::Local sink_id_val = GetChildValue(start_info, "sinkId", isolate); CHECK(sink_id_val->IsInt32()); const int sink_id = sink_id_val->ToInt32(isolate)->Value(); if (GetDisplaySession(sink_id)) { isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8( isolate, kSessionAlreadyStarted))); return; } v8::Local video_stream_val = GetChildValue(start_info, "videoTrack", isolate); v8::Local audio_stream_val = GetChildValue(start_info, "audioTrack", isolate); if ((video_stream_val->IsNull() || video_stream_val->IsUndefined()) && (audio_stream_val->IsNull() || audio_stream_val->IsUndefined())) { isolate->ThrowException(v8::Exception::Error( v8::String::NewFromUtf8(isolate, kInvalidStreamArgs))); return; } blink::WebMediaStreamTrack audio_track, video_track; if (!video_stream_val->IsNull() && !video_stream_val->IsUndefined()) { CHECK(video_stream_val->IsObject()); video_track = blink::WebDOMMediaStreamTrack::fromV8Value( video_stream_val).component(); if (video_track.isNull()) { isolate->ThrowException(v8::Exception::Error( v8::String::NewFromUtf8(isolate, kInvalidStreamArgs))); return; } } if (!audio_stream_val->IsNull() && !audio_stream_val->IsUndefined()) { CHECK(audio_stream_val->IsObject()); audio_track = blink::WebDOMMediaStreamTrack::fromV8Value( audio_stream_val).component(); if (audio_track.isNull()) { isolate->ThrowException(v8::Exception::Error( v8::String::NewFromUtf8(isolate, kInvalidStreamArgs))); return; } } scoped_ptr auth_info; v8::Local auth_info_v8_val = GetChildValue(start_info, "authenticationInfo", isolate); if (!auth_info_v8_val->IsNull()) { CHECK(auth_info_v8_val->IsObject()); scoped_ptr converter(V8ValueConverter::create()); scoped_ptr auth_info_val( converter->FromV8Value(auth_info_v8_val, context()->v8_context())); CHECK(auth_info_val); auth_info = DisplaySourceAuthInfo::FromValue(*auth_info_val); } DisplaySourceSessionParams session_params; session_params.sink_id = sink_id; session_params.video_track = video_track; session_params.audio_track = audio_track; session_params.render_frame = context()->GetRenderFrame(); if (auth_info) { session_params.auth_method = auth_info->method; session_params.auth_data = auth_info->data ? *auth_info->data : ""; } scoped_ptr session = DisplaySourceSessionFactory::CreateSession(session_params); if (!session) { isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8( isolate, kErrorNotSupported))); return; } auto on_started_callback = base::Bind( &DisplaySourceCustomBindings::OnSessionStarted, weak_factory_.GetWeakPtr()); auto on_terminated_callback = base::Bind( &DisplaySourceCustomBindings::OnSessionTerminated, weak_factory_.GetWeakPtr()); auto on_error_callback = base::Bind( &DisplaySourceCustomBindings::OnSessionError, weak_factory_.GetWeakPtr()); session->SetCallbacks(on_started_callback, on_terminated_callback, on_error_callback); CallbackInfo cb_info = GetCallbackInfo(kStarted, sink_id); args.GetReturnValue().Set(static_cast(cb_info.call_id)); callbacks_.push_back(cb_info); session->Start(); session_map_.insert(std::make_pair(sink_id, std::move(session))); } void DisplaySourceCustomBindings::TerminateSession( const v8::FunctionCallbackInfo& args) { CHECK_EQ(2, args.Length()); CHECK(args[0]->IsInt32()); CHECK(args[1]->IsFunction()); v8::Isolate* isolate = context()->isolate(); int sink_id = args[0]->ToInt32(args.GetIsolate())->Value(); DisplaySourceSession* session = GetDisplaySession(sink_id); if (!session) { isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8( isolate, kSessionNotFound))); return; } if (session->state() == DisplaySourceSession::Terminating) { isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8( isolate, kSessionAlreadyTerminating))); return; } CallbackInfo cb_info = GetCallbackInfo(kTerminated, sink_id); args.GetReturnValue().Set(static_cast(cb_info.call_id)); callbacks_.push_back(cb_info); // The session will get removed from session_map_ in OnSessionTerminated. session->Terminate(); } void DisplaySourceCustomBindings::CallCompletionCallback( int sink_id, CallbackType type, const std::string& error_message) { auto predicate = [sink_id, type](const CallbackInfo& info) -> bool { return info.sink_id == sink_id && info.type == type; }; auto it = std::find_if(callbacks_.begin(), callbacks_.end(), predicate); if (it == callbacks_.end()) return; v8::Isolate* isolate = context()->isolate(); ModuleSystem* module_system = context()->module_system(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context()->v8_context()); v8::Local callback_args[2]; callback_args[0] = v8::Integer::New(isolate, it->call_id); if (error_message.empty()) callback_args[1] = v8::Null(isolate); else callback_args[1] = v8::String::NewFromUtf8(isolate, error_message.c_str()); module_system->CallModuleMethod("displaySource", "callCompletionCallback", 2, callback_args); } void DisplaySourceCustomBindings::DispatchSessionTerminated(int sink_id) const { v8::Isolate* isolate = context()->isolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context()->v8_context()); v8::Local event_args = v8::Array::New(isolate, 1); event_args->Set(0, v8::Integer::New(isolate, sink_id)); context()->DispatchEvent("displaySource.onSessionTerminated", event_args); } void DisplaySourceCustomBindings::DispatchSessionError( int sink_id, DisplaySourceErrorType type, const std::string& message) const { v8::Isolate* isolate = context()->isolate(); v8::HandleScope handle_scope(isolate); v8::Context::Scope context_scope(context()->v8_context()); api::display_source::ErrorInfo error_info; error_info.type = type; if (!message.empty()) error_info.description.reset(new std::string(message)); scoped_ptr converter(V8ValueConverter::create()); v8::Local info_arg = converter->ToV8Value(error_info.ToValue().get(), context()->v8_context()); v8::Local event_args = v8::Array::New(isolate, 2); event_args->Set(0, v8::Integer::New(isolate, sink_id)); event_args->Set(1, info_arg); context()->DispatchEvent("displaySource.onSessionErrorOccured", event_args); } DisplaySourceSession* DisplaySourceCustomBindings::GetDisplaySession( int sink_id) const { auto iter = session_map_.find(sink_id); if (iter != session_map_.end()) return iter->second.get(); return nullptr; } void DisplaySourceCustomBindings::OnSessionStarted(int sink_id) { CallCompletionCallback(sink_id, kStarted); } void DisplaySourceCustomBindings::OnSessionTerminated(int sink_id) { DisplaySourceSession* session = GetDisplaySession(sink_id); CHECK(session); session_map_.erase(sink_id); DispatchSessionTerminated(sink_id); CallCompletionCallback(sink_id, kTerminated); } void DisplaySourceCustomBindings::OnSessionError(int sink_id, DisplaySourceErrorType type, const std::string& message) { DisplaySourceSession* session = GetDisplaySession(sink_id); CHECK(session); DispatchSessionError(sink_id, type, message); } DisplaySourceCustomBindings::CallbackInfo DisplaySourceCustomBindings::GetCallbackInfo( DisplaySourceCustomBindings::CallbackType type, int sink_id) const { static int sCallId = 0; return {type, sink_id, ++sCallId}; } } // extensions