1 // Copyright 2022 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include <fuchsia/element/cpp/fidl.h>
6 #include <fuchsia/io/cpp/fidl.h>
7 #include <fuchsia/web/cpp/fidl.h>
8 #include <lib/sys/cpp/component_context.h>
9 #include <lib/sys/cpp/service_directory.h>
11 #include "base/base_paths.h"
12 #include "base/check.h"
13 #include "base/command_line.h"
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/fuchsia/file_utils.h"
17 #include "base/fuchsia/fuchsia_logging.h"
18 #include "base/fuchsia/mem_buffer_util.h"
19 #include "base/fuchsia/process_context.h"
20 #include "base/logging.h"
21 #include "base/path_service.h"
22 #include "base/run_loop.h"
23 #include "base/task/single_thread_task_executor.h"
24 #include "base/test/test_future.h"
25 #include "base/time/time.h"
26 #include "components/cast/message_port/fuchsia/create_web_message.h"
27 #include "components/cast/message_port/platform_message_port.h"
28 #include "components/cast_streaming/test/cast_streaming_test_sender.h"
29 #include "components/fuchsia_component_support/annotations_manager.h"
30 #include "fuchsia_web/cast_streaming/cast_streaming.h"
31 #include "fuchsia_web/common/init_logging.h"
32 #include "fuchsia_web/common/test/fit_adapter.h"
33 #include "fuchsia_web/common/test/frame_test_util.h"
34 #include "fuchsia_web/shell/present_frame.h"
35 #include "fuchsia_web/shell/remote_debugging_port.h"
36 #include "fuchsia_web/shell/shell_relauncher.h"
37 #include "fuchsia_web/webengine/switches.h"
38 #include "fuchsia_web/webinstance_host/web_instance_host.h"
39 #include "media/base/media_util.h"
40 #include "media/gpu/test/video_test_helpers.h"
41 #include "third_party/abseil-cpp/absl/types/optional.h"
45 // Identifier for JavaScript to be injected, only relevant if injecting multiple
47 constexpr int kAddBeforeLoadJavaScriptID = 0;
50 std::cerr << "Usage: "
51 << base::CommandLine::ForCurrentProcess()->GetProgram().BaseName()
52 << " [--" << kRemoteDebuggingPortSwitch
53 << "=<port>] [-- [--{extra_flag1}] [--{extra_flag2}]]\n"
54 << "Setting " << kRemoteDebuggingPortSwitch << "=0"
55 << " will automatically choose an available port.\n"
56 << "Extra flags will be passed to WebEngine to be processed.\n";
59 media::VideoDecoderConfig GetDefaultVideoConfig() {
60 constexpr gfx::Size kVideoSize = {1280, 720};
61 constexpr gfx::Rect kVideoRect(kVideoSize);
63 return media::VideoDecoderConfig(
64 media::VideoCodec::kVP8, media::VideoCodecProfile::VP8PROFILE_MIN,
65 media::VideoDecoderConfig::AlphaMode::kIsOpaque, media::VideoColorSpace(),
66 media::VideoTransformation(), kVideoSize, kVideoRect, kVideoSize,
67 media::EmptyExtraData(), media::EncryptionScheme::kUnencrypted);
70 // Set up content directory and context params.
71 fuchsia::web::CreateContextParams GetCreateContextParams(
72 uint16_t remote_debugging_port) {
73 // Configure the fuchsia-dir://cast-streaming/ directory.
74 fuchsia::web::CreateContextParams create_context_params;
75 ApplyCastStreamingContextParams(&create_context_params);
77 // Enable other WebEngine features.
78 fuchsia::web::ContextFeatureFlags features =
79 fuchsia::web::ContextFeatureFlags::AUDIO |
80 fuchsia::web::ContextFeatureFlags::HARDWARE_VIDEO_DECODER |
81 fuchsia::web::ContextFeatureFlags::NETWORK |
82 fuchsia::web::ContextFeatureFlags::VULKAN;
83 create_context_params.set_features(features);
85 create_context_params.set_remote_debugging_port(remote_debugging_port);
87 return create_context_params;
90 // Set autoplay, enable all logging, and present fullscreen view of `frame`.
91 absl::optional<fuchsia::element::GraphicalPresenterPtr> ConfigureFrame(
92 fuchsia::web::Frame* frame,
93 fidl::InterfaceHandle<fuchsia::element::AnnotationController>
94 annotation_controller) {
95 fuchsia::web::ContentAreaSettings settings;
96 settings.set_autoplay_policy(fuchsia::web::AutoplayPolicy::ALLOW);
97 frame->SetContentAreaSettings(std::move(settings));
98 frame->SetJavaScriptLogLevel(fuchsia::web::ConsoleLogLevel::DEBUG);
99 return PresentFrame(frame, std::move(annotation_controller));
104 int main(int argc, char** argv) {
105 base::CommandLine::Init(argc, argv);
107 base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
108 CHECK(InitLoggingFromCommandLine(*command_line));
110 base::SingleThreadTaskExecutor executor(base::MessagePumpType::IO);
112 if (auto optional_exit_code = RelaunchForWebInstanceHostIfParent(
113 "#meta/cast_streaming_shell_for_web_instance_host.cm", *command_line);
114 optional_exit_code.has_value()) {
115 return optional_exit_code.value();
118 absl::optional<uint16_t> remote_debugging_port =
119 GetRemoteDebuggingPort(*command_line);
120 if (!remote_debugging_port) {
125 // Instantiate Web Instance Host.
126 WebInstanceHostWithServicesFromThisComponent web_instance_host(
127 *base::ComponentContextForProcess()->outgoing(),
128 /*is_web_instance_component_in_same_package=*/false);
129 fidl::InterfaceRequest<fuchsia::io::Directory> services_request;
130 auto services = sys::ServiceDirectory::CreateWithRequest(&services_request);
131 base::CommandLine child_command_line =
132 base::CommandLine(command_line->GetArgs());
133 child_command_line.AppendSwitch(switches::kEnableCastStreamingReceiver);
134 zx_status_t result = web_instance_host.CreateInstanceForContextWithCopiedArgs(
135 GetCreateContextParams(remote_debugging_port.value()),
136 std::move(services_request), child_command_line);
137 if (result != ZX_OK) {
138 ZX_LOG(ERROR, result) << "CreateInstanceForContextWithCopiedArgs failed";
142 base::ComponentContextForProcess()->outgoing()->ServeFromStartupInfo();
144 // Create the browser `context`.
145 fuchsia::web::ContextPtr context;
146 services->Connect(context.NewRequest());
148 base::RunLoop run_loop;
150 context.set_error_handler(
151 [quit_run_loop = run_loop.QuitClosure()](zx_status_t status) {
152 ZX_LOG(ERROR, status) << "Context connection lost:";
156 // Create the browser `frame`.
157 fuchsia::web::CreateFrameParams frame_params;
158 frame_params.set_enable_remote_debugging(true);
160 fuchsia::web::FramePtr frame;
161 context->CreateFrameWithParams(std::move(frame_params), frame.NewRequest());
162 frame.set_error_handler(
163 [quit_run_loop = run_loop.QuitClosure()](zx_status_t status) {
164 ZX_LOG(ERROR, status) << "Frame connection lost:";
168 // The underlying PresentView call expects an AnnotationController and will
169 // return PresentViewError.INVALID_ARGS without one. The AnnotationController
170 // should serve WatchAnnotations, but it doesn't need to do anything.
171 // TODO(b/264899156): Remove this when AnnotationController becomes
173 auto annotations_manager =
174 std::make_unique<fuchsia_component_support::AnnotationsManager>();
175 fuchsia::element::AnnotationControllerPtr annotation_controller;
176 annotations_manager->Connect(annotation_controller.NewRequest());
178 ConfigureFrame(frame.get(), std::move(annotation_controller));
180 // Register the MessagePort for the Cast Streaming Receiver.
181 std::unique_ptr<cast_api_bindings::MessagePort> sender_message_port;
182 std::unique_ptr<cast_api_bindings::MessagePort> receiver_message_port;
183 cast_api_bindings::CreatePlatformMessagePortPair(&sender_message_port,
184 &receiver_message_port);
186 constexpr char kCastStreamingMessagePortOrigin[] = "cast-streaming:receiver";
187 base::test::TestFuture<fuchsia::web::Frame_PostMessage_Result>
189 frame->PostMessage(kCastStreamingMessagePortOrigin,
190 CreateWebMessage("", std::move(receiver_message_port)),
191 CallbackToFitFunction(post_message_result.GetCallback()));
192 if (!post_message_result.Wait()) {
193 LOG(ERROR) << "PostMessage timed out.";
196 if (post_message_result.Get().is_err()) {
197 LOG(ERROR) << "PostMessage failed.";
201 // Inject JavaScript test harness into receiver.html.
202 base::test::TestFuture<fuchsia::web::Frame_AddBeforeLoadJavaScript_Result>
203 add_before_load_javascript_result;
204 base::FilePath pkg_path;
205 CHECK(base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &pkg_path));
206 base::FilePath test_harness_js_path(pkg_path.AppendASCII(
207 "fuchsia_web/shell/cast_streaming_shell_data/injector.js"));
208 std::string test_harness_string;
209 CHECK(base::ReadFileToString(test_harness_js_path, &test_harness_string));
210 frame->AddBeforeLoadJavaScript(
211 kAddBeforeLoadJavaScriptID, {"*"},
212 base::MemBufferFromString(std::move(test_harness_string),
214 CallbackToFitFunction(add_before_load_javascript_result.GetCallback()));
215 if (!add_before_load_javascript_result.Wait()) {
216 LOG(ERROR) << "AddBeforeLoadJavaScript timed out.";
219 if (add_before_load_javascript_result.Get().is_err()) {
220 LOG(ERROR) << "AddBeforeLoadJavaScript failed.";
224 // Send `sender_message_port` to a Sender and start it.
225 cast_streaming::CastStreamingTestSender sender;
226 sender.Start(std::move(sender_message_port), net::IPAddress::IPv6Localhost(),
227 absl::nullopt, GetDefaultVideoConfig());
229 // Navigate `frame` to `receiver.html`.
230 fuchsia::web::LoadUrlParams load_params;
231 load_params.set_type(fuchsia::web::LoadUrlReason::TYPED);
232 load_params.set_was_user_activated(true);
233 fuchsia::web::NavigationControllerPtr nav_controller;
234 frame->GetNavigationController(nav_controller.NewRequest());
235 if (!LoadUrlAndExpectResponse(nav_controller, std::move(load_params),
236 kCastStreamingWebUrl)) {
237 LOG(ERROR) << "LoadUrl failed.";
241 // Log the debugging port, if debugging is requested.
242 base::test::TestFuture<fuchsia::web::Context_GetRemoteDebuggingPort_Result>
243 get_remote_debugging_port_result;
244 context->GetRemoteDebuggingPort(
245 CallbackToFitFunction(get_remote_debugging_port_result.GetCallback()));
246 if (!get_remote_debugging_port_result.Wait()) {
247 LOG(ERROR) << "Remote debugging service timed out.";
250 if (get_remote_debugging_port_result.Get().is_err()) {
251 LOG(ERROR) << "Remote debugging service was not opened.";
254 LOG(INFO) << "Remote debugging port: "
255 << get_remote_debugging_port_result.Get().response().port;
257 if (!sender.RunUntilActive()) {
258 LOG(ERROR) << "RunUntilActive failed.";
263 base::FilePath video_file(
264 pkg_path.AppendASCII("media/test/data/bear-1280x720.ivf"));
265 absl::optional<std::vector<uint8_t>> video_stream =
266 base::ReadFileToBytes(video_file);
267 CHECK(video_stream.has_value());
268 media::test::EncodedDataHelper video_helper(video_stream.value(),
269 media::VideoCodec::kVP8);
271 // Send first key frame.
272 scoped_refptr<media::DecoderBuffer> video_decoder_buffer =
273 video_helper.GetNextBuffer();
274 video_decoder_buffer->set_timestamp(base::TimeDelta());
275 video_decoder_buffer->set_is_key_frame(true);
276 sender.SendVideoBuffer(video_decoder_buffer);