| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include <stddef.h> | |
| 6 #include <stdint.h> | |
| 7 #include <string> | |
| 8 #include <utility> | |
| 9 | |
| 10 #include "base/bind.h" | |
| 11 #include "base/bind_helpers.h" | |
| 12 #include "base/macros.h" | |
| 13 #include "base/memory/ref_counted.h" | |
| 14 #include "base/memory/scoped_ptr.h" | |
| 15 #include "base/run_loop.h" | |
| 16 #include "base/synchronization/waitable_event.h" | |
| 17 #include "base/test/histogram_tester.h" | |
| 18 #include "base/thread_task_runner_handle.h" | |
| 19 #include "chrome/browser/media/router/issue.h" | |
| 20 #include "chrome/browser/media/router/media_route.h" | |
| 21 #include "chrome/browser/media/router/media_router_metrics.h" | |
| 22 #include "chrome/browser/media/router/media_router_mojo_test.h" | |
| 23 #include "chrome/browser/media/router/media_router_type_converters.h" | |
| 24 #include "chrome/browser/media/router/mock_media_router.h" | |
| 25 #include "chrome/browser/media/router/presentation_session_messages_observer.h" | |
| 26 #include "chrome/browser/media/router/test_helper.h" | |
| 27 #include "chrome/test/base/chrome_render_view_host_test_harness.h" | |
| 28 #include "chrome/test/base/testing_browser_process.h" | |
| 29 #include "chrome/test/base/testing_profile.h" | |
| 30 #include "components/version_info/version_info.h" | |
| 31 #include "extensions/browser/extension_registry.h" | |
| 32 #include "extensions/browser/process_manager.h" | |
| 33 #include "extensions/browser/process_manager_factory.h" | |
| 34 #include "extensions/common/extension.h" | |
| 35 #include "extensions/common/extension_builder.h" | |
| 36 #include "extensions/common/test_util.h" | |
| 37 #include "extensions/common/value_builder.h" | |
| 38 #include "media/base/gmock_callback_support.h" | |
| 39 #include "mojo/message_pump/message_pump_mojo.h" | |
| 40 #include "testing/gmock/include/gmock/gmock.h" | |
| 41 #include "testing/gtest/include/gtest/gtest.h" | |
| 42 | |
| 43 using testing::_; | |
| 44 using testing::AtMost; | |
| 45 using testing::Eq; | |
| 46 using testing::Invoke; | |
| 47 using testing::InvokeWithoutArgs; | |
| 48 using testing::IsEmpty; | |
| 49 using testing::Mock; | |
| 50 using testing::Not; | |
| 51 using testing::Pointee; | |
| 52 using testing::Return; | |
| 53 using testing::ReturnRef; | |
| 54 using testing::SaveArg; | |
| 55 | |
| 56 namespace media_router { | |
| 57 | |
| 58 using PresentationConnectionState = | |
| 59 interfaces::MediaRouter::PresentationConnectionState; | |
| 60 using PresentationConnectionCloseReason = | |
| 61 interfaces::MediaRouter::PresentationConnectionCloseReason; | |
| 62 | |
| 63 namespace { | |
| 64 | |
| 65 const char kDescription[] = "description"; | |
| 66 const char kError[] = "error"; | |
| 67 const char kMessage[] = "message"; | |
| 68 const char kSource[] = "source1"; | |
| 69 const char kSource2[] = "source2"; | |
| 70 const char kRouteId[] = "routeId"; | |
| 71 const char kRouteId2[] = "routeId2"; | |
| 72 const char kJoinableRouteId[] = "joinableRouteId"; | |
| 73 const char kJoinableRouteId2[] = "joinableRouteId2"; | |
| 74 const char kSinkId[] = "sink"; | |
| 75 const char kSinkId2[] = "sink2"; | |
| 76 const char kSinkName[] = "sinkName"; | |
| 77 const char kPresentationId[] = "presentationId"; | |
| 78 const char kOrigin[] = "http://origin/"; | |
| 79 const int kInvalidTabId = -1; | |
| 80 const uint8_t kBinaryMessage[] = {0x01, 0x02, 0x03, 0x04}; | |
| 81 const int kTimeoutMillis = 5 * 1000; | |
| 82 | |
| 83 bool ArePresentationSessionMessagesEqual( | |
| 84 const content::PresentationSessionMessage* expected, | |
| 85 const content::PresentationSessionMessage* actual) { | |
| 86 if (expected->type != actual->type) | |
| 87 return false; | |
| 88 | |
| 89 return expected->is_binary() ? *expected->data == *actual->data | |
| 90 : expected->message == actual->message; | |
| 91 } | |
| 92 | |
| 93 interfaces::IssuePtr CreateMojoIssue(const std::string& title) { | |
| 94 interfaces::IssuePtr mojoIssue = interfaces::Issue::New(); | |
| 95 mojoIssue->title = title; | |
| 96 mojoIssue->message = "msg"; | |
| 97 mojoIssue->route_id = ""; | |
| 98 mojoIssue->default_action = interfaces::Issue::ActionType::DISMISS; | |
| 99 mojoIssue->secondary_actions = | |
| 100 mojo::Array<interfaces::Issue::ActionType>::New(0); | |
| 101 mojoIssue->severity = interfaces::Issue::Severity::WARNING; | |
| 102 mojoIssue->is_blocking = false; | |
| 103 mojoIssue->help_url = ""; | |
| 104 return mojoIssue; | |
| 105 } | |
| 106 | |
| 107 } // namespace | |
| 108 | |
| 109 class RouteResponseCallbackHandler { | |
| 110 public: | |
| 111 void Invoke(const RouteRequestResult& result) { | |
| 112 DoInvoke(result.route(), result.presentation_id(), result.error(), | |
| 113 result.result_code()); | |
| 114 } | |
| 115 MOCK_METHOD4(DoInvoke, | |
| 116 void(const MediaRoute* route, | |
| 117 const std::string& presentation_id, | |
| 118 const std::string& error_text, | |
| 119 RouteRequestResult::ResultCode result_code)); | |
| 120 }; | |
| 121 | |
| 122 class SendMessageCallbackHandler { | |
| 123 public: | |
| 124 MOCK_METHOD1(Invoke, void(bool)); | |
| 125 }; | |
| 126 | |
| 127 class ListenForMessagesCallbackHandler { | |
| 128 public: | |
| 129 ListenForMessagesCallbackHandler( | |
| 130 ScopedVector<content::PresentationSessionMessage> expected_messages, | |
| 131 bool pass_ownership) | |
| 132 : expected_messages_(std::move(expected_messages)), | |
| 133 pass_ownership_(pass_ownership) {} | |
| 134 void Invoke(const ScopedVector<content::PresentationSessionMessage>& messages, | |
| 135 bool pass_ownership) { | |
| 136 InvokeObserver(); | |
| 137 EXPECT_EQ(pass_ownership_, pass_ownership); | |
| 138 EXPECT_EQ(messages.size(), expected_messages_.size()); | |
| 139 for (size_t i = 0; i < expected_messages_.size(); ++i) { | |
| 140 EXPECT_TRUE(ArePresentationSessionMessagesEqual(expected_messages_[i], | |
| 141 messages[i])); | |
| 142 } | |
| 143 } | |
| 144 | |
| 145 MOCK_METHOD0(InvokeObserver, void()); | |
| 146 | |
| 147 private: | |
| 148 ScopedVector<content::PresentationSessionMessage> expected_messages_; | |
| 149 bool pass_ownership_; | |
| 150 }; | |
| 151 | |
| 152 template <typename T> | |
| 153 void StoreAndRun(T* result, const base::Closure& closure, const T& result_val) { | |
| 154 *result = result_val; | |
| 155 closure.Run(); | |
| 156 } | |
| 157 | |
| 158 class MediaRouterMojoImplTest : public MediaRouterMojoTest { | |
| 159 public: | |
| 160 MediaRouterMojoImplTest() {} | |
| 161 ~MediaRouterMojoImplTest() override {} | |
| 162 }; | |
| 163 | |
| 164 // ProcessManager with a mocked method subset, for testing extension suspend | |
| 165 // handling. | |
| 166 class TestProcessManager : public extensions::ProcessManager { | |
| 167 public: | |
| 168 explicit TestProcessManager(content::BrowserContext* context) | |
| 169 : extensions::ProcessManager( | |
| 170 context, | |
| 171 context, | |
| 172 extensions::ExtensionRegistry::Get(context)) {} | |
| 173 ~TestProcessManager() override {} | |
| 174 | |
| 175 static scoped_ptr<KeyedService> Create(content::BrowserContext* context) { | |
| 176 return make_scoped_ptr(new TestProcessManager(context)); | |
| 177 } | |
| 178 | |
| 179 MOCK_METHOD1(IsEventPageSuspended, bool(const std::string& ext_id)); | |
| 180 | |
| 181 MOCK_METHOD2(WakeEventPage, | |
| 182 bool(const std::string& extension_id, | |
| 183 const base::Callback<void(bool)>& callback)); | |
| 184 | |
| 185 private: | |
| 186 DISALLOW_COPY_AND_ASSIGN(TestProcessManager); | |
| 187 }; | |
| 188 | |
| 189 // Mockable class for awaiting RegisterMediaRouteProvider callbacks. | |
| 190 class RegisterMediaRouteProviderHandler { | |
| 191 public: | |
| 192 MOCK_METHOD1(Invoke, void(const std::string& instance_id)); | |
| 193 }; | |
| 194 | |
| 195 TEST_F(MediaRouterMojoImplTest, CreateRoute) { | |
| 196 MediaSource media_source(kSource); | |
| 197 MediaRoute expected_route(kRouteId, media_source, kSinkId, "", false, "", | |
| 198 false); | |
| 199 | |
| 200 // Use a lambda function as an invocation target here to work around | |
| 201 // a limitation with GMock::Invoke that prevents it from using move-only types | |
| 202 // in runnable parameter lists. | |
| 203 EXPECT_CALL(mock_media_route_provider_, | |
| 204 CreateRoute(mojo::String(kSource), mojo::String(kSinkId), _, | |
| 205 mojo::String(kOrigin), kInvalidTabId, _, _, _)) | |
| 206 .WillOnce(Invoke( | |
| 207 [](const mojo::String& source, const mojo::String& sink, | |
| 208 const mojo::String& presentation_id, const mojo::String& origin, | |
| 209 int tab_id, int64_t timeout_millis, bool off_the_record, | |
| 210 const interfaces::MediaRouteProvider::CreateRouteCallback& cb) { | |
| 211 interfaces::MediaRoutePtr route = interfaces::MediaRoute::New(); | |
| 212 route->media_source = kSource; | |
| 213 route->media_sink_id = kSinkId; | |
| 214 route->media_route_id = kRouteId; | |
| 215 route->description = kDescription; | |
| 216 route->is_local = true; | |
| 217 route->for_display = true; | |
| 218 route->off_the_record = false; | |
| 219 cb.Run(std::move(route), mojo::String(), | |
| 220 interfaces::RouteRequestResultCode::OK); | |
| 221 })); | |
| 222 | |
| 223 base::RunLoop run_loop; | |
| 224 RouteResponseCallbackHandler handler; | |
| 225 EXPECT_CALL(handler, DoInvoke(Pointee(Equals(expected_route)), Not(""), "", | |
| 226 RouteRequestResult::OK)) | |
| 227 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 228 std::vector<MediaRouteResponseCallback> route_response_callbacks; | |
| 229 route_response_callbacks.push_back(base::Bind( | |
| 230 &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler))); | |
| 231 router()->CreateRoute( | |
| 232 kSource, kSinkId, GURL(kOrigin), nullptr, route_response_callbacks, | |
| 233 base::TimeDelta::FromMilliseconds(kTimeoutMillis), false); | |
| 234 run_loop.Run(); | |
| 235 } | |
| 236 | |
| 237 TEST_F(MediaRouterMojoImplTest, CreateRouteFails) { | |
| 238 EXPECT_CALL( | |
| 239 mock_media_route_provider_, | |
| 240 CreateRoute(mojo::String(kSource), mojo::String(kSinkId), _, | |
| 241 mojo::String(kOrigin), kInvalidTabId, kTimeoutMillis, _, _)) | |
| 242 .WillOnce(Invoke( | |
| 243 [](const mojo::String& source, const mojo::String& sink, | |
| 244 const mojo::String& presentation_id, const mojo::String& origin, | |
| 245 int tab_id, int64_t timeout_millis, bool off_the_record, | |
| 246 const interfaces::MediaRouteProvider::CreateRouteCallback& cb) { | |
| 247 cb.Run(interfaces::MediaRoutePtr(), mojo::String(kError), | |
| 248 interfaces::RouteRequestResultCode::TIMED_OUT); | |
| 249 })); | |
| 250 | |
| 251 RouteResponseCallbackHandler handler; | |
| 252 base::RunLoop run_loop; | |
| 253 EXPECT_CALL(handler, | |
| 254 DoInvoke(nullptr, "", kError, RouteRequestResult::TIMED_OUT)) | |
| 255 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 256 std::vector<MediaRouteResponseCallback> route_response_callbacks; | |
| 257 route_response_callbacks.push_back(base::Bind( | |
| 258 &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler))); | |
| 259 router()->CreateRoute( | |
| 260 kSource, kSinkId, GURL(kOrigin), nullptr, route_response_callbacks, | |
| 261 base::TimeDelta::FromMilliseconds(kTimeoutMillis), false); | |
| 262 run_loop.Run(); | |
| 263 } | |
| 264 | |
| 265 TEST_F(MediaRouterMojoImplTest, CreateRouteOffTheRecordMismatchFails) { | |
| 266 EXPECT_CALL(mock_media_route_provider_, | |
| 267 CreateRoute(mojo::String(kSource), mojo::String(kSinkId), _, | |
| 268 mojo::String(kOrigin), kInvalidTabId, kTimeoutMillis, | |
| 269 true, _)) | |
| 270 .WillOnce(Invoke( | |
| 271 [](const mojo::String& source, const mojo::String& sink, | |
| 272 const mojo::String& presentation_id, const mojo::String& origin, | |
| 273 int tab_id, int64_t timeout_millis, bool off_the_record, | |
| 274 const interfaces::MediaRouteProvider::CreateRouteCallback& cb) { | |
| 275 interfaces::MediaRoutePtr route = interfaces::MediaRoute::New(); | |
| 276 route->media_source = kSource; | |
| 277 route->media_sink_id = kSinkId; | |
| 278 route->media_route_id = kRouteId; | |
| 279 route->description = kDescription; | |
| 280 route->is_local = true; | |
| 281 route->for_display = true; | |
| 282 route->off_the_record = false; | |
| 283 cb.Run(std::move(route), mojo::String(), | |
| 284 interfaces::RouteRequestResultCode::OK); | |
| 285 })); | |
| 286 | |
| 287 RouteResponseCallbackHandler handler; | |
| 288 base::RunLoop run_loop; | |
| 289 std::string error( | |
| 290 "Mismatch in off the record status: request = 1, response = 0"); | |
| 291 EXPECT_CALL(handler, DoInvoke(nullptr, "", error, | |
| 292 RouteRequestResult::OFF_THE_RECORD_MISMATCH)) | |
| 293 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 294 std::vector<MediaRouteResponseCallback> route_response_callbacks; | |
| 295 route_response_callbacks.push_back(base::Bind( | |
| 296 &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler))); | |
| 297 router()->CreateRoute( | |
| 298 kSource, kSinkId, GURL(kOrigin), nullptr, route_response_callbacks, | |
| 299 base::TimeDelta::FromMilliseconds(kTimeoutMillis), true); | |
| 300 run_loop.Run(); | |
| 301 } | |
| 302 | |
| 303 TEST_F(MediaRouterMojoImplTest, OffTheRecordRoutesTerminatedOnProfileShutdown) { | |
| 304 EXPECT_CALL(mock_media_route_provider_, | |
| 305 CreateRoute(mojo::String(kSource), mojo::String(kSinkId), _, | |
| 306 mojo::String(kOrigin), kInvalidTabId, kTimeoutMillis, | |
| 307 true, _)) | |
| 308 .WillOnce(Invoke( | |
| 309 [](const mojo::String& source, const mojo::String& sink, | |
| 310 const mojo::String& presentation_id, const mojo::String& origin, | |
| 311 int tab_id, int64_t timeout_millis, bool off_the_record, | |
| 312 const interfaces::MediaRouteProvider::CreateRouteCallback& cb) { | |
| 313 interfaces::MediaRoutePtr route = interfaces::MediaRoute::New(); | |
| 314 route->media_source = kSource; | |
| 315 route->media_sink_id = kSinkId; | |
| 316 route->media_route_id = kRouteId; | |
| 317 route->description = kDescription; | |
| 318 route->is_local = true; | |
| 319 route->for_display = true; | |
| 320 route->off_the_record = true; | |
| 321 cb.Run(std::move(route), mojo::String(), | |
| 322 interfaces::RouteRequestResultCode::OK); | |
| 323 })); | |
| 324 base::RunLoop run_loop; | |
| 325 router()->CreateRoute(kSource, kSinkId, GURL(kOrigin), nullptr, | |
| 326 std::vector<MediaRouteResponseCallback>(), | |
| 327 base::TimeDelta::FromMilliseconds(kTimeoutMillis), | |
| 328 true); | |
| 329 | |
| 330 // TODO(mfoltz): Where possible, convert other tests to use RunUntilIdle | |
| 331 // instead of manually calling Run/Quit on the run loop. | |
| 332 run_loop.RunUntilIdle(); | |
| 333 | |
| 334 EXPECT_CALL(mock_media_route_provider_, | |
| 335 TerminateRoute(mojo::String(kRouteId))); | |
| 336 base::RunLoop run_loop2; | |
| 337 router()->OnOffTheRecordProfileShutdown(); | |
| 338 run_loop2.RunUntilIdle(); | |
| 339 } | |
| 340 | |
| 341 TEST_F(MediaRouterMojoImplTest, JoinRoute) { | |
| 342 MediaSource media_source(kSource); | |
| 343 MediaRoute expected_route(kRouteId, media_source, kSinkId, "", false, "", | |
| 344 false); | |
| 345 interfaces::MediaRoutePtr route = interfaces::MediaRoute::New(); | |
| 346 route->media_source = kSource; | |
| 347 route->media_sink_id = kSinkId; | |
| 348 route->media_route_id = kRouteId; | |
| 349 route->description = kDescription; | |
| 350 route->is_local = true; | |
| 351 route->for_display = true; | |
| 352 route->off_the_record = false; | |
| 353 | |
| 354 // Use a lambda function as an invocation target here to work around | |
| 355 // a limitation with GMock::Invoke that prevents it from using move-only types | |
| 356 // in runnable parameter lists. | |
| 357 EXPECT_CALL( | |
| 358 mock_media_route_provider_, | |
| 359 JoinRoute(mojo::String(kSource), mojo::String(kPresentationId), | |
| 360 mojo::String(kOrigin), kInvalidTabId, kTimeoutMillis, _, _)) | |
| 361 .WillOnce(Invoke([&route]( | |
| 362 const mojo::String& source, const mojo::String& presentation_id, | |
| 363 const mojo::String& origin, int tab_id, int64_t timeout_millis, | |
| 364 bool off_the_record, | |
| 365 const interfaces::MediaRouteProvider::JoinRouteCallback& cb) { | |
| 366 cb.Run(std::move(route), mojo::String(), | |
| 367 interfaces::RouteRequestResultCode::OK); | |
| 368 })); | |
| 369 | |
| 370 RouteResponseCallbackHandler handler; | |
| 371 base::RunLoop run_loop; | |
| 372 EXPECT_CALL(handler, DoInvoke(Pointee(Equals(expected_route)), Not(""), "", | |
| 373 RouteRequestResult::OK)) | |
| 374 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 375 std::vector<MediaRouteResponseCallback> route_response_callbacks; | |
| 376 route_response_callbacks.push_back(base::Bind( | |
| 377 &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler))); | |
| 378 router()->JoinRoute(kSource, kPresentationId, GURL(kOrigin), nullptr, | |
| 379 route_response_callbacks, | |
| 380 base::TimeDelta::FromMilliseconds(kTimeoutMillis), false); | |
| 381 run_loop.Run(); | |
| 382 } | |
| 383 | |
| 384 TEST_F(MediaRouterMojoImplTest, JoinRouteFails) { | |
| 385 EXPECT_CALL( | |
| 386 mock_media_route_provider_, | |
| 387 JoinRoute(mojo::String(kSource), mojo::String(kPresentationId), | |
| 388 mojo::String(kOrigin), kInvalidTabId, kTimeoutMillis, _, _)) | |
| 389 .WillOnce(Invoke( | |
| 390 [](const mojo::String& source, const mojo::String& presentation_id, | |
| 391 const mojo::String& origin, int tab_id, int64_t timeout_millis, | |
| 392 bool off_the_record, | |
| 393 const interfaces::MediaRouteProvider::JoinRouteCallback& cb) { | |
| 394 cb.Run(interfaces::MediaRoutePtr(), mojo::String(kError), | |
| 395 interfaces::RouteRequestResultCode::TIMED_OUT); | |
| 396 })); | |
| 397 | |
| 398 RouteResponseCallbackHandler handler; | |
| 399 base::RunLoop run_loop; | |
| 400 EXPECT_CALL(handler, | |
| 401 DoInvoke(nullptr, "", kError, RouteRequestResult::TIMED_OUT)) | |
| 402 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 403 std::vector<MediaRouteResponseCallback> route_response_callbacks; | |
| 404 route_response_callbacks.push_back(base::Bind( | |
| 405 &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler))); | |
| 406 router()->JoinRoute(kSource, kPresentationId, GURL(kOrigin), nullptr, | |
| 407 route_response_callbacks, | |
| 408 base::TimeDelta::FromMilliseconds(kTimeoutMillis), false); | |
| 409 run_loop.Run(); | |
| 410 } | |
| 411 | |
| 412 TEST_F(MediaRouterMojoImplTest, JoinRouteOffTheRecordMismatchFails) { | |
| 413 interfaces::MediaRoutePtr route = interfaces::MediaRoute::New(); | |
| 414 route->media_source = kSource; | |
| 415 route->media_sink_id = kSinkId; | |
| 416 route->media_route_id = kRouteId; | |
| 417 route->description = kDescription; | |
| 418 route->is_local = true; | |
| 419 route->for_display = true; | |
| 420 route->off_the_record = false; | |
| 421 | |
| 422 // Use a lambda function as an invocation target here to work around | |
| 423 // a limitation with GMock::Invoke that prevents it from using move-only types | |
| 424 // in runnable parameter lists. | |
| 425 EXPECT_CALL( | |
| 426 mock_media_route_provider_, | |
| 427 JoinRoute(mojo::String(kSource), mojo::String(kPresentationId), | |
| 428 mojo::String(kOrigin), kInvalidTabId, kTimeoutMillis, true, _)) | |
| 429 .WillOnce(Invoke([&route]( | |
| 430 const mojo::String& source, const mojo::String& presentation_id, | |
| 431 const mojo::String& origin, int tab_id, int64_t timeout_millis, | |
| 432 bool off_the_record, | |
| 433 const interfaces::MediaRouteProvider::JoinRouteCallback& cb) { | |
| 434 cb.Run(std::move(route), mojo::String(), | |
| 435 interfaces::RouteRequestResultCode::OK); | |
| 436 })); | |
| 437 | |
| 438 RouteResponseCallbackHandler handler; | |
| 439 base::RunLoop run_loop; | |
| 440 std::string error( | |
| 441 "Mismatch in off the record status: request = 1, response = 0"); | |
| 442 EXPECT_CALL(handler, DoInvoke(nullptr, "", error, | |
| 443 RouteRequestResult::OFF_THE_RECORD_MISMATCH)) | |
| 444 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 445 std::vector<MediaRouteResponseCallback> route_response_callbacks; | |
| 446 route_response_callbacks.push_back(base::Bind( | |
| 447 &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler))); | |
| 448 router()->JoinRoute(kSource, kPresentationId, GURL(kOrigin), nullptr, | |
| 449 route_response_callbacks, | |
| 450 base::TimeDelta::FromMilliseconds(kTimeoutMillis), true); | |
| 451 run_loop.Run(); | |
| 452 } | |
| 453 | |
| 454 TEST_F(MediaRouterMojoImplTest, ConnectRouteByRouteId) { | |
| 455 MediaSource media_source(kSource); | |
| 456 MediaRoute expected_route(kRouteId, media_source, kSinkId, "", false, "", | |
| 457 false); | |
| 458 expected_route.set_off_the_record(false); | |
| 459 interfaces::MediaRoutePtr route = interfaces::MediaRoute::New(); | |
| 460 route->media_source = kSource; | |
| 461 route->media_sink_id = kSinkId; | |
| 462 route->media_route_id = kRouteId; | |
| 463 route->description = kDescription; | |
| 464 route->is_local = true; | |
| 465 route->for_display = true; | |
| 466 route->off_the_record = false; | |
| 467 | |
| 468 // Use a lambda function as an invocation target here to work around | |
| 469 // a limitation with GMock::Invoke that prevents it from using move-only types | |
| 470 // in runnable parameter lists. | |
| 471 EXPECT_CALL( | |
| 472 mock_media_route_provider_, | |
| 473 ConnectRouteByRouteId(mojo::String(kSource), mojo::String(kRouteId), _, | |
| 474 mojo::String(kOrigin), kInvalidTabId, | |
| 475 kTimeoutMillis, false, _)) | |
| 476 .WillOnce(Invoke([&route]( | |
| 477 const mojo::String& source, const mojo::String& route_id, | |
| 478 const mojo::String& presentation_id, const mojo::String& origin, | |
| 479 int tab_id, int64_t timeout_millis, bool off_the_record, | |
| 480 const interfaces::MediaRouteProvider::JoinRouteCallback& cb) { | |
| 481 cb.Run(std::move(route), mojo::String(), | |
| 482 interfaces::RouteRequestResultCode::OK); | |
| 483 })); | |
| 484 | |
| 485 RouteResponseCallbackHandler handler; | |
| 486 base::RunLoop run_loop; | |
| 487 EXPECT_CALL(handler, DoInvoke(Pointee(Equals(expected_route)), Not(""), "", | |
| 488 RouteRequestResult::OK)) | |
| 489 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 490 std::vector<MediaRouteResponseCallback> route_response_callbacks; | |
| 491 route_response_callbacks.push_back(base::Bind( | |
| 492 &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler))); | |
| 493 router()->ConnectRouteByRouteId( | |
| 494 kSource, kRouteId, GURL(kOrigin), nullptr, route_response_callbacks, | |
| 495 base::TimeDelta::FromMilliseconds(kTimeoutMillis), false); | |
| 496 run_loop.Run(); | |
| 497 } | |
| 498 | |
| 499 TEST_F(MediaRouterMojoImplTest, ConnectRouteByRouteIdFails) { | |
| 500 EXPECT_CALL( | |
| 501 mock_media_route_provider_, | |
| 502 ConnectRouteByRouteId(mojo::String(kSource), mojo::String(kRouteId), _, | |
| 503 mojo::String(kOrigin), kInvalidTabId, | |
| 504 kTimeoutMillis, true, _)) | |
| 505 .WillOnce(Invoke( | |
| 506 [](const mojo::String& source, const mojo::String& route_id, | |
| 507 const mojo::String& presentation_id, const mojo::String& origin, | |
| 508 int tab_id, int64_t timeout_millis, bool off_the_record, | |
| 509 const interfaces::MediaRouteProvider::JoinRouteCallback& cb) { | |
| 510 cb.Run(interfaces::MediaRoutePtr(), mojo::String(kError), | |
| 511 interfaces::RouteRequestResultCode::TIMED_OUT); | |
| 512 })); | |
| 513 | |
| 514 RouteResponseCallbackHandler handler; | |
| 515 base::RunLoop run_loop; | |
| 516 EXPECT_CALL(handler, | |
| 517 DoInvoke(nullptr, "", kError, RouteRequestResult::TIMED_OUT)) | |
| 518 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 519 std::vector<MediaRouteResponseCallback> route_response_callbacks; | |
| 520 route_response_callbacks.push_back(base::Bind( | |
| 521 &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler))); | |
| 522 router()->ConnectRouteByRouteId( | |
| 523 kSource, kRouteId, GURL(kOrigin), nullptr, route_response_callbacks, | |
| 524 base::TimeDelta::FromMilliseconds(kTimeoutMillis), true); | |
| 525 run_loop.Run(); | |
| 526 } | |
| 527 | |
| 528 TEST_F(MediaRouterMojoImplTest, ConnectRouteByIdOffTheRecordMismatchFails) { | |
| 529 interfaces::MediaRoutePtr route = interfaces::MediaRoute::New(); | |
| 530 route->media_source = kSource; | |
| 531 route->media_sink_id = kSinkId; | |
| 532 route->media_route_id = kRouteId; | |
| 533 route->description = kDescription; | |
| 534 route->is_local = true; | |
| 535 route->for_display = true; | |
| 536 route->off_the_record = false; | |
| 537 | |
| 538 // Use a lambda function as an invocation target here to work around | |
| 539 // a limitation with GMock::Invoke that prevents it from using move-only types | |
| 540 // in runnable parameter lists. | |
| 541 EXPECT_CALL( | |
| 542 mock_media_route_provider_, | |
| 543 ConnectRouteByRouteId(mojo::String(kSource), mojo::String(kRouteId), _, | |
| 544 mojo::String(kOrigin), kInvalidTabId, | |
| 545 kTimeoutMillis, true, _)) | |
| 546 .WillOnce(Invoke([&route]( | |
| 547 const mojo::String& source, const mojo::String& route_id, | |
| 548 const mojo::String& presentation_id, const mojo::String& origin, | |
| 549 int tab_id, int64_t timeout_millis, bool off_the_record, | |
| 550 const interfaces::MediaRouteProvider::JoinRouteCallback& cb) { | |
| 551 cb.Run(std::move(route), mojo::String(), | |
| 552 interfaces::RouteRequestResultCode::OK); | |
| 553 })); | |
| 554 | |
| 555 RouteResponseCallbackHandler handler; | |
| 556 base::RunLoop run_loop; | |
| 557 std::string error( | |
| 558 "Mismatch in off the record status: request = 1, response = 0"); | |
| 559 EXPECT_CALL(handler, DoInvoke(nullptr, "", error, | |
| 560 RouteRequestResult::OFF_THE_RECORD_MISMATCH)) | |
| 561 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 562 std::vector<MediaRouteResponseCallback> route_response_callbacks; | |
| 563 route_response_callbacks.push_back(base::Bind( | |
| 564 &RouteResponseCallbackHandler::Invoke, base::Unretained(&handler))); | |
| 565 router()->ConnectRouteByRouteId( | |
| 566 kSource, kRouteId, GURL(kOrigin), nullptr, route_response_callbacks, | |
| 567 base::TimeDelta::FromMilliseconds(kTimeoutMillis), true); | |
| 568 run_loop.Run(); | |
| 569 } | |
| 570 | |
| 571 TEST_F(MediaRouterMojoImplTest, DetachRoute) { | |
| 572 base::RunLoop run_loop; | |
| 573 EXPECT_CALL(mock_media_route_provider_, DetachRoute(mojo::String(kRouteId))) | |
| 574 .WillOnce(InvokeWithoutArgs([&run_loop]() { | |
| 575 run_loop.Quit(); | |
| 576 })); | |
| 577 router()->DetachRoute(kRouteId); | |
| 578 run_loop.Run(); | |
| 579 } | |
| 580 | |
| 581 TEST_F(MediaRouterMojoImplTest, TerminateRoute) { | |
| 582 base::RunLoop run_loop; | |
| 583 EXPECT_CALL(mock_media_route_provider_, | |
| 584 TerminateRoute(mojo::String(kRouteId))) | |
| 585 .WillOnce(InvokeWithoutArgs([&run_loop]() { | |
| 586 run_loop.Quit(); | |
| 587 })); | |
| 588 router()->TerminateRoute(kRouteId); | |
| 589 run_loop.Run(); | |
| 590 } | |
| 591 | |
| 592 TEST_F(MediaRouterMojoImplTest, HandleIssue) { | |
| 593 MockIssuesObserver issue_observer1(router()); | |
| 594 MockIssuesObserver issue_observer2(router()); | |
| 595 issue_observer1.RegisterObserver(); | |
| 596 issue_observer2.RegisterObserver(); | |
| 597 | |
| 598 interfaces::IssuePtr mojo_issue1 = CreateMojoIssue("title 1"); | |
| 599 const Issue& expected_issue1 = mojo_issue1.To<Issue>(); | |
| 600 | |
| 601 const Issue* issue; | |
| 602 EXPECT_CALL(issue_observer1, | |
| 603 OnIssueUpdated(Pointee(EqualsIssue(expected_issue1)))) | |
| 604 .WillOnce(SaveArg<0>(&issue)); | |
| 605 base::RunLoop run_loop; | |
| 606 EXPECT_CALL(issue_observer2, | |
| 607 OnIssueUpdated(Pointee(EqualsIssue(expected_issue1)))) | |
| 608 .WillOnce(InvokeWithoutArgs([&run_loop]() { | |
| 609 run_loop.Quit(); | |
| 610 })); | |
| 611 media_router_proxy_->OnIssue(std::move(mojo_issue1)); | |
| 612 run_loop.Run(); | |
| 613 | |
| 614 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&issue_observer1)); | |
| 615 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&issue_observer2)); | |
| 616 | |
| 617 EXPECT_CALL(issue_observer1, OnIssueUpdated(nullptr)); | |
| 618 EXPECT_CALL(issue_observer2, OnIssueUpdated(nullptr)); | |
| 619 | |
| 620 router()->ClearIssue(issue->id()); | |
| 621 | |
| 622 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&issue_observer1)); | |
| 623 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&issue_observer2)); | |
| 624 router()->UnregisterIssuesObserver(&issue_observer1); | |
| 625 interfaces::IssuePtr mojo_issue2 = CreateMojoIssue("title 2"); | |
| 626 const Issue& expected_issue2 = mojo_issue2.To<Issue>(); | |
| 627 | |
| 628 EXPECT_CALL(issue_observer2, | |
| 629 OnIssueUpdated(Pointee(EqualsIssue(expected_issue2)))); | |
| 630 router()->AddIssue(expected_issue2); | |
| 631 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&issue_observer2)); | |
| 632 | |
| 633 EXPECT_CALL(issue_observer2, OnIssueUpdated(nullptr)); | |
| 634 router()->ClearIssue(issue->id()); | |
| 635 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&issue_observer2)); | |
| 636 | |
| 637 base::RunLoop run_loop2; | |
| 638 EXPECT_CALL(issue_observer2, | |
| 639 OnIssueUpdated(Pointee(EqualsIssue(expected_issue2)))) | |
| 640 .WillOnce(InvokeWithoutArgs([&run_loop2]() { | |
| 641 run_loop2.Quit(); | |
| 642 })); | |
| 643 media_router_proxy_->OnIssue(std::move(mojo_issue2)); | |
| 644 run_loop2.Run(); | |
| 645 | |
| 646 issue_observer1.UnregisterObserver(); | |
| 647 issue_observer2.UnregisterObserver(); | |
| 648 } | |
| 649 | |
| 650 TEST_F(MediaRouterMojoImplTest, RegisterAndUnregisterMediaSinksObserver) { | |
| 651 router()->OnSinkAvailabilityUpdated( | |
| 652 interfaces::MediaRouter::SinkAvailability::AVAILABLE); | |
| 653 MediaSource media_source(kSource); | |
| 654 GURL origin("https://google.com"); | |
| 655 | |
| 656 // These should only be called once even if there is more than one observer | |
| 657 // for a given source. | |
| 658 EXPECT_CALL(mock_media_route_provider_, | |
| 659 StartObservingMediaSinks(mojo::String(kSource))); | |
| 660 EXPECT_CALL(mock_media_route_provider_, | |
| 661 StartObservingMediaSinks(mojo::String(kSource2))); | |
| 662 | |
| 663 scoped_ptr<MockMediaSinksObserver> sinks_observer( | |
| 664 new MockMediaSinksObserver(router(), media_source, origin)); | |
| 665 EXPECT_TRUE(sinks_observer->Init()); | |
| 666 scoped_ptr<MockMediaSinksObserver> extra_sinks_observer( | |
| 667 new MockMediaSinksObserver(router(), media_source, origin)); | |
| 668 EXPECT_TRUE(extra_sinks_observer->Init()); | |
| 669 scoped_ptr<MockMediaSinksObserver> unrelated_sinks_observer( | |
| 670 new MockMediaSinksObserver(router(), MediaSource(kSource2), origin)); | |
| 671 EXPECT_TRUE(unrelated_sinks_observer->Init()); | |
| 672 ProcessEventLoop(); | |
| 673 | |
| 674 std::vector<MediaSink> expected_sinks; | |
| 675 expected_sinks.push_back( | |
| 676 MediaSink(kSinkId, kSinkName, MediaSink::IconType::CAST)); | |
| 677 expected_sinks.push_back( | |
| 678 MediaSink(kSinkId2, kSinkName, MediaSink::IconType::CAST)); | |
| 679 | |
| 680 mojo::Array<interfaces::MediaSinkPtr> mojo_sinks(2); | |
| 681 mojo_sinks[0] = interfaces::MediaSink::New(); | |
| 682 mojo_sinks[0]->sink_id = kSinkId; | |
| 683 mojo_sinks[0]->name = kSinkName; | |
| 684 mojo_sinks[0]->icon_type = | |
| 685 media_router::interfaces::MediaSink::IconType::CAST; | |
| 686 mojo_sinks[1] = interfaces::MediaSink::New(); | |
| 687 mojo_sinks[1]->sink_id = kSinkId2; | |
| 688 mojo_sinks[1]->name = kSinkName; | |
| 689 mojo_sinks[1]->icon_type = | |
| 690 media_router::interfaces::MediaSink::IconType::CAST; | |
| 691 | |
| 692 mojo::Array<mojo::String> mojo_origins(1); | |
| 693 mojo_origins[0] = origin.spec(); | |
| 694 | |
| 695 base::RunLoop run_loop; | |
| 696 EXPECT_CALL(*sinks_observer, OnSinksReceived(SequenceEquals(expected_sinks))); | |
| 697 EXPECT_CALL(*extra_sinks_observer, | |
| 698 OnSinksReceived(SequenceEquals(expected_sinks))) | |
| 699 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 700 media_router_proxy_->OnSinksReceived(media_source.id(), std::move(mojo_sinks), | |
| 701 std::move(mojo_origins)); | |
| 702 run_loop.Run(); | |
| 703 | |
| 704 // Since the MediaRouterMojoImpl has already received results for | |
| 705 // |media_source|, return cached results to observers that are subsequently | |
| 706 // registered. | |
| 707 scoped_ptr<MockMediaSinksObserver> cached_sinks_observer( | |
| 708 new MockMediaSinksObserver(router(), media_source, origin)); | |
| 709 EXPECT_CALL(*cached_sinks_observer, | |
| 710 OnSinksReceived(SequenceEquals(expected_sinks))); | |
| 711 EXPECT_TRUE(cached_sinks_observer->Init()); | |
| 712 | |
| 713 // Different origin from cached result. Empty list will be returned. | |
| 714 scoped_ptr<MockMediaSinksObserver> cached_sinks_observer2( | |
| 715 new MockMediaSinksObserver(router(), media_source, | |
| 716 GURL("https://youtube.com"))); | |
| 717 EXPECT_CALL(*cached_sinks_observer2, OnSinksReceived(IsEmpty())); | |
| 718 EXPECT_TRUE(cached_sinks_observer2->Init()); | |
| 719 | |
| 720 base::RunLoop run_loop2; | |
| 721 EXPECT_CALL(mock_media_route_provider_, | |
| 722 StopObservingMediaSinks(mojo::String(kSource))); | |
| 723 EXPECT_CALL(mock_media_route_provider_, | |
| 724 StopObservingMediaSinks(mojo::String(kSource2))) | |
| 725 .WillOnce(InvokeWithoutArgs([&run_loop2]() { | |
| 726 run_loop2.Quit(); | |
| 727 })); | |
| 728 sinks_observer.reset(); | |
| 729 extra_sinks_observer.reset(); | |
| 730 unrelated_sinks_observer.reset(); | |
| 731 cached_sinks_observer.reset(); | |
| 732 cached_sinks_observer2.reset(); | |
| 733 run_loop2.Run(); | |
| 734 } | |
| 735 | |
| 736 TEST_F(MediaRouterMojoImplTest, | |
| 737 RegisterMediaSinksObserverWithAvailabilityChange) { | |
| 738 GURL origin("https://google.com"); | |
| 739 | |
| 740 // When availability is UNAVAILABLE, no calls should be made to MRPM. | |
| 741 router()->OnSinkAvailabilityUpdated( | |
| 742 interfaces::MediaRouter::SinkAvailability::UNAVAILABLE); | |
| 743 MediaSource media_source(kSource); | |
| 744 scoped_ptr<MockMediaSinksObserver> sinks_observer( | |
| 745 new MockMediaSinksObserver(router(), media_source, origin)); | |
| 746 EXPECT_CALL(*sinks_observer, OnSinksReceived(IsEmpty())); | |
| 747 EXPECT_TRUE(sinks_observer->Init()); | |
| 748 MediaSource media_source2(kSource2); | |
| 749 scoped_ptr<MockMediaSinksObserver> sinks_observer2( | |
| 750 new MockMediaSinksObserver(router(), media_source2, origin)); | |
| 751 EXPECT_CALL(*sinks_observer2, OnSinksReceived(IsEmpty())); | |
| 752 EXPECT_TRUE(sinks_observer2->Init()); | |
| 753 EXPECT_CALL(mock_media_route_provider_, | |
| 754 StartObservingMediaSinks(mojo::String(kSource))) | |
| 755 .Times(0); | |
| 756 EXPECT_CALL(mock_media_route_provider_, | |
| 757 StartObservingMediaSinks(mojo::String(kSource2))) | |
| 758 .Times(0); | |
| 759 ProcessEventLoop(); | |
| 760 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&mock_media_route_provider_)); | |
| 761 | |
| 762 // When availability transitions AVAILABLE, existing sink queries should be | |
| 763 // sent to MRPM. | |
| 764 router()->OnSinkAvailabilityUpdated( | |
| 765 interfaces::MediaRouter::SinkAvailability::AVAILABLE); | |
| 766 EXPECT_CALL(mock_media_route_provider_, | |
| 767 StartObservingMediaSinks(mojo::String(kSource))) | |
| 768 .Times(1); | |
| 769 EXPECT_CALL(mock_media_route_provider_, | |
| 770 StartObservingMediaSinks(mojo::String(kSource2))) | |
| 771 .Times(1); | |
| 772 ProcessEventLoop(); | |
| 773 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&mock_media_route_provider_)); | |
| 774 | |
| 775 // No change in availability status; no calls should be made to MRPM. | |
| 776 router()->OnSinkAvailabilityUpdated( | |
| 777 interfaces::MediaRouter::SinkAvailability::AVAILABLE); | |
| 778 EXPECT_CALL(mock_media_route_provider_, | |
| 779 StartObservingMediaSinks(mojo::String(kSource))) | |
| 780 .Times(0); | |
| 781 EXPECT_CALL(mock_media_route_provider_, | |
| 782 StartObservingMediaSinks(mojo::String(kSource2))) | |
| 783 .Times(0); | |
| 784 ProcessEventLoop(); | |
| 785 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&mock_media_route_provider_)); | |
| 786 | |
| 787 // When availability is UNAVAILABLE, queries are already removed from MRPM. | |
| 788 // Unregistering observer won't result in call to MRPM to remove query. | |
| 789 router()->OnSinkAvailabilityUpdated( | |
| 790 interfaces::MediaRouter::SinkAvailability::UNAVAILABLE); | |
| 791 EXPECT_CALL(mock_media_route_provider_, | |
| 792 StopObservingMediaSinks(mojo::String(kSource))) | |
| 793 .Times(0); | |
| 794 sinks_observer.reset(); | |
| 795 ProcessEventLoop(); | |
| 796 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&mock_media_route_provider_)); | |
| 797 | |
| 798 // When availability is AVAILABLE, call is made to MRPM to remove query when | |
| 799 // observer is unregistered. | |
| 800 router()->OnSinkAvailabilityUpdated( | |
| 801 interfaces::MediaRouter::SinkAvailability::AVAILABLE); | |
| 802 EXPECT_CALL(mock_media_route_provider_, | |
| 803 StopObservingMediaSinks(mojo::String(kSource2))); | |
| 804 sinks_observer2.reset(); | |
| 805 ProcessEventLoop(); | |
| 806 } | |
| 807 | |
| 808 TEST_F(MediaRouterMojoImplTest, RegisterAndUnregisterMediaRoutesObserver) { | |
| 809 MockMediaRouter mock_router; | |
| 810 MediaSource media_source(kSource); | |
| 811 MediaSource different_media_source(kSource2); | |
| 812 EXPECT_CALL(mock_media_route_provider_, | |
| 813 StartObservingMediaRoutes(mojo::String(media_source.id()))).Times(2); | |
| 814 EXPECT_CALL(mock_media_route_provider_, | |
| 815 StartObservingMediaRoutes( | |
| 816 mojo::String(different_media_source.id()))).Times(1); | |
| 817 | |
| 818 MediaRoutesObserver* observer_captured; | |
| 819 EXPECT_CALL(mock_router, RegisterMediaRoutesObserver(_)) | |
| 820 .Times(3) | |
| 821 .WillRepeatedly(SaveArg<0>(&observer_captured)); | |
| 822 MockMediaRoutesObserver routes_observer(&mock_router, media_source.id()); | |
| 823 EXPECT_EQ(observer_captured, &routes_observer); | |
| 824 MockMediaRoutesObserver extra_routes_observer(&mock_router, | |
| 825 media_source.id()); | |
| 826 EXPECT_EQ(observer_captured, &extra_routes_observer); | |
| 827 MockMediaRoutesObserver different_routes_observer(&mock_router, | |
| 828 different_media_source.id()); | |
| 829 EXPECT_EQ(observer_captured, &different_routes_observer); | |
| 830 router()->RegisterMediaRoutesObserver(&routes_observer); | |
| 831 router()->RegisterMediaRoutesObserver(&extra_routes_observer); | |
| 832 router()->RegisterMediaRoutesObserver(&different_routes_observer); | |
| 833 | |
| 834 std::vector<MediaRoute> expected_routes; | |
| 835 expected_routes.push_back(MediaRoute(kRouteId, media_source, kSinkId, | |
| 836 kDescription, false, "", false)); | |
| 837 MediaRoute incognito_expected_route(kRouteId2, media_source, kSinkId, | |
| 838 kDescription, false, "", false); | |
| 839 incognito_expected_route.set_off_the_record(true); | |
| 840 expected_routes.push_back(incognito_expected_route); | |
| 841 std::vector<MediaRoute::Id> expected_joinable_route_ids; | |
| 842 expected_joinable_route_ids.push_back(kJoinableRouteId); | |
| 843 expected_joinable_route_ids.push_back(kJoinableRouteId2); | |
| 844 | |
| 845 mojo::Array<mojo::String> mojo_joinable_routes(2); | |
| 846 mojo_joinable_routes[0] = kJoinableRouteId; | |
| 847 mojo_joinable_routes[1] = kJoinableRouteId2; | |
| 848 | |
| 849 mojo::Array<interfaces::MediaRoutePtr> mojo_routes(2); | |
| 850 mojo_routes[0] = interfaces::MediaRoute::New(); | |
| 851 mojo_routes[0]->media_route_id = kRouteId; | |
| 852 mojo_routes[0]->media_source = kSource; | |
| 853 mojo_routes[0]->media_sink_id = kSinkId; | |
| 854 mojo_routes[0]->description = kDescription; | |
| 855 mojo_routes[0]->is_local = false; | |
| 856 mojo_routes[0]->off_the_record = false; | |
| 857 mojo_routes[1] = interfaces::MediaRoute::New(); | |
| 858 mojo_routes[1]->media_route_id = kRouteId2; | |
| 859 mojo_routes[1]->media_source = kSource; | |
| 860 mojo_routes[1]->media_sink_id = kSinkId; | |
| 861 mojo_routes[1]->description = kDescription; | |
| 862 mojo_routes[1]->is_local = false; | |
| 863 mojo_routes[1]->off_the_record = true; | |
| 864 | |
| 865 EXPECT_CALL(routes_observer, | |
| 866 OnRoutesUpdated(SequenceEquals(expected_routes), | |
| 867 expected_joinable_route_ids)); | |
| 868 EXPECT_CALL(extra_routes_observer, | |
| 869 OnRoutesUpdated(SequenceEquals(expected_routes), | |
| 870 expected_joinable_route_ids)); | |
| 871 EXPECT_CALL(different_routes_observer, | |
| 872 OnRoutesUpdated(SequenceEquals(expected_routes), | |
| 873 expected_joinable_route_ids)).Times(0); | |
| 874 media_router_proxy_->OnRoutesUpdated(std::move(mojo_routes), | |
| 875 media_source.id(), | |
| 876 std::move(mojo_joinable_routes)); | |
| 877 ProcessEventLoop(); | |
| 878 | |
| 879 EXPECT_CALL(mock_router, UnregisterMediaRoutesObserver(&routes_observer)); | |
| 880 EXPECT_CALL(mock_router, | |
| 881 UnregisterMediaRoutesObserver(&extra_routes_observer)); | |
| 882 EXPECT_CALL(mock_router, | |
| 883 UnregisterMediaRoutesObserver(&different_routes_observer)); | |
| 884 router()->UnregisterMediaRoutesObserver(&routes_observer); | |
| 885 router()->UnregisterMediaRoutesObserver(&extra_routes_observer); | |
| 886 router()->UnregisterMediaRoutesObserver(&different_routes_observer); | |
| 887 EXPECT_CALL(mock_media_route_provider_, | |
| 888 StopObservingMediaRoutes( | |
| 889 mojo::String(media_source.id()))).Times(1); | |
| 890 EXPECT_CALL(mock_media_route_provider_, | |
| 891 StopObservingMediaRoutes( | |
| 892 mojo::String(different_media_source.id()))); | |
| 893 ProcessEventLoop(); | |
| 894 } | |
| 895 | |
| 896 TEST_F(MediaRouterMojoImplTest, SendRouteMessage) { | |
| 897 EXPECT_CALL( | |
| 898 mock_media_route_provider_, | |
| 899 SendRouteMessage(mojo::String(kRouteId), mojo::String(kMessage), _)) | |
| 900 .WillOnce(Invoke([]( | |
| 901 const MediaRoute::Id& route_id, const std::string& message, | |
| 902 const interfaces::MediaRouteProvider::SendRouteMessageCallback& cb) { | |
| 903 cb.Run(true); | |
| 904 })); | |
| 905 | |
| 906 base::RunLoop run_loop; | |
| 907 SendMessageCallbackHandler handler; | |
| 908 EXPECT_CALL(handler, Invoke(true)) | |
| 909 .WillOnce(InvokeWithoutArgs([&run_loop]() { | |
| 910 run_loop.Quit(); | |
| 911 })); | |
| 912 router()->SendRouteMessage(kRouteId, kMessage, | |
| 913 base::Bind(&SendMessageCallbackHandler::Invoke, | |
| 914 base::Unretained(&handler))); | |
| 915 run_loop.Run(); | |
| 916 } | |
| 917 | |
| 918 TEST_F(MediaRouterMojoImplTest, SendRouteBinaryMessage) { | |
| 919 scoped_ptr<std::vector<uint8_t>> expected_binary_data( | |
| 920 new std::vector<uint8_t>(kBinaryMessage, | |
| 921 kBinaryMessage + arraysize(kBinaryMessage))); | |
| 922 | |
| 923 EXPECT_CALL(mock_media_route_provider_, | |
| 924 SendRouteBinaryMessageInternal(mojo::String(kRouteId), _, _)) | |
| 925 .WillOnce(Invoke([]( | |
| 926 const MediaRoute::Id& route_id, const std::vector<uint8_t>& data, | |
| 927 const interfaces::MediaRouteProvider::SendRouteMessageCallback& cb) { | |
| 928 EXPECT_EQ( | |
| 929 0, memcmp(kBinaryMessage, &(data[0]), arraysize(kBinaryMessage))); | |
| 930 cb.Run(true); | |
| 931 })); | |
| 932 | |
| 933 base::RunLoop run_loop; | |
| 934 SendMessageCallbackHandler handler; | |
| 935 EXPECT_CALL(handler, Invoke(true)) | |
| 936 .WillOnce(InvokeWithoutArgs([&run_loop]() { | |
| 937 run_loop.Quit(); | |
| 938 })); | |
| 939 router()->SendRouteBinaryMessage( | |
| 940 kRouteId, std::move(expected_binary_data), | |
| 941 base::Bind(&SendMessageCallbackHandler::Invoke, | |
| 942 base::Unretained(&handler))); | |
| 943 run_loop.Run(); | |
| 944 } | |
| 945 | |
| 946 TEST_F(MediaRouterMojoImplTest, PresentationSessionMessagesSingleObserver) { | |
| 947 mojo::Array<interfaces::RouteMessagePtr> mojo_messages(2); | |
| 948 mojo_messages[0] = interfaces::RouteMessage::New(); | |
| 949 mojo_messages[0]->type = interfaces::RouteMessage::Type::TEXT; | |
| 950 mojo_messages[0]->message = "text"; | |
| 951 mojo_messages[1] = interfaces::RouteMessage::New(); | |
| 952 mojo_messages[1]->type = interfaces::RouteMessage::Type::BINARY; | |
| 953 mojo_messages[1]->data.push_back(1); | |
| 954 | |
| 955 ScopedVector<content::PresentationSessionMessage> expected_messages; | |
| 956 scoped_ptr<content::PresentationSessionMessage> message; | |
| 957 message.reset(new content::PresentationSessionMessage( | |
| 958 content::PresentationMessageType::TEXT)); | |
| 959 message->message = "text"; | |
| 960 expected_messages.push_back(std::move(message)); | |
| 961 | |
| 962 message.reset(new content::PresentationSessionMessage( | |
| 963 content::PresentationMessageType::ARRAY_BUFFER)); | |
| 964 message->data.reset(new std::vector<uint8_t>(1, 1)); | |
| 965 expected_messages.push_back(std::move(message)); | |
| 966 | |
| 967 base::RunLoop run_loop; | |
| 968 MediaRoute::Id expected_route_id("foo"); | |
| 969 interfaces::MediaRouteProvider::ListenForRouteMessagesCallback mojo_callback; | |
| 970 EXPECT_CALL(mock_media_route_provider_, | |
| 971 ListenForRouteMessages(Eq(expected_route_id), _)) | |
| 972 .WillOnce(DoAll(SaveArg<1>(&mojo_callback), | |
| 973 InvokeWithoutArgs([&run_loop]() { | |
| 974 run_loop.Quit(); | |
| 975 }))); | |
| 976 | |
| 977 // |pass_ownership| param is "true" here because there is only one observer. | |
| 978 ListenForMessagesCallbackHandler handler(std::move(expected_messages), true); | |
| 979 | |
| 980 EXPECT_CALL(handler, InvokeObserver()); | |
| 981 // Creating PresentationSessionMessagesObserver will register itself to the | |
| 982 // MediaRouter, which in turn will start listening for route messages. | |
| 983 scoped_ptr<PresentationSessionMessagesObserver> observer( | |
| 984 new PresentationSessionMessagesObserver( | |
| 985 base::Bind(&ListenForMessagesCallbackHandler::Invoke, | |
| 986 base::Unretained(&handler)), | |
| 987 expected_route_id, router())); | |
| 988 run_loop.Run(); | |
| 989 | |
| 990 base::RunLoop run_loop2; | |
| 991 // Simulate messages by invoking the saved mojo callback. | |
| 992 // We expect one more ListenForRouteMessages call since |observer| was | |
| 993 // still registered when the first set of messages arrived. | |
| 994 mojo_callback.Run(std::move(mojo_messages), false); | |
| 995 interfaces::MediaRouteProvider::ListenForRouteMessagesCallback | |
| 996 mojo_callback_2; | |
| 997 EXPECT_CALL(mock_media_route_provider_, ListenForRouteMessages(_, _)) | |
| 998 .WillOnce(DoAll(SaveArg<1>(&mojo_callback_2), | |
| 999 InvokeWithoutArgs([&run_loop2]() { | |
| 1000 run_loop2.Quit(); | |
| 1001 }))); | |
| 1002 run_loop2.Run(); | |
| 1003 | |
| 1004 base::RunLoop run_loop3; | |
| 1005 // Stop listening for messages. In particular, MediaRouterMojoImpl will not | |
| 1006 // call ListenForRouteMessages again when it sees there are no more observers. | |
| 1007 mojo::Array<interfaces::RouteMessagePtr> mojo_messages_2(1); | |
| 1008 mojo_messages_2[0] = interfaces::RouteMessage::New(); | |
| 1009 mojo_messages_2[0]->type = interfaces::RouteMessage::Type::TEXT; | |
| 1010 mojo_messages_2[0]->message = "foo"; | |
| 1011 observer.reset(); | |
| 1012 mojo_callback_2.Run(std::move(mojo_messages_2), false); | |
| 1013 EXPECT_CALL(mock_media_route_provider_, StopListeningForRouteMessages(_)) | |
| 1014 .WillOnce(InvokeWithoutArgs([&run_loop3]() { | |
| 1015 run_loop3.Quit(); | |
| 1016 })); | |
| 1017 run_loop3.Run(); | |
| 1018 } | |
| 1019 | |
| 1020 TEST_F(MediaRouterMojoImplTest, PresentationSessionMessagesMultipleObservers) { | |
| 1021 mojo::Array<interfaces::RouteMessagePtr> mojo_messages(2); | |
| 1022 mojo_messages[0] = interfaces::RouteMessage::New(); | |
| 1023 mojo_messages[0]->type = interfaces::RouteMessage::Type::TEXT; | |
| 1024 mojo_messages[0]->message = "text"; | |
| 1025 mojo_messages[1] = interfaces::RouteMessage::New(); | |
| 1026 mojo_messages[1]->type = interfaces::RouteMessage::Type::BINARY; | |
| 1027 mojo_messages[1]->data.push_back(1); | |
| 1028 | |
| 1029 ScopedVector<content::PresentationSessionMessage> expected_messages; | |
| 1030 scoped_ptr<content::PresentationSessionMessage> message; | |
| 1031 message.reset(new content::PresentationSessionMessage( | |
| 1032 content::PresentationMessageType::TEXT)); | |
| 1033 message->message = "text"; | |
| 1034 expected_messages.push_back(std::move(message)); | |
| 1035 | |
| 1036 message.reset(new content::PresentationSessionMessage( | |
| 1037 content::PresentationMessageType::ARRAY_BUFFER)); | |
| 1038 message->data.reset(new std::vector<uint8_t>(1, 1)); | |
| 1039 expected_messages.push_back(std::move(message)); | |
| 1040 | |
| 1041 base::RunLoop run_loop; | |
| 1042 MediaRoute::Id expected_route_id("foo"); | |
| 1043 interfaces::MediaRouteProvider::ListenForRouteMessagesCallback mojo_callback; | |
| 1044 EXPECT_CALL(mock_media_route_provider_, | |
| 1045 ListenForRouteMessages(Eq(expected_route_id), _)) | |
| 1046 .WillOnce(DoAll(SaveArg<1>(&mojo_callback), | |
| 1047 InvokeWithoutArgs([&run_loop]() { | |
| 1048 run_loop.Quit(); | |
| 1049 }))); | |
| 1050 | |
| 1051 // |pass_ownership| param is "false" here because there are more than one | |
| 1052 // observers. | |
| 1053 ListenForMessagesCallbackHandler handler(std::move(expected_messages), false); | |
| 1054 | |
| 1055 EXPECT_CALL(handler, InvokeObserver()).Times(2); | |
| 1056 // Creating PresentationSessionMessagesObserver will register itself to the | |
| 1057 // MediaRouter, which in turn will start listening for route messages. | |
| 1058 scoped_ptr<PresentationSessionMessagesObserver> observer1( | |
| 1059 new PresentationSessionMessagesObserver( | |
| 1060 base::Bind(&ListenForMessagesCallbackHandler::Invoke, | |
| 1061 base::Unretained(&handler)), | |
| 1062 expected_route_id, router())); | |
| 1063 scoped_ptr<PresentationSessionMessagesObserver> observer2( | |
| 1064 new PresentationSessionMessagesObserver( | |
| 1065 base::Bind(&ListenForMessagesCallbackHandler::Invoke, | |
| 1066 base::Unretained(&handler)), | |
| 1067 expected_route_id, router())); | |
| 1068 run_loop.Run(); | |
| 1069 | |
| 1070 base::RunLoop run_loop2; | |
| 1071 // Simulate messages by invoking the saved mojo callback. | |
| 1072 // We expect one more ListenForRouteMessages call since |observer| was | |
| 1073 // still registered when the first set of messages arrived. | |
| 1074 mojo_callback.Run(std::move(mojo_messages), false); | |
| 1075 interfaces::MediaRouteProvider::ListenForRouteMessagesCallback | |
| 1076 mojo_callback_2; | |
| 1077 EXPECT_CALL(mock_media_route_provider_, ListenForRouteMessages(_, _)) | |
| 1078 .WillOnce(DoAll(SaveArg<1>(&mojo_callback_2), | |
| 1079 InvokeWithoutArgs([&run_loop2]() { | |
| 1080 run_loop2.Quit(); | |
| 1081 }))); | |
| 1082 run_loop2.Run(); | |
| 1083 | |
| 1084 base::RunLoop run_loop3; | |
| 1085 // Stop listening for messages. In particular, MediaRouterMojoImpl will not | |
| 1086 // call ListenForRouteMessages again when it sees there are no more observers. | |
| 1087 mojo::Array<interfaces::RouteMessagePtr> mojo_messages_2(1); | |
| 1088 mojo_messages_2[0] = interfaces::RouteMessage::New(); | |
| 1089 mojo_messages_2[0]->type = interfaces::RouteMessage::Type::TEXT; | |
| 1090 mojo_messages_2[0]->message = "foo"; | |
| 1091 observer1.reset(); | |
| 1092 observer2.reset(); | |
| 1093 mojo_callback_2.Run(std::move(mojo_messages_2), false); | |
| 1094 EXPECT_CALL(mock_media_route_provider_, StopListeningForRouteMessages(_)) | |
| 1095 .WillOnce(InvokeWithoutArgs([&run_loop3]() { | |
| 1096 run_loop3.Quit(); | |
| 1097 })); | |
| 1098 run_loop3.Run(); | |
| 1099 } | |
| 1100 | |
| 1101 TEST_F(MediaRouterMojoImplTest, PresentationSessionMessagesError) { | |
| 1102 MediaRoute::Id expected_route_id("foo"); | |
| 1103 interfaces::MediaRouteProvider::ListenForRouteMessagesCallback mojo_callback; | |
| 1104 base::RunLoop run_loop; | |
| 1105 EXPECT_CALL(mock_media_route_provider_, | |
| 1106 ListenForRouteMessages(Eq(expected_route_id), _)) | |
| 1107 .WillOnce(DoAll(SaveArg<1>(&mojo_callback), | |
| 1108 InvokeWithoutArgs([&run_loop]() { | |
| 1109 run_loop.Quit(); | |
| 1110 }))); | |
| 1111 | |
| 1112 ListenForMessagesCallbackHandler handler( | |
| 1113 ScopedVector<content::PresentationSessionMessage>(), true); | |
| 1114 | |
| 1115 // Creating PresentationSessionMessagesObserver will register itself to the | |
| 1116 // MediaRouter, which in turn will start listening for route messages. | |
| 1117 scoped_ptr<PresentationSessionMessagesObserver> observer1( | |
| 1118 new PresentationSessionMessagesObserver( | |
| 1119 base::Bind(&ListenForMessagesCallbackHandler::Invoke, | |
| 1120 base::Unretained(&handler)), | |
| 1121 expected_route_id, router())); | |
| 1122 run_loop.Run(); | |
| 1123 | |
| 1124 mojo_callback.Run(mojo::Array<interfaces::RouteMessagePtr>(), true); | |
| 1125 ProcessEventLoop(); | |
| 1126 } | |
| 1127 | |
| 1128 TEST_F(MediaRouterMojoImplTest, PresentationConnectionStateChangedCallback) { | |
| 1129 MediaRoute::Id route_id("route-id"); | |
| 1130 const std::string kPresentationUrl("http://foo.fakeUrl"); | |
| 1131 const std::string kPresentationId("pid"); | |
| 1132 content::PresentationSessionInfo connection(kPresentationUrl, | |
| 1133 kPresentationId); | |
| 1134 MockPresentationConnectionStateChangedCallback callback; | |
| 1135 scoped_ptr<PresentationConnectionStateSubscription> subscription = | |
| 1136 router()->AddPresentationConnectionStateChangedCallback( | |
| 1137 route_id, | |
| 1138 base::Bind(&MockPresentationConnectionStateChangedCallback::Run, | |
| 1139 base::Unretained(&callback))); | |
| 1140 | |
| 1141 { | |
| 1142 base::RunLoop run_loop; | |
| 1143 content::PresentationConnectionStateChangeInfo closed_info( | |
| 1144 content::PRESENTATION_CONNECTION_STATE_CLOSED); | |
| 1145 closed_info.close_reason = | |
| 1146 content::PRESENTATION_CONNECTION_CLOSE_REASON_WENT_AWAY; | |
| 1147 closed_info.message = "Foo"; | |
| 1148 | |
| 1149 EXPECT_CALL(callback, Run(StateChageInfoEquals(closed_info))) | |
| 1150 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 1151 media_router_proxy_->OnPresentationConnectionClosed( | |
| 1152 route_id, PresentationConnectionCloseReason::WENT_AWAY, "Foo"); | |
| 1153 run_loop.Run(); | |
| 1154 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&callback)); | |
| 1155 } | |
| 1156 | |
| 1157 content::PresentationConnectionStateChangeInfo terminated_info( | |
| 1158 content::PRESENTATION_CONNECTION_STATE_TERMINATED); | |
| 1159 { | |
| 1160 base::RunLoop run_loop; | |
| 1161 EXPECT_CALL(callback, Run(StateChageInfoEquals(terminated_info))) | |
| 1162 .WillOnce(InvokeWithoutArgs([&run_loop]() { run_loop.Quit(); })); | |
| 1163 media_router_proxy_->OnPresentationConnectionStateChanged( | |
| 1164 route_id, PresentationConnectionState::TERMINATED); | |
| 1165 run_loop.Run(); | |
| 1166 | |
| 1167 EXPECT_TRUE(Mock::VerifyAndClearExpectations(&callback)); | |
| 1168 } | |
| 1169 } | |
| 1170 | |
| 1171 TEST_F(MediaRouterMojoImplTest, | |
| 1172 PresentationConnectionStateChangedCallbackRemoved) { | |
| 1173 MediaRoute::Id route_id("route-id"); | |
| 1174 MockPresentationConnectionStateChangedCallback callback; | |
| 1175 scoped_ptr<PresentationConnectionStateSubscription> subscription = | |
| 1176 router()->AddPresentationConnectionStateChangedCallback( | |
| 1177 route_id, | |
| 1178 base::Bind(&MockPresentationConnectionStateChangedCallback::Run, | |
| 1179 base::Unretained(&callback))); | |
| 1180 | |
| 1181 // Callback has been removed, so we don't expect it to be called anymore. | |
| 1182 subscription.reset(); | |
| 1183 EXPECT_TRUE(router()->presentation_connection_state_callbacks_.empty()); | |
| 1184 | |
| 1185 EXPECT_CALL(callback, Run(_)).Times(0); | |
| 1186 media_router_proxy_->OnPresentationConnectionStateChanged( | |
| 1187 route_id, PresentationConnectionState::TERMINATED); | |
| 1188 ProcessEventLoop(); | |
| 1189 } | |
| 1190 | |
| 1191 TEST_F(MediaRouterMojoImplTest, QueuedWhileAsleep) { | |
| 1192 base::RunLoop run_loop; | |
| 1193 EXPECT_CALL(mock_event_page_tracker_, IsEventPageSuspended(extension_id())) | |
| 1194 .Times(2) | |
| 1195 .WillRepeatedly(Return(true)); | |
| 1196 EXPECT_CALL(mock_event_page_tracker_, WakeEventPage(extension_id(), _)) | |
| 1197 .Times(2) | |
| 1198 .WillOnce(Return(true)) | |
| 1199 .WillOnce(DoAll(InvokeWithoutArgs([&run_loop]() { | |
| 1200 run_loop.Quit(); | |
| 1201 }), Return(true))); | |
| 1202 router()->DetachRoute(kRouteId); | |
| 1203 router()->DetachRoute(kRouteId2); | |
| 1204 run_loop.Run(); | |
| 1205 EXPECT_CALL(mock_event_page_tracker_, IsEventPageSuspended(extension_id())) | |
| 1206 .Times(1) | |
| 1207 .WillRepeatedly(Return(false)); | |
| 1208 EXPECT_CALL(mock_media_route_provider_, DetachRoute(mojo::String(kRouteId))); | |
| 1209 EXPECT_CALL(mock_media_route_provider_, DetachRoute(mojo::String(kRouteId2))); | |
| 1210 ConnectProviderManagerService(); | |
| 1211 ProcessEventLoop(); | |
| 1212 } | |
| 1213 | |
| 1214 class MediaRouterMojoExtensionTest : public ::testing::Test { | |
| 1215 public: | |
| 1216 MediaRouterMojoExtensionTest() | |
| 1217 : process_manager_(nullptr), | |
| 1218 message_loop_(mojo::common::MessagePumpMojo::Create()) {} | |
| 1219 | |
| 1220 ~MediaRouterMojoExtensionTest() override {} | |
| 1221 | |
| 1222 protected: | |
| 1223 void SetUp() override { | |
| 1224 // Set the extension's version number to be identical to the browser's. | |
| 1225 extension_ = | |
| 1226 extensions::test_util::BuildExtension(extensions::ExtensionBuilder()) | |
| 1227 .MergeManifest(extensions::DictionaryBuilder() | |
| 1228 .Set("version", version_info::GetVersionNumber()) | |
| 1229 .Build()) | |
| 1230 .Build(); | |
| 1231 | |
| 1232 profile_.reset(new TestingProfile); | |
| 1233 // Set up a mock ProcessManager instance. | |
| 1234 extensions::ProcessManagerFactory::GetInstance()->SetTestingFactory( | |
| 1235 profile_.get(), &TestProcessManager::Create); | |
| 1236 process_manager_ = static_cast<TestProcessManager*>( | |
| 1237 extensions::ProcessManager::Get(profile_.get())); | |
| 1238 DCHECK(process_manager_); | |
| 1239 | |
| 1240 // Create MR and its proxy, so that it can be accessed through Mojo. | |
| 1241 media_router_.reset(new MediaRouterMojoImpl(process_manager_)); | |
| 1242 ProcessEventLoop(); | |
| 1243 } | |
| 1244 | |
| 1245 void TearDown() override { | |
| 1246 media_router_.reset(); | |
| 1247 profile_.reset(); | |
| 1248 // Explicitly delete the TestingBrowserProcess before |message_loop_|. | |
| 1249 // This allows it to do cleanup before |message_loop_| goes away. | |
| 1250 TestingBrowserProcess::DeleteInstance(); | |
| 1251 } | |
| 1252 | |
| 1253 // Constructs bindings so that |media_router_| delegates calls to | |
| 1254 // |mojo_media_router_|, which are then handled by | |
| 1255 // |mock_media_route_provider_service_|. | |
| 1256 void BindMediaRouteProvider() { | |
| 1257 binding_.reset(new mojo::Binding<interfaces::MediaRouteProvider>( | |
| 1258 &mock_media_route_provider_, | |
| 1259 mojo::GetProxy(&media_route_provider_proxy_))); | |
| 1260 media_router_->BindToMojoRequest(mojo::GetProxy(&media_router_proxy_), | |
| 1261 *extension_); | |
| 1262 } | |
| 1263 | |
| 1264 void ResetMediaRouteProvider() { | |
| 1265 binding_.reset(); | |
| 1266 media_router_->BindToMojoRequest(mojo::GetProxy(&media_router_proxy_), | |
| 1267 *extension_); | |
| 1268 } | |
| 1269 | |
| 1270 void RegisterMediaRouteProvider() { | |
| 1271 media_router_proxy_->RegisterMediaRouteProvider( | |
| 1272 std::move(media_route_provider_proxy_), | |
| 1273 base::Bind(&RegisterMediaRouteProviderHandler::Invoke, | |
| 1274 base::Unretained(&provide_handler_))); | |
| 1275 } | |
| 1276 | |
| 1277 void ProcessEventLoop() { | |
| 1278 message_loop_.RunUntilIdle(); | |
| 1279 } | |
| 1280 | |
| 1281 void ExpectWakeReasonBucketCount(MediaRouteProviderWakeReason reason, | |
| 1282 int expected_count) { | |
| 1283 histogram_tester_.ExpectBucketCount("MediaRouter.Provider.WakeReason", | |
| 1284 static_cast<int>(reason), | |
| 1285 expected_count); | |
| 1286 } | |
| 1287 | |
| 1288 void ExpectVersionBucketCount(MediaRouteProviderVersion version, | |
| 1289 int expected_count) { | |
| 1290 histogram_tester_.ExpectBucketCount("MediaRouter.Provider.Version", | |
| 1291 static_cast<int>(version), | |
| 1292 expected_count); | |
| 1293 } | |
| 1294 | |
| 1295 void ExpectWakeupBucketCount(MediaRouteProviderWakeup wakeup, | |
| 1296 int expected_count) { | |
| 1297 histogram_tester_.ExpectBucketCount("MediaRouter.Provider.Wakeup", | |
| 1298 static_cast<int>(wakeup), | |
| 1299 expected_count); | |
| 1300 } | |
| 1301 | |
| 1302 scoped_ptr<MediaRouterMojoImpl> media_router_; | |
| 1303 RegisterMediaRouteProviderHandler provide_handler_; | |
| 1304 TestProcessManager* process_manager_; | |
| 1305 testing::StrictMock<MockMediaRouteProvider> mock_media_route_provider_; | |
| 1306 interfaces::MediaRouterPtr media_router_proxy_; | |
| 1307 scoped_refptr<extensions::Extension> extension_; | |
| 1308 | |
| 1309 private: | |
| 1310 scoped_ptr<TestingProfile> profile_; | |
| 1311 base::MessageLoop message_loop_; | |
| 1312 interfaces::MediaRouteProviderPtr media_route_provider_proxy_; | |
| 1313 scoped_ptr<mojo::Binding<interfaces::MediaRouteProvider>> binding_; | |
| 1314 base::HistogramTester histogram_tester_; | |
| 1315 | |
| 1316 DISALLOW_COPY_AND_ASSIGN(MediaRouterMojoExtensionTest); | |
| 1317 }; | |
| 1318 | |
| 1319 TEST_F(MediaRouterMojoExtensionTest, DeferredBindingAndSuspension) { | |
| 1320 // DetachRoute is called before *any* extension has connected. | |
| 1321 // It should be queued. | |
| 1322 media_router_->DetachRoute(kRouteId); | |
| 1323 | |
| 1324 BindMediaRouteProvider(); | |
| 1325 | |
| 1326 base::RunLoop run_loop, run_loop2; | |
| 1327 // |mojo_media_router| signals its readiness to the MR by registering | |
| 1328 // itself via RegisterMediaRouteProvider(). | |
| 1329 // Now that the |media_router| and |mojo_media_router| are fully initialized, | |
| 1330 // the queued DetachRoute() call should be executed. | |
| 1331 EXPECT_CALL(provide_handler_, Invoke(testing::Not(""))) | |
| 1332 .WillOnce(InvokeWithoutArgs([&run_loop]() { | |
| 1333 run_loop.Quit(); | |
| 1334 })); | |
| 1335 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())) | |
| 1336 .WillOnce(Return(false)); | |
| 1337 EXPECT_CALL(mock_media_route_provider_, EnableMdnsDiscovery()) | |
| 1338 .Times(AtMost(1)); | |
| 1339 EXPECT_CALL(mock_media_route_provider_, DetachRoute(mojo::String(kRouteId))) | |
| 1340 .WillOnce(InvokeWithoutArgs([&run_loop2]() { | |
| 1341 run_loop2.Quit(); | |
| 1342 })); | |
| 1343 RegisterMediaRouteProvider(); | |
| 1344 run_loop.Run(); | |
| 1345 run_loop2.Run(); | |
| 1346 | |
| 1347 base::RunLoop run_loop3; | |
| 1348 // Extension is suspended and re-awoken. | |
| 1349 ResetMediaRouteProvider(); | |
| 1350 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())) | |
| 1351 .WillOnce(Return(true)); | |
| 1352 EXPECT_CALL(*process_manager_, WakeEventPage(extension_->id(), _)) | |
| 1353 .WillOnce(testing::DoAll( | |
| 1354 media::RunCallback<1>(true), | |
| 1355 InvokeWithoutArgs([&run_loop3]() { run_loop3.Quit(); }), | |
| 1356 Return(true))); | |
| 1357 media_router_->DetachRoute(kRouteId2); | |
| 1358 run_loop3.Run(); | |
| 1359 | |
| 1360 base::RunLoop run_loop4, run_loop5; | |
| 1361 // RegisterMediaRouteProvider() is called. | |
| 1362 // The queued DetachRoute(kRouteId2) call should be executed. | |
| 1363 EXPECT_CALL(provide_handler_, Invoke(testing::Not(""))) | |
| 1364 .WillOnce(InvokeWithoutArgs([&run_loop4]() { | |
| 1365 run_loop4.Quit(); | |
| 1366 })); | |
| 1367 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())) | |
| 1368 .WillOnce(Return(false)); | |
| 1369 EXPECT_CALL(mock_media_route_provider_, EnableMdnsDiscovery()) | |
| 1370 .Times(AtMost(1)); | |
| 1371 EXPECT_CALL(mock_media_route_provider_, DetachRoute(mojo::String(kRouteId2))) | |
| 1372 .WillOnce(InvokeWithoutArgs([&run_loop5]() { | |
| 1373 run_loop5.Quit(); | |
| 1374 })); | |
| 1375 BindMediaRouteProvider(); | |
| 1376 RegisterMediaRouteProvider(); | |
| 1377 run_loop4.Run(); | |
| 1378 run_loop5.Run(); | |
| 1379 ExpectWakeReasonBucketCount(MediaRouteProviderWakeReason::DETACH_ROUTE, 1); | |
| 1380 ExpectWakeupBucketCount(MediaRouteProviderWakeup::SUCCESS, 1); | |
| 1381 ExpectVersionBucketCount(MediaRouteProviderVersion::SAME_VERSION_AS_CHROME, | |
| 1382 1); | |
| 1383 } | |
| 1384 | |
| 1385 TEST_F(MediaRouterMojoExtensionTest, AttemptedWakeupTooManyTimes) { | |
| 1386 BindMediaRouteProvider(); | |
| 1387 | |
| 1388 // DetachRoute is called while extension is suspended. It should be queued. | |
| 1389 // Schedule a component extension wakeup. | |
| 1390 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())) | |
| 1391 .WillOnce(Return(true)); | |
| 1392 EXPECT_CALL(*process_manager_, WakeEventPage(extension_->id(), _)) | |
| 1393 .WillOnce(testing::DoAll(media::RunCallback<1>(true), Return(true))); | |
| 1394 media_router_->DetachRoute(kRouteId); | |
| 1395 EXPECT_EQ(1u, media_router_->pending_requests_.size()); | |
| 1396 ExpectWakeReasonBucketCount(MediaRouteProviderWakeReason::DETACH_ROUTE, 1); | |
| 1397 ExpectWakeupBucketCount(MediaRouteProviderWakeup::SUCCESS, 1); | |
| 1398 | |
| 1399 // Media route provider fails to connect to media router before extension is | |
| 1400 // suspended again, and |OnConnectionError| is invoked. Retry the wakeup. | |
| 1401 EXPECT_CALL(*process_manager_, WakeEventPage(extension_->id(), _)) | |
| 1402 .Times(MediaRouterMojoImpl::kMaxWakeupAttemptCount - 1) | |
| 1403 .WillRepeatedly( | |
| 1404 testing::DoAll(media::RunCallback<1>(true), Return(true))); | |
| 1405 for (int i = 0; i < MediaRouterMojoImpl::kMaxWakeupAttemptCount - 1; ++i) | |
| 1406 media_router_->OnConnectionError(); | |
| 1407 | |
| 1408 // We have already tried |kMaxWakeupAttemptCount| times. If we get an error | |
| 1409 // again, we will give up and the pending request queue will be drained. | |
| 1410 media_router_->OnConnectionError(); | |
| 1411 EXPECT_TRUE(media_router_->pending_requests_.empty()); | |
| 1412 ExpectWakeReasonBucketCount(MediaRouteProviderWakeReason::CONNECTION_ERROR, | |
| 1413 MediaRouterMojoImpl::kMaxWakeupAttemptCount - 1); | |
| 1414 ExpectWakeupBucketCount(MediaRouteProviderWakeup::ERROR_TOO_MANY_RETRIES, 1); | |
| 1415 | |
| 1416 // Requests that comes in after queue is drained should be queued. | |
| 1417 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())) | |
| 1418 .WillOnce(Return(true)); | |
| 1419 EXPECT_CALL(*process_manager_, WakeEventPage(extension_->id(), _)) | |
| 1420 .WillOnce(testing::DoAll(media::RunCallback<1>(true), Return(true))); | |
| 1421 media_router_->DetachRoute(kRouteId); | |
| 1422 EXPECT_EQ(1u, media_router_->pending_requests_.size()); | |
| 1423 ExpectVersionBucketCount(MediaRouteProviderVersion::SAME_VERSION_AS_CHROME, | |
| 1424 1); | |
| 1425 } | |
| 1426 | |
| 1427 TEST_F(MediaRouterMojoExtensionTest, WakeupFailedDrainsQueue) { | |
| 1428 BindMediaRouteProvider(); | |
| 1429 | |
| 1430 // DetachRoute is called while extension is suspended. It should be queued. | |
| 1431 // Schedule a component extension wakeup. | |
| 1432 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())) | |
| 1433 .WillOnce(Return(true)); | |
| 1434 base::Callback<void(bool)> extension_wakeup_callback; | |
| 1435 EXPECT_CALL(*process_manager_, WakeEventPage(extension_->id(), _)) | |
| 1436 .WillOnce( | |
| 1437 testing::DoAll(SaveArg<1>(&extension_wakeup_callback), Return(true))); | |
| 1438 media_router_->DetachRoute(kRouteId); | |
| 1439 EXPECT_EQ(1u, media_router_->pending_requests_.size()); | |
| 1440 | |
| 1441 // Extension wakeup callback returning false is an non-retryable error. | |
| 1442 // Queue should be drained. | |
| 1443 extension_wakeup_callback.Run(false); | |
| 1444 EXPECT_TRUE(media_router_->pending_requests_.empty()); | |
| 1445 | |
| 1446 // Requests that comes in after queue is drained should be queued. | |
| 1447 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())) | |
| 1448 .WillOnce(Return(true)); | |
| 1449 EXPECT_CALL(*process_manager_, WakeEventPage(extension_->id(), _)) | |
| 1450 .WillOnce(testing::DoAll(media::RunCallback<1>(true), Return(true))); | |
| 1451 media_router_->DetachRoute(kRouteId); | |
| 1452 EXPECT_EQ(1u, media_router_->pending_requests_.size()); | |
| 1453 ExpectWakeReasonBucketCount(MediaRouteProviderWakeReason::DETACH_ROUTE, 1); | |
| 1454 ExpectWakeupBucketCount(MediaRouteProviderWakeup::ERROR_UNKNOWN, 1); | |
| 1455 ExpectVersionBucketCount(MediaRouteProviderVersion::SAME_VERSION_AS_CHROME, | |
| 1456 1); | |
| 1457 } | |
| 1458 | |
| 1459 TEST_F(MediaRouterMojoExtensionTest, DropOldestPendingRequest) { | |
| 1460 const size_t kMaxPendingRequests = MediaRouterMojoImpl::kMaxPendingRequests; | |
| 1461 | |
| 1462 // Request is queued. | |
| 1463 media_router_->DetachRoute(kRouteId); | |
| 1464 EXPECT_EQ(1u, media_router_->pending_requests_.size()); | |
| 1465 | |
| 1466 for (size_t i = 0; i < kMaxPendingRequests; ++i) | |
| 1467 media_router_->DetachRoute(kRouteId2); | |
| 1468 | |
| 1469 // The request queue size should not exceed |kMaxPendingRequests|. | |
| 1470 EXPECT_EQ(kMaxPendingRequests, media_router_->pending_requests_.size()); | |
| 1471 | |
| 1472 base::RunLoop run_loop, run_loop2; | |
| 1473 size_t count = 0; | |
| 1474 // The oldest request should have been dropped, so we don't expect to see | |
| 1475 // DetachRoute(kRouteId) here. | |
| 1476 BindMediaRouteProvider(); | |
| 1477 EXPECT_CALL(provide_handler_, Invoke(testing::Not(""))) | |
| 1478 .WillOnce(InvokeWithoutArgs([&run_loop]() { | |
| 1479 run_loop.Quit(); | |
| 1480 })); | |
| 1481 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())); | |
| 1482 EXPECT_CALL(mock_media_route_provider_, EnableMdnsDiscovery()) | |
| 1483 .Times(AtMost(1)); | |
| 1484 EXPECT_CALL(mock_media_route_provider_, DetachRoute(mojo::String(kRouteId2))) | |
| 1485 .Times(kMaxPendingRequests) | |
| 1486 .WillRepeatedly(InvokeWithoutArgs([&run_loop2, &count]() { | |
| 1487 if (++count == MediaRouterMojoImpl::kMaxPendingRequests) | |
| 1488 run_loop2.Quit(); | |
| 1489 })); | |
| 1490 RegisterMediaRouteProvider(); | |
| 1491 run_loop.Run(); | |
| 1492 run_loop2.Run(); | |
| 1493 ExpectVersionBucketCount(MediaRouteProviderVersion::SAME_VERSION_AS_CHROME, | |
| 1494 1); | |
| 1495 } | |
| 1496 | |
| 1497 #if defined(OS_WIN) | |
| 1498 TEST_F(MediaRouterMojoExtensionTest, EnableMdnsAfterEachRegister) { | |
| 1499 // This should be queued since no MRPM is registered yet. | |
| 1500 media_router_->OnUserGesture(); | |
| 1501 | |
| 1502 BindMediaRouteProvider(); | |
| 1503 | |
| 1504 base::RunLoop run_loop; | |
| 1505 base::RunLoop run_loop2; | |
| 1506 EXPECT_CALL(provide_handler_, Invoke(testing::Not(""))) | |
| 1507 .WillOnce(InvokeWithoutArgs([&run_loop]() { | |
| 1508 run_loop.Quit(); | |
| 1509 })); | |
| 1510 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())) | |
| 1511 .WillOnce(Return(false)); | |
| 1512 // EnableMdnsDisocvery() is never called except on Windows. | |
| 1513 EXPECT_CALL(mock_media_route_provider_, EnableMdnsDiscovery()) | |
| 1514 .WillOnce(InvokeWithoutArgs([&run_loop2]() { | |
| 1515 run_loop2.Quit(); | |
| 1516 })); | |
| 1517 RegisterMediaRouteProvider(); | |
| 1518 run_loop.Run(); | |
| 1519 run_loop2.Run(); | |
| 1520 // Always a no-op at this point. | |
| 1521 media_router_->OnUserGesture(); | |
| 1522 | |
| 1523 // Reset the extension by "suspending" and notifying MR. | |
| 1524 base::RunLoop run_loop3; | |
| 1525 ResetMediaRouteProvider(); | |
| 1526 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())) | |
| 1527 .WillOnce(Return(true)); | |
| 1528 EXPECT_CALL(*process_manager_, WakeEventPage(extension_->id(), _)) | |
| 1529 .WillOnce(testing::DoAll( | |
| 1530 media::RunCallback<1>(true), | |
| 1531 InvokeWithoutArgs([&run_loop3]() { run_loop3.Quit(); }), | |
| 1532 Return(true))); | |
| 1533 // Use DetachRoute because it unconditionally calls RunOrDefer(). | |
| 1534 media_router_->DetachRoute(kRouteId); | |
| 1535 run_loop3.Run(); | |
| 1536 | |
| 1537 base::RunLoop run_loop4; | |
| 1538 base::RunLoop run_loop5; | |
| 1539 // RegisterMediaRouteProvider() is called. | |
| 1540 // The queued DetachRoute(kRouteId) call should be executed. | |
| 1541 EXPECT_CALL(provide_handler_, Invoke(testing::Not(""))) | |
| 1542 .WillOnce(InvokeWithoutArgs([&run_loop4]() { | |
| 1543 run_loop4.Quit(); | |
| 1544 })); | |
| 1545 EXPECT_CALL(*process_manager_, IsEventPageSuspended(extension_->id())) | |
| 1546 .WillOnce(Return(false)); | |
| 1547 // Expected because it was used to wake up the page. | |
| 1548 EXPECT_CALL(mock_media_route_provider_, DetachRoute(mojo::String(kRouteId))); | |
| 1549 // EnableMdnsDisocvery() is never called except on Windows. | |
| 1550 EXPECT_CALL(mock_media_route_provider_, EnableMdnsDiscovery()) | |
| 1551 .WillOnce(InvokeWithoutArgs([&run_loop5]() { | |
| 1552 run_loop5.Quit(); | |
| 1553 })); | |
| 1554 BindMediaRouteProvider(); | |
| 1555 RegisterMediaRouteProvider(); | |
| 1556 run_loop4.Run(); | |
| 1557 run_loop5.Run(); | |
| 1558 // Always a no-op at this point. | |
| 1559 media_router_->OnUserGesture(); | |
| 1560 } | |
| 1561 #endif | |
| 1562 | |
| 1563 } // namespace media_router | |
| OLD | NEW |