1 // Copyright 2015 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.
5 #include "components/web_view/frame.h"
8 #include "base/location.h"
9 #include "base/logging.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/run_loop.h"
12 #include "base/test/test_timeouts.h"
13 #include "components/view_manager/public/cpp/view_observer.h"
14 #include "components/view_manager/public/cpp/view_tree_connection.h"
15 #include "components/view_manager/public/cpp/view_tree_delegate.h"
16 #include "components/view_manager/public/cpp/view_tree_host_factory.h"
17 #include "components/web_view/frame.h"
18 #include "components/web_view/frame_connection.h"
19 #include "components/web_view/frame_tree.h"
20 #include "components/web_view/frame_tree_delegate.h"
21 #include "components/web_view/frame_user_data.h"
22 #include "components/web_view/test_frame_tree_delegate.h"
23 #include "mojo/application/public/cpp/application_connection.h"
24 #include "mojo/application/public/cpp/application_delegate.h"
25 #include "mojo/application/public/cpp/application_impl.h"
26 #include "mojo/application/public/cpp/application_test_base.h"
27 #include "mojo/application/public/cpp/service_provider_impl.h"
30 using mojo::ViewTreeConnection
;
36 base::RunLoop
* current_run_loop
= nullptr;
38 void TimeoutRunLoop(const base::Closure
& timeout_task
, bool* timeout
) {
39 CHECK(current_run_loop
);
44 bool DoRunLoopWithTimeout() {
45 if (current_run_loop
!= nullptr)
49 base::RunLoop run_loop
;
50 base::MessageLoop::current()->PostDelayedTask(
51 FROM_HERE
, base::Bind(&TimeoutRunLoop
, run_loop
.QuitClosure(), &timeout
),
52 TestTimeouts::action_timeout());
54 current_run_loop
= &run_loop
;
55 current_run_loop
->Run();
56 current_run_loop
= nullptr;
61 current_run_loop
->Quit();
62 current_run_loop
= nullptr;
67 void OnGotIdCallback(base::RunLoop
* run_loop
) {
71 // Creates a new FrameConnection. This runs a nested message loop until the
72 // content handler id is obtained.
73 scoped_ptr
<FrameConnection
> CreateFrameConnection(mojo::ApplicationImpl
* app
) {
74 scoped_ptr
<FrameConnection
> frame_connection(new FrameConnection
);
75 mojo::URLRequestPtr
request(mojo::URLRequest::New());
76 request
->url
= mojo::String::From(app
->url());
77 base::RunLoop run_loop
;
78 frame_connection
->Init(app
, request
.Pass(),
79 base::Bind(&OnGotIdCallback
, &run_loop
));
81 return frame_connection
;
84 class TestFrameTreeClient
: public FrameTreeClient
{
86 TestFrameTreeClient() : connect_count_(0) {}
87 ~TestFrameTreeClient() override
{}
89 int connect_count() const { return connect_count_
; }
91 mojo::Array
<FrameDataPtr
> connect_frames() { return connect_frames_
.Pass(); }
93 mojo::Array
<FrameDataPtr
> adds() { return adds_
.Pass(); }
95 // Sets a callback to run once OnConnect() is received.
96 void set_on_connect_callback(const base::Closure
& closure
) {
97 on_connect_callback_
= closure
;
100 FrameTreeServer
* server() { return server_
.get(); }
102 // TestFrameTreeClient:
103 void OnConnect(FrameTreeServerPtr server
,
106 ViewConnectType view_connect_type
,
107 mojo::Array
<FrameDataPtr
> frames
,
108 const OnConnectCallback
& callback
) override
{
110 connect_frames_
= frames
.Pass();
111 server_
= server
.Pass();
113 if (!on_connect_callback_
.is_null())
114 on_connect_callback_
.Run();
116 void OnFrameAdded(uint32_t change_id
, FrameDataPtr frame
) override
{
117 adds_
.push_back(frame
.Pass());
119 void OnFrameRemoved(uint32_t change_id
, uint32_t frame_id
) override
{}
120 void OnFrameClientPropertyChanged(uint32_t frame_id
,
121 const mojo::String
& name
,
122 mojo::Array
<uint8_t> new_data
) override
{}
123 void OnPostMessageEvent(uint32_t source_frame_id
,
124 uint32_t target_frame_id
,
125 HTMLMessageEventPtr event
) override
{}
126 void OnWillNavigate(uint32_t frame_id
) override
{}
130 mojo::Array
<FrameDataPtr
> connect_frames_
;
131 FrameTreeServerPtr server_
;
132 mojo::Array
<FrameDataPtr
> adds_
;
133 base::Closure on_connect_callback_
;
135 DISALLOW_COPY_AND_ASSIGN(TestFrameTreeClient
);
140 // ViewAndFrame maintains the View and TestFrameTreeClient associated with
141 // a single FrameTreeClient. In other words this maintains the data structures
142 // needed to represent a client side frame. To obtain one use
143 // FrameTest::WaitForViewAndFrame().
144 class ViewAndFrame
: public mojo::ViewTreeDelegate
{
146 ~ViewAndFrame() override
{
148 delete view_
->connection();
151 // The View associated with the frame.
152 mojo::View
* view() { return view_
; }
153 TestFrameTreeClient
* test_frame_tree_client() {
154 return &test_frame_tree_client_
;
156 FrameTreeServer
* frame_tree_server() {
157 return test_frame_tree_client_
.server();
161 friend class FrameTest
;
164 : view_(nullptr), frame_tree_binding_(&test_frame_tree_client_
) {}
166 // Runs a message loop until the view and frame data have been received.
167 void WaitForViewAndFrame() { run_loop_
.Run(); }
169 void Bind(mojo::InterfaceRequest
<FrameTreeClient
> request
) {
170 ASSERT_FALSE(frame_tree_binding_
.is_bound());
171 test_frame_tree_client_
.set_on_connect_callback(
172 base::Bind(&ViewAndFrame::OnGotConnect
, base::Unretained(this)));
173 frame_tree_binding_
.Bind(request
.Pass());
176 void OnGotConnect() { QuitRunLoopIfNecessary(); }
178 void QuitRunLoopIfNecessary() {
179 if (view_
&& test_frame_tree_client_
.connect_count())
183 // Overridden from ViewTreeDelegate:
184 void OnEmbed(View
* root
) override
{
186 QuitRunLoopIfNecessary();
188 void OnConnectionLost(ViewTreeConnection
* connection
) override
{
193 base::RunLoop run_loop_
;
194 TestFrameTreeClient test_frame_tree_client_
;
195 mojo::Binding
<FrameTreeClient
> frame_tree_binding_
;
197 DISALLOW_COPY_AND_ASSIGN(ViewAndFrame
);
200 class FrameTest
: public mojo::test::ApplicationTestBase
,
201 public mojo::ApplicationDelegate
,
202 public mojo::ViewTreeDelegate
,
203 public mojo::InterfaceFactory
<mojo::ViewTreeClient
>,
204 public mojo::InterfaceFactory
<FrameTreeClient
> {
206 FrameTest() : most_recent_connection_(nullptr), window_manager_(nullptr) {}
208 ViewTreeConnection
* most_recent_connection() {
209 return most_recent_connection_
;
213 ViewTreeConnection
* window_manager() { return window_manager_
; }
214 TestFrameTreeDelegate
* frame_tree_delegate() {
215 return frame_tree_delegate_
.get();
217 FrameTree
* frame_tree() { return frame_tree_
.get(); }
218 ViewAndFrame
* root_view_and_frame() { return root_view_and_frame_
.get(); }
220 scoped_ptr
<ViewAndFrame
> CreateChildViewAndFrame(ViewAndFrame
* parent
) {
221 mojo::View
* child_frame_view
= parent
->view()->connection()->CreateView();
222 parent
->view()->AddChild(child_frame_view
);
223 mojo::Map
<mojo::String
, mojo::Array
<uint8_t>> client_properties
;
224 client_properties
.mark_non_null();
225 parent
->frame_tree_server()->OnCreatedFrame(
226 child_frame_view
->parent()->id(), child_frame_view
->id(),
227 client_properties
.Pass());
228 frame_tree_delegate()->WaitForCreateFrame();
229 if (HasFatalFailure())
232 // Navigate the child frame, which should trigger a new ViewAndFrame.
233 mojo::URLRequestPtr
request(mojo::URLRequest::New());
234 request
->url
= mojo::String::From(application_impl()->url());
235 parent
->frame_tree_server()->RequestNavigate(
236 NAVIGATION_TARGET_TYPE_EXISTING_FRAME
, child_frame_view
->id(),
238 return WaitForViewAndFrame();
241 // Runs a message loop until the data necessary to represent to a client side
242 // frame has been obtained.
243 scoped_ptr
<ViewAndFrame
> WaitForViewAndFrame() {
244 DCHECK(!view_and_frame_
);
245 view_and_frame_
.reset(new ViewAndFrame
);
246 view_and_frame_
->WaitForViewAndFrame();
247 return view_and_frame_
.Pass();
251 // ApplicationTestBase:
252 ApplicationDelegate
* GetApplicationDelegate() override
{ return this; }
254 // ApplicationDelegate implementation.
255 bool ConfigureIncomingConnection(
256 mojo::ApplicationConnection
* connection
) override
{
257 connection
->AddService
<mojo::ViewTreeClient
>(this);
258 connection
->AddService
<FrameTreeClient
>(this);
262 // Overridden from ViewTreeDelegate:
263 void OnEmbed(View
* root
) override
{
264 most_recent_connection_
= root
->connection();
267 void OnConnectionLost(ViewTreeConnection
* connection
) override
{}
269 // Overridden from testing::Test:
270 void SetUp() override
{
271 ApplicationTestBase::SetUp();
273 mojo::CreateSingleViewTreeHost(application_impl(), this, &host_
);
275 ASSERT_TRUE(DoRunLoopWithTimeout());
276 std::swap(window_manager_
, most_recent_connection_
);
278 // Creates a FrameTree, which creates a single frame. Wait for the
279 // FrameTreeClient to be connected to.
280 frame_tree_delegate_
.reset(new TestFrameTreeDelegate(application_impl()));
281 scoped_ptr
<FrameConnection
> frame_connection
=
282 CreateFrameConnection(application_impl());
283 FrameTreeClient
* frame_tree_client
= frame_connection
->frame_tree_client();
284 mojo::ViewTreeClientPtr view_tree_client
=
285 frame_connection
->GetViewTreeClient();
286 mojo::View
* frame_root_view
= window_manager()->CreateView();
287 window_manager()->GetRoot()->AddChild(frame_root_view
);
289 new FrameTree(0u, frame_root_view
, view_tree_client
.Pass(),
290 frame_tree_delegate_
.get(), frame_tree_client
,
291 frame_connection
.Pass(), Frame::ClientPropertyMap()));
292 root_view_and_frame_
= WaitForViewAndFrame();
295 // Overridden from testing::Test:
296 void TearDown() override
{
297 root_view_and_frame_
.reset();
299 frame_tree_delegate_
.reset();
300 ApplicationTestBase::TearDown();
303 // Overridden from mojo::InterfaceFactory<mojo::ViewTreeClient>:
305 mojo::ApplicationConnection
* connection
,
306 mojo::InterfaceRequest
<mojo::ViewTreeClient
> request
) override
{
307 if (view_and_frame_
) {
308 mojo::ViewTreeConnection::Create(view_and_frame_
.get(), request
.Pass());
310 mojo::ViewTreeConnection::Create(this, request
.Pass());
314 // Overridden from mojo::InterfaceFactory<FrameTreeClient>:
315 void Create(mojo::ApplicationConnection
* connection
,
316 mojo::InterfaceRequest
<FrameTreeClient
> request
) override
{
317 ASSERT_TRUE(view_and_frame_
);
318 view_and_frame_
->Bind(request
.Pass());
321 scoped_ptr
<TestFrameTreeDelegate
> frame_tree_delegate_
;
322 scoped_ptr
<FrameTree
> frame_tree_
;
323 scoped_ptr
<ViewAndFrame
> root_view_and_frame_
;
325 mojo::ViewTreeHostPtr host_
;
327 // Used to receive the most recent view manager loaded by an embed action.
328 ViewTreeConnection
* most_recent_connection_
;
329 // The View Manager connection held by the window manager (app running at the
331 ViewTreeConnection
* window_manager_
;
333 scoped_ptr
<ViewAndFrame
> view_and_frame_
;
335 MOJO_DISALLOW_COPY_AND_ASSIGN(FrameTest
);
338 // Verifies the FrameData supplied to the root FrameTreeClient::OnConnect().
339 TEST_F(FrameTest
, RootFrameClientConnectData
) {
340 mojo::Array
<FrameDataPtr
> frames
=
341 root_view_and_frame()->test_frame_tree_client()->connect_frames();
342 ASSERT_EQ(1u, frames
.size());
343 EXPECT_EQ(root_view_and_frame()->view()->id(), frames
[0]->frame_id
);
344 EXPECT_EQ(0u, frames
[0]->parent_id
);
347 // Verifies the FrameData supplied to a child FrameTreeClient::OnConnect().
348 TEST_F(FrameTest
, ChildFrameClientConnectData
) {
349 scoped_ptr
<ViewAndFrame
> child_view_and_frame(
350 CreateChildViewAndFrame(root_view_and_frame()));
351 ASSERT_TRUE(child_view_and_frame
);
352 mojo::Array
<FrameDataPtr
> frames_in_child
=
353 child_view_and_frame
->test_frame_tree_client()->connect_frames();
354 // We expect 2 frames. One for the root, one for the child.
355 ASSERT_EQ(2u, frames_in_child
.size());
356 EXPECT_EQ(frame_tree()->root()->id(), frames_in_child
[0]->frame_id
);
357 EXPECT_EQ(0u, frames_in_child
[0]->parent_id
);
358 EXPECT_EQ(child_view_and_frame
->view()->id(), frames_in_child
[1]->frame_id
);
359 EXPECT_EQ(frame_tree()->root()->id(), frames_in_child
[1]->parent_id
);
362 TEST_F(FrameTest
, DisconnectingViewDestroysFrame
) {
363 scoped_ptr
<ViewAndFrame
> child_view_and_frame(
364 CreateChildViewAndFrame(root_view_and_frame()));
365 ASSERT_TRUE(child_view_and_frame
);
367 // Delete the ViewTreeConnection for the child, which should trigger
369 // the frame. Deleting the ViewTreeConnection triggers
370 // Frame::OnViewEmbeddedAppDisconnected.
371 delete child_view_and_frame
->view()->connection();
372 ASSERT_EQ(1u, frame_tree()->root()->children().size());
373 ASSERT_NO_FATAL_FAILURE(frame_tree_delegate()->WaitForDestroyFrame(
374 frame_tree()->root()->children()[0]));
375 ASSERT_EQ(0u, frame_tree()->root()->children().size());
378 } // namespace web_view