| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "content/browser/renderer_host/media/video_capture_host.h" | 5 #include "content/browser/renderer_host/media/video_capture_host.h" |
| 6 | 6 |
| 7 #include <stdint.h> | 7 #include <stdint.h> |
| 8 | 8 |
| 9 #include <map> | 9 #include <map> |
| 10 #include <memory> | 10 #include <memory> |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 93 MockVideoCaptureHost(MediaStreamManager* manager) | 93 MockVideoCaptureHost(MediaStreamManager* manager) |
| 94 : VideoCaptureHost(manager), return_buffers_immediately_(false) {} | 94 : VideoCaptureHost(manager), return_buffers_immediately_(false) {} |
| 95 | 95 |
| 96 MOCK_METHOD4(OnNewBufferCreated, | 96 MOCK_METHOD4(OnNewBufferCreated, |
| 97 void(int device_id, | 97 void(int device_id, |
| 98 base::SharedMemoryHandle handle, | 98 base::SharedMemoryHandle handle, |
| 99 int length, | 99 int length, |
| 100 int buffer_id)); | 100 int buffer_id)); |
| 101 MOCK_METHOD2(OnBufferFreed, void(int device_id, int buffer_id)); | 101 MOCK_METHOD2(OnBufferFreed, void(int device_id, int buffer_id)); |
| 102 MOCK_METHOD1(OnBufferFilled, void(int device_id)); | 102 MOCK_METHOD1(OnBufferFilled, void(int device_id)); |
| 103 MOCK_METHOD2(OnStateChanged, void(int device_id, VideoCaptureState state)); | |
| 104 | 103 |
| 105 void SetReturnReceivedBuffersImmediately(bool enable) { | 104 void SetReturnReceivedBuffersImmediately(bool enable) { |
| 106 return_buffers_immediately_ = enable; | 105 return_buffers_immediately_ = enable; |
| 107 } | 106 } |
| 108 | 107 |
| 109 void ReturnReceivedBuffers(int device_id) { | 108 void ReturnReceivedBuffers(int device_id) { |
| 110 while (!buffer_ids_.empty()) { | 109 while (!buffer_ids_.empty()) { |
| 111 this->OnRendererFinishedWithBuffer(device_id, *buffer_ids_.begin(), | 110 this->ReleaseBuffer(device_id, *buffer_ids_.begin(), gpu::SyncToken(), |
| 112 gpu::SyncToken(), -1.0); | 111 -1.0); |
| 113 buffer_ids_.pop_front(); | 112 buffer_ids_.pop_front(); |
| 114 } | 113 } |
| 115 } | 114 } |
| 116 | 115 |
| 117 private: | 116 private: |
| 118 ~MockVideoCaptureHost() override {} | 117 ~MockVideoCaptureHost() override {} |
| 119 | 118 |
| 120 // This method is used to dispatch IPC messages to the renderer. We intercept | 119 // This method is used to dispatch IPC messages to the renderer. We intercept |
| 121 // some of these messages here to MOCK them and to map/unmap buffers. | 120 // some of these messages here to MOCK them and to map/unmap buffers. |
| 122 bool Send(IPC::Message* message) override { | 121 bool Send(IPC::Message* message) override { |
| 123 CHECK(message); | 122 CHECK(message); |
| 124 | 123 |
| 125 // In this method we dispatch the messages to the according handlers as if | 124 // In this method we dispatch the messages to the according handlers as if |
| 126 // we are the renderer. | 125 // we are the renderer. |
| 127 bool handled = true; | 126 bool handled = true; |
| 128 IPC_BEGIN_MESSAGE_MAP(MockVideoCaptureHost, *message) | 127 IPC_BEGIN_MESSAGE_MAP(MockVideoCaptureHost, *message) |
| 129 IPC_MESSAGE_HANDLER(VideoCaptureMsg_NewBuffer, OnNewBufferCreatedDispatch) | 128 IPC_MESSAGE_HANDLER(VideoCaptureMsg_NewBuffer, OnNewBufferCreatedDispatch) |
| 130 IPC_MESSAGE_HANDLER(VideoCaptureMsg_FreeBuffer, OnBufferFreedDispatch) | 129 IPC_MESSAGE_HANDLER(VideoCaptureMsg_FreeBuffer, OnBufferFreedDispatch) |
| 131 IPC_MESSAGE_HANDLER(VideoCaptureMsg_BufferReady, OnBufferFilledDispatch) | 130 IPC_MESSAGE_HANDLER(VideoCaptureMsg_BufferReady, OnBufferFilledDispatch) |
| 132 IPC_MESSAGE_HANDLER(VideoCaptureMsg_StateChanged, OnStateChanged) | |
| 133 IPC_MESSAGE_UNHANDLED(handled = false) | 131 IPC_MESSAGE_UNHANDLED(handled = false) |
| 134 IPC_END_MESSAGE_MAP() | 132 IPC_END_MESSAGE_MAP() |
| 135 EXPECT_TRUE(handled); | 133 EXPECT_TRUE(handled); |
| 136 | 134 |
| 137 delete message; | 135 delete message; |
| 138 return true; | 136 return true; |
| 139 } | 137 } |
| 140 | 138 |
| 141 // These handler methods do minimal things and delegate to the mock methods. | 139 // These handler methods do minimal things and delegate to the mock methods. |
| 142 void OnNewBufferCreatedDispatch(int device_id, | 140 void OnNewBufferCreatedDispatch(int device_id, |
| (...skipping 11 matching lines...) Expand all Loading... |
| 154 ASSERT_TRUE(buffer != buffer_ids_.end()); | 152 ASSERT_TRUE(buffer != buffer_ids_.end()); |
| 155 buffer_ids_.erase(buffer); | 153 buffer_ids_.erase(buffer); |
| 156 } | 154 } |
| 157 | 155 |
| 158 void OnBufferFilledDispatch( | 156 void OnBufferFilledDispatch( |
| 159 const VideoCaptureMsg_BufferReady_Params& params) { | 157 const VideoCaptureMsg_BufferReady_Params& params) { |
| 160 OnBufferFilled(params.device_id); | 158 OnBufferFilled(params.device_id); |
| 161 if (!return_buffers_immediately_) | 159 if (!return_buffers_immediately_) |
| 162 return; | 160 return; |
| 163 | 161 |
| 164 VideoCaptureHost::OnRendererFinishedWithBuffer( | 162 VideoCaptureHost::ReleaseBuffer(params.device_id, params.buffer_id, |
| 165 params.device_id, params.buffer_id, gpu::SyncToken(), -1.0); | 163 gpu::SyncToken(), -1.0); |
| 166 } | 164 } |
| 167 | 165 |
| 168 std::list<int> buffer_ids_; | 166 std::list<int> buffer_ids_; |
| 169 bool return_buffers_immediately_; | 167 bool return_buffers_immediately_; |
| 170 }; | 168 }; |
| 171 | 169 |
| 172 ACTION_P2(ExitMessageLoop, task_runner, quit_closure) { | 170 ACTION_P2(ExitMessageLoop, task_runner, quit_closure) { |
| 173 task_runner->PostTask(FROM_HERE, quit_closure); | 171 task_runner->PostTask(FROM_HERE, quit_closure); |
| 174 } | 172 } |
| 175 | 173 |
| 176 // This is an integration test of VideoCaptureHost in conjunction with | 174 // This is an integration test of VideoCaptureHost in conjunction with |
| 177 // MediaStreamManager, VideoCaptureManager, VideoCaptureController, and | 175 // MediaStreamManager, VideoCaptureManager, VideoCaptureController, and |
| 178 // VideoCaptureDevice. | 176 // VideoCaptureDevice. |
| 179 class VideoCaptureHostTest : public testing::Test { | 177 class VideoCaptureHostTest : public testing::Test, |
| 178 public mojom::VideoCaptureObserver { |
| 180 public: | 179 public: |
| 181 VideoCaptureHostTest() | 180 VideoCaptureHostTest() |
| 182 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), | 181 : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), |
| 183 task_runner_(base::ThreadTaskRunnerHandle::Get()), | 182 task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| 184 opened_session_id_(kInvalidMediaCaptureSessionId) {} | 183 opened_session_id_(kInvalidMediaCaptureSessionId), |
| 184 observer_binding_(this) {} |
| 185 | 185 |
| 186 void SetUp() override { | 186 void SetUp() override { |
| 187 SetBrowserClientForTesting(&browser_client_); | 187 SetBrowserClientForTesting(&browser_client_); |
| 188 | 188 |
| 189 #if defined(OS_CHROMEOS) | 189 #if defined(OS_CHROMEOS) |
| 190 chromeos::CrasAudioHandler::InitializeForTesting(); | 190 chromeos::CrasAudioHandler::InitializeForTesting(); |
| 191 #endif | 191 #endif |
| 192 | 192 |
| 193 audio_manager_ = media::AudioManager::CreateForTesting(task_runner_); | 193 audio_manager_ = media::AudioManager::CreateForTesting(task_runner_); |
| 194 base::CommandLine::ForCurrentProcess()->AppendSwitch( | 194 base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| (...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 282 | 282 |
| 283 void CloseSession() { | 283 void CloseSession() { |
| 284 if (opened_device_label_.empty()) | 284 if (opened_device_label_.empty()) |
| 285 return; | 285 return; |
| 286 media_stream_manager_->CancelRequest(opened_device_label_); | 286 media_stream_manager_->CancelRequest(opened_device_label_); |
| 287 opened_device_label_.clear(); | 287 opened_device_label_.clear(); |
| 288 opened_session_id_ = kInvalidMediaCaptureSessionId; | 288 opened_session_id_ = kInvalidMediaCaptureSessionId; |
| 289 } | 289 } |
| 290 | 290 |
| 291 protected: | 291 protected: |
| 292 MOCK_METHOD1(OnStateChanged, void(mojom::VideoCaptureState)); |
| 293 |
| 292 void StartCapture() { | 294 void StartCapture() { |
| 293 EXPECT_CALL(*host_.get(), OnNewBufferCreated(kDeviceId, _, _, _)) | 295 EXPECT_CALL(*host_.get(), OnNewBufferCreated(kDeviceId, _, _, _)) |
| 294 .Times(AnyNumber()) | 296 .Times(AnyNumber()) |
| 295 .WillRepeatedly(Return()); | 297 .WillRepeatedly(Return()); |
| 296 | 298 |
| 297 base::RunLoop run_loop; | 299 base::RunLoop run_loop; |
| 298 EXPECT_CALL(*host_.get(), OnBufferFilled(kDeviceId)) | 300 EXPECT_CALL(*host_.get(), OnBufferFilled(kDeviceId)) |
| 299 .Times(AnyNumber()) | 301 .Times(AnyNumber()) |
| 300 .WillOnce(ExitMessageLoop(task_runner_, run_loop.QuitClosure())); | 302 .WillOnce(ExitMessageLoop(task_runner_, run_loop.QuitClosure())); |
| 301 | 303 |
| 302 media::VideoCaptureParams params; | 304 media::VideoCaptureParams params; |
| 303 params.requested_format = media::VideoCaptureFormat( | 305 params.requested_format = media::VideoCaptureFormat( |
| 304 gfx::Size(352, 288), 30, media::PIXEL_FORMAT_I420); | 306 gfx::Size(352, 288), 30, media::PIXEL_FORMAT_I420); |
| 305 host_->Start(kDeviceId, opened_session_id_, params); | 307 |
| 308 EXPECT_CALL(*this, OnStateChanged(mojom::VideoCaptureState::STARTED)); |
| 309 host_->Start(kDeviceId, opened_session_id_, params, |
| 310 observer_binding_.CreateInterfacePtrAndBind()); |
| 311 |
| 306 run_loop.Run(); | 312 run_loop.Run(); |
| 307 } | 313 } |
| 308 | 314 |
| 309 void StartStopCapture() { | 315 void StartStopCapture() { |
| 310 // Quickly start and then stop capture, without giving much chance for | 316 // Quickly start and then stop capture, without giving much chance for |
| 311 // asynchronous start operations to complete. | 317 // asynchronous start operations to complete. |
| 312 InSequence s; | 318 InSequence s; |
| 313 base::RunLoop run_loop; | 319 base::RunLoop run_loop; |
| 314 EXPECT_CALL(*host_.get(), | 320 |
| 315 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)); | |
| 316 media::VideoCaptureParams params; | 321 media::VideoCaptureParams params; |
| 317 params.requested_format = media::VideoCaptureFormat( | 322 params.requested_format = media::VideoCaptureFormat( |
| 318 gfx::Size(352, 288), 30, media::PIXEL_FORMAT_I420); | 323 gfx::Size(352, 288), 30, media::PIXEL_FORMAT_I420); |
| 319 host_->Start(kDeviceId, opened_session_id_, params); | 324 |
| 325 EXPECT_CALL(*this, OnStateChanged(mojom::VideoCaptureState::STARTED)); |
| 326 host_->Start(kDeviceId, opened_session_id_, params, |
| 327 observer_binding_.CreateInterfacePtrAndBind()); |
| 328 |
| 329 EXPECT_CALL(*this, OnStateChanged(mojom::VideoCaptureState::STOPPED)); |
| 320 host_->Stop(kDeviceId); | 330 host_->Stop(kDeviceId); |
| 321 run_loop.RunUntilIdle(); | 331 run_loop.RunUntilIdle(); |
| 322 WaitForVideoDeviceThread(); | 332 WaitForVideoDeviceThread(); |
| 323 } | 333 } |
| 324 | 334 |
| 325 void PauseResumeCapture() { | 335 void PauseResumeCapture() { |
| 326 InSequence s; | 336 InSequence s; |
| 327 base::RunLoop run_loop; | 337 base::RunLoop run_loop; |
| 328 EXPECT_CALL(*host_.get(), | 338 |
| 329 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_PAUSED)); | 339 EXPECT_CALL(*this, OnStateChanged(mojom::VideoCaptureState::PAUSED)); |
| 330 host_->Pause(kDeviceId); | 340 host_->Pause(kDeviceId); |
| 331 | 341 |
| 332 EXPECT_CALL(*host_.get(), | |
| 333 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_RESUMED)); | |
| 334 media::VideoCaptureParams params; | 342 media::VideoCaptureParams params; |
| 335 params.requested_format = media::VideoCaptureFormat( | 343 params.requested_format = media::VideoCaptureFormat( |
| 336 gfx::Size(352, 288), 30, media::PIXEL_FORMAT_I420); | 344 gfx::Size(352, 288), 30, media::PIXEL_FORMAT_I420); |
| 345 |
| 346 EXPECT_CALL(*this, OnStateChanged(mojom::VideoCaptureState::RESUMED)); |
| 337 host_->Resume(kDeviceId, opened_session_id_, params); | 347 host_->Resume(kDeviceId, opened_session_id_, params); |
| 338 run_loop.RunUntilIdle(); | 348 run_loop.RunUntilIdle(); |
| 339 WaitForVideoDeviceThread(); | 349 WaitForVideoDeviceThread(); |
| 340 } | 350 } |
| 341 | 351 |
| 342 void StopCapture() { | 352 void StopCapture() { |
| 343 base::RunLoop run_loop; | 353 base::RunLoop run_loop; |
| 344 EXPECT_CALL(*host_.get(), | 354 |
| 345 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)) | 355 EXPECT_CALL(*this, OnStateChanged(mojom::VideoCaptureState::STOPPED)) |
| 346 .WillOnce(ExitMessageLoop(task_runner_, run_loop.QuitClosure())); | 356 .WillOnce(ExitMessageLoop(task_runner_, run_loop.QuitClosure())); |
| 347 | |
| 348 host_->Stop(kDeviceId); | 357 host_->Stop(kDeviceId); |
| 349 host_->SetReturnReceivedBuffersImmediately(true); | 358 host_->SetReturnReceivedBuffersImmediately(true); |
| 350 host_->ReturnReceivedBuffers(kDeviceId); | 359 host_->ReturnReceivedBuffers(kDeviceId); |
| 351 | 360 |
| 352 run_loop.Run(); | 361 run_loop.Run(); |
| 353 | 362 |
| 354 // Expect the VideoCaptureDevice has been stopped | 363 // Expect the VideoCaptureDevice has been stopped |
| 355 EXPECT_TRUE(host_->controllers_.empty()); | 364 EXPECT_TRUE(host_->controllers_.empty()); |
| 356 } | 365 } |
| 357 | 366 |
| 358 void NotifyPacketReady() { | 367 void NotifyPacketReady() { |
| 359 base::RunLoop run_loop; | 368 base::RunLoop run_loop; |
| 360 EXPECT_CALL(*host_.get(), OnBufferFilled(kDeviceId)) | 369 EXPECT_CALL(*host_.get(), OnBufferFilled(kDeviceId)) |
| 361 .Times(AnyNumber()) | 370 .Times(AnyNumber()) |
| 362 .WillOnce(ExitMessageLoop(task_runner_, run_loop.QuitClosure())) | 371 .WillOnce(ExitMessageLoop(task_runner_, run_loop.QuitClosure())) |
| 363 .RetiresOnSaturation(); | 372 .RetiresOnSaturation(); |
| 364 run_loop.Run(); | 373 run_loop.Run(); |
| 365 } | 374 } |
| 366 | 375 |
| 367 void ReturnReceivedPackets() { | 376 void ReturnReceivedPackets() { |
| 368 host_->ReturnReceivedBuffers(kDeviceId); | 377 host_->ReturnReceivedBuffers(kDeviceId); |
| 369 } | 378 } |
| 370 | 379 |
| 371 void SimulateError() { | 380 void SimulateError() { |
| 372 // Expect a change state to error state sent through IPC. | 381 // Expect a change state to error state sent through IPC. |
| 373 EXPECT_CALL(*host_.get(), | 382 EXPECT_CALL(*this, OnStateChanged(mojom::VideoCaptureState::FAILED)); |
| 374 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_ERROR)).Times(1); | |
| 375 VideoCaptureControllerID id(kDeviceId); | 383 VideoCaptureControllerID id(kDeviceId); |
| 376 host_->OnError(id); | 384 host_->OnError(id); |
| 377 // Wait for the error callback. | 385 // Wait for the error callback. |
| 378 base::RunLoop().RunUntilIdle(); | 386 base::RunLoop().RunUntilIdle(); |
| 379 } | 387 } |
| 380 | 388 |
| 381 void WaitForVideoDeviceThread() { | 389 void WaitForVideoDeviceThread() { |
| 382 base::RunLoop run_loop; | 390 base::RunLoop run_loop; |
| 383 media_stream_manager_->video_capture_manager()->device_task_runner() | 391 media_stream_manager_->video_capture_manager()->device_task_runner() |
| 384 ->PostTaskAndReply( | 392 ->PostTaskAndReply( |
| (...skipping 13 matching lines...) Expand all Loading... |
| 398 const content::TestBrowserThreadBundle thread_bundle_; | 406 const content::TestBrowserThreadBundle thread_bundle_; |
| 399 // |audio_manager_| needs to outlive |thread_bundle_| because it uses the | 407 // |audio_manager_| needs to outlive |thread_bundle_| because it uses the |
| 400 // underlying message loop. | 408 // underlying message loop. |
| 401 media::ScopedAudioManagerPtr audio_manager_; | 409 media::ScopedAudioManagerPtr audio_manager_; |
| 402 content::TestBrowserContext browser_context_; | 410 content::TestBrowserContext browser_context_; |
| 403 content::TestContentBrowserClient browser_client_; | 411 content::TestContentBrowserClient browser_client_; |
| 404 const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; | 412 const scoped_refptr<base::SingleThreadTaskRunner> task_runner_; |
| 405 int opened_session_id_; | 413 int opened_session_id_; |
| 406 std::string opened_device_label_; | 414 std::string opened_device_label_; |
| 407 | 415 |
| 416 mojo::Binding<mojom::VideoCaptureObserver> observer_binding_; |
| 417 |
| 408 DISALLOW_COPY_AND_ASSIGN(VideoCaptureHostTest); | 418 DISALLOW_COPY_AND_ASSIGN(VideoCaptureHostTest); |
| 409 }; | 419 }; |
| 410 | 420 |
| 411 TEST_F(VideoCaptureHostTest, CloseSessionWithoutStopping) { | 421 TEST_F(VideoCaptureHostTest, CloseSessionWithoutStopping) { |
| 412 StartCapture(); | 422 StartCapture(); |
| 413 | 423 |
| 414 // When the session is closed via the stream without stopping capture, the | 424 // When the session is closed via the stream without stopping capture, the |
| 415 // ENDED event is sent. | 425 // ENDED event is sent. |
| 416 EXPECT_CALL(*host_.get(), | 426 EXPECT_CALL(*this, OnStateChanged(mojom::VideoCaptureState::ENDED)); |
| 417 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_ENDED)).Times(1); | |
| 418 CloseSession(); | 427 CloseSession(); |
| 419 base::RunLoop().RunUntilIdle(); | 428 base::RunLoop().RunUntilIdle(); |
| 420 } | 429 } |
| 421 | 430 |
| 422 TEST_F(VideoCaptureHostTest, StopWhileStartPending) { | 431 TEST_F(VideoCaptureHostTest, StopWhileStartPending) { |
| 423 StartStopCapture(); | 432 StartStopCapture(); |
| 424 } | 433 } |
| 425 | 434 |
| 426 TEST_F(VideoCaptureHostTest, StartCapturePlayStop) { | 435 TEST_F(VideoCaptureHostTest, StartCapturePlayStop) { |
| 427 StartCapture(); | 436 StartCapture(); |
| 428 NotifyPacketReady(); | 437 NotifyPacketReady(); |
| 429 NotifyPacketReady(); | 438 NotifyPacketReady(); |
| 430 ReturnReceivedPackets(); | 439 ReturnReceivedPackets(); |
| 431 StopCapture(); | 440 StopCapture(); |
| 432 } | 441 } |
| 433 | 442 |
| 434 TEST_F(VideoCaptureHostTest, StartCaptureErrorStop) { | 443 TEST_F(VideoCaptureHostTest, StartCaptureErrorStop) { |
| 435 StartCapture(); | 444 StartCapture(); |
| 436 SimulateError(); | 445 SimulateError(); |
| 437 StopCapture(); | 446 StopCapture(); |
| 438 } | 447 } |
| 439 | 448 |
| 440 TEST_F(VideoCaptureHostTest, StartCaptureError) { | 449 TEST_F(VideoCaptureHostTest, StartCaptureError) { |
| 441 EXPECT_CALL(*host_.get(), | 450 EXPECT_CALL(*this, OnStateChanged(mojom::VideoCaptureState::STOPPED)) |
| 442 OnStateChanged(kDeviceId, VIDEO_CAPTURE_STATE_STOPPED)).Times(0); | 451 .Times(0); |
| 443 StartCapture(); | 452 StartCapture(); |
| 444 NotifyPacketReady(); | 453 NotifyPacketReady(); |
| 445 SimulateError(); | 454 SimulateError(); |
| 446 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); | 455 base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(200)); |
| 447 } | 456 } |
| 448 | 457 |
| 449 TEST_F(VideoCaptureHostTest, PauseResumeCapture) { | 458 TEST_F(VideoCaptureHostTest, PauseResumeCapture) { |
| 450 StartCapture(); | 459 StartCapture(); |
| 451 PauseResumeCapture(); | 460 PauseResumeCapture(); |
| 452 StopCapture(); | 461 StopCapture(); |
| 453 } | 462 } |
| 454 | 463 |
| 455 } // namespace content | 464 } // namespace content |
| OLD | NEW |