Fix missing data dep for wm_unittests.
[chromium-blink-merge.git] / components / html_viewer / html_frame_apptest.cc
blob2d452a83c11d7581bd5f7339d9a96f140235bdb6
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"
6 #include "base/bind.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/view_manager/public/cpp/tests/view_manager_test_base.h"
16 #include "components/view_manager/public/cpp/view.h"
17 #include "components/view_manager/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_tree.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 web_view::Frame;
28 using web_view::FrameConnection;
29 using web_view::FrameTree;
30 using web_view::FrameTreeClient;
31 using web_view::FrameTreeDelegate;
33 namespace mojo {
35 namespace {
37 const char kAddFrameWithEmptyPageScript[] =
38 "var iframe = document.createElement(\"iframe\");"
39 "iframe.src = \"http://127.0.0.1:%u/files/empty_page.html\";"
40 "document.body.appendChild(iframe);";
42 void OnGotContentHandlerForRoot(bool* got_callback) {
43 *got_callback = true;
44 ignore_result(ViewManagerTestBase::QuitRunLoop());
47 mojo::ApplicationConnection* ApplicationConnectionForFrame(Frame* frame) {
48 return static_cast<FrameConnection*>(frame->user_data())
49 ->application_connection();
52 std::string GetFrameText(ApplicationConnection* connection) {
53 html_viewer::TestHTMLViewerPtr test_html_viewer;
54 connection->ConnectToService(&test_html_viewer);
55 std::string result;
56 test_html_viewer->GetContentAsText([&result](const String& mojo_string) {
57 result = mojo_string;
58 ASSERT_TRUE(ViewManagerTestBase::QuitRunLoop());
59 });
60 if (!ViewManagerTestBase::DoRunLoopWithTimeout())
61 ADD_FAILURE() << "Timed out waiting for execute to complete";
62 // test_html_viewer.WaitForIncomingResponse();
63 return result;
66 scoped_ptr<base::Value> ExecuteScript(ApplicationConnection* connection,
67 const std::string& script) {
68 html_viewer::TestHTMLViewerPtr test_html_viewer;
69 connection->ConnectToService(&test_html_viewer);
70 scoped_ptr<base::Value> result;
71 test_html_viewer->ExecuteScript(script, [&result](const String& json_string) {
72 result = base::JSONReader::Read(json_string.To<std::string>());
73 ASSERT_TRUE(ViewManagerTestBase::QuitRunLoop());
74 });
75 if (!ViewManagerTestBase::DoRunLoopWithTimeout())
76 ADD_FAILURE() << "Timed out waiting for execute to complete";
77 return result.Pass();
80 // FrameTreeDelegate that can block waiting for navigation to start.
81 class TestFrameTreeDelegateImpl : public web_view::TestFrameTreeDelegate {
82 public:
83 explicit TestFrameTreeDelegateImpl(mojo::ApplicationImpl* app)
84 : app_(app),
85 frame_tree_(nullptr),
86 waiting_for_navigate_(false),
87 got_navigate_(false) {}
88 ~TestFrameTreeDelegateImpl() override {}
90 void set_frame_tree(FrameTree* frame_tree) { frame_tree_ = frame_tree; }
92 void clear_got_navigate() { got_navigate_ = false; }
94 bool waiting_for_navigate() const { return waiting_for_navigate_; }
96 // Waits for a navigation to occur. This immediately returns true if a
97 // navigation has already occurred. In other words, take care when using this,
98 // you may need to clear_got_navigate() before calling this.
99 bool WaitForNavigateFrame() {
100 if (waiting_for_navigate_)
101 return false;
103 if (got_navigate_)
104 return true;
106 base::AutoReset<bool> resetter(&waiting_for_navigate_, true);
107 return ViewManagerTestBase::DoRunLoopWithTimeout() && got_navigate_;
110 // TestFrameTreeDelegate:
111 void CanNavigateFrame(Frame* target,
112 mojo::URLRequestPtr request,
113 const CanNavigateFrameCallback& callback) override {
114 FrameConnection::CreateConnectionForCanNavigateFrame(
115 app_, target, request.Pass(), callback);
118 void DidStartNavigation(Frame* frame) override {
119 got_navigate_ = true;
121 if (waiting_for_navigate_)
122 ignore_result(ViewManagerTestBase::QuitRunLoop());
125 private:
126 mojo::ApplicationImpl* app_;
127 FrameTree* frame_tree_;
128 bool waiting_for_navigate_;
129 bool got_navigate_;
131 DISALLOW_COPY_AND_ASSIGN(TestFrameTreeDelegateImpl);
134 } // namespace
136 class HTMLFrameTest : public ViewManagerTestBase {
137 public:
138 HTMLFrameTest() {}
139 ~HTMLFrameTest() override {}
141 protected:
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 if (!root_connection) {
151 ADD_FAILURE() << "unable to establish root connection";
152 return nullptr;
154 const std::string frame_text =
155 GetFrameText(root_connection->application_connection());
156 if (frame_text != "child2") {
157 ADD_FAILURE() << "unexpected text " << frame_text;
158 return nullptr;
161 return CreateEmptyChildFrame(frame_tree_->root());
164 Frame* CreateEmptyChildFrame(Frame* parent) {
165 const size_t initial_frame_count = parent->children().size();
166 // Dynamically add a new frame.
167 ExecuteScript(ApplicationConnectionForFrame(parent),
168 AddPortToString(kAddFrameWithEmptyPageScript));
170 // Wait for the frame to appear.
171 if ((parent->children().size() != initial_frame_count + 1u ||
172 !parent->children().back()->user_data()) &&
173 !WaitForNavigateFrame()) {
174 ADD_FAILURE() << "timed out waiting for child";
175 return nullptr;
178 if (parent->view()->children().size() != initial_frame_count + 1u) {
179 ADD_FAILURE() << "unexpected number of children "
180 << parent->view()->children().size();
181 return nullptr;
184 return parent->FindFrame(parent->view()->children().back()->id());
187 std::string AddPortToString(const std::string& string) {
188 const uint16_t assigned_port = http_server_->host_port_pair().port();
189 return base::StringPrintf(string.c_str(), assigned_port);
192 mojo::URLRequestPtr BuildRequestForURL(const std::string& url_string) {
193 mojo::URLRequestPtr request(mojo::URLRequest::New());
194 request->url = mojo::String::From(AddPortToString(url_string));
195 return request.Pass();
198 FrameConnection* InitFrameTree(View* view, const std::string& url_string) {
199 frame_tree_delegate_.reset(
200 new TestFrameTreeDelegateImpl(application_impl()));
201 scoped_ptr<FrameConnection> frame_connection(new FrameConnection);
202 bool got_callback = false;
203 frame_connection->Init(
204 application_impl(), BuildRequestForURL(url_string),
205 base::Bind(&OnGotContentHandlerForRoot, &got_callback));
206 ignore_result(ViewManagerTestBase::DoRunLoopWithTimeout());
207 if (!got_callback)
208 return nullptr;
209 FrameConnection* result = frame_connection.get();
210 FrameTreeClient* frame_tree_client = frame_connection->frame_tree_client();
211 ViewTreeClientPtr tree_client = frame_connection->GetViewTreeClient();
212 frame_tree_.reset(new FrameTree(result->GetContentHandlerID(), view,
213 frame_tree_delegate_.get(),
214 frame_tree_client, frame_connection.Pass(),
215 Frame::ClientPropertyMap()));
216 frame_tree_delegate_->set_frame_tree(frame_tree_.get());
217 view->Embed(tree_client.Pass());
218 return result;
221 bool WaitForNavigateFrame() {
222 if (frame_tree_delegate_->waiting_for_navigate())
223 return false;
225 frame_tree_delegate_->clear_got_navigate();
226 return frame_tree_delegate_->WaitForNavigateFrame();
229 // ViewManagerTest:
230 void SetUp() override {
231 ViewManagerTestBase::SetUp();
233 // Make it so we get OnEmbedForDescendant().
234 window_manager()->SetEmbedRoot();
236 // Start a test server.
237 http_server_.reset(new net::SpawnedTestServer(
238 net::SpawnedTestServer::TYPE_HTTP, net::SpawnedTestServer::kLocalhost,
239 base::FilePath(FILE_PATH_LITERAL("components/test/data/html_viewer"))));
240 ASSERT_TRUE(http_server_->Start());
242 void TearDown() override {
243 frame_tree_.reset();
244 http_server_.reset();
245 ViewManagerTestBase::TearDown();
248 scoped_ptr<net::SpawnedTestServer> http_server_;
249 scoped_ptr<FrameTree> frame_tree_;
251 scoped_ptr<TestFrameTreeDelegateImpl> frame_tree_delegate_;
253 private:
254 DISALLOW_COPY_AND_ASSIGN(HTMLFrameTest);
257 TEST_F(HTMLFrameTest, PageWithSingleFrame) {
258 View* embed_view = window_manager()->CreateView();
260 FrameConnection* root_connection = InitFrameTree(
261 embed_view, "http://127.0.0.1:%u/files/page_with_single_frame.html");
262 ASSERT_TRUE(root_connection);
264 ASSERT_EQ("Page with single frame",
265 GetFrameText(root_connection->application_connection()));
267 // page_with_single_frame contains a child frame. The child frame should
268 // create a new View and Frame.
269 if (frame_tree_->root()->children().empty() ||
270 !frame_tree_->root()->children().back()->user_data()) {
271 ASSERT_TRUE(WaitForNavigateFrame());
274 ASSERT_EQ(1u, embed_view->children().size());
275 Frame* child_frame =
276 frame_tree_->root()->FindFrame(embed_view->children()[0]->id());
277 ASSERT_TRUE(child_frame);
279 ASSERT_EQ("child",
280 GetFrameText(static_cast<FrameConnection*>(child_frame->user_data())
281 ->application_connection()));
284 // Creates two frames. The parent navigates the child frame by way of changing
285 // the location of the child frame.
286 TEST_F(HTMLFrameTest, ChangeLocationOfChildFrame) {
287 View* embed_view = window_manager()->CreateView();
289 ASSERT_TRUE(InitFrameTree(
290 embed_view, "http://127.0.0.1:%u/files/page_with_single_frame.html"));
292 // page_with_single_frame contains a child frame. The child frame should
293 // create a new View and Frame.
294 if (frame_tree_->root()->children().empty() ||
295 !frame_tree_->root()->children().back()->user_data()) {
296 ASSERT_TRUE(WaitForNavigateFrame());
299 ASSERT_EQ(
300 "child",
301 GetFrameText(static_cast<FrameConnection*>(
302 frame_tree_->root()->children().back()->user_data())
303 ->application_connection()));
305 // Change the location and wait for the navigation to occur.
306 const char kNavigateFrame[] =
307 "window.frames[0].location = "
308 "'http://127.0.0.1:%u/files/empty_page2.html'";
309 frame_tree_delegate_->clear_got_navigate();
310 ExecuteScript(ApplicationConnectionForFrame(frame_tree_->root()),
311 AddPortToString(kNavigateFrame));
312 ASSERT_TRUE(WaitForNavigateFrame());
314 // The navigation should have changed the text of the frame.
315 ASSERT_EQ(1u, frame_tree_->root()->children().size());
316 Frame* child_frame = frame_tree_->root()->children()[0];
317 ASSERT_TRUE(child_frame->user_data());
318 ASSERT_EQ("child2",
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;
360 do {
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();"
393 "else"
394 " '0';";
395 const base::TimeTicks start_time(base::TimeTicks::Now());
396 std::string child_child_frame_count;
397 do {
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);
418 do {
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 frame_tree_delegate_->clear_got_navigate();
456 // Post a message from the parent to the child.
457 const char kPostMessageFromParent[] =
458 "window.frames[0].postMessage('hello from parent', '*');";
459 ExecuteScript(ApplicationConnectionForFrame(frame_tree_->root()),
460 kPostMessageFromParent);
462 // Wait for the child frame to see the message.
463 const base::TimeTicks start_time(base::TimeTicks::Now());
464 std::string message_in_child;
465 do {
466 const char kGetMessageData[] = "window.messageData;";
467 scoped_ptr<base::Value> script_value(
468 ExecuteScript(child_frame_connection, kGetMessageData));
469 if (script_value->IsType(base::Value::TYPE_LIST)) {
470 base::ListValue* script_value_as_list;
471 if (script_value->GetAsList(&script_value_as_list) &&
472 script_value_as_list->GetSize() == 1) {
473 script_value_as_list->GetString(0u, &message_in_child);
476 } while (message_in_child != "hello from parent" &&
477 base::TimeTicks::Now() - start_time <
478 TestTimeouts::action_timeout());
479 EXPECT_EQ("hello from parent", message_in_child);
482 } // namespace mojo