web_ui_mojo_inttest.mm 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // Copyright 2016 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 <memory>
  5. #include <string>
  6. #include "base/bind.h"
  7. #include "base/run_loop.h"
  8. #import "base/test/ios/wait_util.h"
  9. #include "base/threading/thread_task_runner_handle.h"
  10. #include "ios/web/grit/ios_web_resources.h"
  11. #import "ios/web/public/navigation/navigation_manager.h"
  12. #import "ios/web/public/test/navigation_test_util.h"
  13. #import "ios/web/public/web_state.h"
  14. #include "ios/web/public/webui/web_ui_ios_controller.h"
  15. #include "ios/web/public/webui/web_ui_ios_controller_factory.h"
  16. #include "ios/web/public/webui/web_ui_ios_data_source.h"
  17. #include "ios/web/test/grit/test_resources.h"
  18. #include "ios/web/test/mojo_test.mojom.h"
  19. #include "ios/web/test/test_url_constants.h"
  20. #import "ios/web/test/web_int_test.h"
  21. #include "mojo/public/cpp/bindings/pending_remote.h"
  22. #include "mojo/public/cpp/bindings/receiver_set.h"
  23. #include "mojo/public/cpp/bindings/remote.h"
  24. #include "url/gurl.h"
  25. #include "url/scheme_host_port.h"
  26. #if !defined(__has_feature) || !__has_feature(objc_arc)
  27. #error "This file requires ARC support."
  28. #endif
  29. namespace web {
  30. namespace {
  31. // Hostname for test WebUI page.
  32. const char kTestWebUIURLHost[] = "testwebui";
  33. // Timeout in seconds to wait for a successful message exchange between native
  34. // code and a web page using Mojo.
  35. const NSTimeInterval kMessageTimeout = 5.0;
  36. // UI handler class which communicates with test WebUI page as follows:
  37. // - page sends "syn" message to |TestUIHandler|
  38. // - |TestUIHandler| replies with "ack" message
  39. // - page replies back with "fin"
  40. //
  41. // Once "fin" is received |IsFinReceived()| call will return true, indicating
  42. // that communication was successful. See test WebUI page code here:
  43. // ios/web/test/data/mojo_test.js
  44. class TestUIHandler : public TestUIHandlerMojo {
  45. public:
  46. TestUIHandler() {}
  47. ~TestUIHandler() override {}
  48. // Returns true if "fin" has been received.
  49. bool IsFinReceived() { return fin_received_; }
  50. // TestUIHandlerMojo overrides.
  51. void SetClientPage(mojo::PendingRemote<TestPage> page) override {
  52. page_.Bind(std::move(page));
  53. }
  54. void HandleJsMessage(const std::string& message) override {
  55. if (message == "syn") {
  56. // Received "syn" message from WebUI page, send "ack" as reply.
  57. DCHECK(!syn_received_);
  58. DCHECK(!fin_received_);
  59. syn_received_ = true;
  60. NativeMessageResultMojoPtr result(NativeMessageResultMojo::New());
  61. result->message = "ack";
  62. page_->HandleNativeMessage(std::move(result));
  63. } else if (message == "fin") {
  64. // Received "fin" from the WebUI page in response to "ack".
  65. DCHECK(syn_received_);
  66. DCHECK(!fin_received_);
  67. fin_received_ = true;
  68. } else {
  69. NOTREACHED();
  70. }
  71. }
  72. void BindTestHandler(mojo::PendingReceiver<TestUIHandlerMojo> receiver) {
  73. receivers_.Add(this, std::move(receiver));
  74. }
  75. private:
  76. mojo::ReceiverSet<TestUIHandlerMojo> receivers_;
  77. mojo::Remote<TestPage> page_;
  78. // |true| if "syn" has been received.
  79. bool syn_received_ = false;
  80. // |true| if "fin" has been received.
  81. bool fin_received_ = false;
  82. };
  83. // Controller for test WebUI.
  84. class TestUI : public WebUIIOSController {
  85. public:
  86. // Constructs controller from |web_ui| and |ui_handler| which will communicate
  87. // with test WebUI page.
  88. TestUI(WebUIIOS* web_ui, const std::string& host, TestUIHandler* ui_handler)
  89. : WebUIIOSController(web_ui, host) {
  90. web::WebUIIOSDataSource* source =
  91. web::WebUIIOSDataSource::Create(kTestWebUIURLHost);
  92. source->AddResourcePath("mojo_test.js", IDR_MOJO_TEST_JS);
  93. source->AddResourcePath("mojo_bindings.js", IDR_IOS_MOJO_BINDINGS_JS);
  94. source->AddResourcePath("mojo_test.mojom.js", IDR_MOJO_TEST_MOJO_JS);
  95. source->SetDefaultResource(IDR_MOJO_TEST_HTML);
  96. web::WebState* web_state = web_ui->GetWebState();
  97. web::WebUIIOSDataSource::Add(web_state->GetBrowserState(), source);
  98. web_state->GetInterfaceBinderForMainFrame()->AddInterface(
  99. base::BindRepeating(&TestUIHandler::BindTestHandler,
  100. base::Unretained(ui_handler)));
  101. }
  102. ~TestUI() override = default;
  103. };
  104. // Factory that creates TestUI controller.
  105. class TestWebUIControllerFactory : public WebUIIOSControllerFactory {
  106. public:
  107. // Constructs a controller factory which will eventually create |ui_handler|.
  108. explicit TestWebUIControllerFactory(TestUIHandler* ui_handler)
  109. : ui_handler_(ui_handler) {}
  110. // WebUIIOSControllerFactory overrides.
  111. std::unique_ptr<WebUIIOSController> CreateWebUIIOSControllerForURL(
  112. WebUIIOS* web_ui,
  113. const GURL& url) const override {
  114. if (!url.SchemeIs(kTestWebUIScheme))
  115. return nullptr;
  116. DCHECK_EQ(url.host(), kTestWebUIURLHost);
  117. return std::make_unique<TestUI>(web_ui, url.host(), ui_handler_);
  118. }
  119. NSInteger GetErrorCodeForWebUIURL(const GURL& url) const override {
  120. if (url.SchemeIs(kTestWebUIScheme))
  121. return 0;
  122. return NSURLErrorUnsupportedURL;
  123. }
  124. private:
  125. // UI handler class which communicates with test WebUI page.
  126. TestUIHandler* ui_handler_;
  127. };
  128. } // namespace
  129. // A test fixture for verifying mojo communication for WebUI.
  130. class WebUIMojoTest : public WebIntTest {
  131. protected:
  132. void SetUp() override {
  133. WebIntTest::SetUp();
  134. @autoreleasepool {
  135. ui_handler_ = std::make_unique<TestUIHandler>();
  136. WebState::CreateParams params(GetBrowserState());
  137. web_state_ = WebState::Create(params);
  138. factory_ =
  139. std::make_unique<TestWebUIControllerFactory>(ui_handler_.get());
  140. WebUIIOSControllerFactory::RegisterFactory(factory_.get());
  141. }
  142. }
  143. void TearDown() override {
  144. @autoreleasepool {
  145. // WebState owns CRWWebUIManager. When WebState is destroyed,
  146. // CRWWebUIManager is autoreleased and will be destroyed upon autorelease
  147. // pool purge. However in this test, WebTest destructor is called before
  148. // PlatformTest, thus CRWWebUIManager outlives the WebTaskenvironment.
  149. // However, CRWWebUIManager owns a URLFetcherImpl, which DCHECKs that its
  150. // destructor is called on UI web thread. Hence, URLFetcherImpl has to
  151. // outlive the WebTaskenvironment, since [NSThread mainThread] will not be
  152. // WebThread::UI once WebTaskenvironment is destroyed.
  153. web_state_.reset();
  154. ui_handler_.reset();
  155. WebUIIOSControllerFactory::DeregisterFactory(factory_.get());
  156. }
  157. WebIntTest::TearDown();
  158. }
  159. // Returns WebState which loads test WebUI page.
  160. WebState* web_state() { return web_state_.get(); }
  161. // Returns UI handler which communicates with WebUI page.
  162. TestUIHandler* test_ui_handler() { return ui_handler_.get(); }
  163. private:
  164. std::unique_ptr<WebState> web_state_;
  165. std::unique_ptr<TestUIHandler> ui_handler_;
  166. std::unique_ptr<TestWebUIControllerFactory> factory_;
  167. };
  168. // Tests that JS can send messages to the native code and vice versa.
  169. // TestUIHandler is used for communication and test succeeds only when
  170. // |TestUIHandler| successfully receives "ack" message from WebUI page.
  171. TEST_F(WebUIMojoTest, MessageExchange) {
  172. @autoreleasepool {
  173. url::SchemeHostPort tuple(kTestWebUIScheme, kTestWebUIURLHost, 0);
  174. GURL url(tuple.Serialize());
  175. test::LoadUrl(web_state(), url);
  176. // LoadIfNecessary is needed because the view is not created (but needed)
  177. // when loading the page. TODO(crbug.com/705819): Remove this call.
  178. web_state()->GetNavigationManager()->LoadIfNecessary();
  179. // Wait until |TestUIHandler| receives "fin" message from WebUI page.
  180. bool fin_received =
  181. base::test::ios::WaitUntilConditionOrTimeout(kMessageTimeout, ^{
  182. // Flush any pending tasks. Don't RunUntilIdle() because
  183. // RunUntilIdle() is incompatible with mojo::SimpleWatcher's
  184. // automatic arming behavior, which Mojo JS still depends upon.
  185. //
  186. // TODO(crbug.com/701875): Introduce the full watcher API to JS and
  187. // get rid of this hack.
  188. base::RunLoop loop;
  189. base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
  190. loop.QuitClosure());
  191. loop.Run();
  192. return test_ui_handler()->IsFinReceived();
  193. });
  194. ASSERT_TRUE(fin_received);
  195. EXPECT_FALSE(web_state()->IsLoading());
  196. EXPECT_EQ(url, web_state()->GetLastCommittedURL());
  197. }
  198. }
  199. } // namespace web