cast_runner_integration_test.cc 49 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280
  1. // Copyright 2018 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. #include <fuchsia/camera3/cpp/fidl.h>
  5. #include <fuchsia/legacymetrics/cpp/fidl.h>
  6. #include <fuchsia/legacymetrics/cpp/fidl_test_base.h>
  7. #include <fuchsia/media/cpp/fidl.h>
  8. #include <fuchsia/modular/cpp/fidl.h>
  9. #include <fuchsia/web/cpp/fidl.h>
  10. #include <lib/fdio/directory.h>
  11. #include <lib/fidl/cpp/binding.h>
  12. #include <lib/sys/cpp/component_context.h>
  13. #include <lib/ui/scenic/cpp/view_token_pair.h>
  14. #include <lib/zx/channel.h>
  15. #include <zircon/processargs.h>
  16. #include <utility>
  17. #include <vector>
  18. #include "base/auto_reset.h"
  19. #include "base/barrier_closure.h"
  20. #include "base/base_paths.h"
  21. #include "base/callback_helpers.h"
  22. #include "base/files/file_util.h"
  23. #include "base/fuchsia/file_utils.h"
  24. #include "base/fuchsia/filtered_service_directory.h"
  25. #include "base/fuchsia/fuchsia_logging.h"
  26. #include "base/fuchsia/mem_buffer_util.h"
  27. #include "base/fuchsia/scoped_service_binding.h"
  28. #include "base/fuchsia/test_component_controller.h"
  29. #include "base/path_service.h"
  30. #include "base/run_loop.h"
  31. #include "base/strings/strcat.h"
  32. #include "base/strings/string_piece.h"
  33. #include "base/test/bind.h"
  34. #include "base/test/scoped_run_loop_timeout.h"
  35. #include "base/test/task_environment.h"
  36. #include "base/test/test_future.h"
  37. #include "base/test/test_timeouts.h"
  38. #include "base/threading/sequenced_task_runner_handle.h"
  39. #include "build/build_config.h"
  40. #include "fuchsia_web/common/string_util.h"
  41. #include "fuchsia_web/common/test/fit_adapter.h"
  42. #include "fuchsia_web/common/test/frame_test_util.h"
  43. #include "fuchsia_web/common/test/test_devtools_list_fetcher.h"
  44. #include "fuchsia_web/common/test/url_request_rewrite_test_util.h"
  45. #include "fuchsia_web/runners/cast/cast_runner.h"
  46. #include "fuchsia_web/runners/cast/cast_runner_integration_test_base.h"
  47. #include "fuchsia_web/runners/cast/cast_runner_switches.h"
  48. #include "fuchsia_web/runners/cast/fake_api_bindings.h"
  49. #include "fuchsia_web/runners/cast/fake_application_config_manager.h"
  50. #include "fuchsia_web/runners/cast/fidl/fidl/chromium/cast/cpp/fidl.h"
  51. #include "fuchsia_web/runners/common/modular/agent_impl.h"
  52. #include "fuchsia_web/runners/common/modular/fake_component_context.h"
  53. #include "testing/gtest/include/gtest/gtest.h"
  54. namespace {
  55. constexpr char kTestAppId[] = "00000000";
  56. constexpr char kSecondTestAppId[] = "FFFFFFFF";
  57. constexpr char kBlankAppUrl[] = "/defaultresponse";
  58. constexpr char kEchoHeaderPath[] = "/echoheader?Test";
  59. constexpr char kDummyAgentUrl[] =
  60. "fuchsia-pkg://fuchsia.com/dummy_agent#meta/dummy_agent.cmx";
  61. class FakeCorsExemptHeaderProvider final
  62. : public chromium::cast::CorsExemptHeaderProvider {
  63. public:
  64. FakeCorsExemptHeaderProvider() = default;
  65. ~FakeCorsExemptHeaderProvider() override = default;
  66. FakeCorsExemptHeaderProvider(const FakeCorsExemptHeaderProvider&) = delete;
  67. FakeCorsExemptHeaderProvider& operator=(const FakeCorsExemptHeaderProvider&) =
  68. delete;
  69. private:
  70. void GetCorsExemptHeaderNames(
  71. GetCorsExemptHeaderNamesCallback callback) override {
  72. callback({StringToBytes("Test")});
  73. }
  74. };
  75. class FakeUrlRequestRewriteRulesProvider final
  76. : public chromium::cast::UrlRequestRewriteRulesProvider {
  77. public:
  78. FakeUrlRequestRewriteRulesProvider() = default;
  79. ~FakeUrlRequestRewriteRulesProvider() override = default;
  80. FakeUrlRequestRewriteRulesProvider(
  81. const FakeUrlRequestRewriteRulesProvider&) = delete;
  82. FakeUrlRequestRewriteRulesProvider& operator=(
  83. const FakeUrlRequestRewriteRulesProvider&) = delete;
  84. private:
  85. void GetUrlRequestRewriteRules(
  86. GetUrlRequestRewriteRulesCallback callback) override {
  87. // Only send the rules once. They do not expire
  88. if (rules_sent_)
  89. return;
  90. rules_sent_ = true;
  91. std::vector<fuchsia::web::UrlRequestRewrite> rewrites;
  92. rewrites.push_back(CreateRewriteAddHeaders("Test", "TestHeaderValue"));
  93. fuchsia::web::UrlRequestRewriteRule rule;
  94. rule.set_rewrites(std::move(rewrites));
  95. std::vector<fuchsia::web::UrlRequestRewriteRule> rules;
  96. rules.push_back(std::move(rule));
  97. callback(std::move(rules));
  98. }
  99. bool rules_sent_ = false;
  100. };
  101. class FakeApplicationContext final : public chromium::cast::ApplicationContext {
  102. public:
  103. FakeApplicationContext() = default;
  104. ~FakeApplicationContext() override = default;
  105. FakeApplicationContext(const FakeApplicationContext&) = delete;
  106. FakeApplicationContext& operator=(const FakeApplicationContext&) = delete;
  107. chromium::cast::ApplicationController* controller() {
  108. if (!controller_)
  109. return nullptr;
  110. return controller_.get();
  111. }
  112. absl::optional<int64_t> WaitForApplicationTerminated() {
  113. base::RunLoop loop;
  114. on_application_terminated_ = loop.QuitClosure();
  115. loop.Run();
  116. return application_exit_code_;
  117. }
  118. private:
  119. // chromium::cast::ApplicationContext implementation.
  120. void GetMediaSessionId(GetMediaSessionIdCallback callback) override {
  121. callback(1);
  122. }
  123. void SetApplicationController(
  124. fidl::InterfaceHandle<chromium::cast::ApplicationController> controller)
  125. override {
  126. controller_ = controller.Bind();
  127. }
  128. void OnApplicationExit(int64_t exit_code) override {
  129. application_exit_code_ = exit_code;
  130. if (on_application_terminated_)
  131. std::move(on_application_terminated_).Run();
  132. }
  133. chromium::cast::ApplicationControllerPtr controller_;
  134. absl::optional<int64_t> application_exit_code_;
  135. base::OnceClosure on_application_terminated_;
  136. };
  137. class FakeComponentState : public cr_fuchsia::AgentImpl::ComponentStateBase {
  138. public:
  139. FakeComponentState(
  140. base::StringPiece component_url,
  141. chromium::cast::ApplicationConfigManager* app_config_manager,
  142. chromium::cast::ApiBindings* bindings_manager,
  143. chromium::cast::UrlRequestRewriteRulesProvider*
  144. url_request_rules_provider)
  145. : ComponentStateBase(component_url),
  146. app_config_binding_(outgoing_directory(), app_config_manager),
  147. bindings_manager_binding_(outgoing_directory(), bindings_manager),
  148. context_binding_(outgoing_directory(), &application_context_) {
  149. if (url_request_rules_provider) {
  150. url_request_rules_provider_binding_.emplace(outgoing_directory(),
  151. url_request_rules_provider);
  152. }
  153. }
  154. ~FakeComponentState() override {
  155. if (on_delete_)
  156. std::move(on_delete_).Run();
  157. }
  158. FakeComponentState(const FakeComponentState&) = delete;
  159. FakeComponentState& operator=(const FakeComponentState&) = delete;
  160. // Make outgoing_directory() public.
  161. using ComponentStateBase::outgoing_directory;
  162. FakeApplicationContext* application_context() {
  163. return &application_context_;
  164. }
  165. void set_on_delete(base::OnceClosure on_delete) {
  166. on_delete_ = std::move(on_delete);
  167. }
  168. void Disconnect() { DisconnectClientsAndTeardown(); }
  169. bool api_bindings_has_clients() {
  170. return bindings_manager_binding_.has_clients();
  171. }
  172. bool url_request_rules_provider_has_clients() {
  173. if (url_request_rules_provider_binding_) {
  174. return url_request_rules_provider_binding_->has_clients();
  175. }
  176. return false;
  177. }
  178. protected:
  179. const base::ScopedServiceBinding<chromium::cast::ApplicationConfigManager>
  180. app_config_binding_;
  181. const base::ScopedServiceBinding<chromium::cast::ApiBindings>
  182. bindings_manager_binding_;
  183. absl::optional<base::ScopedServiceBinding<
  184. chromium::cast::UrlRequestRewriteRulesProvider>>
  185. url_request_rules_provider_binding_;
  186. FakeApplicationContext application_context_;
  187. const base::ScopedServiceBinding<chromium::cast::ApplicationContext>
  188. context_binding_;
  189. base::OnceClosure on_delete_;
  190. };
  191. class TestCastComponent {
  192. public:
  193. explicit TestCastComponent(fuchsia::sys::RunnerPtr& cast_runner)
  194. : app_config_manager_binding_(&component_services_, &app_config_manager_),
  195. cast_runner_(cast_runner.get()) {
  196. EXPECT_TRUE(cast_runner_);
  197. }
  198. ~TestCastComponent() {
  199. if (component_controller_)
  200. ShutdownComponent();
  201. }
  202. TestCastComponent(const TestCastComponent&) = delete;
  203. TestCastComponent& operator=(const FakeComponentState&) = delete;
  204. void CreateComponentContextAndStartComponent(
  205. base::StringPiece app_id = kTestAppId) {
  206. ASSERT_FALSE(component_context_)
  207. << "ComponentContext may only be created once";
  208. auto component_url = base::StrCat({"cast:", app_id});
  209. InjectQueryApi();
  210. CreateComponentContext(component_url);
  211. StartCastComponent(component_url);
  212. WaitComponentStateCreated();
  213. WaitQueryApiConnected();
  214. }
  215. void CreateComponentContext(const base::StringPiece& component_url,
  216. bool with_fake_agent = true) {
  217. ASSERT_FALSE(component_context_)
  218. << "ComponentContext may only be created once";
  219. url_request_rewrite_rules_provider_ =
  220. std::make_unique<FakeUrlRequestRewriteRulesProvider>();
  221. component_context_ = std::make_unique<cr_fuchsia::FakeComponentContext>(
  222. &component_services_, component_url);
  223. if (with_fake_agent) {
  224. component_context_->RegisterCreateComponentStateCallback(
  225. FakeApplicationConfigManager::kFakeAgentUrl,
  226. base::BindRepeating(&TestCastComponent::OnComponentConnect,
  227. base::Unretained(this)));
  228. }
  229. }
  230. void StartCastComponent(base::StringPiece component_url) {
  231. ASSERT_FALSE(component_services_client_)
  232. << "Component may only be started once";
  233. // Configure the Runner, including a service directory channel to publish
  234. // services to.
  235. fuchsia::sys::StartupInfo startup_info;
  236. startup_info.launch_info.url = std::string(component_url);
  237. fidl::InterfaceHandle<fuchsia::io::Directory> outgoing_directory;
  238. startup_info.launch_info.directory_request =
  239. outgoing_directory.NewRequest().TakeChannel();
  240. fidl::InterfaceHandle<fuchsia::io::Directory> svc_directory;
  241. EXPECT_EQ(fdio_service_connect_at(
  242. outgoing_directory.channel().get(), "svc",
  243. svc_directory.NewRequest().TakeChannel().release()),
  244. ZX_OK);
  245. component_services_client_ =
  246. std::make_unique<sys::ServiceDirectory>(std::move(svc_directory));
  247. // Populate |component_services_| with services for the component to use.
  248. fidl::InterfaceHandle<fuchsia::io::Directory> directory;
  249. component_services_.GetOrCreateDirectory("svc")->Serve(
  250. fuchsia::io::OpenFlags::RIGHT_READABLE |
  251. fuchsia::io::OpenFlags::RIGHT_WRITABLE,
  252. directory.NewRequest().TakeChannel());
  253. ASSERT_EQ(component_services_.AddPublicService(
  254. cors_exempt_header_provider_binding_.GetHandler(
  255. &cors_exempt_header_provider_)),
  256. ZX_OK);
  257. // Provide the directory of services in the |flat_namespace|.
  258. startup_info.flat_namespace.paths.emplace_back(base::kServiceDirectoryPath);
  259. startup_info.flat_namespace.directories.emplace_back(
  260. directory.TakeChannel());
  261. fuchsia::sys::Package package;
  262. package.resolved_url = std::string(component_url);
  263. cast_runner_->StartComponent(std::move(package), std::move(startup_info),
  264. component_controller_.ptr().NewRequest());
  265. component_controller_.ptr().set_error_handler([](zx_status_t status) {
  266. ZX_LOG(ERROR, status) << "Component launch failed";
  267. ADD_FAILURE();
  268. });
  269. }
  270. void RegisterAppWithTestData(GURL url) {
  271. fuchsia::web::ContentDirectoryProvider provider;
  272. provider.set_name("testdata");
  273. base::FilePath pkg_path;
  274. ASSERT_TRUE(
  275. base::PathService::Get(base::DIR_SRC_TEST_DATA_ROOT, &pkg_path));
  276. provider.set_directory(base::OpenDirectoryHandle(
  277. pkg_path.AppendASCII("fuchsia_web/runners/cast/testdata")));
  278. std::vector<fuchsia::web::ContentDirectoryProvider> providers;
  279. providers.emplace_back(std::move(provider));
  280. auto app_config =
  281. FakeApplicationConfigManager::CreateConfig(kTestAppId, url);
  282. app_config.set_content_directories_for_isolated_application(
  283. std::move(providers));
  284. app_config_manager_.AddAppConfig(std::move(app_config));
  285. }
  286. // Executes |code| in the context of the test application and then returns
  287. // the result serialized as string. If the code evaluates to a promise then
  288. // execution is blocked until the promise is complete and the result of the
  289. // promise is returned.
  290. std::string ExecuteJavaScript(const std::string& code) {
  291. fuchsia::web::WebMessage message;
  292. message.set_data(base::MemBufferFromString(code, "test-msg"));
  293. test_port_->PostMessage(
  294. std::move(message),
  295. [](fuchsia::web::MessagePort_PostMessage_Result result) {
  296. EXPECT_TRUE(result.is_response());
  297. });
  298. base::test::TestFuture<fuchsia::web::WebMessage> response;
  299. test_port_->ReceiveMessage(CallbackToFitFunction(response.GetCallback()));
  300. EXPECT_TRUE(response.Wait());
  301. absl::optional<std::string> response_string =
  302. base::StringFromMemBuffer(response.Get().data());
  303. EXPECT_TRUE(response_string.has_value());
  304. return response_string.value_or(std::string());
  305. }
  306. void CheckAppUrl(const GURL& app_url) {
  307. EXPECT_EQ(ExecuteJavaScript("window.location.href"), app_url.spec());
  308. }
  309. void ShutdownComponent() {
  310. EXPECT_TRUE(component_controller_);
  311. if (component_state_) {
  312. base::RunLoop run_loop;
  313. component_state_->set_on_delete(run_loop.QuitClosure());
  314. component_controller_.ptr().Unbind();
  315. run_loop.Run();
  316. }
  317. }
  318. void ExpectControllerDisconnectWithStatus(zx_status_t expected_status) {
  319. EXPECT_TRUE(component_controller_);
  320. base::RunLoop loop;
  321. component_controller_.ptr().set_error_handler(
  322. [&loop, expected_status](zx_status_t status) {
  323. loop.Quit();
  324. EXPECT_EQ(expected_status, status);
  325. });
  326. loop.Run();
  327. }
  328. void WaitForComponentDestroyed() {
  329. ASSERT_TRUE(component_state_);
  330. base::RunLoop state_loop;
  331. component_state_->set_on_delete(state_loop.QuitClosure());
  332. if (component_controller_)
  333. ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED);
  334. state_loop.Run();
  335. ResetComponentState();
  336. }
  337. void ResetComponentState() {
  338. component_context_ = nullptr;
  339. component_services_client_ = nullptr;
  340. component_state_ = nullptr;
  341. test_port_ = nullptr;
  342. }
  343. void OnComponentStateCreated(base::OnceClosure callback) {
  344. ASSERT_FALSE(component_state_created_callback_);
  345. component_state_created_callback_ = std::move(callback);
  346. }
  347. FakeApplicationConfigManager* app_config_manager() {
  348. return &app_config_manager_;
  349. }
  350. FakeApiBindingsImpl* api_bindings() { return &api_bindings_; }
  351. cr_fuchsia::FakeComponentContext* component_context() {
  352. return component_context_.get();
  353. }
  354. base::TestComponentController* component_controller() {
  355. return &component_controller_;
  356. }
  357. sys::OutgoingDirectory* component_services() { return &component_services_; }
  358. sys::ServiceDirectory* component_services_client() {
  359. return component_services_client_.get();
  360. }
  361. FakeComponentState* component_state() { return component_state_; }
  362. private:
  363. void InjectQueryApi() {
  364. // Inject an API which can be used to evaluate arbitrary Javascript and
  365. // return the results over a MessagePort.
  366. std::vector<chromium::cast::ApiBinding> binding_list;
  367. chromium::cast::ApiBinding eval_js_binding;
  368. eval_js_binding.set_before_load_script(base::MemBufferFromString(
  369. "function valueOrUndefinedString(value) {"
  370. " return (typeof(value) == 'undefined') ? 'undefined' : value;"
  371. "}"
  372. "window.addEventListener('DOMContentLoaded', (event) => {"
  373. " var port = cast.__platform__.PortConnector.bind('testport');"
  374. " port.onmessage = (e) => {"
  375. " var result = eval(e.data);"
  376. " if (result && typeof(result.then) == 'function') {"
  377. " result"
  378. " .then(result =>"
  379. " port.postMessage(valueOrUndefinedString(result)))"
  380. " .catch(e => port.postMessage(JSON.stringify(e)));"
  381. " } else {"
  382. " port.postMessage(valueOrUndefinedString(result));"
  383. " }"
  384. " };"
  385. "});",
  386. "test"));
  387. binding_list.emplace_back(std::move(eval_js_binding));
  388. api_bindings_.set_bindings(std::move(binding_list));
  389. }
  390. void WaitQueryApiConnected() {
  391. EXPECT_FALSE(test_port_);
  392. test_port_ = api_bindings_.RunAndReturnConnectedPort("testport").Bind();
  393. }
  394. void WaitComponentStateCreated() {
  395. base::RunLoop run_loop;
  396. base::OnceClosure old_component_state_created_callback =
  397. std::move(component_state_created_callback_);
  398. component_state_created_callback_ = base::BindLambdaForTesting([&] {
  399. if (old_component_state_created_callback) {
  400. std::move(old_component_state_created_callback).Run();
  401. }
  402. run_loop.Quit();
  403. });
  404. run_loop.Run();
  405. }
  406. std::unique_ptr<cr_fuchsia::AgentImpl::ComponentStateBase> OnComponentConnect(
  407. base::StringPiece component_url) {
  408. auto component_state = std::make_unique<FakeComponentState>(
  409. component_url, &app_config_manager_, &api_bindings_,
  410. url_request_rewrite_rules_provider_.get());
  411. component_state_ = component_state.get();
  412. if (component_state_created_callback_)
  413. std::move(component_state_created_callback_).Run();
  414. return component_state;
  415. }
  416. FakeApplicationConfigManager app_config_manager_;
  417. FakeApiBindingsImpl api_bindings_;
  418. std::unique_ptr<FakeUrlRequestRewriteRulesProvider>
  419. url_request_rewrite_rules_provider_;
  420. FakeCorsExemptHeaderProvider cors_exempt_header_provider_;
  421. fidl::BindingSet<chromium::cast::CorsExemptHeaderProvider>
  422. cors_exempt_header_provider_binding_;
  423. // Incoming service directory, ComponentContext and per-component state.
  424. sys::OutgoingDirectory component_services_;
  425. base::ScopedServiceBinding<chromium::cast::ApplicationConfigManager>
  426. app_config_manager_binding_;
  427. std::unique_ptr<cr_fuchsia::FakeComponentContext> component_context_;
  428. base::TestComponentController component_controller_;
  429. std::unique_ptr<sys::ServiceDirectory> component_services_client_;
  430. FakeComponentState* component_state_ = nullptr;
  431. fuchsia::web::MessagePortPtr test_port_;
  432. base::OnceClosure component_state_created_callback_;
  433. fuchsia::sys::Runner* const cast_runner_;
  434. };
  435. } // namespace
  436. // A basic integration test ensuring a basic cast request launches the right
  437. // URL in the Chromium service.
  438. TEST_F(CastRunnerIntegrationTest, BasicRequest) {
  439. TestCastComponent component(cast_runner());
  440. GURL app_url = test_server().GetURL(kBlankAppUrl);
  441. component.app_config_manager()->AddApp(kTestAppId, app_url);
  442. component.CreateComponentContextAndStartComponent();
  443. component.CheckAppUrl(app_url);
  444. }
  445. // Verify that the Runner can continue to be used even after its Context has
  446. // crashed. Regression test for https://crbug.com/1066826.
  447. // TODO(crbug.com/1066833): Replace this with a WebRunner test, ideally a
  448. // unit-test, which can simulate Context disconnection more simply.
  449. // TODO(crbug.com/1010222): Once CastRunner migrates to creating the WebEngine
  450. // component directly, it should be possible to rehabilitate and re-enable
  451. // this test. At present it is not straightforward to terminate the
  452. // WebEngine component instance, only the ContextProvider, which will not
  453. // result in the WebEngine instance being torn-down.
  454. TEST_F(CastRunnerIntegrationTest, DISABLED_CanRecreateContext) {
  455. TestCastComponent component(cast_runner());
  456. const GURL app_url = test_server().GetURL(kBlankAppUrl);
  457. component.app_config_manager()->AddApp(kTestAppId, app_url);
  458. // Create a Cast component and verify that it has loaded.
  459. component.CreateComponentContextAndStartComponent(kTestAppId);
  460. component.CheckAppUrl(app_url);
  461. // Terminate the component that provides the ContextProvider service and
  462. // wait for the Cast component to terminate, without allowing the message-
  463. // loop to spin in-between.
  464. component.WaitForComponentDestroyed();
  465. // Create a second Cast component and verify that it has loaded.
  466. // There is no guarantee that the CastRunner has detected the old web.Context
  467. // disconnecting yet, so attempts to launch Cast components could fail.
  468. // WebContentRunner::CreateFrameWithParams() will synchronously verify that
  469. // the web.Context is not-yet-closed, to work-around that.
  470. TestCastComponent second_component(cast_runner());
  471. second_component.app_config_manager()->AddApp(kTestAppId, app_url);
  472. second_component.CreateComponentContextAndStartComponent(kTestAppId);
  473. second_component.CheckAppUrl(app_url);
  474. }
  475. TEST_F(CastRunnerIntegrationTest, ApiBindings) {
  476. TestCastComponent component(cast_runner());
  477. component.app_config_manager()->AddApp(kTestAppId,
  478. test_server().GetURL(kBlankAppUrl));
  479. component.CreateComponentContextAndStartComponent();
  480. // Verify that we can communicate with the binding added in
  481. // CastRunnerIntegrationTest().
  482. EXPECT_EQ(component.ExecuteJavaScript("1+2+\"\""), "3");
  483. }
  484. TEST_F(CastRunnerIntegrationTest, IncorrectCastAppId) {
  485. TestCastComponent component(cast_runner());
  486. const char kIncorrectComponentUrl[] = "cast:99999999";
  487. component.CreateComponentContext(kIncorrectComponentUrl);
  488. component.StartCastComponent(kIncorrectComponentUrl);
  489. // Run the loop until the ComponentController is dropped.
  490. component.ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED);
  491. EXPECT_TRUE(!component.component_state());
  492. }
  493. TEST_F(CastRunnerIntegrationTest, UrlRequestRewriteRulesProvider) {
  494. TestCastComponent component(cast_runner());
  495. GURL echo_app_url = test_server().GetURL(kEchoHeaderPath);
  496. component.app_config_manager()->AddApp(kTestAppId, echo_app_url);
  497. component.CreateComponentContextAndStartComponent();
  498. component.CheckAppUrl(echo_app_url);
  499. EXPECT_EQ(component.ExecuteJavaScript("document.body.innerText"),
  500. "TestHeaderValue");
  501. }
  502. TEST_F(CastRunnerIntegrationTest, ApplicationControllerBound) {
  503. TestCastComponent component(cast_runner());
  504. component.app_config_manager()->AddApp(kTestAppId,
  505. test_server().GetURL(kBlankAppUrl));
  506. component.CreateComponentContextAndStartComponent();
  507. // Spin the message loop to handle creation of the component state.
  508. base::RunLoop().RunUntilIdle();
  509. ASSERT_TRUE(component.component_state());
  510. EXPECT_TRUE(component.component_state()->application_context()->controller());
  511. }
  512. // Verify an App launched with remote debugging enabled is properly reachable.
  513. TEST_F(CastRunnerIntegrationTest, RemoteDebugging) {
  514. TestCastComponent component(cast_runner());
  515. GURL app_url = test_server().GetURL(kBlankAppUrl);
  516. auto app_config =
  517. FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url);
  518. app_config.set_enable_remote_debugging(true);
  519. component.app_config_manager()->AddAppConfig(std::move(app_config));
  520. component.CreateComponentContextAndStartComponent();
  521. // Connect to the debug service and ensure we get the proper response.
  522. base::Value devtools_list =
  523. GetDevToolsListFromPort(CastRunner::kRemoteDebuggingPort);
  524. ASSERT_TRUE(devtools_list.is_list());
  525. EXPECT_EQ(devtools_list.GetListDeprecated().size(), 1u);
  526. base::Value* devtools_url =
  527. devtools_list.GetListDeprecated()[0].FindPath("url");
  528. ASSERT_TRUE(devtools_url->is_string());
  529. EXPECT_EQ(devtools_url->GetString(), app_url.spec());
  530. }
  531. TEST_F(CastRunnerIntegrationTest, IsolatedContext) {
  532. TestCastComponent component(cast_runner());
  533. const GURL kContentDirectoryUrl("fuchsia-dir://testdata/empty.html");
  534. component.RegisterAppWithTestData(kContentDirectoryUrl);
  535. component.CreateComponentContextAndStartComponent();
  536. component.CheckAppUrl(kContentDirectoryUrl);
  537. }
  538. // Test the lack of CastAgent service does not cause a CastRunner crash.
  539. TEST_F(CastRunnerIntegrationTest, NoCastAgent) {
  540. TestCastComponent component(cast_runner());
  541. component.app_config_manager()->AddApp(kTestAppId,
  542. test_server().GetURL(kEchoHeaderPath));
  543. component.StartCastComponent(base::StrCat({"cast:", kTestAppId}));
  544. component.ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED);
  545. }
  546. // Test the CastAgent disconnecting does not cause a CastRunner crash.
  547. TEST_F(CastRunnerIntegrationTest, DisconnectedCastAgent) {
  548. TestCastComponent component(cast_runner());
  549. component.app_config_manager()->AddApp(kTestAppId,
  550. test_server().GetURL(kEchoHeaderPath));
  551. component.CreateComponentContextAndStartComponent();
  552. // Tear down the ComponentState, this should close the Agent connection and
  553. // shut down the CastComponent.
  554. component.component_state()->Disconnect();
  555. component.ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED);
  556. }
  557. // Test that the ApiBindings and RewriteRules are received from the secondary
  558. // DummyAgent. This validates that the |agent_url| retrieved from
  559. // AppConfigManager is the one used to retrieve the bindings and the rewrite
  560. // rules.
  561. TEST_F(CastRunnerIntegrationTest, ApplicationConfigAgentUrl) {
  562. TestCastComponent component(cast_runner());
  563. // These are part of the secondary agent, and CastRunner will contact
  564. // the secondary agent for both of them.
  565. FakeUrlRequestRewriteRulesProvider dummy_url_request_rewrite_rules_provider;
  566. FakeApiBindingsImpl dummy_agent_api_bindings;
  567. // Indicate that this app is to get bindings from a secondary agent.
  568. auto app_config = FakeApplicationConfigManager::CreateConfig(
  569. kTestAppId, test_server().GetURL(kBlankAppUrl));
  570. app_config.set_agent_url(kDummyAgentUrl);
  571. component.app_config_manager()->AddAppConfig(std::move(app_config));
  572. // Instantiate the bindings that are returned in the multi-agent scenario. The
  573. // bindings returned for the single-agent scenario are not initialized.
  574. std::vector<chromium::cast::ApiBinding> binding_list;
  575. chromium::cast::ApiBinding echo_binding;
  576. echo_binding.set_before_load_script(base::MemBufferFromString(
  577. "window.echo = cast.__platform__.PortConnector.bind('dummyService');",
  578. "test"));
  579. binding_list.emplace_back(std::move(echo_binding));
  580. // Assign the bindings to the multi-agent binding.
  581. dummy_agent_api_bindings.set_bindings(std::move(binding_list));
  582. auto component_url = base::StrCat({"cast:", kTestAppId});
  583. component.CreateComponentContext(component_url, /*with_fake_agent=*/false);
  584. EXPECT_TRUE(component.component_context());
  585. base::RunLoop run_loop;
  586. FakeComponentState* dummy_component_state = nullptr;
  587. component.component_context()->RegisterCreateComponentStateCallback(
  588. kDummyAgentUrl,
  589. base::BindLambdaForTesting(
  590. [&](base::StringPiece component_url)
  591. -> std::unique_ptr<cr_fuchsia::AgentImpl::ComponentStateBase> {
  592. run_loop.Quit();
  593. auto result = std::make_unique<FakeComponentState>(
  594. component_url, component.app_config_manager(),
  595. &dummy_agent_api_bindings,
  596. &dummy_url_request_rewrite_rules_provider);
  597. dummy_component_state = result.get();
  598. return result;
  599. }));
  600. component.StartCastComponent(component_url);
  601. // Wait for the component state to be created.
  602. run_loop.Run();
  603. // Validate that the component state in the default agent wasn't crated.
  604. EXPECT_FALSE(component.component_state());
  605. // Shutdown component before destroying dummy_agent_api_bindings.
  606. base::RunLoop shutdown_run_loop;
  607. dummy_component_state->set_on_delete(shutdown_run_loop.QuitClosure());
  608. component.component_controller()->ptr().Unbind();
  609. shutdown_run_loop.Run();
  610. }
  611. // Test that when RewriteRules are not provided, a WebComponent is still
  612. // created. Further validate that the primary agent does not provide ApiBindings
  613. // or RewriteRules.
  614. TEST_F(CastRunnerIntegrationTest, ApplicationConfigAgentUrlRewriteOptional) {
  615. TestCastComponent component(cast_runner());
  616. FakeApiBindingsImpl dummy_agent_api_bindings;
  617. // Indicate that this app is to get bindings from a secondary agent.
  618. auto app_config = FakeApplicationConfigManager::CreateConfig(
  619. kTestAppId, test_server().GetURL(kBlankAppUrl));
  620. app_config.set_agent_url(kDummyAgentUrl);
  621. component.app_config_manager()->AddAppConfig(std::move(app_config));
  622. // Instantiate the bindings that are returned in the multi-agent scenario. The
  623. // bindings returned for the single-agent scenario are not initialized.
  624. std::vector<chromium::cast::ApiBinding> binding_list;
  625. chromium::cast::ApiBinding echo_binding;
  626. echo_binding.set_before_load_script(base::MemBufferFromString(
  627. "window.echo = cast.__platform__.PortConnector.bind('dummyService');",
  628. "test"));
  629. binding_list.emplace_back(std::move(echo_binding));
  630. // Assign the bindings to the multi-agent binding.
  631. dummy_agent_api_bindings.set_bindings(std::move(binding_list));
  632. auto component_url = base::StrCat({"cast:", kTestAppId});
  633. component.CreateComponentContext(component_url, /*with_fake_agent=*/false);
  634. base::RunLoop run_loop;
  635. FakeComponentState* dummy_component_state = nullptr;
  636. component.component_context()->RegisterCreateComponentStateCallback(
  637. kDummyAgentUrl,
  638. base::BindLambdaForTesting(
  639. [&](base::StringPiece component_url)
  640. -> std::unique_ptr<cr_fuchsia::AgentImpl::ComponentStateBase> {
  641. run_loop.Quit();
  642. auto result = std::make_unique<FakeComponentState>(
  643. component_url, component.app_config_manager(),
  644. &dummy_agent_api_bindings, nullptr);
  645. dummy_component_state = result.get();
  646. return result;
  647. }));
  648. component.StartCastComponent(component_url);
  649. // Wait for the component state to be created.
  650. run_loop.Run();
  651. // Validate that the component state in the default agent wasn't crated.
  652. EXPECT_FALSE(component.component_state());
  653. // Shutdown component before destroying dummy_agent_api_bindings.
  654. base::RunLoop shutdown_run_loop;
  655. dummy_component_state->set_on_delete(shutdown_run_loop.QuitClosure());
  656. component.component_controller()->ptr().Unbind();
  657. shutdown_run_loop.Run();
  658. }
  659. class AudioCastRunnerIntegrationTest : public CastRunnerIntegrationTest {
  660. public:
  661. AudioCastRunnerIntegrationTest()
  662. : CastRunnerIntegrationTest(
  663. test::kCastRunnerFeaturesFakeAudioDeviceEnumerator) {}
  664. };
  665. TEST_F(AudioCastRunnerIntegrationTest, MicrophoneRedirect) {
  666. TestCastComponent component(cast_runner());
  667. GURL app_url = test_server().GetURL("/microphone.html");
  668. auto app_config =
  669. FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url);
  670. fuchsia::web::PermissionDescriptor mic_permission;
  671. mic_permission.set_type(fuchsia::web::PermissionType::MICROPHONE);
  672. app_config.mutable_permissions()->push_back(std::move(mic_permission));
  673. component.app_config_manager()->AddAppConfig(std::move(app_config));
  674. component.CreateComponentContextAndStartComponent();
  675. // Expect fuchsia.media.Audio connection to be redirected to the agent.
  676. base::RunLoop run_loop;
  677. ASSERT_EQ(
  678. component.component_state()->outgoing_directory()->AddPublicService(
  679. std::make_unique<vfs::Service>(
  680. [quit_closure = run_loop.QuitClosure()](
  681. zx::channel channel, async_dispatcher_t* dispatcher) mutable {
  682. std::move(quit_closure).Run();
  683. }),
  684. fuchsia::media::Audio::Name_),
  685. ZX_OK);
  686. component.ExecuteJavaScript("connectMicrophone();");
  687. // Will quit once AudioCapturer is connected.
  688. run_loop.Run();
  689. }
  690. TEST_F(CastRunnerIntegrationTest, CameraRedirect) {
  691. TestCastComponent component(cast_runner());
  692. GURL app_url = test_server().GetURL("/camera.html");
  693. auto app_config =
  694. FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url);
  695. fuchsia::web::PermissionDescriptor camera_permission;
  696. camera_permission.set_type(fuchsia::web::PermissionType::CAMERA);
  697. app_config.mutable_permissions()->push_back(std::move(camera_permission));
  698. component.app_config_manager()->AddAppConfig(std::move(app_config));
  699. component.CreateComponentContextAndStartComponent();
  700. // Expect fuchsia.camera3.DeviceWatcher connection to be redirected to the
  701. // agent.
  702. bool received_device_watcher_request = false;
  703. ASSERT_EQ(
  704. component.component_state()->outgoing_directory()->AddPublicService(
  705. std::make_unique<vfs::Service>(
  706. [&received_device_watcher_request](
  707. zx::channel channel, async_dispatcher_t* dispatcher) mutable {
  708. received_device_watcher_request = true;
  709. }),
  710. fuchsia::camera3::DeviceWatcher::Name_),
  711. ZX_OK);
  712. component.ExecuteJavaScript("connectCamera();");
  713. EXPECT_TRUE(received_device_watcher_request);
  714. }
  715. TEST_F(CastRunnerIntegrationTest, CameraAccessAfterComponentShutdown) {
  716. TestCastComponent component(cast_runner());
  717. GURL app_url = test_server().GetURL("/camera.html");
  718. // First app with camera permission.
  719. auto app_config =
  720. FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url);
  721. fuchsia::web::PermissionDescriptor camera_permission;
  722. camera_permission.set_type(fuchsia::web::PermissionType::CAMERA);
  723. app_config.mutable_permissions()->push_back(std::move(camera_permission));
  724. component.app_config_manager()->AddAppConfig(std::move(app_config));
  725. // Second app without camera permission (but it will still try to access
  726. // fuchsia.camera3.DeviceWatcher service to enumerate devices).
  727. TestCastComponent second_component(cast_runner());
  728. auto app_config_2 =
  729. FakeApplicationConfigManager::CreateConfig(kSecondTestAppId, app_url);
  730. second_component.app_config_manager()->AddAppConfig(std::move(app_config_2));
  731. // Start and then shutdown the first app.
  732. component.CreateComponentContextAndStartComponent(kTestAppId);
  733. component.ShutdownComponent();
  734. component.ResetComponentState();
  735. // Start the second app and try to connect the camera. It's expected to fail
  736. // to initialize the camera without crashing CastRunner.
  737. second_component.CreateComponentContextAndStartComponent(kSecondTestAppId);
  738. EXPECT_EQ(second_component.ExecuteJavaScript("connectCamera();"),
  739. "getUserMediaFailed");
  740. }
  741. TEST_F(CastRunnerIntegrationTest, MultipleComponentsUsingCamera) {
  742. TestCastComponent first_component(cast_runner());
  743. TestCastComponent second_component(cast_runner());
  744. GURL app_url = test_server().GetURL("/camera.html");
  745. // Start two apps, both with camera permission.
  746. auto app_config1 =
  747. FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url);
  748. fuchsia::web::PermissionDescriptor camera_permission1;
  749. camera_permission1.set_type(fuchsia::web::PermissionType::CAMERA);
  750. app_config1.mutable_permissions()->push_back(std::move(camera_permission1));
  751. first_component.app_config_manager()->AddAppConfig(std::move(app_config1));
  752. first_component.CreateComponentContextAndStartComponent(kTestAppId);
  753. auto app_config2 =
  754. FakeApplicationConfigManager::CreateConfig(kSecondTestAppId, app_url);
  755. fuchsia::web::PermissionDescriptor camera_permission2;
  756. camera_permission2.set_type(fuchsia::web::PermissionType::CAMERA);
  757. app_config2.mutable_permissions()->push_back(std::move(camera_permission2));
  758. second_component.app_config_manager()->AddAppConfig(std::move(app_config2));
  759. second_component.CreateComponentContextAndStartComponent(kSecondTestAppId);
  760. // Shut down the first component.
  761. first_component.ShutdownComponent();
  762. first_component.ResetComponentState();
  763. // Expect fuchsia.camera3.DeviceWatcher connection to be redirected to the
  764. // agent.
  765. bool received_device_watcher_request = false;
  766. ASSERT_EQ(
  767. second_component.component_state()
  768. ->outgoing_directory()
  769. ->AddPublicService(std::make_unique<vfs::Service>(
  770. [&received_device_watcher_request](
  771. zx::channel channel,
  772. async_dispatcher_t* dispatcher) mutable {
  773. received_device_watcher_request = true;
  774. }),
  775. fuchsia::camera3::DeviceWatcher::Name_),
  776. ZX_OK);
  777. second_component.ExecuteJavaScript("connectCamera();");
  778. EXPECT_TRUE(received_device_watcher_request);
  779. }
  780. class HeadlessCastRunnerIntegrationTest : public CastRunnerIntegrationTest {
  781. public:
  782. HeadlessCastRunnerIntegrationTest()
  783. : CastRunnerIntegrationTest(test::kCastRunnerFeaturesHeadless) {}
  784. };
  785. // A basic integration test ensuring a basic cast request launches the right
  786. // URL in the Chromium service.
  787. TEST_F(HeadlessCastRunnerIntegrationTest, Headless) {
  788. TestCastComponent component(cast_runner());
  789. const char kAnimationPath[] = "/css_animation.html";
  790. const GURL animation_url = test_server().GetURL(kAnimationPath);
  791. component.app_config_manager()->AddApp(kTestAppId, animation_url);
  792. component.CreateComponentContextAndStartComponent();
  793. auto tokens = scenic::ViewTokenPair::New();
  794. // Create a view.
  795. auto view_provider = component.component_services_client()
  796. ->Connect<fuchsia::ui::app::ViewProvider>();
  797. view_provider->CreateView(std::move(tokens.view_holder_token.value), {}, {});
  798. component.api_bindings()->RunAndReturnConnectedPort("animation_finished");
  799. // Verify that dropped "view" EventPair is handled properly.
  800. tokens.view_token.value.reset();
  801. component.api_bindings()->RunAndReturnConnectedPort("view_hidden");
  802. }
  803. // Isolated *and* headless? Doesn't sound like much fun!
  804. TEST_F(HeadlessCastRunnerIntegrationTest, IsolatedAndHeadless) {
  805. TestCastComponent component(cast_runner());
  806. const GURL kContentDirectoryUrl("fuchsia-dir://testdata/empty.html");
  807. component.RegisterAppWithTestData(kContentDirectoryUrl);
  808. component.CreateComponentContextAndStartComponent();
  809. component.CheckAppUrl(kContentDirectoryUrl);
  810. }
  811. // Verifies that the Context can establish a connection to the Agent's
  812. // MetricsRecorder service.
  813. TEST_F(CastRunnerIntegrationTest, LegacyMetricsRedirect) {
  814. TestCastComponent component(cast_runner());
  815. GURL app_url = test_server().GetURL(kBlankAppUrl);
  816. component.app_config_manager()->AddApp(kTestAppId, app_url);
  817. auto component_url = base::StrCat({"cast:", kTestAppId});
  818. component.CreateComponentContext(component_url);
  819. EXPECT_TRUE(component.component_context());
  820. base::RunLoop run_loop;
  821. // Add MetricsRecorder the the component's incoming_services.
  822. ASSERT_EQ(
  823. component.component_services()->AddPublicService(
  824. std::make_unique<vfs::Service>(
  825. [&run_loop](zx::channel request, async_dispatcher_t* dispatcher) {
  826. run_loop.Quit();
  827. }),
  828. fuchsia::legacymetrics::MetricsRecorder::Name_),
  829. ZX_OK);
  830. component.StartCastComponent(component_url);
  831. // Wait until we see the CastRunner connect to the MetricsRecorder service.
  832. run_loop.Run();
  833. }
  834. // Verifies that the ApplicationContext::OnApplicationTerminated() is notified
  835. // with the component exit code if the web content closes itself.
  836. TEST_F(CastRunnerIntegrationTest, OnApplicationTerminated_WindowClose) {
  837. TestCastComponent component(cast_runner());
  838. const GURL url = test_server().GetURL(kBlankAppUrl);
  839. component.app_config_manager()->AddApp(kTestAppId, url);
  840. component.CreateComponentContextAndStartComponent();
  841. // It is possible to observe the ComponentController close before
  842. // OnApplicationTerminated() is received, so ignore that.
  843. component.component_controller()->ptr().set_error_handler([](zx_status_t) {});
  844. // Have the web content close itself, and wait for OnApplicationTerminated().
  845. EXPECT_EQ(component.ExecuteJavaScript("window.close()"), "undefined");
  846. absl::optional<zx_status_t> exit_code = component.component_state()
  847. ->application_context()
  848. ->WaitForApplicationTerminated();
  849. ASSERT_TRUE(exit_code);
  850. EXPECT_EQ(exit_code.value(), ZX_OK);
  851. }
  852. // Verifies that the ComponentController reports TerminationReason::EXITED and
  853. // exit code ZX_OK if the web content terminates itself.
  854. // TODO(https://crbug.com/1066833): Make this a WebRunner test.
  855. TEST_F(CastRunnerIntegrationTest, OnTerminated_WindowClose) {
  856. TestCastComponent component(cast_runner());
  857. const GURL url = test_server().GetURL(kBlankAppUrl);
  858. component.app_config_manager()->AddApp(kTestAppId, url);
  859. component.CreateComponentContextAndStartComponent();
  860. // Register an handler on the ComponentController channel, for the
  861. // OnTerminated event.
  862. base::RunLoop exit_code_loop;
  863. component.component_controller()->ptr().set_error_handler(
  864. [quit_loop = exit_code_loop.QuitClosure()](zx_status_t) {
  865. quit_loop.Run();
  866. ADD_FAILURE();
  867. });
  868. component.component_controller()->ptr().events().OnTerminated =
  869. [quit_loop = exit_code_loop.QuitClosure()](
  870. int64_t exit_code, fuchsia::sys::TerminationReason reason) {
  871. quit_loop.Run();
  872. EXPECT_EQ(reason, fuchsia::sys::TerminationReason::EXITED);
  873. EXPECT_EQ(exit_code, ZX_OK);
  874. };
  875. // Have the web content close itself, and wait for OnTerminated().
  876. EXPECT_EQ(component.ExecuteJavaScript("window.close()"), "undefined");
  877. exit_code_loop.Run();
  878. component.component_controller()->ptr().Unbind();
  879. }
  880. // Verifies that the ComponentController reports TerminationReason::EXITED and
  881. // exit code ZX_OK if Kill() is used.
  882. // TODO(https://crbug.com/1066833): Make this a WebRunner test.
  883. TEST_F(CastRunnerIntegrationTest, OnTerminated_ComponentKill) {
  884. TestCastComponent component(cast_runner());
  885. const GURL url = test_server().GetURL(kBlankAppUrl);
  886. component.app_config_manager()->AddApp(kTestAppId, url);
  887. component.CreateComponentContextAndStartComponent();
  888. // Register an handler on the ComponentController channel, for the
  889. // OnTerminated event.
  890. base::RunLoop exit_code_loop;
  891. component.component_controller()->ptr().set_error_handler(
  892. [quit_loop = exit_code_loop.QuitClosure()](zx_status_t) {
  893. quit_loop.Run();
  894. ADD_FAILURE();
  895. });
  896. component.component_controller()->ptr().events().OnTerminated =
  897. [quit_loop = exit_code_loop.QuitClosure()](
  898. int64_t exit_code, fuchsia::sys::TerminationReason reason) {
  899. quit_loop.Run();
  900. EXPECT_EQ(reason, fuchsia::sys::TerminationReason::EXITED);
  901. EXPECT_EQ(exit_code, ZX_OK);
  902. };
  903. // Kill() the component and wait for OnTerminated().
  904. component.component_controller()->ptr()->Kill();
  905. exit_code_loop.Run();
  906. component.component_controller()->ptr().Unbind();
  907. }
  908. // Ensures that CastRunner handles the value not being specified.
  909. // TODO(https://crrev.com/c/2516246): Check for no logging.
  910. TEST_F(CastRunnerIntegrationTest, InitialMinConsoleLogSeverity_NotSet) {
  911. TestCastComponent component(cast_runner());
  912. GURL app_url = test_server().GetURL(kBlankAppUrl);
  913. auto app_config =
  914. FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url);
  915. EXPECT_FALSE(app_config.has_initial_min_console_log_severity());
  916. component.app_config_manager()->AddAppConfig(std::move(app_config));
  917. component.CreateComponentContextAndStartComponent();
  918. component.CheckAppUrl(app_url);
  919. }
  920. // TODO(https://crrev.com/c/2516246): Check for logging.
  921. TEST_F(CastRunnerIntegrationTest, InitialMinConsoleLogSeverity_DEBUG) {
  922. TestCastComponent component(cast_runner());
  923. GURL app_url = test_server().GetURL(kBlankAppUrl);
  924. auto app_config =
  925. FakeApplicationConfigManager::CreateConfig(kTestAppId, app_url);
  926. *app_config.mutable_initial_min_console_log_severity() =
  927. fuchsia::diagnostics::Severity::DEBUG;
  928. component.app_config_manager()->AddAppConfig(std::move(app_config));
  929. component.CreateComponentContextAndStartComponent();
  930. component.CheckAppUrl(app_url);
  931. }
  932. TEST_F(CastRunnerIntegrationTest, WebGLContextAbsentWithoutVulkanFeature) {
  933. TestCastComponent component(cast_runner());
  934. const char kTestPath[] = "/webgl_presence.html";
  935. const GURL test_url = test_server().GetURL(kTestPath);
  936. component.app_config_manager()->AddApp(kTestAppId, test_url);
  937. component.CreateComponentContextAndStartComponent();
  938. EXPECT_EQ(component.ExecuteJavaScript("document.title"), "absent");
  939. }
  940. TEST_F(CastRunnerIntegrationTest,
  941. WebGLContextAbsentWithoutVulkanFeature_IsolatedRunner) {
  942. TestCastComponent component(cast_runner());
  943. const GURL kContentDirectoryUrl("fuchsia-dir://testdata/webgl_presence.html");
  944. component.RegisterAppWithTestData(kContentDirectoryUrl);
  945. component.CreateComponentContextAndStartComponent();
  946. component.CheckAppUrl(kContentDirectoryUrl);
  947. EXPECT_EQ(component.ExecuteJavaScript("document.title"), "absent");
  948. }
  949. // Verifies that starting a component fails if CORS exempt headers cannot be
  950. // fetched.
  951. TEST_F(CastRunnerIntegrationTest, MissingCorsExemptHeaderProvider) {
  952. TestCastComponent component(cast_runner());
  953. GURL app_url = test_server().GetURL(kBlankAppUrl);
  954. component.app_config_manager()->AddApp(kTestAppId, app_url);
  955. // Start the Cast component, and wait for the controller to disconnect.
  956. component.StartCastComponent(base::StrCat({"cast:", kTestAppId}));
  957. component.ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED);
  958. EXPECT_FALSE(component.component_state());
  959. }
  960. // Verifies that CastRunner offers a chromium.cast.DataReset service.
  961. // Verifies that after the DeletePersistentData() API is invoked, no further
  962. // component-start requests are honoured.
  963. // TODO(crbug.com/1146474): Expand the test to verify that the persisted data is
  964. // correctly cleared (e.g. using a custom test HTML app that uses persisted
  965. // data).
  966. TEST_F(CastRunnerIntegrationTest, DataReset_Service) {
  967. base::RunLoop loop;
  968. auto data_reset = cast_runner_services().Connect<chromium::cast::DataReset>();
  969. data_reset.set_error_handler([quit_loop = loop.QuitClosure()](zx_status_t) {
  970. quit_loop.Run();
  971. ADD_FAILURE();
  972. });
  973. bool succeeded = false;
  974. data_reset->DeletePersistentData([&succeeded, &loop](bool result) {
  975. succeeded = result;
  976. loop.Quit();
  977. });
  978. loop.Run();
  979. EXPECT_TRUE(succeeded);
  980. // Verify that it is no longer possible to launch a component.
  981. TestCastComponent component(cast_runner());
  982. GURL app_url = test_server().GetURL(kBlankAppUrl);
  983. component.app_config_manager()->AddApp(kTestAppId, app_url);
  984. component.StartCastComponent(base::StrCat({"cast:", kTestAppId}));
  985. component.ExpectControllerDisconnectWithStatus(ZX_ERR_PEER_CLOSED);
  986. }
  987. // Verifies that CastRunner offers a chromium.cast.DataReset component, that
  988. // provides the DataReset service.
  989. TEST_F(CastRunnerIntegrationTest, DataReset_component) {
  990. TestCastComponent component(cast_runner());
  991. constexpr char kDataResetComponentName[] = "cast:chromium.cast.DataReset";
  992. component.StartCastComponent(kDataResetComponentName);
  993. base::RunLoop loop;
  994. auto data_reset = component.component_services_client()
  995. ->Connect<chromium::cast::DataReset>();
  996. data_reset.set_error_handler([quit_loop = loop.QuitClosure()](zx_status_t) {
  997. quit_loop.Run();
  998. ADD_FAILURE();
  999. });
  1000. bool succeeded = false;
  1001. data_reset->DeletePersistentData([&succeeded, &loop](bool result) {
  1002. succeeded = result;
  1003. loop.Quit();
  1004. });
  1005. loop.Run();
  1006. EXPECT_TRUE(succeeded);
  1007. }
  1008. class CastRunnerFrameHostIntegrationTest : public CastRunnerIntegrationTest {
  1009. public:
  1010. CastRunnerFrameHostIntegrationTest()
  1011. : CastRunnerIntegrationTest(test::kCastRunnerFeaturesFrameHost) {}
  1012. };
  1013. // Verifies that the CastRunner offers a fuchsia.web.FrameHost service.
  1014. // TODO(crbug.com/1144102): Clean up config-data vs command-line flags handling
  1015. // and add a not-enabled test here.
  1016. TEST_F(CastRunnerFrameHostIntegrationTest, FrameHostComponent) {
  1017. TestCastComponent component(cast_runner());
  1018. constexpr char kFrameHostComponentName[] = "cast:fuchsia.web.FrameHost";
  1019. component.StartCastComponent(kFrameHostComponentName);
  1020. // Connect to the fuchsia.web.FrameHost service and create a Frame.
  1021. auto frame_host =
  1022. component.component_services_client()->Connect<fuchsia::web::FrameHost>();
  1023. fuchsia::web::FramePtr frame;
  1024. frame_host->CreateFrameWithParams(fuchsia::web::CreateFrameParams(),
  1025. frame.NewRequest());
  1026. // Verify that a response is received for a LoadUrl() request to the frame.
  1027. fuchsia::web::NavigationControllerPtr controller;
  1028. frame->GetNavigationController(controller.NewRequest());
  1029. const GURL url = test_server().GetURL(kBlankAppUrl);
  1030. EXPECT_TRUE(LoadUrlAndExpectResponse(
  1031. controller.get(), fuchsia::web::LoadUrlParams(), url.spec()));
  1032. }
  1033. #if defined(ARCH_CPU_ARM_FAMILY)
  1034. // TODO(crbug.com/1058247): Support Vulkan in tests on ARM64.
  1035. #define MAYBE_VulkanCastRunnerIntegrationTest \
  1036. DISABLED_VulkanCastRunnerIntegrationTest
  1037. #else
  1038. #define MAYBE_VulkanCastRunnerIntegrationTest VulkanCastRunnerIntegrationTest
  1039. #endif
  1040. class MAYBE_VulkanCastRunnerIntegrationTest : public CastRunnerIntegrationTest {
  1041. public:
  1042. MAYBE_VulkanCastRunnerIntegrationTest()
  1043. : CastRunnerIntegrationTest(test::kCastRunnerFeaturesVulkan) {}
  1044. };
  1045. TEST_F(MAYBE_VulkanCastRunnerIntegrationTest,
  1046. WebGLContextPresentWithVulkanFeature) {
  1047. TestCastComponent component(cast_runner());
  1048. const char kTestPath[] = "/webgl_presence.html";
  1049. const GURL test_url = test_server().GetURL(kTestPath);
  1050. component.app_config_manager()->AddApp(kTestAppId, test_url);
  1051. component.CreateComponentContextAndStartComponent();
  1052. EXPECT_EQ(component.ExecuteJavaScript("document.title"), "present");
  1053. }
  1054. TEST_F(MAYBE_VulkanCastRunnerIntegrationTest,
  1055. WebGLContextPresentWithVulkanFeature_IsolatedRunner) {
  1056. TestCastComponent component(cast_runner());
  1057. const GURL kContentDirectoryUrl("fuchsia-dir://testdata/webgl_presence.html");
  1058. component.RegisterAppWithTestData(kContentDirectoryUrl);
  1059. component.CreateComponentContextAndStartComponent();
  1060. component.CheckAppUrl(kContentDirectoryUrl);
  1061. EXPECT_EQ(component.ExecuteJavaScript("document.title"), "present");
  1062. }