| // 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 <stdint.h> |
| |
| #include "base/bind.h" |
| #include "content/public/renderer/v8_value_converter.h" |
| #include "extensions/renderer/extension_bindings_system.h" |
| #include "extensions/renderer/script_context.h" |
| #include "third_party/blink/public/platform/web_media_stream.h" |
| #include "third_party/blink/public/platform/web_media_stream_track.h" |
| #include "third_party/blink/public/web/web_dom_media_stream_track.h" |
| #include "v8/include/v8.h" |
| |
| namespace extensions { |
| |
| 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, |
| ExtensionBindingsSystem* bindings_system) |
| : ObjectBackedNativeHandler(context), |
| bindings_system_(bindings_system), |
| weak_factory_(this) {} |
| |
| DisplaySourceCustomBindings::~DisplaySourceCustomBindings() {} |
| |
| void DisplaySourceCustomBindings::AddRoutes() { |
| RouteHandlerFunction("StartSession", "displaySource", |
| base::Bind(&DisplaySourceCustomBindings::StartSession, |
| weak_factory_.GetWeakPtr())); |
| RouteHandlerFunction( |
| "TerminateSession", "displaySource", |
| base::Bind(&DisplaySourceCustomBindings::TerminateSession, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void DisplaySourceCustomBindings::Invalidate() { |
| session_map_.clear(); |
| weak_factory_.InvalidateWeakPtrs(); |
| ObjectBackedNativeHandler::Invalidate(); |
| } |
| |
| namespace { |
| |
| v8::Local<v8::Value> GetChildValue(v8::Local<v8::Object> value, |
| const std::string& key_name, |
| v8::Isolate* isolate) { |
| v8::Local<v8::Array> property_names(value->GetOwnPropertyNames()); |
| for (uint32_t i = 0; i < property_names->Length(); ++i) { |
| v8::Local<v8::Value> key(property_names->Get(i)); |
| if (key_name == *v8::String::Utf8Value(isolate, key)) { |
| v8::TryCatch try_catch(isolate); |
| v8::Local<v8::Value> child_v8 = value->Get(key); |
| if (try_catch.HasCaught()) { |
| return v8::Null(isolate); |
| } |
| return child_v8; |
| } |
| } |
| |
| return v8::Null(isolate); |
| } |
| |
| int32_t GetCallbackId() { |
| static int32_t sCallId = 0; |
| return ++sCallId; |
| } |
| |
| } // namespace |
| |
| void DisplaySourceCustomBindings::StartSession( |
| const v8::FunctionCallbackInfo<v8::Value>& args) { |
| CHECK_EQ(1, args.Length()); |
| CHECK(args[0]->IsObject()); |
| |
| v8::Isolate* isolate = context()->isolate(); |
| v8::Local<v8::Object> start_info = args[0].As<v8::Object>(); |
| |
| v8::Local<v8::Value> 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<v8::Value> video_stream_val = |
| GetChildValue(start_info, "videoTrack", isolate); |
| v8::Local<v8::Value> 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; |
| } |
| } |
| |
| std::unique_ptr<DisplaySourceAuthInfo> auth_info; |
| v8::Local<v8::Value> auth_info_v8_val = |
| GetChildValue(start_info, "authenticationInfo", isolate); |
| if (!auth_info_v8_val->IsNull()) { |
| CHECK(auth_info_v8_val->IsObject()); |
| std::unique_ptr<base::Value> auth_info_val = |
| content::V8ValueConverter::Create()->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 : ""; |
| } |
| std::unique_ptr<DisplaySourceSession> session = |
| DisplaySourceSessionFactory::CreateSession(session_params); |
| if (!session) { |
| isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8( |
| isolate, kErrorNotSupported))); |
| return; |
| } |
| |
| auto on_terminated_callback = |
| base::Bind(&DisplaySourceCustomBindings::OnSessionTerminated, |
| weak_factory_.GetWeakPtr(), sink_id); |
| auto on_error_callback = |
| base::Bind(&DisplaySourceCustomBindings::OnSessionError, |
| weak_factory_.GetWeakPtr(), sink_id); |
| session->SetNotificationCallbacks(on_terminated_callback, on_error_callback); |
| |
| int32_t call_id = GetCallbackId(); |
| args.GetReturnValue().Set(call_id); |
| |
| auto on_call_completed = |
| base::Bind(&DisplaySourceCustomBindings::OnSessionStarted, |
| weak_factory_.GetWeakPtr(), sink_id, call_id); |
| session->Start(on_call_completed); |
| session_map_.insert(std::make_pair(sink_id, std::move(session))); |
| } |
| |
| void DisplaySourceCustomBindings::TerminateSession( |
| const v8::FunctionCallbackInfo<v8::Value>& args) { |
| CHECK_EQ(1, args.Length()); |
| CHECK(args[0]->IsInt32()); |
| |
| 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; |
| } |
| |
| DisplaySourceSession::State state = session->state(); |
| DCHECK_NE(state, DisplaySourceSession::Idle); |
| if (state == DisplaySourceSession::Establishing) { |
| // 'session started' callback has not yet been invoked. |
| // This session is not existing for the user. |
| isolate->ThrowException(v8::Exception::Error( |
| v8::String::NewFromUtf8(isolate, kSessionNotFound))); |
| return; |
| } |
| |
| if (state == DisplaySourceSession::Terminating) { |
| isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8( |
| isolate, kSessionAlreadyTerminating))); |
| return; |
| } |
| |
| int32_t call_id = GetCallbackId(); |
| args.GetReturnValue().Set(call_id); |
| |
| auto on_call_completed = |
| base::Bind(&DisplaySourceCustomBindings::OnCallCompleted, |
| weak_factory_.GetWeakPtr(), call_id); |
| // The session will get removed from session_map_ in OnSessionTerminated. |
| session->Terminate(on_call_completed); |
| } |
| |
| void DisplaySourceCustomBindings::OnCallCompleted( |
| int call_id, |
| bool success, |
| const std::string& error_message) { |
| 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<v8::Value> callback_args[2]; |
| callback_args[0] = v8::Integer::New(isolate, call_id); |
| if (success) |
| callback_args[1] = v8::Null(isolate); |
| else |
| callback_args[1] = v8::String::NewFromUtf8(isolate, error_message.c_str()); |
| |
| module_system->CallModuleMethodSafe("displaySource", "callCompletionCallback", |
| 2, callback_args); |
| } |
| |
| void DisplaySourceCustomBindings::OnSessionStarted( |
| int sink_id, |
| int call_id, |
| bool success, |
| const std::string& error_message) { |
| CHECK(GetDisplaySession(sink_id)); |
| if (!success) { |
| // Session has failed to start, removing it. |
| session_map_.erase(sink_id); |
| } |
| OnCallCompleted(call_id, success, error_message); |
| } |
| |
| void DisplaySourceCustomBindings::DispatchSessionTerminated(int sink_id) const { |
| base::ListValue event_args; |
| event_args.AppendInteger(sink_id); |
| bindings_system_->DispatchEventInContext("displaySource.onSessionTerminated", |
| &event_args, nullptr, context()); |
| } |
| |
| void DisplaySourceCustomBindings::DispatchSessionError( |
| int sink_id, |
| DisplaySourceErrorType type, |
| const std::string& message) const { |
| api::display_source::ErrorInfo error_info; |
| error_info.type = type; |
| if (!message.empty()) |
| error_info.description.reset(new std::string(message)); |
| |
| base::ListValue event_args; |
| event_args.AppendInteger(sink_id); |
| event_args.Append(error_info.ToValue()); |
| bindings_system_->DispatchEventInContext( |
| "displaySource.onSessionErrorOccured", &event_args, nullptr, context()); |
| } |
| |
| 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::OnSessionTerminated(int sink_id) { |
| CHECK(GetDisplaySession(sink_id)); |
| session_map_.erase(sink_id); |
| DispatchSessionTerminated(sink_id); |
| } |
| |
| void DisplaySourceCustomBindings::OnSessionError(int sink_id, |
| DisplaySourceErrorType type, |
| const std::string& message) { |
| CHECK(GetDisplaySession(sink_id)); |
| DispatchSessionError(sink_id, type, message); |
| } |
| |
| } // extensions |