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 "base/auto_reset.h"
7 #include "base/callback.h"
8 #include "base/command_line.h"
9 #include "base/json/json_reader.h"
10 #include "base/run_loop.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/test/test_timeouts.h"
13 #include "base/values.h"
14 #include "components/html_viewer/public/interfaces/test_html_viewer.mojom.h"
15 #include "components/mus/public/cpp/tests/view_manager_test_base.h"
16 #include "components/mus/public/cpp/view.h"
17 #include "components/mus/public/cpp/view_tree_connection.h"
18 #include "components/web_view/frame.h"
19 #include "components/web_view/frame_connection.h"
20 #include "components/web_view/frame_tree.h"
21 #include "components/web_view/public/interfaces/frame.mojom.h"
22 #include "components/web_view/test_frame_tree_delegate.h"
23 #include "mojo/application/public/cpp/application_impl.h"
24 #include "net/test/spawned_test_server/spawned_test_server.h"
25 #include "third_party/mojo_services/src/accessibility/public/interfaces/accessibility.mojom.h"
27 using mus::ViewManagerTestBase
;
28 using web_view::Frame
;
29 using web_view::FrameConnection
;
30 using web_view::FrameTree
;
31 using web_view::FrameTreeDelegate
;
32 using web_view::mojom::FrameClient
;
38 const char kAddFrameWithEmptyPageScript
[] =
39 "var iframe = document.createElement(\"iframe\");"
40 "iframe.src = \"http://127.0.0.1:%u/files/empty_page.html\";"
41 "document.body.appendChild(iframe);";
43 void OnGotContentHandlerForRoot(bool* got_callback
) {
45 ignore_result(ViewManagerTestBase::QuitRunLoop());
48 mojo::ApplicationConnection
* ApplicationConnectionForFrame(Frame
* frame
) {
49 return static_cast<FrameConnection
*>(frame
->user_data())
50 ->application_connection();
53 std::string
GetFrameText(ApplicationConnection
* connection
) {
54 html_viewer::TestHTMLViewerPtr test_html_viewer
;
55 connection
->ConnectToService(&test_html_viewer
);
57 test_html_viewer
->GetContentAsText([&result
](const String
& mojo_string
) {
59 ASSERT_TRUE(ViewManagerTestBase::QuitRunLoop());
61 if (!ViewManagerTestBase::DoRunLoopWithTimeout())
62 ADD_FAILURE() << "Timed out waiting for execute to complete";
63 // test_html_viewer.WaitForIncomingResponse();
67 scoped_ptr
<base::Value
> ExecuteScript(ApplicationConnection
* connection
,
68 const std::string
& script
) {
69 html_viewer::TestHTMLViewerPtr test_html_viewer
;
70 connection
->ConnectToService(&test_html_viewer
);
71 scoped_ptr
<base::Value
> result
;
72 test_html_viewer
->ExecuteScript(script
, [&result
](const String
& json_string
) {
73 result
= base::JSONReader::Read(json_string
.To
<std::string
>());
74 ASSERT_TRUE(ViewManagerTestBase::QuitRunLoop());
76 if (!ViewManagerTestBase::DoRunLoopWithTimeout())
77 ADD_FAILURE() << "Timed out waiting for execute to complete";
81 // FrameTreeDelegate that can block waiting for navigation to start.
82 class TestFrameTreeDelegateImpl
: public web_view::TestFrameTreeDelegate
{
84 explicit TestFrameTreeDelegateImpl(mojo::ApplicationImpl
* app
)
85 : TestFrameTreeDelegate(app
), frame_tree_(nullptr) {}
86 ~TestFrameTreeDelegateImpl() override
{}
88 void set_frame_tree(FrameTree
* frame_tree
) { frame_tree_
= frame_tree
; }
90 // Resets the navigation state for |frame|.
91 void ClearGotNavigate(Frame
* frame
) { frames_navigated_
.erase(frame
); }
93 // Waits until |frame| has |count| children and the last child has navigated.
94 bool WaitForChildFrameCount(Frame
* frame
, size_t count
) {
95 if (DidChildNavigate(frame
, count
))
98 waiting_for_frame_child_count_
.reset(new FrameAndChildCount
);
99 waiting_for_frame_child_count_
->frame
= frame
;
100 waiting_for_frame_child_count_
->count
= count
;
102 return ViewManagerTestBase::DoRunLoopWithTimeout();
105 // Returns true if |frame| has navigated. If |frame| hasn't navigated runs
106 // a nested message loop until |frame| navigates.
107 bool WaitForFrameNavigation(Frame
* frame
) {
108 if (frames_navigated_
.count(frame
))
111 frames_waiting_for_navigate_
.insert(frame
);
112 return ViewManagerTestBase::DoRunLoopWithTimeout();
115 // TestFrameTreeDelegate:
116 void DidStartNavigation(Frame
* frame
) override
{
117 frames_navigated_
.insert(frame
);
119 if (waiting_for_frame_child_count_
&&
120 DidChildNavigate(waiting_for_frame_child_count_
->frame
,
121 waiting_for_frame_child_count_
->count
)) {
122 waiting_for_frame_child_count_
.reset();
123 ASSERT_TRUE(ViewManagerTestBase::QuitRunLoop());
126 if (frames_waiting_for_navigate_
.count(frame
)) {
127 frames_waiting_for_navigate_
.erase(frame
);
128 ignore_result(ViewManagerTestBase::QuitRunLoop());
133 struct FrameAndChildCount
{
138 // Returns true if |frame| has |count| children and the last child frame
140 bool DidChildNavigate(Frame
* frame
, size_t count
) {
141 return ((frame
->children().size() == count
) &&
142 (frames_navigated_
.count(frame
->children()[count
- 1])));
145 FrameTree
* frame_tree_
;
146 // Any time DidStartNavigation() is invoked the frame is added here. Frames
147 // are inserted as void* as this does not track destruction of the frames.
148 std::set
<void*> frames_navigated_
;
150 // The set of frames waiting for a navigation to occur.
151 std::set
<Frame
*> frames_waiting_for_navigate_
;
153 // Set of frames waiting for a certain number of children and navigation.
154 scoped_ptr
<FrameAndChildCount
> waiting_for_frame_child_count_
;
156 DISALLOW_COPY_AND_ASSIGN(TestFrameTreeDelegateImpl
);
161 class HTMLFrameTest
: public ViewManagerTestBase
{
164 ~HTMLFrameTest() override
{}
167 // Creates the frame tree showing an empty page at the root and adds (via
168 // script) a frame showing the same empty page.
169 Frame
* LoadEmptyPageAndCreateFrame() {
170 mus::View
* embed_view
= window_manager()->CreateView();
171 frame_tree_delegate_
.reset(
172 new TestFrameTreeDelegateImpl(application_impl()));
173 FrameConnection
* root_connection
=
174 InitFrameTree(embed_view
, "http://127.0.0.1:%u/files/empty_page2.html");
175 if (!root_connection
) {
176 ADD_FAILURE() << "unable to establish root connection";
179 const std::string frame_text
=
180 GetFrameText(root_connection
->application_connection());
181 if (frame_text
!= "child2") {
182 ADD_FAILURE() << "unexpected text " << frame_text
;
186 return CreateEmptyChildFrame(frame_tree_
->root());
189 Frame
* CreateEmptyChildFrame(Frame
* parent
) {
190 const size_t initial_frame_count
= parent
->children().size();
191 // Dynamically add a new frame.
192 ExecuteScript(ApplicationConnectionForFrame(parent
),
193 AddPortToString(kAddFrameWithEmptyPageScript
));
195 frame_tree_delegate_
->WaitForChildFrameCount(parent
,
196 initial_frame_count
+ 1);
197 if (HasFatalFailure())
200 return parent
->FindFrame(parent
->view()->children().back()->id());
203 std::string
AddPortToString(const std::string
& string
) {
204 const uint16_t assigned_port
= http_server_
->host_port_pair().port();
205 return base::StringPrintf(string
.c_str(), assigned_port
);
208 mojo::URLRequestPtr
BuildRequestForURL(const std::string
& url_string
) {
209 mojo::URLRequestPtr
request(mojo::URLRequest::New());
210 request
->url
= mojo::String::From(AddPortToString(url_string
));
211 return request
.Pass();
214 FrameConnection
* InitFrameTree(mus::View
* view
,
215 const std::string
& url_string
) {
216 frame_tree_delegate_
.reset(
217 new TestFrameTreeDelegateImpl(application_impl()));
218 scoped_ptr
<FrameConnection
> frame_connection(new FrameConnection
);
219 bool got_callback
= false;
220 frame_connection
->Init(
221 application_impl(), BuildRequestForURL(url_string
),
222 base::Bind(&OnGotContentHandlerForRoot
, &got_callback
));
223 ignore_result(ViewManagerTestBase::DoRunLoopWithTimeout());
226 FrameConnection
* result
= frame_connection
.get();
227 FrameClient
* frame_client
= frame_connection
->frame_client();
228 ViewTreeClientPtr tree_client
= frame_connection
->GetViewTreeClient();
230 new FrameTree(result
->GetContentHandlerID(), view
, tree_client
.Pass(),
231 frame_tree_delegate_
.get(), frame_client
,
232 frame_connection
.Pass(), Frame::ClientPropertyMap()));
233 frame_tree_delegate_
->set_frame_tree(frame_tree_
.get());
238 void SetUp() override
{
239 ViewManagerTestBase::SetUp();
241 // Start a test server.
242 http_server_
.reset(new net::SpawnedTestServer(
243 net::SpawnedTestServer::TYPE_HTTP
, net::SpawnedTestServer::kLocalhost
,
244 base::FilePath(FILE_PATH_LITERAL("components/test/data/html_viewer"))));
245 ASSERT_TRUE(http_server_
->Start());
247 void TearDown() override
{
249 http_server_
.reset();
250 ViewManagerTestBase::TearDown();
253 scoped_ptr
<net::SpawnedTestServer
> http_server_
;
254 scoped_ptr
<FrameTree
> frame_tree_
;
256 scoped_ptr
<TestFrameTreeDelegateImpl
> frame_tree_delegate_
;
259 DISALLOW_COPY_AND_ASSIGN(HTMLFrameTest
);
262 TEST_F(HTMLFrameTest
, PageWithSingleFrame
) {
263 mus::View
* embed_view
= window_manager()->CreateView();
265 FrameConnection
* root_connection
= InitFrameTree(
266 embed_view
, "http://127.0.0.1:%u/files/page_with_single_frame.html");
267 ASSERT_TRUE(root_connection
);
269 ASSERT_EQ("Page with single frame",
270 GetFrameText(root_connection
->application_connection()));
272 ASSERT_NO_FATAL_FAILURE(
273 frame_tree_delegate_
->WaitForChildFrameCount(frame_tree_
->root(), 1u));
275 ASSERT_EQ(1u, embed_view
->children().size());
277 frame_tree_
->root()->FindFrame(embed_view
->children()[0]->id());
278 ASSERT_TRUE(child_frame
);
281 GetFrameText(static_cast<FrameConnection
*>(child_frame
->user_data())
282 ->application_connection()));
285 // Creates two frames. The parent navigates the child frame by way of changing
286 // the location of the child frame.
287 TEST_F(HTMLFrameTest
, ChangeLocationOfChildFrame
) {
288 mus::View
* embed_view
= window_manager()->CreateView();
290 ASSERT_TRUE(InitFrameTree(
291 embed_view
, "http://127.0.0.1:%u/files/page_with_single_frame.html"));
293 // page_with_single_frame contains a child frame. The child frame should
294 // create a new View and Frame.
295 ASSERT_NO_FATAL_FAILURE(
296 frame_tree_delegate_
->WaitForChildFrameCount(frame_tree_
->root(), 1u));
298 Frame
* child_frame
= frame_tree_
->root()->children().back();
301 GetFrameText(static_cast<FrameConnection
*>(child_frame
->user_data())
302 ->application_connection()));
304 // Change the location and wait for the navigation to occur.
305 const char kNavigateFrame
[] =
306 "window.frames[0].location = "
307 "'http://127.0.0.1:%u/files/empty_page2.html'";
308 frame_tree_delegate_
->ClearGotNavigate(child_frame
);
309 ExecuteScript(ApplicationConnectionForFrame(frame_tree_
->root()),
310 AddPortToString(kNavigateFrame
));
311 ASSERT_TRUE(frame_tree_delegate_
->WaitForFrameNavigation(child_frame
));
313 // There should still only be one frame.
314 ASSERT_EQ(1u, frame_tree_
->root()->children().size());
316 // The navigation should have changed the text of the frame.
317 ASSERT_TRUE(child_frame
->user_data());
319 GetFrameText(static_cast<FrameConnection
*>(child_frame
->user_data())
320 ->application_connection()));
323 TEST_F(HTMLFrameTest
, DynamicallyAddFrameAndVerifyParent
) {
324 Frame
* child_frame
= LoadEmptyPageAndCreateFrame();
325 ASSERT_TRUE(child_frame
);
327 mojo::ApplicationConnection
* child_frame_connection
=
328 ApplicationConnectionForFrame(child_frame
);
330 ASSERT_EQ("child", GetFrameText(child_frame_connection
));
331 // The child's parent should not be itself:
332 const char kGetWindowParentNameScript
[] =
333 "window.parent == window ? 'parent is self' : 'parent not self';";
334 scoped_ptr
<base::Value
> parent_value(
335 ExecuteScript(child_frame_connection
, kGetWindowParentNameScript
));
336 ASSERT_TRUE(parent_value
->IsType(base::Value::TYPE_LIST
));
337 base::ListValue
* parent_list
;
338 ASSERT_TRUE(parent_value
->GetAsList(&parent_list
));
339 ASSERT_EQ(1u, parent_list
->GetSize());
340 std::string parent_name
;
341 ASSERT_TRUE(parent_list
->GetString(0u, &parent_name
));
342 EXPECT_EQ("parent not self", parent_name
);
345 TEST_F(HTMLFrameTest
, DynamicallyAddFrameAndSeeNameChange
) {
346 Frame
* child_frame
= LoadEmptyPageAndCreateFrame();
347 ASSERT_TRUE(child_frame
);
349 mojo::ApplicationConnection
* child_frame_connection
=
350 ApplicationConnectionForFrame(child_frame
);
352 // Change the name of the child's window.
353 ExecuteScript(child_frame_connection
, "window.name = 'new_child';");
355 // Eventually the parent should see the change. There is no convenient way
356 // to observe this change, so we repeatedly ask for it and timeout if we
357 // never get the right value.
358 const base::TimeTicks
start_time(base::TimeTicks::Now());
359 std::string find_window_result
;
361 find_window_result
.clear();
362 scoped_ptr
<base::Value
> script_value(
363 ExecuteScript(ApplicationConnectionForFrame(frame_tree_
->root()),
364 "window.frames['new_child'] != null ? 'found frame' : "
365 "'unable to find frame';"));
366 if (script_value
->IsType(base::Value::TYPE_LIST
)) {
367 base::ListValue
* script_value_as_list
;
368 if (script_value
->GetAsList(&script_value_as_list
) &&
369 script_value_as_list
->GetSize() == 1) {
370 script_value_as_list
->GetString(0u, &find_window_result
);
373 } while (find_window_result
!= "found frame" &&
374 base::TimeTicks::Now() - start_time
<
375 TestTimeouts::action_timeout());
376 EXPECT_EQ("found frame", find_window_result
);
379 // Triggers dynamic addition and removal of a frame.
380 TEST_F(HTMLFrameTest
, FrameTreeOfThreeLevels
) {
381 // Create a child frame, and in that child frame create another child frame.
382 Frame
* child_frame
= LoadEmptyPageAndCreateFrame();
383 ASSERT_TRUE(child_frame
);
385 ASSERT_TRUE(CreateEmptyChildFrame(child_frame
));
387 // Make sure the parent can see the child and child's child. There is no
388 // convenient way to observe this change, so we repeatedly ask for it and
389 // timeout if we never get the right value.
390 const char kGetChildChildFrameCount
[] =
391 "if (window.frames.length > 0)"
392 " window.frames[0].frames.length.toString();"
395 const base::TimeTicks
start_time(base::TimeTicks::Now());
396 std::string child_child_frame_count
;
398 child_child_frame_count
.clear();
399 scoped_ptr
<base::Value
> script_value(
400 ExecuteScript(ApplicationConnectionForFrame(frame_tree_
->root()),
401 kGetChildChildFrameCount
));
402 if (script_value
->IsType(base::Value::TYPE_LIST
)) {
403 base::ListValue
* script_value_as_list
;
404 if (script_value
->GetAsList(&script_value_as_list
) &&
405 script_value_as_list
->GetSize() == 1) {
406 script_value_as_list
->GetString(0u, &child_child_frame_count
);
409 } while (child_child_frame_count
!= "1" &&
410 base::TimeTicks::Now() - start_time
<
411 TestTimeouts::action_timeout());
412 EXPECT_EQ("1", child_child_frame_count
);
414 // Remove the child's child and make sure the root doesn't see it anymore.
415 const char kRemoveLastIFrame
[] =
416 "document.body.removeChild(document.body.lastChild);";
417 ExecuteScript(ApplicationConnectionForFrame(child_frame
), kRemoveLastIFrame
);
419 child_child_frame_count
.clear();
420 scoped_ptr
<base::Value
> script_value(
421 ExecuteScript(ApplicationConnectionForFrame(frame_tree_
->root()),
422 kGetChildChildFrameCount
));
423 if (script_value
->IsType(base::Value::TYPE_LIST
)) {
424 base::ListValue
* script_value_as_list
;
425 if (script_value
->GetAsList(&script_value_as_list
) &&
426 script_value_as_list
->GetSize() == 1) {
427 script_value_as_list
->GetString(0u, &child_child_frame_count
);
430 } while (child_child_frame_count
!= "0" &&
431 base::TimeTicks::Now() - start_time
<
432 TestTimeouts::action_timeout());
433 ASSERT_EQ("0", child_child_frame_count
);
436 // Verifies PostMessage() works across frames.
437 TEST_F(HTMLFrameTest
, PostMessage
) {
438 Frame
* child_frame
= LoadEmptyPageAndCreateFrame();
439 ASSERT_TRUE(child_frame
);
441 mojo::ApplicationConnection
* child_frame_connection
=
442 ApplicationConnectionForFrame(child_frame
);
443 ASSERT_EQ("child", GetFrameText(child_frame_connection
));
445 // Register an event handler in the child frame.
446 const char kRegisterPostMessageHandler
[] =
447 "window.messageData = null;"
448 "function messageFunction(event) {"
449 " window.messageData = event.data;"
451 "window.addEventListener('message', messageFunction, false);";
452 ExecuteScript(child_frame_connection
, kRegisterPostMessageHandler
);
454 // Post a message from the parent to the child.
455 const char kPostMessageFromParent
[] =
456 "window.frames[0].postMessage('hello from parent', '*');";
457 ExecuteScript(ApplicationConnectionForFrame(frame_tree_
->root()),
458 kPostMessageFromParent
);
460 // Wait for the child frame to see the message.
461 const base::TimeTicks
start_time(base::TimeTicks::Now());
462 std::string message_in_child
;
464 const char kGetMessageData
[] = "window.messageData;";
465 scoped_ptr
<base::Value
> script_value(
466 ExecuteScript(child_frame_connection
, kGetMessageData
));
467 if (script_value
->IsType(base::Value::TYPE_LIST
)) {
468 base::ListValue
* script_value_as_list
;
469 if (script_value
->GetAsList(&script_value_as_list
) &&
470 script_value_as_list
->GetSize() == 1) {
471 script_value_as_list
->GetString(0u, &message_in_child
);
474 } while (message_in_child
!= "hello from parent" &&
475 base::TimeTicks::Now() - start_time
<
476 TestTimeouts::action_timeout());
477 EXPECT_EQ("hello from parent", message_in_child
);