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 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 mojo::Binding
<FrameTreeServer
>* frame_tree_server_binding(Frame
* frame
) {
221 return frame
->frame_tree_server_binding_
.get();
224 // Creates a new shared frame as a child of |parent|.
225 Frame
* CreateSharedFrame(ViewAndFrame
* parent
) {
226 mojo::View
* child_frame_view
= parent
->view()->connection()->CreateView();
227 parent
->view()->AddChild(child_frame_view
);
228 mojo::Map
<mojo::String
, mojo::Array
<uint8_t>> client_properties
;
229 client_properties
.mark_non_null();
230 parent
->frame_tree_server()->OnCreatedFrame(
231 child_frame_view
->parent()->id(), child_frame_view
->id(),
232 client_properties
.Pass());
233 Frame
* frame
= frame_tree_delegate()->WaitForCreateFrame();
234 return HasFatalFailure() ? nullptr : frame
;
237 scoped_ptr
<ViewAndFrame
> CreateChildViewAndFrame(ViewAndFrame
* parent
) {
238 // All frames start out initially shared.
239 Frame
* child_frame
= CreateSharedFrame(parent
);
243 // Navigate the child frame, which should trigger a new ViewAndFrame.
244 mojo::URLRequestPtr
request(mojo::URLRequest::New());
245 request
->url
= mojo::String::From(application_impl()->url());
246 parent
->frame_tree_server()->RequestNavigate(
247 NAVIGATION_TARGET_TYPE_EXISTING_FRAME
, child_frame
->id(),
249 return WaitForViewAndFrame();
252 // Runs a message loop until the data necessary to represent to a client side
253 // frame has been obtained.
254 scoped_ptr
<ViewAndFrame
> WaitForViewAndFrame() {
255 DCHECK(!view_and_frame_
);
256 view_and_frame_
.reset(new ViewAndFrame
);
257 view_and_frame_
->WaitForViewAndFrame();
258 return view_and_frame_
.Pass();
262 // ApplicationTestBase:
263 ApplicationDelegate
* GetApplicationDelegate() override
{ return this; }
265 // ApplicationDelegate implementation.
266 bool ConfigureIncomingConnection(
267 mojo::ApplicationConnection
* connection
) override
{
268 connection
->AddService
<mojo::ViewTreeClient
>(this);
269 connection
->AddService
<FrameTreeClient
>(this);
273 // Overridden from ViewTreeDelegate:
274 void OnEmbed(View
* root
) override
{
275 most_recent_connection_
= root
->connection();
278 void OnConnectionLost(ViewTreeConnection
* connection
) override
{}
280 // Overridden from testing::Test:
281 void SetUp() override
{
282 ApplicationTestBase::SetUp();
284 mojo::CreateSingleViewTreeHost(application_impl(), this, &host_
);
286 ASSERT_TRUE(DoRunLoopWithTimeout());
287 std::swap(window_manager_
, most_recent_connection_
);
289 // Creates a FrameTree, which creates a single frame. Wait for the
290 // FrameTreeClient to be connected to.
291 frame_tree_delegate_
.reset(new TestFrameTreeDelegate(application_impl()));
292 scoped_ptr
<FrameConnection
> frame_connection
=
293 CreateFrameConnection(application_impl());
294 FrameTreeClient
* frame_tree_client
= frame_connection
->frame_tree_client();
295 mojo::ViewTreeClientPtr view_tree_client
=
296 frame_connection
->GetViewTreeClient();
297 mojo::View
* frame_root_view
= window_manager()->CreateView();
298 window_manager()->GetRoot()->AddChild(frame_root_view
);
300 new FrameTree(0u, frame_root_view
, view_tree_client
.Pass(),
301 frame_tree_delegate_
.get(), frame_tree_client
,
302 frame_connection
.Pass(), Frame::ClientPropertyMap()));
303 root_view_and_frame_
= WaitForViewAndFrame();
306 // Overridden from testing::Test:
307 void TearDown() override
{
308 root_view_and_frame_
.reset();
310 frame_tree_delegate_
.reset();
311 ApplicationTestBase::TearDown();
314 // Overridden from mojo::InterfaceFactory<mojo::ViewTreeClient>:
316 mojo::ApplicationConnection
* connection
,
317 mojo::InterfaceRequest
<mojo::ViewTreeClient
> request
) override
{
318 if (view_and_frame_
) {
319 mojo::ViewTreeConnection::Create(view_and_frame_
.get(), request
.Pass());
321 mojo::ViewTreeConnection::Create(this, request
.Pass());
325 // Overridden from mojo::InterfaceFactory<FrameTreeClient>:
326 void Create(mojo::ApplicationConnection
* connection
,
327 mojo::InterfaceRequest
<FrameTreeClient
> request
) override
{
328 ASSERT_TRUE(view_and_frame_
);
329 view_and_frame_
->Bind(request
.Pass());
332 scoped_ptr
<TestFrameTreeDelegate
> frame_tree_delegate_
;
333 scoped_ptr
<FrameTree
> frame_tree_
;
334 scoped_ptr
<ViewAndFrame
> root_view_and_frame_
;
336 mojo::ViewTreeHostPtr host_
;
338 // Used to receive the most recent view manager loaded by an embed action.
339 ViewTreeConnection
* most_recent_connection_
;
340 // The View Manager connection held by the window manager (app running at the
342 ViewTreeConnection
* window_manager_
;
344 scoped_ptr
<ViewAndFrame
> view_and_frame_
;
346 MOJO_DISALLOW_COPY_AND_ASSIGN(FrameTest
);
349 // Verifies the FrameData supplied to the root FrameTreeClient::OnConnect().
350 TEST_F(FrameTest
, RootFrameClientConnectData
) {
351 mojo::Array
<FrameDataPtr
> frames
=
352 root_view_and_frame()->test_frame_tree_client()->connect_frames();
353 ASSERT_EQ(1u, frames
.size());
354 EXPECT_EQ(root_view_and_frame()->view()->id(), frames
[0]->frame_id
);
355 EXPECT_EQ(0u, frames
[0]->parent_id
);
358 // Verifies the FrameData supplied to a child FrameTreeClient::OnConnect().
359 TEST_F(FrameTest
, ChildFrameClientConnectData
) {
360 scoped_ptr
<ViewAndFrame
> child_view_and_frame(
361 CreateChildViewAndFrame(root_view_and_frame()));
362 ASSERT_TRUE(child_view_and_frame
);
363 mojo::Array
<FrameDataPtr
> frames_in_child
=
364 child_view_and_frame
->test_frame_tree_client()->connect_frames();
365 // We expect 2 frames. One for the root, one for the child.
366 ASSERT_EQ(2u, frames_in_child
.size());
367 EXPECT_EQ(frame_tree()->root()->id(), frames_in_child
[0]->frame_id
);
368 EXPECT_EQ(0u, frames_in_child
[0]->parent_id
);
369 EXPECT_EQ(child_view_and_frame
->view()->id(), frames_in_child
[1]->frame_id
);
370 EXPECT_EQ(frame_tree()->root()->id(), frames_in_child
[1]->parent_id
);
373 TEST_F(FrameTest
, OnViewEmbeddedInFrameDisconnected
) {
374 scoped_ptr
<ViewAndFrame
> child_view_and_frame(
375 CreateChildViewAndFrame(root_view_and_frame()));
376 ASSERT_TRUE(child_view_and_frame
);
378 // Delete the ViewTreeConnection for the child, which should trigger
380 delete child_view_and_frame
->view()->connection();
381 ASSERT_EQ(1u, frame_tree()->root()->children().size());
382 ASSERT_NO_FATAL_FAILURE(frame_tree_delegate()->WaitForFrameDisconnected(
383 frame_tree()->root()->children()[0]));
384 ASSERT_EQ(1u, frame_tree()->root()->children().size());
387 TEST_F(FrameTest
, CantSendProgressChangeTargettingWrongApp
) {
388 ASSERT_FALSE(frame_tree()->root()->IsLoading());
390 scoped_ptr
<ViewAndFrame
> child_view_and_frame(
391 CreateChildViewAndFrame(root_view_and_frame()));
392 ASSERT_TRUE(child_view_and_frame
);
394 Frame
* shared_frame
= CreateSharedFrame(child_view_and_frame
.get());
396 // Send LoadingStarted() from the root targetting a frame from another
397 // connection. It should be ignored (not update the loading status).
398 root_view_and_frame()->frame_tree_server()->LoadingStarted(
400 frame_tree_server_binding(frame_tree()->root())->WaitForIncomingMethodCall();
401 EXPECT_FALSE(frame_tree()->root()->IsLoading());
404 } // namespace web_view