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