123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- // Copyright 2016 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include <memory>
- #include <string>
- #include "base/bind.h"
- #include "base/run_loop.h"
- #import "base/test/ios/wait_util.h"
- #include "base/threading/thread_task_runner_handle.h"
- #include "ios/web/grit/ios_web_resources.h"
- #import "ios/web/public/navigation/navigation_manager.h"
- #import "ios/web/public/test/navigation_test_util.h"
- #import "ios/web/public/web_state.h"
- #include "ios/web/public/webui/web_ui_ios_controller.h"
- #include "ios/web/public/webui/web_ui_ios_controller_factory.h"
- #include "ios/web/public/webui/web_ui_ios_data_source.h"
- #include "ios/web/test/grit/test_resources.h"
- #include "ios/web/test/mojo_test.mojom.h"
- #include "ios/web/test/test_url_constants.h"
- #import "ios/web/test/web_int_test.h"
- #include "mojo/public/cpp/bindings/pending_remote.h"
- #include "mojo/public/cpp/bindings/receiver_set.h"
- #include "mojo/public/cpp/bindings/remote.h"
- #include "url/gurl.h"
- #include "url/scheme_host_port.h"
- #if !defined(__has_feature) || !__has_feature(objc_arc)
- #error "This file requires ARC support."
- #endif
- namespace web {
- namespace {
- // Hostname for test WebUI page.
- const char kTestWebUIURLHost[] = "testwebui";
- // Timeout in seconds to wait for a successful message exchange between native
- // code and a web page using Mojo.
- const NSTimeInterval kMessageTimeout = 5.0;
- // UI handler class which communicates with test WebUI page as follows:
- // - page sends "syn" message to |TestUIHandler|
- // - |TestUIHandler| replies with "ack" message
- // - page replies back with "fin"
- //
- // Once "fin" is received |IsFinReceived()| call will return true, indicating
- // that communication was successful. See test WebUI page code here:
- // ios/web/test/data/mojo_test.js
- class TestUIHandler : public TestUIHandlerMojo {
- public:
- TestUIHandler() {}
- ~TestUIHandler() override {}
- // Returns true if "fin" has been received.
- bool IsFinReceived() { return fin_received_; }
- // TestUIHandlerMojo overrides.
- void SetClientPage(mojo::PendingRemote<TestPage> page) override {
- page_.Bind(std::move(page));
- }
- void HandleJsMessage(const std::string& message) override {
- if (message == "syn") {
- // Received "syn" message from WebUI page, send "ack" as reply.
- DCHECK(!syn_received_);
- DCHECK(!fin_received_);
- syn_received_ = true;
- NativeMessageResultMojoPtr result(NativeMessageResultMojo::New());
- result->message = "ack";
- page_->HandleNativeMessage(std::move(result));
- } else if (message == "fin") {
- // Received "fin" from the WebUI page in response to "ack".
- DCHECK(syn_received_);
- DCHECK(!fin_received_);
- fin_received_ = true;
- } else {
- NOTREACHED();
- }
- }
- void BindTestHandler(mojo::PendingReceiver<TestUIHandlerMojo> receiver) {
- receivers_.Add(this, std::move(receiver));
- }
- private:
- mojo::ReceiverSet<TestUIHandlerMojo> receivers_;
- mojo::Remote<TestPage> page_;
- // |true| if "syn" has been received.
- bool syn_received_ = false;
- // |true| if "fin" has been received.
- bool fin_received_ = false;
- };
- // Controller for test WebUI.
- class TestUI : public WebUIIOSController {
- public:
- // Constructs controller from |web_ui| and |ui_handler| which will communicate
- // with test WebUI page.
- TestUI(WebUIIOS* web_ui, const std::string& host, TestUIHandler* ui_handler)
- : WebUIIOSController(web_ui, host) {
- web::WebUIIOSDataSource* source =
- web::WebUIIOSDataSource::Create(kTestWebUIURLHost);
- source->AddResourcePath("mojo_test.js", IDR_MOJO_TEST_JS);
- source->AddResourcePath("mojo_bindings.js", IDR_IOS_MOJO_BINDINGS_JS);
- source->AddResourcePath("mojo_test.mojom.js", IDR_MOJO_TEST_MOJO_JS);
- source->SetDefaultResource(IDR_MOJO_TEST_HTML);
- web::WebState* web_state = web_ui->GetWebState();
- web::WebUIIOSDataSource::Add(web_state->GetBrowserState(), source);
- web_state->GetInterfaceBinderForMainFrame()->AddInterface(
- base::BindRepeating(&TestUIHandler::BindTestHandler,
- base::Unretained(ui_handler)));
- }
- ~TestUI() override = default;
- };
- // Factory that creates TestUI controller.
- class TestWebUIControllerFactory : public WebUIIOSControllerFactory {
- public:
- // Constructs a controller factory which will eventually create |ui_handler|.
- explicit TestWebUIControllerFactory(TestUIHandler* ui_handler)
- : ui_handler_(ui_handler) {}
- // WebUIIOSControllerFactory overrides.
- std::unique_ptr<WebUIIOSController> CreateWebUIIOSControllerForURL(
- WebUIIOS* web_ui,
- const GURL& url) const override {
- if (!url.SchemeIs(kTestWebUIScheme))
- return nullptr;
- DCHECK_EQ(url.host(), kTestWebUIURLHost);
- return std::make_unique<TestUI>(web_ui, url.host(), ui_handler_);
- }
- NSInteger GetErrorCodeForWebUIURL(const GURL& url) const override {
- if (url.SchemeIs(kTestWebUIScheme))
- return 0;
- return NSURLErrorUnsupportedURL;
- }
- private:
- // UI handler class which communicates with test WebUI page.
- TestUIHandler* ui_handler_;
- };
- } // namespace
- // A test fixture for verifying mojo communication for WebUI.
- class WebUIMojoTest : public WebIntTest {
- protected:
- void SetUp() override {
- WebIntTest::SetUp();
- @autoreleasepool {
- ui_handler_ = std::make_unique<TestUIHandler>();
- WebState::CreateParams params(GetBrowserState());
- web_state_ = WebState::Create(params);
- factory_ =
- std::make_unique<TestWebUIControllerFactory>(ui_handler_.get());
- WebUIIOSControllerFactory::RegisterFactory(factory_.get());
- }
- }
- void TearDown() override {
- @autoreleasepool {
- // WebState owns CRWWebUIManager. When WebState is destroyed,
- // CRWWebUIManager is autoreleased and will be destroyed upon autorelease
- // pool purge. However in this test, WebTest destructor is called before
- // PlatformTest, thus CRWWebUIManager outlives the WebTaskenvironment.
- // However, CRWWebUIManager owns a URLFetcherImpl, which DCHECKs that its
- // destructor is called on UI web thread. Hence, URLFetcherImpl has to
- // outlive the WebTaskenvironment, since [NSThread mainThread] will not be
- // WebThread::UI once WebTaskenvironment is destroyed.
- web_state_.reset();
- ui_handler_.reset();
- WebUIIOSControllerFactory::DeregisterFactory(factory_.get());
- }
- WebIntTest::TearDown();
- }
- // Returns WebState which loads test WebUI page.
- WebState* web_state() { return web_state_.get(); }
- // Returns UI handler which communicates with WebUI page.
- TestUIHandler* test_ui_handler() { return ui_handler_.get(); }
- private:
- std::unique_ptr<WebState> web_state_;
- std::unique_ptr<TestUIHandler> ui_handler_;
- std::unique_ptr<TestWebUIControllerFactory> factory_;
- };
- // Tests that JS can send messages to the native code and vice versa.
- // TestUIHandler is used for communication and test succeeds only when
- // |TestUIHandler| successfully receives "ack" message from WebUI page.
- TEST_F(WebUIMojoTest, MessageExchange) {
- @autoreleasepool {
- url::SchemeHostPort tuple(kTestWebUIScheme, kTestWebUIURLHost, 0);
- GURL url(tuple.Serialize());
- test::LoadUrl(web_state(), url);
- // LoadIfNecessary is needed because the view is not created (but needed)
- // when loading the page. TODO(crbug.com/705819): Remove this call.
- web_state()->GetNavigationManager()->LoadIfNecessary();
- // Wait until |TestUIHandler| receives "fin" message from WebUI page.
- bool fin_received =
- base::test::ios::WaitUntilConditionOrTimeout(kMessageTimeout, ^{
- // Flush any pending tasks. Don't RunUntilIdle() because
- // RunUntilIdle() is incompatible with mojo::SimpleWatcher's
- // automatic arming behavior, which Mojo JS still depends upon.
- //
- // TODO(crbug.com/701875): Introduce the full watcher API to JS and
- // get rid of this hack.
- base::RunLoop loop;
- base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
- loop.QuitClosure());
- loop.Run();
- return test_ui_handler()->IsFinReceived();
- });
- ASSERT_TRUE(fin_received);
- EXPECT_FALSE(web_state()->IsLoading());
- EXPECT_EQ(url, web_state()->GetLastCommittedURL());
- }
- }
- } // namespace web
|