1// Copyright (c) 2012 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_fullscreen.h"
6
7#include <stdio.h>
8#include <string.h>
9#include <string>
10
11#include "ppapi/c/ppb_fullscreen.h"
12#include "ppapi/cpp/image_data.h"
13#include "ppapi/cpp/input_event.h"
14#include "ppapi/cpp/instance.h"
15#include "ppapi/cpp/module.h"
16#include "ppapi/cpp/point.h"
17#include "ppapi/tests/test_utils.h"
18#include "ppapi/tests/testing_instance.h"
19
20REGISTER_TEST_CASE(Fullscreen);
21
22namespace {
23
24const ColorPremul kSheerBlue = { 0x88, 0x00, 0x00, 0x88 };
25const ColorPremul kOpaqueYellow = { 0xFF, 0xFF, 0xFF, 0x00 };
26const int kBytesPerPixel = sizeof(uint32_t);  // 4 bytes for BGRA or RGBA.
27
28uint32_t FormatColor(PP_ImageDataFormat format, ColorPremul color) {
29  if (format == PP_IMAGEDATAFORMAT_BGRA_PREMUL)
30    return (color.A << 24) | (color.R << 16) | (color.G << 8) | (color.B);
31  else if (format == PP_IMAGEDATAFORMAT_RGBA_PREMUL)
32    return (color.A << 24) | (color.B << 16) | (color.G << 8) | (color.R);
33  else
34    return 0;
35}
36
37bool HasMidScreen(const pp::Rect& position, const pp::Size& screen_size) {
38  static int32_t mid_x = screen_size.width() / 2;
39  static int32_t mid_y = screen_size.height() / 2;
40  return (position.Contains(mid_x, mid_y));
41}
42
43void FlushCallbackCheckImageData(void* data, int32_t result) {
44  static_cast<TestFullscreen*>(data)->CheckPluginPaint();
45}
46
47}  // namespace
48
49TestFullscreen::TestFullscreen(TestingInstance* instance)
50    : TestCase(instance),
51      error_(),
52      screen_mode_(instance),
53      painted_color_(0),
54      fullscreen_pending_(false),
55      normal_pending_(false),
56      fullscreen_event_(instance->pp_instance()),
57      normal_event_(instance->pp_instance()) {
58  screen_mode_.GetScreenSize(&screen_size_);
59}
60
61bool TestFullscreen::Init() {
62  if (screen_size_.IsEmpty()) {
63    instance_->AppendError("Failed to initialize screen_size_");
64    return false;
65  }
66  graphics2d_ = pp::Graphics2D(instance_, screen_size_, true);
67  if (!instance_->BindGraphics(graphics2d_)) {
68    instance_->AppendError("Failed to initialize graphics2d_");
69    return false;
70  }
71  return CheckTestingInterface();
72}
73
74void TestFullscreen::RunTests(const std::string& filter) {
75  RUN_TEST(GetScreenSize, filter);
76  RUN_TEST(NormalToFullscreenToNormal, filter);
77}
78
79bool TestFullscreen::GotError() {
80  return !error_.empty();
81}
82
83std::string TestFullscreen::Error() {
84  std::string last_error = error_;
85  error_.clear();
86  return last_error;
87}
88
89// TODO(polina): consider adding custom logic to JS for this test to
90// get screen.width and screen.height and postMessage those to this code,
91// so the dimensions can be checked exactly.
92std::string TestFullscreen::TestGetScreenSize() {
93  if (screen_size_.width() < 320 || screen_size_.width() > 2560)
94    return ReportError("screen_size.width()", screen_size_.width());
95  if (screen_size_.height() < 200 || screen_size_.height() > 2048)
96    return ReportError("screen_size.height()", screen_size_.height());
97  PASS();
98}
99
100std::string TestFullscreen::TestNormalToFullscreenToNormal() {
101  // 0. Start in normal mode.
102  if (screen_mode_.IsFullscreen())
103    return ReportError("IsFullscreen() at start", true);
104
105  // 1. Switch to fullscreen.
106  // This is only allowed within a context of a user gesture (e.g. mouse click).
107  if (screen_mode_.SetFullscreen(true))
108    return ReportError("SetFullscreen(true) outside of user gesture", true);
109  // Trigger another call to SetFullscreen(true) from HandleInputEvent().
110  // The transition is asynchronous and ends at the next DidChangeView().
111  instance_->RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
112  SimulateUserGesture();
113  // DidChangeView() will call the callback once in fullscreen mode.
114  fullscreen_event_.Wait();
115  if (GotError())
116    return Error();
117  if (fullscreen_pending_)
118    return "fullscreen_pending_ has not been reset";
119  if (!screen_mode_.IsFullscreen())
120    return ReportError("IsFullscreen() in fullscreen", false);
121
122  // 2. Stay in fullscreen. No change.
123  if (screen_mode_.SetFullscreen(true))
124    return ReportError("SetFullscreen(true) in fullscreen", true);
125  if (!screen_mode_.IsFullscreen())
126    return ReportError("IsFullscreen() in fullscreen^2", false);
127
128  // 3. Switch to normal.
129  // The transition is asynchronous and ends at DidChangeView().
130  // No graphics devices can be bound while in transition.
131  normal_pending_ = true;
132  if (!screen_mode_.SetFullscreen(false))
133    return ReportError("SetFullscreen(false) in fullscreen", false);
134  // DidChangeView() will signal once out of fullscreen mode.
135  normal_event_.Wait();
136  if (GotError())
137    return Error();
138  if (normal_pending_)
139    return "normal_pending_ has not been reset";
140  if (screen_mode_.IsFullscreen())
141    return ReportError("IsFullscreen() in normal", true);
142
143  // 4. Stay in normal. No change.
144  if (screen_mode_.SetFullscreen(false))
145    return ReportError("SetFullscreen(false) in normal", true);
146  if (screen_mode_.IsFullscreen())
147    return ReportError("IsFullscreen() in normal^2", true);
148
149  PASS();
150}
151
152void TestFullscreen::SimulateUserGesture() {
153  pp::Point plugin_center(
154      normal_position_.x() + normal_position_.width() / 2,
155      normal_position_.y() + normal_position_.height() / 2);
156  pp::Point mouse_movement;
157  pp::MouseInputEvent input_event(
158      instance_,
159      PP_INPUTEVENT_TYPE_MOUSEDOWN,
160      0,  // time_stamp
161      0,  // modifiers
162      PP_INPUTEVENT_MOUSEBUTTON_LEFT,
163      plugin_center,
164      1,  // click_count
165      mouse_movement);
166
167  testing_interface_->SimulateInputEvent(instance_->pp_instance(),
168                                         input_event.pp_resource());
169}
170
171void TestFullscreen::FailFullscreenTest(const std::string& error) {
172  error_ = error;
173  fullscreen_event_.Signal();
174}
175
176void TestFullscreen::FailNormalTest(const std::string& error) {
177  error_ = error;
178  normal_event_.Signal();
179}
180
181void TestFullscreen::PassFullscreenTest() {
182  fullscreen_event_.Signal();
183}
184
185void TestFullscreen::PassNormalTest() {
186  normal_event_.Signal();
187}
188
189// Transition to fullscreen can only happen when processing a user gesture.
190bool TestFullscreen::HandleInputEvent(const pp::InputEvent& event) {
191  // We only let mouse events through and only mouse clicks count.
192  if (event.GetType() != PP_INPUTEVENT_TYPE_MOUSEDOWN &&
193      event.GetType() != PP_INPUTEVENT_TYPE_MOUSEUP)
194    return false;
195  // We got the gesture. No need to handle any more events.
196  instance_->ClearInputEventRequest(PP_INPUTEVENT_CLASS_MOUSE);
197  if (screen_mode_.IsFullscreen()) {
198    FailFullscreenTest(
199        ReportError("IsFullscreen() before fullscreen transition", true));
200    return false;
201  }
202  fullscreen_pending_ = true;
203  if (!screen_mode_.SetFullscreen(true)) {
204    FailFullscreenTest(ReportError("SetFullscreen(true) in normal", false));
205    return false;
206  }
207  // DidChangeView() will complete the transition to fullscreen.
208  return false;
209}
210
211bool TestFullscreen::PaintPlugin(pp::Size size, ColorPremul color) {
212  painted_size_ = size;
213  PP_ImageDataFormat image_format = pp::ImageData::GetNativeImageDataFormat();
214  painted_color_ = FormatColor(image_format, color);
215  if (painted_color_ == 0)
216    return false;
217  pp::Point origin(0, 0);
218
219  pp::ImageData image(instance_, image_format, size, false);
220  if (image.is_null())
221    return false;
222  uint32_t* pixels = static_cast<uint32_t*>(image.data());
223  int num_pixels = image.stride() / kBytesPerPixel * image.size().height();
224  for (int i = 0; i < num_pixels; i++)
225    pixels[i] = painted_color_;
226  graphics2d_.PaintImageData(image, origin);
227  pp::CompletionCallback cc(FlushCallbackCheckImageData, this);
228  if (graphics2d_.Flush(cc) != PP_OK_COMPLETIONPENDING)
229    return false;
230
231  return true;
232}
233
234void TestFullscreen::CheckPluginPaint() {
235  PP_ImageDataFormat image_format = pp::ImageData::GetNativeImageDataFormat();
236  pp::ImageData readback(instance_, image_format, painted_size_, false);
237  pp::Point origin(0, 0);
238  if (readback.is_null() ||
239      PP_TRUE != testing_interface_->ReadImageData(graphics2d_.pp_resource(),
240                                                   readback.pp_resource(),
241                                                   &origin.pp_point())) {
242    error_ = "Can't read plugin image";
243    return;
244  }
245  for (int y = 0; y < painted_size_.height(); y++) {
246    for (int x = 0; x < painted_size_.width(); x++) {
247      uint32_t* readback_color = readback.GetAddr32(pp::Point(x, y));
248      if (painted_color_ != *readback_color) {
249        error_ = "Plugin image contains incorrect pixel value";
250        return;
251      }
252    }
253  }
254  if (screen_mode_.IsFullscreen())
255    PassFullscreenTest();
256  else
257    PassNormalTest();
258}
259
260// Transitions to/from fullscreen is asynchronous ending at DidChangeView.
261// The number of calls to DidChangeView during fullscreen / normal transitions
262// isn't specified by the API. The test waits until it the screen has
263// transitioned to the desired state.
264//
265// WebKit does not change the plugin size, but Pepper does explicitly set
266// it to screen width and height when SetFullscreen(true) is called and
267// resets it back when ViewChanged is received indicating that we exited
268// fullscreen.
269//
270// NOTE: The number of DidChangeView calls for <object> might be different.
271// TODO(bbudge) Figure out how to test that the plugin positon eventually
272// changes to normal_position_.
273void TestFullscreen::DidChangeView(const pp::View& view) {
274  pp::Rect position = view.GetRect();
275  pp::Rect clip = view.GetClipRect();
276
277  if (normal_position_.IsEmpty())
278    normal_position_ = position;
279
280  bool is_fullscreen = screen_mode_.IsFullscreen();
281  if (fullscreen_pending_ && is_fullscreen) {
282    fullscreen_pending_ = false;
283    if (!HasMidScreen(position, screen_size_))
284      FailFullscreenTest("DidChangeView is not in the middle of the screen");
285    else if (position.size() != screen_size_)
286      FailFullscreenTest("DidChangeView does not have screen size");
287    // NOTE: we cannot reliably test for clip size being equal to the screen
288    // because it might be affected by JS console, info bars, etc.
289    else if (!instance_->BindGraphics(graphics2d_))
290      FailFullscreenTest("Failed to BindGraphics() in fullscreen");
291    else if (!PaintPlugin(position.size(), kOpaqueYellow))
292      FailFullscreenTest("Failed to paint plugin image in fullscreen");
293  } else if (normal_pending_ && !is_fullscreen) {
294    normal_pending_ = false;
295    if (screen_mode_.IsFullscreen())
296      FailNormalTest("DidChangeview is in fullscreen");
297    else if (!instance_->BindGraphics(graphics2d_))
298      FailNormalTest("Failed to BindGraphics() in normal");
299    else if (!PaintPlugin(position.size(), kSheerBlue))
300      FailNormalTest("Failed to paint plugin image in normal");
301  }
302}
303