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/mus/public/cpp/view_observer.h"
14 #include "components/mus/public/cpp/view_tree_connection.h"
15 #include "components/mus/public/cpp/view_tree_delegate.h"
16 #include "components/mus/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 mus::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 TestFrameClient
: public mojom::FrameClient
{
87 : connect_count_(0), last_dispatch_load_event_frame_id_(0) {}
88 ~TestFrameClient() override
{}
90 int connect_count() const { return connect_count_
; }
92 mojo::Array
<mojom::FrameDataPtr
> connect_frames() {
93 return connect_frames_
.Pass();
96 mojo::Array
<mojom::FrameDataPtr
> adds() { return adds_
.Pass(); }
98 // Sets a callback to run once OnConnect() is received.
99 void set_on_connect_callback(const base::Closure
& closure
) {
100 on_connect_callback_
= closure
;
103 void set_on_loading_state_changed_callback(const base::Closure
& closure
) {
104 on_loading_state_changed_callback_
= closure
;
107 void set_on_dispatch_load_event_callback(const base::Closure
& closure
) {
108 on_dispatch_load_event_callback_
= closure
;
111 mojom::Frame
* server_frame() { return server_frame_
.get(); }
113 mojo::InterfaceRequest
<mojom::Frame
> GetServerFrameRequest() {
114 return GetProxy(&server_frame_
);
117 void last_loading_state_changed_notification(uint32_t* frame_id
,
118 bool* loading
) const {
119 *frame_id
= last_loading_state_changed_notification_
.frame_id
;
120 *loading
= last_loading_state_changed_notification_
.loading
;
123 uint32_t last_dispatch_load_event_frame_id() const {
124 return last_dispatch_load_event_frame_id_
;
127 // mojom::FrameClient:
128 void OnConnect(mojom::FramePtr frame
,
131 mojom::ViewConnectType view_connect_type
,
132 mojo::Array
<mojom::FrameDataPtr
> frames
,
133 const OnConnectCallback
& callback
) override
{
135 connect_frames_
= frames
.Pass();
137 server_frame_
= frame
.Pass();
139 if (!on_connect_callback_
.is_null())
140 on_connect_callback_
.Run();
142 void OnFrameAdded(uint32_t change_id
, mojom::FrameDataPtr frame
) override
{
143 adds_
.push_back(frame
.Pass());
145 void OnFrameRemoved(uint32_t change_id
, uint32_t frame_id
) override
{}
146 void OnFrameClientPropertyChanged(uint32_t frame_id
,
147 const mojo::String
& name
,
148 mojo::Array
<uint8_t> new_data
) override
{}
149 void OnPostMessageEvent(uint32_t source_frame_id
,
150 uint32_t target_frame_id
,
151 mojom::HTMLMessageEventPtr event
) override
{}
152 void OnWillNavigate() override
{}
153 void OnFrameLoadingStateChanged(uint32_t frame_id
, bool loading
) override
{
154 last_loading_state_changed_notification_
.frame_id
= frame_id
;
155 last_loading_state_changed_notification_
.loading
= loading
;
157 if (!on_loading_state_changed_callback_
.is_null())
158 on_loading_state_changed_callback_
.Run();
160 void OnDispatchFrameLoadEvent(uint32_t frame_id
) override
{
161 last_dispatch_load_event_frame_id_
= frame_id
;
163 if (!on_dispatch_load_event_callback_
.is_null())
164 on_dispatch_load_event_callback_
.Run();
168 struct LoadingStateChangedNotification
{
169 LoadingStateChangedNotification() : frame_id(0), loading(false) {}
170 ~LoadingStateChangedNotification() {}
177 mojo::Array
<mojom::FrameDataPtr
> connect_frames_
;
178 mojom::FramePtr server_frame_
;
179 mojo::Array
<mojom::FrameDataPtr
> adds_
;
180 base::Closure on_connect_callback_
;
181 base::Closure on_loading_state_changed_callback_
;
182 base::Closure on_dispatch_load_event_callback_
;
183 LoadingStateChangedNotification last_loading_state_changed_notification_
;
184 uint32_t last_dispatch_load_event_frame_id_
;
186 DISALLOW_COPY_AND_ASSIGN(TestFrameClient
);
191 // ViewAndFrame maintains the View and TestFrameClient associated with
192 // a single FrameClient. In other words this maintains the data structures
193 // needed to represent a client side frame. To obtain one use
194 // FrameTest::WaitForViewAndFrame().
195 class ViewAndFrame
: public mus::ViewTreeDelegate
{
197 ~ViewAndFrame() override
{
199 delete view_
->connection();
202 // The View associated with the frame.
203 mus::View
* view() { return view_
; }
204 TestFrameClient
* test_frame_client() { return &test_frame_tree_client_
; }
205 mojom::Frame
* server_frame() {
206 return test_frame_tree_client_
.server_frame();
210 friend class FrameTest
;
213 : view_(nullptr), frame_client_binding_(&test_frame_tree_client_
) {}
215 void set_view(View
* view
) { view_
= view
; }
217 // Runs a message loop until the view and frame data have been received.
218 void WaitForViewAndFrame() { run_loop_
.Run(); }
220 mojo::InterfaceRequest
<mojom::Frame
> GetServerFrameRequest() {
221 return test_frame_tree_client_
.GetServerFrameRequest();
224 mojom::FrameClientPtr
GetFrameClientPtr() {
225 mojom::FrameClientPtr client_ptr
;
226 frame_client_binding_
.Bind(GetProxy(&client_ptr
));
227 return client_ptr
.Pass();
230 void Bind(mojo::InterfaceRequest
<mojom::FrameClient
> request
) {
231 ASSERT_FALSE(frame_client_binding_
.is_bound());
232 test_frame_tree_client_
.set_on_connect_callback(
233 base::Bind(&ViewAndFrame::OnGotConnect
, base::Unretained(this)));
234 frame_client_binding_
.Bind(request
.Pass());
237 void OnGotConnect() { QuitRunLoopIfNecessary(); }
239 void QuitRunLoopIfNecessary() {
240 if (view_
&& test_frame_tree_client_
.connect_count())
244 // Overridden from ViewTreeDelegate:
245 void OnEmbed(View
* root
) override
{
247 QuitRunLoopIfNecessary();
249 void OnConnectionLost(ViewTreeConnection
* connection
) override
{
254 base::RunLoop run_loop_
;
255 TestFrameClient test_frame_tree_client_
;
256 mojo::Binding
<mojom::FrameClient
> frame_client_binding_
;
258 DISALLOW_COPY_AND_ASSIGN(ViewAndFrame
);
261 class FrameTest
: public mojo::test::ApplicationTestBase
,
262 public mojo::ApplicationDelegate
,
263 public mus::ViewTreeDelegate
,
264 public mojo::InterfaceFactory
<mojo::ViewTreeClient
>,
265 public mojo::InterfaceFactory
<mojom::FrameClient
> {
267 FrameTest() : most_recent_connection_(nullptr), window_manager_(nullptr) {}
269 ViewTreeConnection
* most_recent_connection() {
270 return most_recent_connection_
;
274 ViewTreeConnection
* window_manager() { return window_manager_
; }
275 TestFrameTreeDelegate
* frame_tree_delegate() {
276 return frame_tree_delegate_
.get();
278 FrameTree
* frame_tree() { return frame_tree_
.get(); }
279 ViewAndFrame
* root_view_and_frame() { return root_view_and_frame_
.get(); }
281 scoped_ptr
<ViewAndFrame
> NavigateFrame(ViewAndFrame
* view_and_frame
) {
282 mojo::URLRequestPtr
request(mojo::URLRequest::New());
283 request
->url
= mojo::String::From(application_impl()->url());
284 view_and_frame
->server_frame()->RequestNavigate(
285 mojom::NAVIGATION_TARGET_TYPE_EXISTING_FRAME
,
286 view_and_frame
->view()->id(), request
.Pass());
287 return WaitForViewAndFrame();
290 // Creates a new shared frame as a child of |parent|.
291 scoped_ptr
<ViewAndFrame
> CreateChildViewAndFrame(ViewAndFrame
* parent
) {
292 mus::View
* child_frame_view
= parent
->view()->connection()->CreateView();
293 parent
->view()->AddChild(child_frame_view
);
295 scoped_ptr
<ViewAndFrame
> view_and_frame(new ViewAndFrame
);
296 view_and_frame
->set_view(child_frame_view
);
298 mojo::Map
<mojo::String
, mojo::Array
<uint8_t>> client_properties
;
299 client_properties
.mark_non_null();
300 parent
->server_frame()->OnCreatedFrame(
301 view_and_frame
->GetServerFrameRequest(),
302 view_and_frame
->GetFrameClientPtr(), child_frame_view
->id(),
303 client_properties
.Pass());
304 frame_tree_delegate()->WaitForCreateFrame();
305 return HasFatalFailure() ? nullptr : view_and_frame
.Pass();
308 // Runs a message loop until the data necessary to represent to a client side
309 // frame has been obtained.
310 scoped_ptr
<ViewAndFrame
> WaitForViewAndFrame() {
311 DCHECK(!view_and_frame_
);
312 view_and_frame_
.reset(new ViewAndFrame
);
313 view_and_frame_
->WaitForViewAndFrame();
314 return view_and_frame_
.Pass();
318 // ApplicationTestBase:
319 ApplicationDelegate
* GetApplicationDelegate() override
{ return this; }
321 // ApplicationDelegate implementation.
322 bool ConfigureIncomingConnection(
323 mojo::ApplicationConnection
* connection
) override
{
324 connection
->AddService
<mojo::ViewTreeClient
>(this);
325 connection
->AddService
<mojom::FrameClient
>(this);
329 // Overridden from ViewTreeDelegate:
330 void OnEmbed(View
* root
) override
{
331 most_recent_connection_
= root
->connection();
334 void OnConnectionLost(ViewTreeConnection
* connection
) override
{}
336 // Overridden from testing::Test:
337 void SetUp() override
{
338 ApplicationTestBase::SetUp();
340 mus::CreateSingleViewTreeHost(application_impl(), this, &host_
);
342 ASSERT_TRUE(DoRunLoopWithTimeout());
343 std::swap(window_manager_
, most_recent_connection_
);
345 // Creates a FrameTree, which creates a single frame. Wait for the
346 // FrameClient to be connected to.
347 frame_tree_delegate_
.reset(new TestFrameTreeDelegate(application_impl()));
348 scoped_ptr
<FrameConnection
> frame_connection
=
349 CreateFrameConnection(application_impl());
350 mojom::FrameClient
* frame_client
= frame_connection
->frame_client();
351 mojo::ViewTreeClientPtr view_tree_client
=
352 frame_connection
->GetViewTreeClient();
353 mus::View
* frame_root_view
= window_manager()->CreateView();
354 window_manager()->GetRoot()->AddChild(frame_root_view
);
356 new FrameTree(0u, frame_root_view
, view_tree_client
.Pass(),
357 frame_tree_delegate_
.get(), frame_client
,
358 frame_connection
.Pass(), Frame::ClientPropertyMap()));
359 root_view_and_frame_
= WaitForViewAndFrame();
362 // Overridden from testing::Test:
363 void TearDown() override
{
364 root_view_and_frame_
.reset();
366 frame_tree_delegate_
.reset();
367 ApplicationTestBase::TearDown();
370 // Overridden from mojo::InterfaceFactory<mojo::ViewTreeClient>:
372 mojo::ApplicationConnection
* connection
,
373 mojo::InterfaceRequest
<mojo::ViewTreeClient
> request
) override
{
374 if (view_and_frame_
) {
375 mus::ViewTreeConnection::Create(view_and_frame_
.get(), request
.Pass());
377 mus::ViewTreeConnection::Create(this, request
.Pass());
381 // Overridden from mojo::InterfaceFactory<mojom::FrameClient>:
382 void Create(mojo::ApplicationConnection
* connection
,
383 mojo::InterfaceRequest
<mojom::FrameClient
> request
) override
{
384 ASSERT_TRUE(view_and_frame_
);
385 view_and_frame_
->Bind(request
.Pass());
388 scoped_ptr
<TestFrameTreeDelegate
> frame_tree_delegate_
;
389 scoped_ptr
<FrameTree
> frame_tree_
;
390 scoped_ptr
<ViewAndFrame
> root_view_and_frame_
;
392 mojo::ViewTreeHostPtr host_
;
394 // Used to receive the most recent view manager loaded by an embed action.
395 ViewTreeConnection
* most_recent_connection_
;
396 // The View Manager connection held by the window manager (app running at the
398 ViewTreeConnection
* window_manager_
;
400 scoped_ptr
<ViewAndFrame
> view_and_frame_
;
402 MOJO_DISALLOW_COPY_AND_ASSIGN(FrameTest
);
405 // Verifies the FrameData supplied to the root FrameClient::OnConnect().
406 TEST_F(FrameTest
, RootFrameClientConnectData
) {
407 mojo::Array
<mojom::FrameDataPtr
> frames
=
408 root_view_and_frame()->test_frame_client()->connect_frames();
409 ASSERT_EQ(1u, frames
.size());
410 EXPECT_EQ(root_view_and_frame()->view()->id(), frames
[0]->frame_id
);
411 EXPECT_EQ(0u, frames
[0]->parent_id
);
414 // Verifies the FrameData supplied to a child FrameClient::OnConnect().
415 TEST_F(FrameTest
, ChildFrameClientConnectData
) {
416 scoped_ptr
<ViewAndFrame
> child_view_and_frame(
417 CreateChildViewAndFrame(root_view_and_frame()));
418 ASSERT_TRUE(child_view_and_frame
);
419 // Initially created child frames don't get OnConnect().
420 EXPECT_EQ(0, child_view_and_frame
->test_frame_client()->connect_count());
422 scoped_ptr
<ViewAndFrame
> navigated_child_view_and_frame
=
423 NavigateFrame(child_view_and_frame
.get()).Pass();
425 mojo::Array
<mojom::FrameDataPtr
> frames_in_child
=
426 navigated_child_view_and_frame
->test_frame_client()->connect_frames();
427 EXPECT_EQ(child_view_and_frame
->view()->id(),
428 navigated_child_view_and_frame
->view()->id());
429 // We expect 2 frames. One for the root, one for the child.
430 ASSERT_EQ(2u, frames_in_child
.size());
431 EXPECT_EQ(frame_tree()->root()->id(), frames_in_child
[0]->frame_id
);
432 EXPECT_EQ(0u, frames_in_child
[0]->parent_id
);
433 EXPECT_EQ(navigated_child_view_and_frame
->view()->id(),
434 frames_in_child
[1]->frame_id
);
435 EXPECT_EQ(frame_tree()->root()->id(), frames_in_child
[1]->parent_id
);
438 TEST_F(FrameTest
, OnViewEmbeddedInFrameDisconnected
) {
439 scoped_ptr
<ViewAndFrame
> child_view_and_frame(
440 CreateChildViewAndFrame(root_view_and_frame()));
441 ASSERT_TRUE(child_view_and_frame
);
443 scoped_ptr
<ViewAndFrame
> navigated_child_view_and_frame
=
444 NavigateFrame(child_view_and_frame
.get()).Pass();
446 // Delete the ViewTreeConnection for the child, which should trigger
448 delete navigated_child_view_and_frame
->view()->connection();
449 ASSERT_EQ(1u, frame_tree()->root()->children().size());
450 ASSERT_NO_FATAL_FAILURE(frame_tree_delegate()->WaitForFrameDisconnected(
451 frame_tree()->root()->children()[0]));
452 ASSERT_EQ(1u, frame_tree()->root()->children().size());
455 TEST_F(FrameTest
, NotifyRemoteParentWithLoadingState
) {
456 scoped_ptr
<ViewAndFrame
> child_view_and_frame(
457 CreateChildViewAndFrame(root_view_and_frame()));
458 uint32_t child_frame_id
= child_view_and_frame
->view()->id();
461 base::RunLoop run_loop
;
462 root_view_and_frame()
463 ->test_frame_client()
464 ->set_on_loading_state_changed_callback(run_loop
.QuitClosure());
466 child_view_and_frame
->server_frame()->LoadingStateChanged(true, .5);
470 uint32_t frame_id
= 0;
471 bool loading
= false;
472 root_view_and_frame()
473 ->test_frame_client()
474 ->last_loading_state_changed_notification(&frame_id
, &loading
);
475 EXPECT_EQ(child_frame_id
, frame_id
);
476 EXPECT_TRUE(loading
);
479 base::RunLoop run_loop
;
480 root_view_and_frame()
481 ->test_frame_client()
482 ->set_on_loading_state_changed_callback(run_loop
.QuitClosure());
484 ASSERT_TRUE(child_view_and_frame
);
485 ASSERT_TRUE(child_view_and_frame
->server_frame());
487 child_view_and_frame
->server_frame()->LoadingStateChanged(false, 1);
491 uint32_t frame_id
= 0;
492 bool loading
= false;
493 root_view_and_frame()
494 ->test_frame_client()
495 ->last_loading_state_changed_notification(&frame_id
, &loading
);
496 EXPECT_EQ(child_frame_id
, frame_id
);
497 EXPECT_FALSE(loading
);
501 TEST_F(FrameTest
, NotifyRemoteParentWithLoadEvent
) {
502 scoped_ptr
<ViewAndFrame
> child_view_and_frame(
503 CreateChildViewAndFrame(root_view_and_frame()));
504 uint32_t child_frame_id
= child_view_and_frame
->view()->id();
506 base::RunLoop run_loop
;
507 root_view_and_frame()
508 ->test_frame_client()
509 ->set_on_dispatch_load_event_callback(run_loop
.QuitClosure());
511 child_view_and_frame
->server_frame()->DispatchLoadEventToParent();
515 uint32_t frame_id
= root_view_and_frame()
516 ->test_frame_client()
517 ->last_dispatch_load_event_frame_id();
518 EXPECT_EQ(child_frame_id
, frame_id
);
520 } // namespace web_view