1 // Copyright (c) 2020 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that
3 // can be found in the LICENSE file.
4
5 #include "tests/cefclient/browser/media_router_test.h"
6
7 #include <string>
8 #include <vector>
9
10 #include "include/base/cef_logging.h"
11 #include "include/cef_media_router.h"
12 #include "include/cef_parser.h"
13 #include "tests/cefclient/browser/test_runner.h"
14
15 namespace client {
16 namespace media_router_test {
17
18 namespace {
19
20 const char kTestUrlPath[] = "/media_router";
21
22 // Application-specific error codes.
23 const int kMessageFormatError = 1;
24 const int kRequestFailedError = 2;
25
26 // Message strings.
27 const char kNameKey[] = "name";
28 const char kNameValueSubscribe[] = "subscribe";
29 const char kNameValueCreateRoute[] = "createRoute";
30 const char kNameValueTerminateRoute[] = "terminateRoute";
31 const char kNameValueSendMessage[] = "sendMessage";
32 const char kSourceKey[] = "source_urn";
33 const char kSinkKey[] = "sink_id";
34 const char kRouteKey[] = "route_id";
35 const char kMessageKey[] = "message";
36 const char kSuccessKey[] = "success";
37 const char kPayloadKey[] = "payload";
38
39 // Convert a dictionary value to a JSON string.
GetJSON(CefRefPtr<CefDictionaryValue> dictionary)40 CefString GetJSON(CefRefPtr<CefDictionaryValue> dictionary) {
41 CefRefPtr<CefValue> value = CefValue::Create();
42 value->SetDictionary(dictionary);
43 return CefWriteJSON(value, JSON_WRITER_DEFAULT);
44 }
45
46 typedef CefMessageRouterBrowserSide::Callback CallbackType;
47
SendSuccess(CefRefPtr<CallbackType> callback,CefRefPtr<CefDictionaryValue> result)48 void SendSuccess(CefRefPtr<CallbackType> callback,
49 CefRefPtr<CefDictionaryValue> result) {
50 callback->Success(GetJSON(result));
51 }
52
SendFailure(CefRefPtr<CallbackType> callback,int error_code,const std::string & error_message)53 void SendFailure(CefRefPtr<CallbackType> callback,
54 int error_code,
55 const std::string& error_message) {
56 callback->Failure(error_code, error_message);
57 }
58
59 // Callback for CefMediaRouter::CreateRoute.
60 class MediaRouteCreateCallback : public CefMediaRouteCreateCallback {
61 public:
MediaRouteCreateCallback(CefRefPtr<CallbackType> create_callback)62 explicit MediaRouteCreateCallback(CefRefPtr<CallbackType> create_callback)
63 : create_callback_(create_callback) {}
64
65 // CefMediaRouteCreateCallback method:
OnMediaRouteCreateFinished(RouteCreateResult result,const CefString & error,CefRefPtr<CefMediaRoute> route)66 void OnMediaRouteCreateFinished(RouteCreateResult result,
67 const CefString& error,
68 CefRefPtr<CefMediaRoute> route) OVERRIDE {
69 CEF_REQUIRE_UI_THREAD();
70 if (result == CEF_MRCR_OK) {
71 CefRefPtr<CefDictionaryValue> dict = CefDictionaryValue::Create();
72 dict->SetString(kRouteKey, route->GetId());
73 SendSuccess(create_callback_, dict);
74 } else {
75 SendFailure(create_callback_, kRequestFailedError + result, error);
76 }
77 create_callback_ = NULL;
78 }
79
80 private:
81 CefRefPtr<CallbackType> create_callback_;
82
83 IMPLEMENT_REFCOUNTING(MediaRouteCreateCallback);
84 DISALLOW_COPY_AND_ASSIGN(MediaRouteCreateCallback);
85 };
86
87 // Observes MediaRouter events. Only accessed on the UI thread.
88 class MediaObserver : public CefMediaObserver {
89 public:
90 typedef std::vector<CefRefPtr<CefMediaRoute>> MediaRouteVector;
91 typedef std::vector<CefRefPtr<CefMediaSink>> MediaSinkVector;
92
MediaObserver(CefRefPtr<CefMediaRouter> media_router,CefRefPtr<CallbackType> subscription_callback)93 MediaObserver(CefRefPtr<CefMediaRouter> media_router,
94 CefRefPtr<CallbackType> subscription_callback)
95 : media_router_(media_router),
96 subscription_callback_(subscription_callback),
97 next_sink_query_id_(0),
98 pending_sink_query_id_(-1),
99 pending_sink_callbacks_(0U) {}
100
~MediaObserver()101 ~MediaObserver() OVERRIDE { ClearSinkInfoMap(); }
102
CreateRoute(const std::string & source_urn,const std::string & sink_id,CefRefPtr<CallbackType> callback,std::string & error)103 bool CreateRoute(const std::string& source_urn,
104 const std::string& sink_id,
105 CefRefPtr<CallbackType> callback,
106 std::string& error) {
107 CefRefPtr<CefMediaSource> source = GetSource(source_urn);
108 if (!source) {
109 error = "Invalid source: " + source_urn;
110 return false;
111 }
112
113 CefRefPtr<CefMediaSink> sink = GetSink(sink_id);
114 if (!sink) {
115 error = "Invalid sink: " + sink_id;
116 return false;
117 }
118
119 media_router_->CreateRoute(source, sink,
120 new MediaRouteCreateCallback(callback));
121 return true;
122 }
123
TerminateRoute(const std::string & route_id,std::string & error)124 bool TerminateRoute(const std::string& route_id, std::string& error) {
125 CefRefPtr<CefMediaRoute> route = GetRoute(route_id);
126 if (!route) {
127 error = "Invalid route: " + route_id;
128 return false;
129 }
130
131 route->Terminate();
132 return true;
133 }
134
SendRouteMessage(const std::string & route_id,const std::string & message,std::string & error)135 bool SendRouteMessage(const std::string& route_id,
136 const std::string& message,
137 std::string& error) {
138 CefRefPtr<CefMediaRoute> route = GetRoute(route_id);
139 if (!route) {
140 error = "Invalid route: " + route_id;
141 return false;
142 }
143
144 route->SendRouteMessage(message.c_str(), message.size());
145 return true;
146 }
147
148 protected:
149 class DeviceInfoCallback : public CefMediaSinkDeviceInfoCallback {
150 public:
151 // Callback to be executed when the device info is available.
152 typedef base::Callback<void(const std::string& sink_id,
153 const CefMediaSinkDeviceInfo& device_info)>
154 CallbackType;
155
DeviceInfoCallback(const std::string & sink_id,const CallbackType & callback)156 DeviceInfoCallback(const std::string& sink_id, const CallbackType& callback)
157 : sink_id_(sink_id), callback_(callback) {}
158
OnMediaSinkDeviceInfo(const CefMediaSinkDeviceInfo & device_info)159 void OnMediaSinkDeviceInfo(
160 const CefMediaSinkDeviceInfo& device_info) OVERRIDE {
161 CEF_REQUIRE_UI_THREAD();
162 callback_.Run(sink_id_, device_info);
163 callback_.Reset();
164 }
165
166 private:
167 const std::string sink_id_;
168 CallbackType callback_;
169
170 IMPLEMENT_REFCOUNTING(DeviceInfoCallback);
171 DISALLOW_COPY_AND_ASSIGN(DeviceInfoCallback);
172 };
173
174 // CefMediaObserver methods:
OnSinks(const MediaSinkVector & sinks)175 void OnSinks(const MediaSinkVector& sinks) OVERRIDE {
176 CEF_REQUIRE_UI_THREAD();
177
178 ClearSinkInfoMap();
179
180 // Reset pending sink state.
181 pending_sink_callbacks_ = sinks.size();
182 pending_sink_query_id_ = ++next_sink_query_id_;
183
184 if (sinks.empty()) {
185 // No sinks, send the response immediately.
186 SendSinksResponse();
187 return;
188 }
189
190 DeviceInfoCallback::CallbackType callback = base::Bind(
191 &MediaObserver::OnSinkDeviceInfo, this, pending_sink_query_id_);
192
193 MediaSinkVector::const_iterator it = sinks.begin();
194 for (size_t idx = 0; it != sinks.end(); ++it, ++idx) {
195 CefRefPtr<CefMediaSink> sink = *it;
196 const std::string& sink_id = sink->GetId();
197 SinkInfo* info = new SinkInfo;
198 info->sink = sink;
199 sink_info_map_.insert(std::make_pair(sink_id, info));
200
201 // Request the device info asynchronously. Send the response once all
202 // callbacks have executed.
203 sink->GetDeviceInfo(new DeviceInfoCallback(sink_id, callback));
204 }
205 }
206
OnRoutes(const MediaRouteVector & routes)207 void OnRoutes(const MediaRouteVector& routes) OVERRIDE {
208 CEF_REQUIRE_UI_THREAD();
209
210 route_map_.clear();
211
212 CefRefPtr<CefDictionaryValue> payload = CefDictionaryValue::Create();
213 CefRefPtr<CefListValue> routes_list = CefListValue::Create();
214 routes_list->SetSize(routes.size());
215
216 MediaRouteVector::const_iterator it = routes.begin();
217 for (size_t idx = 0; it != routes.end(); ++it, ++idx) {
218 CefRefPtr<CefMediaRoute> route = *it;
219 const std::string& route_id = route->GetId();
220 route_map_.insert(std::make_pair(route_id, route));
221
222 CefRefPtr<CefDictionaryValue> route_dict = CefDictionaryValue::Create();
223 route_dict->SetString("id", route_id);
224 route_dict->SetString(kSourceKey, route->GetSource()->GetId());
225 route_dict->SetString(kSinkKey, route->GetSink()->GetId());
226 routes_list->SetDictionary(idx, route_dict);
227 }
228
229 payload->SetList("routes_list", routes_list);
230 SendResponse("onRoutes", payload);
231 }
232
OnRouteStateChanged(CefRefPtr<CefMediaRoute> route,ConnectionState state)233 void OnRouteStateChanged(CefRefPtr<CefMediaRoute> route,
234 ConnectionState state) OVERRIDE {
235 CEF_REQUIRE_UI_THREAD();
236
237 CefRefPtr<CefDictionaryValue> payload = CefDictionaryValue::Create();
238 payload->SetString(kRouteKey, route->GetId());
239 payload->SetInt("connection_state", state);
240 SendResponse("onRouteStateChanged", payload);
241 }
242
OnRouteMessageReceived(CefRefPtr<CefMediaRoute> route,const void * message,size_t message_size)243 void OnRouteMessageReceived(CefRefPtr<CefMediaRoute> route,
244 const void* message,
245 size_t message_size) OVERRIDE {
246 CEF_REQUIRE_UI_THREAD();
247
248 std::string message_str(static_cast<const char*>(message), message_size);
249
250 CefRefPtr<CefDictionaryValue> payload = CefDictionaryValue::Create();
251 payload->SetString(kRouteKey, route->GetId());
252 payload->SetString(kMessageKey, message_str);
253 SendResponse("onRouteMessageReceived", payload);
254 }
255
256 private:
GetSource(const std::string & source_urn)257 CefRefPtr<CefMediaSource> GetSource(const std::string& source_urn) {
258 CefRefPtr<CefMediaSource> source = media_router_->GetSource(source_urn);
259 if (!source)
260 return NULL;
261 return source;
262 }
263
GetSink(const std::string & sink_id)264 CefRefPtr<CefMediaSink> GetSink(const std::string& sink_id) {
265 SinkInfoMap::const_iterator it = sink_info_map_.find(sink_id);
266 if (it != sink_info_map_.end())
267 return it->second->sink;
268 return NULL;
269 }
270
ClearSinkInfoMap()271 void ClearSinkInfoMap() {
272 SinkInfoMap::const_iterator it = sink_info_map_.begin();
273 for (; it != sink_info_map_.end(); ++it) {
274 delete it->second;
275 }
276 sink_info_map_.clear();
277 }
278
OnSinkDeviceInfo(int sink_query_id,const std::string & sink_id,const CefMediaSinkDeviceInfo & device_info)279 void OnSinkDeviceInfo(int sink_query_id,
280 const std::string& sink_id,
281 const CefMediaSinkDeviceInfo& device_info) {
282 // Discard callbacks that arrive after a new call to OnSinks().
283 if (sink_query_id != pending_sink_query_id_)
284 return;
285
286 SinkInfoMap::const_iterator it = sink_info_map_.find(sink_id);
287 if (it != sink_info_map_.end()) {
288 it->second->device_info = device_info;
289 }
290
291 // Send the response once we've received all expected callbacks.
292 DCHECK_GT(pending_sink_callbacks_, 0U);
293 if (--pending_sink_callbacks_ == 0U) {
294 SendSinksResponse();
295 }
296 }
297
GetRoute(const std::string & route_id)298 CefRefPtr<CefMediaRoute> GetRoute(const std::string& route_id) {
299 RouteMap::const_iterator it = route_map_.find(route_id);
300 if (it != route_map_.end())
301 return it->second;
302 return NULL;
303 }
304
SendResponse(const std::string & name,CefRefPtr<CefDictionaryValue> payload)305 void SendResponse(const std::string& name,
306 CefRefPtr<CefDictionaryValue> payload) {
307 CefRefPtr<CefDictionaryValue> result = CefDictionaryValue::Create();
308 result->SetString(kNameKey, name);
309 result->SetDictionary(kPayloadKey, payload);
310 SendSuccess(subscription_callback_, result);
311 }
312
SendSinksResponse()313 void SendSinksResponse() {
314 CefRefPtr<CefDictionaryValue> payload = CefDictionaryValue::Create();
315 CefRefPtr<CefListValue> sinks_list = CefListValue::Create();
316 sinks_list->SetSize(sink_info_map_.size());
317
318 SinkInfoMap::const_iterator it = sink_info_map_.begin();
319 for (size_t idx = 0; it != sink_info_map_.end(); ++it, ++idx) {
320 const SinkInfo* info = it->second;
321
322 CefRefPtr<CefDictionaryValue> sink_dict = CefDictionaryValue::Create();
323 sink_dict->SetString("id", it->first);
324 sink_dict->SetString("name", info->sink->GetName());
325 sink_dict->SetString("desc", info->sink->GetDescription());
326 sink_dict->SetInt("icon", info->sink->GetIconType());
327 sink_dict->SetString("ip_address",
328 CefString(&info->device_info.ip_address));
329 sink_dict->SetInt("port", info->device_info.port);
330 sink_dict->SetString("model_name",
331 CefString(&info->device_info.model_name));
332 sink_dict->SetString("type",
333 info->sink->IsCastSink()
334 ? "cast"
335 : info->sink->IsDialSink() ? "dial" : "unknown");
336 sinks_list->SetDictionary(idx, sink_dict);
337 }
338
339 payload->SetList("sinks_list", sinks_list);
340 SendResponse("onSinks", payload);
341 }
342
343 CefRefPtr<CefMediaRouter> media_router_;
344 CefRefPtr<CallbackType> subscription_callback_;
345
346 struct SinkInfo {
347 CefRefPtr<CefMediaSink> sink;
348 CefMediaSinkDeviceInfo device_info;
349 };
350 typedef std::map<std::string, SinkInfo*> SinkInfoMap;
351
352 // Used to uniquely identify a call to OnSinks(), for the purpose of
353 // associating OnMediaSinkDeviceInfo() callbacks.
354 int next_sink_query_id_;
355
356 // State from the most recent call to OnSinks().
357 SinkInfoMap sink_info_map_;
358 int pending_sink_query_id_;
359 size_t pending_sink_callbacks_;
360
361 // State from the most recent call to OnRoutes().
362 typedef std::map<std::string, CefRefPtr<CefMediaRoute>> RouteMap;
363 RouteMap route_map_;
364
365 IMPLEMENT_REFCOUNTING(MediaObserver);
366 DISALLOW_COPY_AND_ASSIGN(MediaObserver);
367 };
368
369 // Handle messages in the browser process. Only accessed on the UI thread.
370 class Handler : public CefMessageRouterBrowserSide::Handler {
371 public:
372 typedef std::vector<std::string> NameVector;
373
Handler()374 Handler() { CEF_REQUIRE_UI_THREAD(); }
375
~Handler()376 virtual ~Handler() {
377 SubscriptionStateMap::iterator it = subscription_state_map_.begin();
378 for (; it != subscription_state_map_.end(); ++it) {
379 delete it->second;
380 }
381 }
382
383 // Called due to cefQuery execution in media_router.html.
OnQuery(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,int64 query_id,const CefString & request,bool persistent,CefRefPtr<Callback> callback)384 bool OnQuery(CefRefPtr<CefBrowser> browser,
385 CefRefPtr<CefFrame> frame,
386 int64 query_id,
387 const CefString& request,
388 bool persistent,
389 CefRefPtr<Callback> callback) OVERRIDE {
390 CEF_REQUIRE_UI_THREAD();
391
392 // Only handle messages from the test URL.
393 const std::string& url = frame->GetURL();
394 if (!test_runner::IsTestURL(url, kTestUrlPath))
395 return false;
396
397 // Parse |request| as a JSON dictionary.
398 CefRefPtr<CefDictionaryValue> request_dict = ParseJSON(request);
399 if (!request_dict) {
400 SendFailure(callback, kMessageFormatError, "Incorrect message format");
401 return true;
402 }
403
404 // Verify the "name" key.
405 if (!VerifyKey(request_dict, kNameKey, VTYPE_STRING, callback))
406 return true;
407
408 const std::string& message_name = request_dict->GetString(kNameKey);
409 if (message_name == kNameValueSubscribe) {
410 // Subscribe to notifications from the media router.
411
412 if (!persistent) {
413 SendFailure(callback, kMessageFormatError,
414 "Subscriptions must be persistent");
415 return true;
416 }
417
418 if (!CreateSubscription(browser, query_id, callback)) {
419 SendFailure(callback, kRequestFailedError,
420 "Browser is already subscribed");
421 }
422 return true;
423 }
424
425 // All other messages require a current subscription.
426 CefRefPtr<MediaObserver> media_observer =
427 GetMediaObserver(browser->GetIdentifier());
428 if (!media_observer) {
429 SendFailure(callback, kRequestFailedError,
430 "Browser is not currently subscribed");
431 }
432
433 if (message_name == kNameValueCreateRoute) {
434 // Create a new route.
435
436 // Verify the "source_urn" key.
437 if (!VerifyKey(request_dict, kSourceKey, VTYPE_STRING, callback))
438 return true;
439 // Verify the "sink_id" key.
440 if (!VerifyKey(request_dict, kSinkKey, VTYPE_STRING, callback))
441 return true;
442
443 const std::string& source_urn = request_dict->GetString(kSourceKey);
444 const std::string& sink_id = request_dict->GetString(kSinkKey);
445
446 // |callback| will be executed once the route is created.
447 std::string error;
448 if (!media_observer->CreateRoute(source_urn, sink_id, callback, error)) {
449 SendFailure(callback, kRequestFailedError, error);
450 }
451 return true;
452 } else if (message_name == kNameValueTerminateRoute) {
453 // Terminate an existing route.
454
455 // Verify the "route" key.
456 if (!VerifyKey(request_dict, kRouteKey, VTYPE_STRING, callback))
457 return true;
458
459 const std::string& route_id = request_dict->GetString(kRouteKey);
460 std::string error;
461 if (!media_observer->TerminateRoute(route_id, error)) {
462 SendFailure(callback, kRequestFailedError, error);
463 } else {
464 SendSuccessACK(callback);
465 }
466 return true;
467 } else if (message_name == kNameValueSendMessage) {
468 // Send a route message.
469
470 // Verify the "route_id" key.
471 if (!VerifyKey(request_dict, kRouteKey, VTYPE_STRING, callback))
472 return true;
473 // Verify the "message" key.
474 if (!VerifyKey(request_dict, kMessageKey, VTYPE_STRING, callback))
475 return true;
476
477 const std::string& route_id = request_dict->GetString(kRouteKey);
478 const std::string& message = request_dict->GetString(kMessageKey);
479 std::string error;
480 if (!media_observer->SendRouteMessage(route_id, message, error)) {
481 SendFailure(callback, kRequestFailedError, error);
482 } else {
483 SendSuccessACK(callback);
484 }
485 return true;
486 }
487
488 return false;
489 }
490
OnQueryCanceled(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,int64 query_id)491 void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
492 CefRefPtr<CefFrame> frame,
493 int64 query_id) OVERRIDE {
494 CEF_REQUIRE_UI_THREAD();
495 RemoveSubscription(browser->GetIdentifier(), query_id);
496 }
497
498 private:
SendSuccessACK(CefRefPtr<Callback> callback)499 static void SendSuccessACK(CefRefPtr<Callback> callback) {
500 CefRefPtr<CefDictionaryValue> result = CefDictionaryValue::Create();
501 result->SetBool(kSuccessKey, true);
502 SendSuccess(callback, result);
503 }
504
505 // Convert a JSON string to a dictionary value.
ParseJSON(const CefString & string)506 static CefRefPtr<CefDictionaryValue> ParseJSON(const CefString& string) {
507 CefRefPtr<CefValue> value = CefParseJSON(string, JSON_PARSER_RFC);
508 if (value.get() && value->GetType() == VTYPE_DICTIONARY)
509 return value->GetDictionary();
510 return nullptr;
511 }
512
513 // Verify that |key| exists in |dictionary| and has type |value_type|. Fails
514 // |callback| and returns false on failure.
VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,const char * key,cef_value_type_t value_type,CefRefPtr<Callback> callback)515 static bool VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,
516 const char* key,
517 cef_value_type_t value_type,
518 CefRefPtr<Callback> callback) {
519 if (!dictionary->HasKey(key) || dictionary->GetType(key) != value_type) {
520 SendFailure(
521 callback, kMessageFormatError,
522 "Missing or incorrectly formatted message key: " + std::string(key));
523 return false;
524 }
525 return true;
526 }
527
528 // Subscription state associated with a single browser.
529 struct SubscriptionState {
530 int64 query_id;
531 CefRefPtr<MediaObserver> observer;
532 CefRefPtr<CefRegistration> registration;
533 };
534
CreateSubscription(CefRefPtr<CefBrowser> browser,int64 query_id,CefRefPtr<Callback> callback)535 bool CreateSubscription(CefRefPtr<CefBrowser> browser,
536 int64 query_id,
537 CefRefPtr<Callback> callback) {
538 const int browser_id = browser->GetIdentifier();
539 if (subscription_state_map_.find(browser_id) !=
540 subscription_state_map_.end()) {
541 // An subscription already exists for this browser.
542 return false;
543 }
544
545 CefRefPtr<CefMediaRouter> media_router =
546 browser->GetHost()->GetRequestContext()->GetMediaRouter(nullptr);
547
548 SubscriptionState* state = new SubscriptionState();
549 state->query_id = query_id;
550 state->observer = new MediaObserver(media_router, callback);
551 state->registration = media_router->AddObserver(state->observer);
552 subscription_state_map_.insert(std::make_pair(browser_id, state));
553
554 // Trigger sink and route callbacks.
555 media_router->NotifyCurrentSinks();
556 media_router->NotifyCurrentRoutes();
557
558 return true;
559 }
560
RemoveSubscription(int browser_id,int64 query_id)561 void RemoveSubscription(int browser_id, int64 query_id) {
562 SubscriptionStateMap::iterator it =
563 subscription_state_map_.find(browser_id);
564 if (it != subscription_state_map_.end() &&
565 it->second->query_id == query_id) {
566 delete it->second;
567 subscription_state_map_.erase(it);
568 }
569 }
570
GetMediaObserver(int browser_id)571 CefRefPtr<MediaObserver> GetMediaObserver(int browser_id) {
572 SubscriptionStateMap::const_iterator it =
573 subscription_state_map_.find(browser_id);
574 if (it != subscription_state_map_.end()) {
575 return it->second->observer;
576 }
577 return NULL;
578 }
579
580 // Map of browser ID to SubscriptionState object.
581 typedef std::map<int, SubscriptionState*> SubscriptionStateMap;
582 SubscriptionStateMap subscription_state_map_;
583
584 DISALLOW_COPY_AND_ASSIGN(Handler);
585 };
586
587 } // namespace
588
CreateMessageHandlers(test_runner::MessageHandlerSet & handlers)589 void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers) {
590 handlers.insert(new Handler());
591 }
592
593 } // namespace media_router_test
594 } // namespace client
595