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_ = nullptr;
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 using CallbackType =
153 base::OnceCallback<void(const std::string& sink_id,
154 const CefMediaSinkDeviceInfo& device_info)>;
155
DeviceInfoCallback(const std::string & sink_id,CallbackType callback)156 DeviceInfoCallback(const std::string& sink_id, CallbackType callback)
157 : sink_id_(sink_id), callback_(std::move(callback)) {}
158
OnMediaSinkDeviceInfo(const CefMediaSinkDeviceInfo & device_info)159 void OnMediaSinkDeviceInfo(
160 const CefMediaSinkDeviceInfo& device_info) override {
161 CEF_REQUIRE_UI_THREAD();
162 std::move(callback_).Run(sink_id_, device_info);
163 }
164
165 private:
166 const std::string sink_id_;
167 CallbackType callback_;
168
169 IMPLEMENT_REFCOUNTING(DeviceInfoCallback);
170 DISALLOW_COPY_AND_ASSIGN(DeviceInfoCallback);
171 };
172
173 // CefMediaObserver methods:
OnSinks(const MediaSinkVector & sinks)174 void OnSinks(const MediaSinkVector& sinks) override {
175 CEF_REQUIRE_UI_THREAD();
176
177 ClearSinkInfoMap();
178
179 // Reset pending sink state.
180 pending_sink_callbacks_ = sinks.size();
181 pending_sink_query_id_ = ++next_sink_query_id_;
182
183 if (sinks.empty()) {
184 // No sinks, send the response immediately.
185 SendSinksResponse();
186 return;
187 }
188
189 MediaSinkVector::const_iterator it = sinks.begin();
190 for (size_t idx = 0; it != sinks.end(); ++it, ++idx) {
191 CefRefPtr<CefMediaSink> sink = *it;
192 const std::string& sink_id = sink->GetId();
193 SinkInfo* info = new SinkInfo;
194 info->sink = sink;
195 sink_info_map_.insert(std::make_pair(sink_id, info));
196
197 // Request the device info asynchronously. Send the response once all
198 // callbacks have executed.
199 auto callback = base::BindOnce(&MediaObserver::OnSinkDeviceInfo, this,
200 pending_sink_query_id_);
201 sink->GetDeviceInfo(new DeviceInfoCallback(sink_id, std::move(callback)));
202 }
203 }
204
OnRoutes(const MediaRouteVector & routes)205 void OnRoutes(const MediaRouteVector& routes) override {
206 CEF_REQUIRE_UI_THREAD();
207
208 route_map_.clear();
209
210 CefRefPtr<CefDictionaryValue> payload = CefDictionaryValue::Create();
211 CefRefPtr<CefListValue> routes_list = CefListValue::Create();
212 routes_list->SetSize(routes.size());
213
214 MediaRouteVector::const_iterator it = routes.begin();
215 for (size_t idx = 0; it != routes.end(); ++it, ++idx) {
216 CefRefPtr<CefMediaRoute> route = *it;
217 const std::string& route_id = route->GetId();
218 route_map_.insert(std::make_pair(route_id, route));
219
220 CefRefPtr<CefDictionaryValue> route_dict = CefDictionaryValue::Create();
221 route_dict->SetString("id", route_id);
222 route_dict->SetString(kSourceKey, route->GetSource()->GetId());
223 route_dict->SetString(kSinkKey, route->GetSink()->GetId());
224 routes_list->SetDictionary(idx, route_dict);
225 }
226
227 payload->SetList("routes_list", routes_list);
228 SendResponse("onRoutes", payload);
229 }
230
OnRouteStateChanged(CefRefPtr<CefMediaRoute> route,ConnectionState state)231 void OnRouteStateChanged(CefRefPtr<CefMediaRoute> route,
232 ConnectionState state) override {
233 CEF_REQUIRE_UI_THREAD();
234
235 CefRefPtr<CefDictionaryValue> payload = CefDictionaryValue::Create();
236 payload->SetString(kRouteKey, route->GetId());
237 payload->SetInt("connection_state", state);
238 SendResponse("onRouteStateChanged", payload);
239 }
240
OnRouteMessageReceived(CefRefPtr<CefMediaRoute> route,const void * message,size_t message_size)241 void OnRouteMessageReceived(CefRefPtr<CefMediaRoute> route,
242 const void* message,
243 size_t message_size) override {
244 CEF_REQUIRE_UI_THREAD();
245
246 std::string message_str(static_cast<const char*>(message), message_size);
247
248 CefRefPtr<CefDictionaryValue> payload = CefDictionaryValue::Create();
249 payload->SetString(kRouteKey, route->GetId());
250 payload->SetString(kMessageKey, message_str);
251 SendResponse("onRouteMessageReceived", payload);
252 }
253
254 private:
GetSource(const std::string & source_urn)255 CefRefPtr<CefMediaSource> GetSource(const std::string& source_urn) {
256 CefRefPtr<CefMediaSource> source = media_router_->GetSource(source_urn);
257 if (!source)
258 return nullptr;
259 return source;
260 }
261
GetSink(const std::string & sink_id)262 CefRefPtr<CefMediaSink> GetSink(const std::string& sink_id) {
263 SinkInfoMap::const_iterator it = sink_info_map_.find(sink_id);
264 if (it != sink_info_map_.end())
265 return it->second->sink;
266 return nullptr;
267 }
268
ClearSinkInfoMap()269 void ClearSinkInfoMap() {
270 SinkInfoMap::const_iterator it = sink_info_map_.begin();
271 for (; it != sink_info_map_.end(); ++it) {
272 delete it->second;
273 }
274 sink_info_map_.clear();
275 }
276
OnSinkDeviceInfo(int sink_query_id,const std::string & sink_id,const CefMediaSinkDeviceInfo & device_info)277 void OnSinkDeviceInfo(int sink_query_id,
278 const std::string& sink_id,
279 const CefMediaSinkDeviceInfo& device_info) {
280 // Discard callbacks that arrive after a new call to OnSinks().
281 if (sink_query_id != pending_sink_query_id_)
282 return;
283
284 SinkInfoMap::const_iterator it = sink_info_map_.find(sink_id);
285 if (it != sink_info_map_.end()) {
286 it->second->device_info = device_info;
287 }
288
289 // Send the response once we've received all expected callbacks.
290 DCHECK_GT(pending_sink_callbacks_, 0U);
291 if (--pending_sink_callbacks_ == 0U) {
292 SendSinksResponse();
293 }
294 }
295
GetRoute(const std::string & route_id)296 CefRefPtr<CefMediaRoute> GetRoute(const std::string& route_id) {
297 RouteMap::const_iterator it = route_map_.find(route_id);
298 if (it != route_map_.end())
299 return it->second;
300 return nullptr;
301 }
302
SendResponse(const std::string & name,CefRefPtr<CefDictionaryValue> payload)303 void SendResponse(const std::string& name,
304 CefRefPtr<CefDictionaryValue> payload) {
305 CefRefPtr<CefDictionaryValue> result = CefDictionaryValue::Create();
306 result->SetString(kNameKey, name);
307 result->SetDictionary(kPayloadKey, payload);
308 SendSuccess(subscription_callback_, result);
309 }
310
SendSinksResponse()311 void SendSinksResponse() {
312 CefRefPtr<CefDictionaryValue> payload = CefDictionaryValue::Create();
313 CefRefPtr<CefListValue> sinks_list = CefListValue::Create();
314 sinks_list->SetSize(sink_info_map_.size());
315
316 SinkInfoMap::const_iterator it = sink_info_map_.begin();
317 for (size_t idx = 0; it != sink_info_map_.end(); ++it, ++idx) {
318 const SinkInfo* info = it->second;
319
320 CefRefPtr<CefDictionaryValue> sink_dict = CefDictionaryValue::Create();
321 sink_dict->SetString("id", it->first);
322 sink_dict->SetString("name", info->sink->GetName());
323 sink_dict->SetString("desc", info->sink->GetDescription());
324 sink_dict->SetInt("icon", info->sink->GetIconType());
325 sink_dict->SetString("ip_address",
326 CefString(&info->device_info.ip_address));
327 sink_dict->SetInt("port", info->device_info.port);
328 sink_dict->SetString("model_name",
329 CefString(&info->device_info.model_name));
330 sink_dict->SetString("type",
331 info->sink->IsCastSink()
332 ? "cast"
333 : info->sink->IsDialSink() ? "dial" : "unknown");
334 sinks_list->SetDictionary(idx, sink_dict);
335 }
336
337 payload->SetList("sinks_list", sinks_list);
338 SendResponse("onSinks", payload);
339 }
340
341 CefRefPtr<CefMediaRouter> media_router_;
342 CefRefPtr<CallbackType> subscription_callback_;
343
344 struct SinkInfo {
345 CefRefPtr<CefMediaSink> sink;
346 CefMediaSinkDeviceInfo device_info;
347 };
348 typedef std::map<std::string, SinkInfo*> SinkInfoMap;
349
350 // Used to uniquely identify a call to OnSinks(), for the purpose of
351 // associating OnMediaSinkDeviceInfo() callbacks.
352 int next_sink_query_id_;
353
354 // State from the most recent call to OnSinks().
355 SinkInfoMap sink_info_map_;
356 int pending_sink_query_id_;
357 size_t pending_sink_callbacks_;
358
359 // State from the most recent call to OnRoutes().
360 typedef std::map<std::string, CefRefPtr<CefMediaRoute>> RouteMap;
361 RouteMap route_map_;
362
363 IMPLEMENT_REFCOUNTING(MediaObserver);
364 DISALLOW_COPY_AND_ASSIGN(MediaObserver);
365 };
366
367 // Handle messages in the browser process. Only accessed on the UI thread.
368 class Handler : public CefMessageRouterBrowserSide::Handler {
369 public:
370 typedef std::vector<std::string> NameVector;
371
Handler()372 Handler() { CEF_REQUIRE_UI_THREAD(); }
373
~Handler()374 virtual ~Handler() {
375 SubscriptionStateMap::iterator it = subscription_state_map_.begin();
376 for (; it != subscription_state_map_.end(); ++it) {
377 delete it->second;
378 }
379 }
380
381 // 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)382 bool OnQuery(CefRefPtr<CefBrowser> browser,
383 CefRefPtr<CefFrame> frame,
384 int64 query_id,
385 const CefString& request,
386 bool persistent,
387 CefRefPtr<Callback> callback) override {
388 CEF_REQUIRE_UI_THREAD();
389
390 // Only handle messages from the test URL.
391 const std::string& url = frame->GetURL();
392 if (!test_runner::IsTestURL(url, kTestUrlPath))
393 return false;
394
395 // Parse |request| as a JSON dictionary.
396 CefRefPtr<CefDictionaryValue> request_dict = ParseJSON(request);
397 if (!request_dict) {
398 SendFailure(callback, kMessageFormatError, "Incorrect message format");
399 return true;
400 }
401
402 // Verify the "name" key.
403 if (!VerifyKey(request_dict, kNameKey, VTYPE_STRING, callback))
404 return true;
405
406 const std::string& message_name = request_dict->GetString(kNameKey);
407 if (message_name == kNameValueSubscribe) {
408 // Subscribe to notifications from the media router.
409
410 if (!persistent) {
411 SendFailure(callback, kMessageFormatError,
412 "Subscriptions must be persistent");
413 return true;
414 }
415
416 if (!CreateSubscription(browser, query_id, callback)) {
417 SendFailure(callback, kRequestFailedError,
418 "Browser is already subscribed");
419 }
420 return true;
421 }
422
423 // All other messages require a current subscription.
424 CefRefPtr<MediaObserver> media_observer =
425 GetMediaObserver(browser->GetIdentifier());
426 if (!media_observer) {
427 SendFailure(callback, kRequestFailedError,
428 "Browser is not currently subscribed");
429 }
430
431 if (message_name == kNameValueCreateRoute) {
432 // Create a new route.
433
434 // Verify the "source_urn" key.
435 if (!VerifyKey(request_dict, kSourceKey, VTYPE_STRING, callback))
436 return true;
437 // Verify the "sink_id" key.
438 if (!VerifyKey(request_dict, kSinkKey, VTYPE_STRING, callback))
439 return true;
440
441 const std::string& source_urn = request_dict->GetString(kSourceKey);
442 const std::string& sink_id = request_dict->GetString(kSinkKey);
443
444 // |callback| will be executed once the route is created.
445 std::string error;
446 if (!media_observer->CreateRoute(source_urn, sink_id, callback, error)) {
447 SendFailure(callback, kRequestFailedError, error);
448 }
449 return true;
450 } else if (message_name == kNameValueTerminateRoute) {
451 // Terminate an existing route.
452
453 // Verify the "route" key.
454 if (!VerifyKey(request_dict, kRouteKey, VTYPE_STRING, callback))
455 return true;
456
457 const std::string& route_id = request_dict->GetString(kRouteKey);
458 std::string error;
459 if (!media_observer->TerminateRoute(route_id, error)) {
460 SendFailure(callback, kRequestFailedError, error);
461 } else {
462 SendSuccessACK(callback);
463 }
464 return true;
465 } else if (message_name == kNameValueSendMessage) {
466 // Send a route message.
467
468 // Verify the "route_id" key.
469 if (!VerifyKey(request_dict, kRouteKey, VTYPE_STRING, callback))
470 return true;
471 // Verify the "message" key.
472 if (!VerifyKey(request_dict, kMessageKey, VTYPE_STRING, callback))
473 return true;
474
475 const std::string& route_id = request_dict->GetString(kRouteKey);
476 const std::string& message = request_dict->GetString(kMessageKey);
477 std::string error;
478 if (!media_observer->SendRouteMessage(route_id, message, error)) {
479 SendFailure(callback, kRequestFailedError, error);
480 } else {
481 SendSuccessACK(callback);
482 }
483 return true;
484 }
485
486 return false;
487 }
488
OnQueryCanceled(CefRefPtr<CefBrowser> browser,CefRefPtr<CefFrame> frame,int64 query_id)489 void OnQueryCanceled(CefRefPtr<CefBrowser> browser,
490 CefRefPtr<CefFrame> frame,
491 int64 query_id) override {
492 CEF_REQUIRE_UI_THREAD();
493 RemoveSubscription(browser->GetIdentifier(), query_id);
494 }
495
496 private:
SendSuccessACK(CefRefPtr<Callback> callback)497 static void SendSuccessACK(CefRefPtr<Callback> callback) {
498 CefRefPtr<CefDictionaryValue> result = CefDictionaryValue::Create();
499 result->SetBool(kSuccessKey, true);
500 SendSuccess(callback, result);
501 }
502
503 // Convert a JSON string to a dictionary value.
ParseJSON(const CefString & string)504 static CefRefPtr<CefDictionaryValue> ParseJSON(const CefString& string) {
505 CefRefPtr<CefValue> value = CefParseJSON(string, JSON_PARSER_RFC);
506 if (value.get() && value->GetType() == VTYPE_DICTIONARY)
507 return value->GetDictionary();
508 return nullptr;
509 }
510
511 // Verify that |key| exists in |dictionary| and has type |value_type|. Fails
512 // |callback| and returns false on failure.
VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,const char * key,cef_value_type_t value_type,CefRefPtr<Callback> callback)513 static bool VerifyKey(CefRefPtr<CefDictionaryValue> dictionary,
514 const char* key,
515 cef_value_type_t value_type,
516 CefRefPtr<Callback> callback) {
517 if (!dictionary->HasKey(key) || dictionary->GetType(key) != value_type) {
518 SendFailure(
519 callback, kMessageFormatError,
520 "Missing or incorrectly formatted message key: " + std::string(key));
521 return false;
522 }
523 return true;
524 }
525
526 // Subscription state associated with a single browser.
527 struct SubscriptionState {
528 int64 query_id;
529 CefRefPtr<MediaObserver> observer;
530 CefRefPtr<CefRegistration> registration;
531 };
532
CreateSubscription(CefRefPtr<CefBrowser> browser,int64 query_id,CefRefPtr<Callback> callback)533 bool CreateSubscription(CefRefPtr<CefBrowser> browser,
534 int64 query_id,
535 CefRefPtr<Callback> callback) {
536 const int browser_id = browser->GetIdentifier();
537 if (subscription_state_map_.find(browser_id) !=
538 subscription_state_map_.end()) {
539 // An subscription already exists for this browser.
540 return false;
541 }
542
543 CefRefPtr<CefMediaRouter> media_router =
544 browser->GetHost()->GetRequestContext()->GetMediaRouter(nullptr);
545
546 SubscriptionState* state = new SubscriptionState();
547 state->query_id = query_id;
548 state->observer = new MediaObserver(media_router, callback);
549 state->registration = media_router->AddObserver(state->observer);
550 subscription_state_map_.insert(std::make_pair(browser_id, state));
551
552 // Trigger sink and route callbacks.
553 media_router->NotifyCurrentSinks();
554 media_router->NotifyCurrentRoutes();
555
556 return true;
557 }
558
RemoveSubscription(int browser_id,int64 query_id)559 void RemoveSubscription(int browser_id, int64 query_id) {
560 SubscriptionStateMap::iterator it =
561 subscription_state_map_.find(browser_id);
562 if (it != subscription_state_map_.end() &&
563 it->second->query_id == query_id) {
564 delete it->second;
565 subscription_state_map_.erase(it);
566 }
567 }
568
GetMediaObserver(int browser_id)569 CefRefPtr<MediaObserver> GetMediaObserver(int browser_id) {
570 SubscriptionStateMap::const_iterator it =
571 subscription_state_map_.find(browser_id);
572 if (it != subscription_state_map_.end()) {
573 return it->second->observer;
574 }
575 return nullptr;
576 }
577
578 // Map of browser ID to SubscriptionState object.
579 typedef std::map<int, SubscriptionState*> SubscriptionStateMap;
580 SubscriptionStateMap subscription_state_map_;
581
582 DISALLOW_COPY_AND_ASSIGN(Handler);
583 };
584
585 } // namespace
586
CreateMessageHandlers(test_runner::MessageHandlerSet & handlers)587 void CreateMessageHandlers(test_runner::MessageHandlerSet& handlers) {
588 handlers.insert(new Handler());
589 }
590
591 } // namespace media_router_test
592 } // namespace client
593