1// Copyright (c) 2011 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.
4
5#include "ppapi/tests/test_view.h"
6
7#include <sstream>
8
9#include "ppapi/c/pp_time.h"
10#include "ppapi/c/private/ppb_testing_private.h"
11#include "ppapi/cpp/completion_callback.h"
12#include "ppapi/tests/testing_instance.h"
13
14REGISTER_TEST_CASE(View);
15
16// When waiting for view changed events, wait no longer than this.
17#if !defined(THREAD_SANITIZER)
18static int kViewChangeTimeoutSec = 5;
19#else
20// ThreadSanitizer may slow the interaction down significantly.
21static int kViewChangeTimeoutSec = 30;
22#endif
23
24TestView::TestView(TestingInstance* instance)
25    : TestCase(instance),
26      post_quit_on_view_changed_(false) {
27}
28
29void TestView::DidChangeView(const pp::View& view) {
30  last_view_ = view;
31  page_visibility_log_.push_back(view.IsPageVisible());
32
33  if (post_quit_on_view_changed_) {
34    post_quit_on_view_changed_ = false;
35    testing_interface_->QuitMessageLoop(instance_->pp_instance());
36  }
37}
38
39bool TestView::Init() {
40  return CheckTestingInterface();
41}
42
43void TestView::RunTests(const std::string& filter) {
44  RUN_TEST(CreatedVisible, filter);
45  RUN_TEST(CreatedInvisible, filter);
46  RUN_TEST(PageHideShow, filter);
47  RUN_TEST(SizeChange, filter);
48  RUN_TEST(ClipChange, filter);
49  RUN_TEST(ScrollOffsetChange, filter);
50}
51
52bool TestView::WaitUntilViewChanged() {
53  // Schedule a callback so this step times out if we don't get a ViewChanged
54  // in a reasonable amount of time.
55  pp::CompletionCallbackFactory<TestView> factory(this);
56  pp::CompletionCallback timeout =
57      factory.NewCallback(&TestView::QuitMessageLoop);
58  pp::Module::Get()->core()->CallOnMainThread(
59      kViewChangeTimeoutSec * 1000, timeout);
60
61  size_t old_page_visibility_change_count = page_visibility_log_.size();
62
63  // Run a nested message loop. It will exit either on ViewChanged or if the
64  // timeout happens.
65  post_quit_on_view_changed_ = true;
66  testing_interface_->RunMessageLoop(instance_->pp_instance());
67  post_quit_on_view_changed_ = false;
68
69  // We know we got a view changed event if something was appended to the log.
70  return page_visibility_log_.size() > old_page_visibility_change_count;
71}
72
73void TestView::QuitMessageLoop(int32_t result) {
74  testing_interface_->QuitMessageLoop(instance_->pp_instance());
75}
76
77std::string TestView::TestCreatedVisible() {
78  ASSERT_FALSE(page_visibility_log_.empty());
79  ASSERT_TRUE(page_visibility_log_[0]);
80  PASS();
81}
82
83std::string TestView::TestCreatedInvisible() {
84  ASSERT_FALSE(page_visibility_log_.empty());
85
86  if (page_visibility_log_[0]) {
87    // Add more error message since this test has some extra requirements.
88    instance_->AppendError("Initial page is set to visible. NOTE: "
89        "This test must be run in a background tab. "
90        "Either run in the UI test which does this, or you can middle-click "
91        "on the test link to run manually.");
92  }
93  ASSERT_FALSE(page_visibility_log_[0]);
94  PASS();
95}
96
97std::string TestView::TestPageHideShow() {
98  // Initial state should be visible.
99  ASSERT_FALSE(page_visibility_log_.empty());
100  ASSERT_TRUE(page_visibility_log_[0]);
101
102  // Now that we're alive, tell the test knows it can change our visibility.
103  instance_->ReportProgress("TestPageHideShow:Created");
104
105  // Wait until we get a hide event, being careful to handle spurious
106  // notifications of ViewChanged.
107  PP_Time begin_time = pp::Module::Get()->core()->GetTime();
108  while (WaitUntilViewChanged() &&
109         page_visibility_log_[page_visibility_log_.size() - 1] &&
110         pp::Module::Get()->core()->GetTime() - begin_time <
111             kViewChangeTimeoutSec) {
112  }
113  if (page_visibility_log_[page_visibility_log_.size() - 1]) {
114    // Didn't get a view changed event that changed visibility (though there
115    // may have been some that didn't change visibility).
116    // Add more error message since this test has some extra requirements.
117    return "Didn't receive a hide event in timeout. NOTE: "
118        "This test requires tab visibility to change and won't pass if you "
119        "just run it in a browser. Normally the UI test should handle "
120        "this. You can also run manually by waiting 2 secs, creating a new "
121        "tab, waiting 2 more secs, and closing the new tab.";
122  }
123
124  // Tell the test so it can show us again.
125  instance_->ReportProgress("TestPageHideShow:Hidden");
126
127  // Wait until we get a show event.
128  begin_time = pp::Module::Get()->core()->GetTime();
129  while (WaitUntilViewChanged() &&
130         !page_visibility_log_[page_visibility_log_.size() - 1] &&
131         pp::Module::Get()->core()->GetTime() - begin_time <
132             kViewChangeTimeoutSec) {
133  }
134  ASSERT_TRUE(page_visibility_log_[page_visibility_log_.size() - 1]);
135
136  PASS();
137}
138
139std::string TestView::TestSizeChange() {
140  pp::Rect original_rect = last_view_.GetRect();
141
142  pp::Rect desired_rect = original_rect;
143  desired_rect.set_width(original_rect.width() + 10);
144  desired_rect.set_height(original_rect.height() + 12);
145
146  std::ostringstream script_stream;
147  script_stream << "var plugin = document.getElementById('plugin');";
148  script_stream << "plugin.setAttribute('width', "
149                << desired_rect.width() << ");";
150  script_stream << "plugin.setAttribute('height', "
151                << desired_rect.height() << ");";
152
153  instance_->EvalScript(script_stream.str());
154
155  PP_Time begin_time = pp::Module::Get()->core()->GetTime();
156  while (WaitUntilViewChanged() && last_view_.GetRect() != desired_rect &&
157         pp::Module::Get()->core()->GetTime() - begin_time <
158             kViewChangeTimeoutSec) {
159  }
160  ASSERT_TRUE(last_view_.GetRect() == desired_rect);
161
162  PASS();
163}
164
165std::string TestView::TestClipChange() {
166  pp::Rect original_rect = last_view_.GetRect();
167
168  // Original clip should be the full frame.
169  pp::Rect original_clip = last_view_.GetClipRect();
170  ASSERT_TRUE(original_clip.x() == 0);
171  ASSERT_TRUE(original_clip.y() == 0);
172  ASSERT_TRUE(original_clip.width() == original_rect.width());
173  ASSERT_TRUE(original_clip.height() == original_rect.height());
174
175  int clip_amount = original_rect.height() / 2;
176
177  // It might be nice to set the position to be absolute and set the location,
178  // but this will cause WebKit to actually tear down the plugin and recreate
179  // it. So instead we add a big div to cause the document to be scrollable,
180  // and scroll it down.
181  std::ostringstream script_stream;
182  script_stream
183      << "var big = document.createElement('div');"
184      << "big.setAttribute('style', 'position:absolute; left:100px; "
185                                    "top:0px; width:1px; height:5000px;');"
186      << "document.body.appendChild(big);"
187      << "window.scrollBy(0, " << original_rect.y() + clip_amount << ");";
188
189  instance_->EvalScript(script_stream.str());
190
191  pp::Rect desired_clip = original_clip;
192  desired_clip.set_y(clip_amount);
193  desired_clip.set_height(desired_clip.height() - desired_clip.y());
194
195  PP_Time begin_time = pp::Module::Get()->core()->GetTime();
196  while (WaitUntilViewChanged() && last_view_.GetClipRect() != desired_clip &&
197         pp::Module::Get()->core()->GetTime() - begin_time <
198             kViewChangeTimeoutSec) {
199  }
200  ASSERT_TRUE(last_view_.GetClipRect() == desired_clip);
201  PASS();
202}
203
204std::string TestView::TestScrollOffsetChange() {
205  instance_->EvalScript("document.body.style.width = '5000px';"
206                        "document.body.style.height = '5000px';");
207  instance_->EvalScript("window.scrollTo(5, 1);");
208
209  PP_Time begin_time = pp::Module::Get()->core()->GetTime();
210  while (WaitUntilViewChanged() &&
211         last_view_.GetScrollOffset() != pp::Point(5, 1) &&
212         pp::Module::Get()->core()->GetTime() - begin_time <
213             kViewChangeTimeoutSec) {
214  }
215  ASSERT_EQ(pp::Point(5, 1), last_view_.GetScrollOffset());
216
217  instance_->EvalScript("window.scrollTo(0, 0);");
218
219  begin_time = pp::Module::Get()->core()->GetTime();
220  while (WaitUntilViewChanged() &&
221         last_view_.GetScrollOffset() != pp::Point(0, 0) &&
222         pp::Module::Get()->core()->GetTime() - begin_time <
223             kViewChangeTimeoutSec) {
224  }
225  ASSERT_EQ(pp::Point(0, 0), last_view_.GetScrollOffset());
226
227  PASS();
228}
229