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/command_line.h"
8 #include "base/json/json_reader.h"
9 #include "base/run_loop.h"
10 #include "base/strings/stringprintf.h"
11 #include "base/test/test_timeouts.h"
12 #include "base/values.h"
13 #include "components/html_viewer/public/interfaces/test_html_viewer.mojom.h"
14 #include "components/view_manager/public/cpp/tests/view_manager_test_base.h"
15 #include "components/view_manager/public/cpp/view.h"
16 #include "components/view_manager/public/cpp/view_tree_connection.h"
17 #include "mandoline/tab/frame.h"
18 #include "mandoline/tab/frame_connection.h"
19 #include "mandoline/tab/frame_tree.h"
20 #include "mandoline/tab/public/interfaces/frame_tree.mojom.h"
21 #include "mandoline/tab/test_frame_tree_delegate.h"
22 #include "mojo/application/public/cpp/application_impl.h"
23 #include "net/test/spawned_test_server/spawned_test_server.h"
24 #include "third_party/mojo_services/src/accessibility/public/interfaces/accessibility.mojom.h"
26 using mandoline::Frame
;
27 using mandoline::FrameConnection
;
28 using mandoline::FrameTree
;
29 using mandoline::FrameTreeClient
;
35 const char kAddFrameWithEmptyPageScript
[] =
36 "var iframe = document.createElement(\"iframe\");"
37 "iframe.src = \"http://127.0.0.1:%u/files/empty_page.html\";"
38 "document.body.appendChild(iframe);";
40 mojo::ApplicationConnection
* ApplicationConnectionForFrame(Frame
* frame
) {
41 return static_cast<FrameConnection
*>(frame
->user_data())
42 ->application_connection();
45 std::string
GetFrameText(ApplicationConnection
* connection
) {
46 html_viewer::TestHTMLViewerPtr test_html_viewer
;
47 connection
->ConnectToService(&test_html_viewer
);
49 test_html_viewer
->GetContentAsText([&result
](const String
& mojo_string
) {
51 ASSERT_TRUE(ViewManagerTestBase::QuitRunLoop());
53 if (!ViewManagerTestBase::DoRunLoopWithTimeout())
54 ADD_FAILURE() << "Timed out waiting for execute to complete";
55 // test_html_viewer.WaitForIncomingResponse();
59 scoped_ptr
<base::Value
> ExecuteScript(ApplicationConnection
* connection
,
60 const std::string
& script
) {
61 html_viewer::TestHTMLViewerPtr test_html_viewer
;
62 connection
->ConnectToService(&test_html_viewer
);
63 scoped_ptr
<base::Value
> result
;
64 test_html_viewer
->ExecuteScript(script
, [&result
](const String
& json_string
) {
65 result
= base::JSONReader::Read(json_string
.To
<std::string
>());
66 ASSERT_TRUE(ViewManagerTestBase::QuitRunLoop());
68 if (!ViewManagerTestBase::DoRunLoopWithTimeout())
69 ADD_FAILURE() << "Timed out waiting for execute to complete";
73 // FrameTreeDelegate that can block waiting for navigation to start.
74 class TestFrameTreeDelegateImpl
: public mandoline::TestFrameTreeDelegate
{
76 explicit TestFrameTreeDelegateImpl(mojo::ApplicationImpl
* app
)
79 waiting_for_navigate_(false),
80 got_navigate_(false) {}
81 ~TestFrameTreeDelegateImpl() override
{}
83 void set_frame_tree(FrameTree
* frame_tree
) { frame_tree_
= frame_tree
; }
85 void clear_got_navigate() { got_navigate_
= false; }
87 bool waiting_for_navigate() const { return waiting_for_navigate_
; }
89 // Waits for a navigation to occur. This immediately returns true if a
90 // navigation has already occurred. In other words, take care when using this,
91 // you may need to clear_got_navigate() before calling this.
92 bool WaitForNavigateFrame() {
93 if (waiting_for_navigate_
)
99 base::AutoReset
<bool> resetter(&waiting_for_navigate_
, true);
100 return ViewManagerTestBase::DoRunLoopWithTimeout() && got_navigate_
;
103 // TestFrameTreeDelegate:
104 bool CanNavigateFrame(
106 mojo::URLRequestPtr request
,
107 mandoline::FrameTreeClient
** frame_tree_client
,
108 scoped_ptr
<mandoline::FrameUserData
>* frame_user_data
,
109 mojo::ViewTreeClientPtr
* tree_client
) override
{
110 scoped_ptr
<FrameConnection
> frame_connection(new FrameConnection
);
111 frame_connection
->Init(app_
, request
.Pass(), tree_client
);
112 *frame_tree_client
= frame_connection
->frame_tree_client();
113 *frame_user_data
= frame_connection
.Pass();
118 void DidStartNavigation(Frame
* frame
) override
{
119 got_navigate_
= true;
121 if (waiting_for_navigate_
)
122 ignore_result(ViewManagerTestBase::QuitRunLoop());
126 mojo::ApplicationImpl
* app_
;
127 FrameTree
* frame_tree_
;
128 bool waiting_for_navigate_
;
131 DISALLOW_COPY_AND_ASSIGN(TestFrameTreeDelegateImpl
);
136 class HTMLFrameTest
: public ViewManagerTestBase
{
139 ~HTMLFrameTest() override
{}
142 // Creates the frame tree showing an empty page at the root and adds (via
143 // script) a frame showing the same empty page.
144 Frame
* LoadEmptyPageAndCreateFrame() {
145 View
* embed_view
= window_manager()->CreateView();
146 frame_tree_delegate_
.reset(
147 new TestFrameTreeDelegateImpl(application_impl()));
148 FrameConnection
* root_connection
=
149 InitFrameTree(embed_view
, "http://127.0.0.1:%u/files/empty_page2.html");
150 const std::string frame_text
=
151 GetFrameText(root_connection
->application_connection());
152 if (frame_text
!= "child2") {
153 ADD_FAILURE() << "unexpected text " << frame_text
;
157 return CreateEmptyChildFrame(frame_tree_
->root());
160 Frame
* CreateEmptyChildFrame(Frame
* parent
) {
161 const size_t initial_frame_count
= parent
->children().size();
162 // Dynamically add a new frame.
163 ExecuteScript(ApplicationConnectionForFrame(parent
),
164 AddPortToString(kAddFrameWithEmptyPageScript
));
166 // Wait for the frame to appear.
167 if ((parent
->children().size() != initial_frame_count
+ 1u ||
168 !parent
->children().back()->user_data()) &&
169 !WaitForNavigateFrame()) {
170 ADD_FAILURE() << "timed out waiting for child";
174 if (parent
->view()->children().size() != initial_frame_count
+ 1u) {
175 ADD_FAILURE() << "unexpected number of children "
176 << parent
->view()->children().size();
180 return parent
->FindFrame(parent
->view()->children().back()->id());
183 std::string
AddPortToString(const std::string
& string
) {
184 const uint16_t assigned_port
= http_server_
->host_port_pair().port();
185 return base::StringPrintf(string
.c_str(), assigned_port
);
188 mojo::URLRequestPtr
BuildRequestForURL(const std::string
& url_string
) {
189 mojo::URLRequestPtr
request(mojo::URLRequest::New());
190 request
->url
= mojo::String::From(AddPortToString(url_string
));
191 return request
.Pass();
194 FrameConnection
* InitFrameTree(View
* view
, const std::string
& url_string
) {
195 frame_tree_delegate_
.reset(
196 new TestFrameTreeDelegateImpl(application_impl()));
197 scoped_ptr
<FrameConnection
> frame_connection(new FrameConnection
);
198 FrameConnection
* result
= frame_connection
.get();
199 ViewTreeClientPtr tree_client
;
200 frame_connection
->Init(application_impl(), BuildRequestForURL(url_string
),
202 FrameTreeClient
* frame_tree_client
= frame_connection
->frame_tree_client();
203 frame_tree_
.reset(new FrameTree(view
, frame_tree_delegate_
.get(),
205 frame_connection
.Pass(),
206 Frame::ClientPropertyMap()));
207 frame_tree_delegate_
->set_frame_tree(frame_tree_
.get());
208 view
->Embed(tree_client
.Pass());
212 bool WaitForNavigateFrame() {
213 if (frame_tree_delegate_
->waiting_for_navigate())
216 frame_tree_delegate_
->clear_got_navigate();
217 return frame_tree_delegate_
->WaitForNavigateFrame();
221 void SetUp() override
{
222 ViewManagerTestBase::SetUp();
224 // Make it so we get OnEmbedForDescendant().
225 window_manager()->SetEmbedRoot();
227 // Start a test server.
228 http_server_
.reset(new net::SpawnedTestServer(
229 net::SpawnedTestServer::TYPE_HTTP
, net::SpawnedTestServer::kLocalhost
,
230 base::FilePath(FILE_PATH_LITERAL("components/test/data/html_viewer"))));
231 ASSERT_TRUE(http_server_
->Start());
233 void TearDown() override
{
235 http_server_
.reset();
236 ViewManagerTestBase::TearDown();
239 scoped_ptr
<net::SpawnedTestServer
> http_server_
;
240 scoped_ptr
<FrameTree
> frame_tree_
;
242 scoped_ptr
<TestFrameTreeDelegateImpl
> frame_tree_delegate_
;
245 DISALLOW_COPY_AND_ASSIGN(HTMLFrameTest
);
248 TEST_F(HTMLFrameTest
, PageWithSingleFrame
) {
249 View
* embed_view
= window_manager()->CreateView();
251 FrameConnection
* root_connection
= InitFrameTree(
252 embed_view
, "http://127.0.0.1:%u/files/page_with_single_frame.html");
254 ASSERT_EQ("Page with single frame",
255 GetFrameText(root_connection
->application_connection()));
257 // page_with_single_frame contains a child frame. The child frame should
258 // create a new View and Frame.
259 if (frame_tree_
->root()->children().empty() ||
260 !frame_tree_
->root()->children().back()->user_data()) {
261 ASSERT_TRUE(WaitForNavigateFrame());
264 ASSERT_EQ(1u, embed_view
->children().size());
266 frame_tree_
->root()->FindFrame(embed_view
->children()[0]->id());
267 ASSERT_TRUE(child_frame
);
270 GetFrameText(static_cast<FrameConnection
*>(child_frame
->user_data())
271 ->application_connection()));
274 // Creates two frames. The parent navigates the child frame by way of changing
275 // the location of the child frame.
276 TEST_F(HTMLFrameTest
, ChangeLocationOfChildFrame
) {
277 View
* embed_view
= window_manager()->CreateView();
279 InitFrameTree(embed_view
,
280 "http://127.0.0.1:%u/files/page_with_single_frame.html");
282 // page_with_single_frame contains a child frame. The child frame should
283 // create a new View and Frame.
284 if (frame_tree_
->root()->children().empty() ||
285 !frame_tree_
->root()->children().back()->user_data()) {
286 ASSERT_TRUE(WaitForNavigateFrame());
291 GetFrameText(static_cast<FrameConnection
*>(
292 frame_tree_
->root()->children().back()->user_data())
293 ->application_connection()));
295 // Change the location and wait for the navigation to occur.
296 const char kNavigateFrame
[] =
297 "window.frames[0].location = "
298 "'http://127.0.0.1:%u/files/empty_page2.html'";
299 frame_tree_delegate_
->clear_got_navigate();
300 ExecuteScript(ApplicationConnectionForFrame(frame_tree_
->root()),
301 AddPortToString(kNavigateFrame
));
302 ASSERT_TRUE(WaitForNavigateFrame());
304 // The navigation should have changed the text of the frame.
305 ASSERT_EQ(1u, frame_tree_
->root()->children().size());
306 Frame
* child_frame
= frame_tree_
->root()->children()[0];
307 ASSERT_TRUE(child_frame
->user_data());
309 GetFrameText(static_cast<FrameConnection
*>(child_frame
->user_data())
310 ->application_connection()));
313 TEST_F(HTMLFrameTest
, DynamicallyAddFrameAndVerifyParent
) {
314 Frame
* child_frame
= LoadEmptyPageAndCreateFrame();
315 ASSERT_TRUE(child_frame
);
317 mojo::ApplicationConnection
* child_frame_connection
=
318 ApplicationConnectionForFrame(child_frame
);
320 ASSERT_EQ("child", GetFrameText(child_frame_connection
));
321 // The child's parent should not be itself:
322 const char kGetWindowParentNameScript
[] =
323 "window.parent == window ? 'parent is self' : 'parent not self';";
324 scoped_ptr
<base::Value
> parent_value(
325 ExecuteScript(child_frame_connection
, kGetWindowParentNameScript
));
326 ASSERT_TRUE(parent_value
->IsType(base::Value::TYPE_LIST
));
327 base::ListValue
* parent_list
;
328 ASSERT_TRUE(parent_value
->GetAsList(&parent_list
));
329 ASSERT_EQ(1u, parent_list
->GetSize());
330 std::string parent_name
;
331 ASSERT_TRUE(parent_list
->GetString(0u, &parent_name
));
332 EXPECT_EQ("parent not self", parent_name
);
335 TEST_F(HTMLFrameTest
, DynamicallyAddFrameAndSeeNameChange
) {
336 Frame
* child_frame
= LoadEmptyPageAndCreateFrame();
337 ASSERT_TRUE(child_frame
);
339 mojo::ApplicationConnection
* child_frame_connection
=
340 ApplicationConnectionForFrame(child_frame
);
342 // Change the name of the child's window.
343 ExecuteScript(child_frame_connection
, "window.name = 'new_child';");
345 // Eventually the parent should see the change. There is no convenient way
346 // to observe this change, so we repeatedly ask for it and timeout if we
347 // never get the right value.
348 const base::TimeTicks
start_time(base::TimeTicks::Now());
349 std::string find_window_result
;
351 find_window_result
.clear();
352 scoped_ptr
<base::Value
> script_value(
353 ExecuteScript(ApplicationConnectionForFrame(frame_tree_
->root()),
354 "window.frames['new_child'] != null ? 'found frame' : "
355 "'unable to find frame';"));
356 if (script_value
->IsType(base::Value::TYPE_LIST
)) {
357 base::ListValue
* script_value_as_list
;
358 if (script_value
->GetAsList(&script_value_as_list
) &&
359 script_value_as_list
->GetSize() == 1) {
360 script_value_as_list
->GetString(0u, &find_window_result
);
363 } while (find_window_result
!= "found frame" &&
364 base::TimeTicks::Now() - start_time
<
365 TestTimeouts::action_timeout());
366 EXPECT_EQ("found frame", find_window_result
);
369 // Triggers dynamic addition and removal of a frame.
370 TEST_F(HTMLFrameTest
, FrameTreeOfThreeLevels
) {
371 // Create a child frame, and in that child frame create another child frame.
372 Frame
* child_frame
= LoadEmptyPageAndCreateFrame();
373 ASSERT_TRUE(child_frame
);
375 ASSERT_TRUE(CreateEmptyChildFrame(child_frame
));
377 // Make sure the parent can see the child and child's child. There is no
378 // convenient way to observe this change, so we repeatedly ask for it and
379 // timeout if we never get the right value.
380 const char kGetChildChildFrameCount
[] =
381 "if (window.frames.length > 0)"
382 " window.frames[0].frames.length.toString();"
385 const base::TimeTicks
start_time(base::TimeTicks::Now());
386 std::string child_child_frame_count
;
388 child_child_frame_count
.clear();
389 scoped_ptr
<base::Value
> script_value(
390 ExecuteScript(ApplicationConnectionForFrame(frame_tree_
->root()),
391 kGetChildChildFrameCount
));
392 if (script_value
->IsType(base::Value::TYPE_LIST
)) {
393 base::ListValue
* script_value_as_list
;
394 if (script_value
->GetAsList(&script_value_as_list
) &&
395 script_value_as_list
->GetSize() == 1) {
396 script_value_as_list
->GetString(0u, &child_child_frame_count
);
399 } while (child_child_frame_count
!= "1" &&
400 base::TimeTicks::Now() - start_time
<
401 TestTimeouts::action_timeout());
402 EXPECT_EQ("1", child_child_frame_count
);
404 // Remove the child's child and make sure the root doesn't see it anymore.
405 const char kRemoveLastIFrame
[] =
406 "document.body.removeChild(document.body.lastChild);";
407 ExecuteScript(ApplicationConnectionForFrame(child_frame
), kRemoveLastIFrame
);
409 child_child_frame_count
.clear();
410 scoped_ptr
<base::Value
> script_value(
411 ExecuteScript(ApplicationConnectionForFrame(frame_tree_
->root()),
412 kGetChildChildFrameCount
));
413 if (script_value
->IsType(base::Value::TYPE_LIST
)) {
414 base::ListValue
* script_value_as_list
;
415 if (script_value
->GetAsList(&script_value_as_list
) &&
416 script_value_as_list
->GetSize() == 1) {
417 script_value_as_list
->GetString(0u, &child_child_frame_count
);
420 } while (child_child_frame_count
!= "0" &&
421 base::TimeTicks::Now() - start_time
<
422 TestTimeouts::action_timeout());
423 ASSERT_EQ("0", child_child_frame_count
);
426 // Verifies PostMessage() works across frames.
427 TEST_F(HTMLFrameTest
, PostMessage
) {
428 Frame
* child_frame
= LoadEmptyPageAndCreateFrame();
429 ASSERT_TRUE(child_frame
);
431 mojo::ApplicationConnection
* child_frame_connection
=
432 ApplicationConnectionForFrame(child_frame
);
433 ASSERT_EQ("child", GetFrameText(child_frame_connection
));
435 // Register an event handler in the child frame.
436 const char kRegisterPostMessageHandler
[] =
437 "window.messageData = null;"
438 "function messageFunction(event) {"
439 " window.messageData = event.data;"
441 "window.addEventListener('message', messageFunction, false);";
442 ExecuteScript(child_frame_connection
, kRegisterPostMessageHandler
);
444 frame_tree_delegate_
->clear_got_navigate();
446 // Post a message from the parent to the child.
447 const char kPostMessageFromParent
[] =
448 "window.frames[0].postMessage('hello from parent', '*');";
449 ExecuteScript(ApplicationConnectionForFrame(frame_tree_
->root()),
450 kPostMessageFromParent
);
452 // Wait for the child frame to see the message.
453 const base::TimeTicks
start_time(base::TimeTicks::Now());
454 std::string message_in_child
;
456 const char kGetMessageData
[] = "window.messageData;";
457 scoped_ptr
<base::Value
> script_value(
458 ExecuteScript(child_frame_connection
, kGetMessageData
));
459 if (script_value
->IsType(base::Value::TYPE_LIST
)) {
460 base::ListValue
* script_value_as_list
;
461 if (script_value
->GetAsList(&script_value_as_list
) &&
462 script_value_as_list
->GetSize() == 1) {
463 script_value_as_list
->GetString(0u, &message_in_child
);
466 } while (message_in_child
!= "hello from parent" &&
467 base::TimeTicks::Now() - start_time
<
468 TestTimeouts::action_timeout());
469 EXPECT_EQ("hello from parent", message_in_child
);