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_graphics_2d.h"
6
7#include <stdlib.h>
8#include <string.h>
9
10#include <set>
11
12#include "ppapi/c/pp_errors.h"
13#include "ppapi/c/ppb_graphics_2d.h"
14#include "ppapi/cpp/completion_callback.h"
15#include "ppapi/cpp/graphics_2d.h"
16#include "ppapi/cpp/graphics_3d.h"
17#include "ppapi/cpp/image_data.h"
18#include "ppapi/cpp/instance.h"
19#include "ppapi/cpp/module.h"
20#include "ppapi/cpp/rect.h"
21#include "ppapi/tests/test_utils.h"
22#include "ppapi/tests/testing_instance.h"
23
24REGISTER_TEST_CASE(Graphics2D);
25
26namespace {
27
28bool CanFlushContext(pp::Instance* instance, pp::Graphics2D* context) {
29  TestCompletionCallback callback(instance->pp_instance());
30  callback.WaitForResult(context->Flush(callback.GetCallback()));
31  return (callback.result() == PP_OK);
32}
33
34bool CanFlushContextC(pp::Instance* instance, PP_Resource graphics_2d,
35                      const PPB_Graphics2D_1_1* graphics_2d_if) {
36  TestCompletionCallback callback(instance->pp_instance());
37  callback.WaitForResult(graphics_2d_if->Flush(
38      graphics_2d, callback.GetCallback().pp_completion_callback()));
39  return (callback.result() == PP_OK);
40}
41
42}  // namespace
43
44TestGraphics2D::TestGraphics2D(TestingInstance* instance)
45  : TestCase(instance),
46    is_view_changed_(false),
47    post_quit_on_view_changed_(false) {
48}
49
50bool TestGraphics2D::Init() {
51  graphics_2d_interface_ = static_cast<const PPB_Graphics2D*>(
52      pp::Module::Get()->GetBrowserInterface(PPB_GRAPHICS_2D_INTERFACE_1_1));
53  image_data_interface_ = static_cast<const PPB_ImageData*>(
54      pp::Module::Get()->GetBrowserInterface(PPB_IMAGEDATA_INTERFACE_1_0));
55  return graphics_2d_interface_ && image_data_interface_ &&
56         CheckTestingInterface();
57}
58
59void TestGraphics2D::RunTests(const std::string& filter) {
60  RUN_TEST(InvalidResource, filter);
61  RUN_TEST(InvalidSize, filter);
62  RUN_TEST(Humongous, filter);
63  RUN_TEST(InitToZero, filter);
64  RUN_TEST(Describe, filter);
65  RUN_TEST(Scale, filter);
66  RUN_TEST_FORCEASYNC_AND_NOT(Paint, filter);
67  RUN_TEST_FORCEASYNC_AND_NOT(Scroll, filter);
68  RUN_TEST_FORCEASYNC_AND_NOT(Replace, filter);
69  RUN_TEST_FORCEASYNC_AND_NOT(Flush, filter);
70  RUN_TEST_FORCEASYNC_AND_NOT(FlushOffscreenUpdate, filter);
71  RUN_TEST(Dev, filter);
72  RUN_TEST(ReplaceContentsCaching, filter);
73  RUN_TEST(BindNull, filter);
74}
75
76void TestGraphics2D::QuitMessageLoop() {
77  testing_interface_->QuitMessageLoop(instance_->pp_instance());
78}
79
80bool TestGraphics2D::ReadImageData(const pp::Graphics2D& dc,
81                                   pp::ImageData* image,
82                                   const pp::Point& top_left) const {
83  return PP_ToBool(testing_interface_->ReadImageData(
84      dc.pp_resource(),
85      image->pp_resource(),
86      &top_left.pp_point()));
87}
88
89bool TestGraphics2D::IsDCUniformColor(const pp::Graphics2D& dc,
90                                      uint32_t color) const {
91  pp::ImageData readback(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
92                         dc.size(), false);
93  if (readback.is_null())
94    return false;
95  if (!ReadImageData(dc, &readback, pp::Point(0, 0)))
96    return false;
97  return IsSquareInImage(readback, 0, pp::Rect(dc.size()), color);
98}
99
100std::string TestGraphics2D::FlushAndWaitForDone(pp::Graphics2D* context) {
101  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
102  callback.WaitForResult(context->Flush(callback.GetCallback()));
103  CHECK_CALLBACK_BEHAVIOR(callback);
104  ASSERT_EQ(PP_OK, callback.result());
105  PASS();
106}
107
108void TestGraphics2D::FillRectInImage(pp::ImageData* image,
109                                     const pp::Rect& rect,
110                                     uint32_t color) const {
111  for (int y = rect.y(); y < rect.bottom(); y++) {
112    uint32_t* row = image->GetAddr32(pp::Point(rect.x(), y));
113    for (int pixel = 0; pixel < rect.width(); pixel++)
114      row[pixel] = color;
115  }
116}
117
118void TestGraphics2D::FillImageWithGradient(pp::ImageData* image) const {
119  for (int y = 0; y < image->size().height(); y++) {
120    uint32_t red = ((y * 256) / image->size().height()) & 0xFF;
121    for (int x = 0; x < image->size().width(); x++) {
122      uint32_t green = ((x * 256) / image->size().width()) & 0xFF;
123      uint32_t blue = ((red + green) / 2) & 0xFF;
124      uint32_t* pixel = image->GetAddr32(pp::Point(x, y));
125      *pixel = (blue << 24) | (green << 16) | (red << 8);
126    }
127  }
128}
129
130bool TestGraphics2D::CompareImages(const pp::ImageData& image1,
131                                   const pp::ImageData& image2) {
132  return CompareImageRect(
133      image1, pp::Rect(0, 0, image1.size().width(), image1.size().height()),
134      image2, pp::Rect(0, 0, image2.size().width(), image2.size().height()));
135}
136
137bool TestGraphics2D::CompareImageRect(const pp::ImageData& image1,
138                                      const pp::Rect& rc1,
139                                      const pp::ImageData& image2,
140                                      const pp::Rect& rc2) const {
141  if (rc1.width() != rc2.width() || rc1.height() != rc2.height())
142    return false;
143
144  for (int y = 0; y < rc1.height(); y++) {
145    for (int x = 0; x < rc1.width(); x++) {
146      if (*(image1.GetAddr32(pp::Point(rc1.x() + x, rc1.y() + y))) !=
147          *(image2.GetAddr32(pp::Point(rc2.x() + x, rc2.y() + y))))
148        return false;
149    }
150  }
151  return true;
152}
153
154bool TestGraphics2D::IsSquareInImage(const pp::ImageData& image_data,
155                                     uint32_t background_color,
156                                     const pp::Rect& square,
157                                     uint32_t square_color) const {
158  for (int y = 0; y < image_data.size().height(); y++) {
159    for (int x = 0; x < image_data.size().width(); x++) {
160      uint32_t pixel = *image_data.GetAddr32(pp::Point(x, y));
161      uint32_t desired_color;
162      if (square.Contains(x, y))
163        desired_color = square_color;
164      else
165        desired_color = background_color;
166      if (pixel != desired_color)
167        return false;
168    }
169  }
170  return true;
171}
172
173bool TestGraphics2D::IsSquareInDC(const pp::Graphics2D& dc,
174                                  uint32_t background_color,
175                                  const pp::Rect& square,
176                                  uint32_t square_color) const {
177  pp::ImageData readback(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
178                         dc.size(), false);
179  if (readback.is_null())
180    return false;
181  if (!ReadImageData(dc, &readback, pp::Point(0, 0)))
182    return false;
183  return IsSquareInImage(readback, background_color, square, square_color);
184}
185
186
187PP_Resource TestGraphics2D::ReplaceContentsAndReturnID(
188    pp::Graphics2D* dc,
189    const pp::Size& size) {
190  pp::ImageData image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL, size, true);
191
192  PP_Resource id = image.pp_resource();
193
194  dc->ReplaceContents(&image);
195  std::string result = FlushAndWaitForDone(dc);
196  if (!result.empty())
197    return 0;
198
199  return id;
200}
201
202// Test all the functions with an invalid handle. Most of these just check for
203// a crash since the browser don't return a value.
204std::string TestGraphics2D::TestInvalidResource() {
205  pp::Graphics2D null_context;
206  pp::ImageData image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
207                      pp::Size(16, 16), true);
208
209  // Describe.
210  PP_Size size;
211  PP_Bool opaque;
212  graphics_2d_interface_->Describe(image.pp_resource(), &size, &opaque);
213  graphics_2d_interface_->Describe(null_context.pp_resource(),
214                                   &size, &opaque);
215
216  // PaintImageData.
217  PP_Point zero_zero;
218  zero_zero.x = 0;
219  zero_zero.y = 0;
220  graphics_2d_interface_->PaintImageData(image.pp_resource(),
221                                         image.pp_resource(),
222                                         &zero_zero, NULL);
223  graphics_2d_interface_->PaintImageData(null_context.pp_resource(),
224                                         image.pp_resource(),
225                                         &zero_zero, NULL);
226
227  // Scroll.
228  PP_Point zero_ten;
229  zero_ten.x = 0;
230  zero_ten.y = 10;
231  graphics_2d_interface_->Scroll(image.pp_resource(), NULL, &zero_ten);
232  graphics_2d_interface_->Scroll(null_context.pp_resource(),
233                                 NULL, &zero_ten);
234
235  // ReplaceContents.
236  graphics_2d_interface_->ReplaceContents(image.pp_resource(),
237                                          image.pp_resource());
238  graphics_2d_interface_->ReplaceContents(null_context.pp_resource(),
239                                          image.pp_resource());
240
241  // Flush.
242  TestCompletionCallback cb(instance_->pp_instance(), PP_OPTIONAL);
243  cb.WaitForResult(
244      graphics_2d_interface_->Flush(image.pp_resource(),
245                                    cb.GetCallback().pp_completion_callback()));
246  ASSERT_EQ(PP_ERROR_BADRESOURCE, cb.result());
247  cb.WaitForResult(
248      graphics_2d_interface_->Flush(null_context.pp_resource(),
249                                    cb.GetCallback().pp_completion_callback()));
250  ASSERT_EQ(PP_ERROR_BADRESOURCE, cb.result());
251
252  // ReadImageData.
253  ASSERT_FALSE(testing_interface_->ReadImageData(image.pp_resource(),
254                                                 image.pp_resource(),
255                                                 &zero_zero));
256  ASSERT_FALSE(testing_interface_->ReadImageData(null_context.pp_resource(),
257                                                 image.pp_resource(),
258                                                 &zero_zero));
259
260  PASS();
261}
262
263std::string TestGraphics2D::TestInvalidSize() {
264  pp::Graphics2D a(instance_, pp::Size(16, 0), false);
265  ASSERT_FALSE(CanFlushContext(instance_, &a));
266
267  pp::Graphics2D b(instance_, pp::Size(0, 16), false);
268  ASSERT_FALSE(CanFlushContext(instance_, &b));
269
270  // Need to use the C API since pp::Size prevents negative sizes.
271  PP_Size size;
272  size.width = 16;
273  size.height = -16;
274  PP_Resource graphics = graphics_2d_interface_->Create(
275      instance_->pp_instance(), &size, PP_FALSE);
276  ASSERT_FALSE(CanFlushContextC(instance_, graphics, graphics_2d_interface_));
277  pp::Module::Get()->core()->ReleaseResource(graphics);
278
279  size.width = -16;
280  size.height = 16;
281  graphics = graphics_2d_interface_->Create(
282      instance_->pp_instance(), &size, PP_FALSE);
283  ASSERT_FALSE(CanFlushContextC(instance_, graphics, graphics_2d_interface_));
284  pp::Module::Get()->core()->ReleaseResource(graphics);
285
286  // Overflow to negative size
287  size.width = std::numeric_limits<int32_t>::max();
288  size.height = std::numeric_limits<int32_t>::max();
289  graphics = graphics_2d_interface_->Create(
290      instance_->pp_instance(), &size, PP_FALSE);
291  ASSERT_FALSE(CanFlushContextC(instance_, graphics, graphics_2d_interface_));
292  pp::Module::Get()->core()->ReleaseResource(graphics);
293
294  PASS();
295}
296
297std::string TestGraphics2D::TestHumongous() {
298  pp::Graphics2D a(instance_, pp::Size(100000, 100000), false);
299  ASSERT_FALSE(CanFlushContext(instance_, &a));
300  PASS();
301}
302
303std::string TestGraphics2D::TestInitToZero() {
304  const int w = 15, h = 17;
305  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
306  ASSERT_FALSE(dc.is_null());
307
308  // Make an image with nonzero data in it (so we can test that zeros were
309  // actually read versus ReadImageData being a NOP).
310  pp::ImageData image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
311                      pp::Size(w, h), true);
312  ASSERT_FALSE(image.is_null());
313  ASSERT_FALSE(image.size().IsEmpty());
314  memset(image.data(), 0xFF, image.stride() * image.size().height() * 4);
315
316  // Read out the initial data from the device & check.
317  ASSERT_TRUE(ReadImageData(dc, &image, pp::Point(0, 0)));
318  ASSERT_TRUE(IsSquareInImage(image, 0, pp::Rect(0, 0, w, h), 0));
319
320  PASS();
321}
322
323std::string TestGraphics2D::TestDescribe() {
324  const int w = 15, h = 17;
325  const bool always_opaque = (::rand() % 2 == 1);
326  pp::Graphics2D dc(instance_, pp::Size(w, h), always_opaque);
327  ASSERT_FALSE(dc.is_null());
328
329  PP_Size size;
330  size.width = -1;
331  size.height = -1;
332  PP_Bool is_always_opaque = PP_FALSE;
333  ASSERT_TRUE(graphics_2d_interface_->Describe(dc.pp_resource(), &size,
334                                               &is_always_opaque));
335  ASSERT_EQ(w, size.width);
336  ASSERT_EQ(h, size.height);
337  ASSERT_EQ(PP_FromBool(always_opaque), is_always_opaque);
338
339  PASS();
340}
341
342std::string TestGraphics2D::TestScale() {
343  // Tests GetScale/SetScale
344  const int w = 20, h = 16;
345  const float scale = 1.0f/2.0f;
346  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
347  ASSERT_FALSE(dc.is_null());
348  ASSERT_EQ(1.0,  dc.GetScale());
349  ASSERT_TRUE(dc.SetScale(scale));
350  ASSERT_EQ(scale, dc.GetScale());
351  // Try setting a few invalid scale factors. Ensure that we catch these errors
352  // and don't change the actual scale
353  ASSERT_FALSE(dc.SetScale(-1.0f));
354  ASSERT_FALSE(dc.SetScale(0.0f));
355  ASSERT_EQ(scale, dc.GetScale());
356
357  // Verify that the context has the specified number of pixels, despite the
358  // non-identity scale
359  PP_Size size;
360  size.width = -1;
361  size.height = -1;
362  PP_Bool is_always_opaque = PP_FALSE;
363  ASSERT_TRUE(graphics_2d_interface_->Describe(dc.pp_resource(), &size,
364                                               &is_always_opaque));
365  ASSERT_EQ(w, size.width);
366  ASSERT_EQ(h, size.height);
367  ASSERT_EQ(PP_FALSE, is_always_opaque);
368
369  PASS();
370}
371
372std::string TestGraphics2D::TestPaint() {
373  const int w = 15, h = 17;
374  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
375  ASSERT_FALSE(dc.is_null());
376
377  // Make sure the device background is 0.
378  ASSERT_TRUE(IsDCUniformColor(dc, 0));
379
380  // Fill the backing store with white.
381  const uint32_t background_color = 0xFFFFFFFF;
382  pp::ImageData background(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
383                           pp::Size(w, h), false);
384  FillRectInImage(&background, pp::Rect(0, 0, w, h), background_color);
385  dc.PaintImageData(background, pp::Point(0, 0));
386  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
387
388  // Make an image to paint with that's opaque white and enqueue a paint.
389  const int fill_w = 2, fill_h = 3;
390  pp::ImageData fill(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
391                     pp::Size(fill_w, fill_h), true);
392  ASSERT_FALSE(fill.is_null());
393  FillRectInImage(&fill, pp::Rect(fill.size()), background_color);
394  const int paint_x = 4, paint_y = 5;
395  dc.PaintImageData(fill, pp::Point(paint_x, paint_y));
396
397  // Validate that nothing has been actually painted.
398  ASSERT_TRUE(IsDCUniformColor(dc, background_color));
399
400  // The paint hasn't been flushed so we can still change the bitmap. Fill with
401  // 50% blue. This will also verify that the backing store is replaced
402  // with the contents rather than blended.
403  const uint32_t fill_color = 0x80000080;
404  FillRectInImage(&fill, pp::Rect(fill.size()), fill_color);
405  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
406
407  ASSERT_TRUE(IsSquareInDC(dc, background_color,
408                           pp::Rect(paint_x, paint_y, fill_w, fill_h),
409                           fill_color));
410
411  // Reset the DC to blank white & paint our image slightly off the buffer.
412  // This should succeed. We also try painting the same thing where the
413  // dirty rect falls outeside of the device, which should fail.
414  dc.PaintImageData(background, pp::Point(0, 0));
415  const int second_paint_x = -1, second_paint_y = -2;
416  dc.PaintImageData(fill, pp::Point(second_paint_x, second_paint_y));
417  dc.PaintImageData(fill, pp::Point(second_paint_x, second_paint_y),
418                    pp::Rect(-second_paint_x, -second_paint_y, 1, 1));
419  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
420
421  // Now we should have a little bit of the image peeking out the top left.
422  ASSERT_TRUE(IsSquareInDC(dc, background_color, pp::Rect(0, 0, 1, 1),
423                           fill_color));
424
425  // Now repaint that top left pixel by doing a subset of the source image.
426  pp::ImageData subset(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
427                       pp::Size(w, h), false);
428  uint32_t subset_color = 0x80808080;
429  const int subset_x = 2, subset_y = 1;
430  *subset.GetAddr32(pp::Point(subset_x, subset_y)) = subset_color;
431  dc.PaintImageData(subset, pp::Point(-subset_x, -subset_y),
432                    pp::Rect(subset_x, subset_y, 1, 1));
433  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
434  ASSERT_TRUE(IsSquareInDC(dc, background_color, pp::Rect(0, 0, 1, 1),
435                           subset_color));
436
437  PASS();
438}
439
440std::string TestGraphics2D::TestScroll() {
441  const int w = 115, h = 117;
442  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
443  ASSERT_FALSE(dc.is_null());
444  ASSERT_TRUE(instance_->BindGraphics(dc));
445
446  // Make sure the device background is 0.
447  ASSERT_TRUE(IsDCUniformColor(dc, 0));
448
449  const int image_width = 15, image_height = 23;
450  pp::ImageData test_image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
451                           pp::Size(image_width, image_height), false);
452  FillImageWithGradient(&test_image);
453  pp::ImageData no_image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
454                         pp::Size(image_width, image_height), false);
455  FillRectInImage(&no_image, pp::Rect(0, 0, image_width, image_height), 0);
456  pp::ImageData readback_image(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
457                               pp::Size(image_width, image_height), false);
458  pp::ImageData readback_scroll(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
459                                pp::Size(image_width, image_height), false);
460
461  ASSERT_EQ(pp::Size(image_width, image_height), test_image.size());
462
463  int image_x = 51, image_y = 72;
464  dc.PaintImageData(test_image, pp::Point(image_x, image_y));
465  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
466
467  // Test Case 1. Incorrect usage when scrolling image to a free space.
468  // The clip area is *not* the area to shift around within the graphics device
469  // by specified amount. It's the area to which the scroll is limited. So if
470  // the clip area is the size of the image and the amount points to free space,
471  // the scroll won't result in additional images.
472  int dx = -40, dy = -48;
473  int scroll_x = image_x + dx, scroll_y = image_y + dy;
474  pp::Rect clip(image_x, image_y, image_width, image_height);
475  dc.Scroll(clip, pp::Point(dx, dy));
476  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
477  ASSERT_TRUE(
478      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
479  ASSERT_TRUE(CompareImages(no_image, readback_scroll));
480
481  // Test Case 2.
482  // The amount is intended to place the image in the free space outside
483  // of the original, but the clip area extends beyond the graphics device area.
484  // This scroll is invalid and will be a noop.
485  scroll_x = 11, scroll_y = 24;
486  clip = pp::Rect(0, 0, w, h + 1);
487  dc.Scroll(clip, pp::Point(scroll_x - image_x, scroll_y - image_y));
488  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
489  ASSERT_TRUE(
490      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
491  ASSERT_TRUE(CompareImages(no_image, readback_scroll));
492
493  // Test Case 3.
494  // The amount is intended to place the image in the free space outside
495  // of the original, but the clip area does not cover the image,
496  // so there is nothing to scroll.
497  scroll_x = 11, scroll_y = 24;
498  clip = pp::Rect(0, 0, image_x, image_y);
499  dc.Scroll(clip, pp::Point(scroll_x - image_x, scroll_y - image_y));
500  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
501  ASSERT_TRUE(
502      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
503  ASSERT_TRUE(CompareImages(no_image, readback_scroll));
504
505  // Test Case 4.
506  // Same as TC3, but the clip covers part of the image.
507  // This part will be scrolled to the intended origin.
508  int part_w = image_width / 2, part_h = image_height / 2;
509  clip = pp::Rect(0, 0, image_x + part_w, image_y + part_h);
510  dc.Scroll(clip, pp::Point(scroll_x - image_x, scroll_y - image_y));
511  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
512  ASSERT_TRUE(
513      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
514  ASSERT_FALSE(CompareImages(test_image, readback_scroll));
515  pp::Rect part_rect(part_w, part_h);
516  ASSERT_TRUE(
517      CompareImageRect(test_image, part_rect, readback_scroll, part_rect));
518
519  // Test Case 5
520  // Same as TC3, but the clip area covers the entire image.
521  // It will be scrolled to the intended origin.
522  clip = pp::Rect(0, 0, image_x + image_width, image_y + image_height);
523  dc.Scroll(clip, pp::Point(scroll_x - image_x, scroll_y - image_y));
524  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
525  ASSERT_TRUE(
526      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
527  ASSERT_TRUE(CompareImages(test_image, readback_scroll));
528
529  // Note that the undefined area left by the scroll does not actually get
530  // cleared, so the original image is still there. This is not guaranteed and
531  // is not something for users to rely on, but we can test for this here, so
532  // we know when the underlying behavior changes.
533  ASSERT_TRUE(ReadImageData(dc, &readback_image, pp::Point(image_x, image_y)));
534  ASSERT_TRUE(CompareImages(test_image, readback_image));
535
536  // Test Case 6.
537  // Scroll image to an overlapping space. The clip area is limited
538  // to the image, so this will just modify its area.
539  dx = 6;
540  dy = 9;
541  scroll_x = image_x + dx;
542  scroll_y = image_y + dy;
543  clip = pp::Rect(image_x, image_y, image_width, image_height);
544  dc.Scroll(clip, pp::Point(dx, dy));
545  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
546  ASSERT_TRUE(ReadImageData(dc, &readback_image, pp::Point(image_x, image_y)));
547  ASSERT_FALSE(CompareImages(test_image, readback_image));
548  pp::Rect scroll_rect(image_width - dx, image_height - dy);
549  ASSERT_TRUE(
550      ReadImageData(dc, &readback_scroll, pp::Point(scroll_x, scroll_y)));
551  ASSERT_TRUE(
552      CompareImageRect(test_image, scroll_rect, readback_scroll, scroll_rect));
553
554  PASS();
555}
556
557std::string TestGraphics2D::TestReplace() {
558  const int w = 15, h = 17;
559  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
560  ASSERT_FALSE(dc.is_null());
561
562  // Replacing with a different size image should fail.
563  pp::ImageData weird_size(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
564                           pp::Size(w - 1, h), true);
565  ASSERT_FALSE(weird_size.is_null());
566  dc.ReplaceContents(&weird_size);
567
568  // Fill the background with blue but don't flush yet.
569  const uint32_t background_color = 0xFF0000FF;
570  pp::ImageData background(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
571                           pp::Size(w, h), true);
572  ASSERT_FALSE(background.is_null());
573  FillRectInImage(&background, pp::Rect(0, 0, w, h), background_color);
574  dc.PaintImageData(background, pp::Point(0, 0));
575
576  // Replace with a green background but don't flush yet.
577  const int32_t swapped_color = 0x00FF00FF;
578  pp::ImageData swapped(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
579                        pp::Size(w, h), true);
580  ASSERT_FALSE(swapped.is_null());
581  FillRectInImage(&swapped, pp::Rect(0, 0, w, h), swapped_color);
582  dc.ReplaceContents(&swapped);
583
584  // The background should be unchanged since we didn't flush yet.
585  ASSERT_TRUE(IsDCUniformColor(dc, 0));
586
587  // Test the C++ wrapper. The size of the swapped image should be reset.
588  ASSERT_TRUE(!swapped.pp_resource() && !swapped.size().width() &&
589              !swapped.size().height() && !swapped.data());
590
591  // Painting with the swapped image should fail.
592  dc.PaintImageData(swapped, pp::Point(0, 0));
593
594  // Flush and make sure the result is correct.
595  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
596
597  // The background should be green from the swapped image.
598  ASSERT_TRUE(IsDCUniformColor(dc, swapped_color));
599
600  PASS();
601}
602
603std::string TestGraphics2D::TestFlush() {
604  // Tests that synchronous flushes (NULL callback) fail on the main thread
605  // (which is the current one).
606  const int w = 15, h = 17;
607  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
608  ASSERT_FALSE(dc.is_null());
609
610  // Fill the background with blue but don't flush yet.
611  pp::ImageData background(instance_, PP_IMAGEDATAFORMAT_BGRA_PREMUL,
612                           pp::Size(w, h), true);
613  ASSERT_FALSE(background.is_null());
614  dc.PaintImageData(background, pp::Point(0, 0));
615
616  int32_t rv = dc.Flush(pp::BlockUntilComplete());
617  ASSERT_EQ(PP_ERROR_BLOCKS_MAIN_THREAD, rv);
618
619  // Test flushing with no operations still issues a callback.
620  // (This may also hang if the browser never issues the callback).
621  pp::Graphics2D dc_nopaints(instance_, pp::Size(w, h), false);
622  ASSERT_FALSE(dc.is_null());
623  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc_nopaints));
624
625  TestCompletionCallback callback_1(instance_->pp_instance(), callback_type());
626
627  // Test that multiple flushes fail if we don't get a callback in between.
628  rv = dc_nopaints.Flush(callback_1.GetCallback());
629  if (rv == PP_OK_COMPLETIONPENDING) {
630    // If the first flush completes asynchronously, then a second should fail.
631    TestCompletionCallback callback_2(instance_->pp_instance(),
632                                      callback_type());
633    callback_2.WaitForResult(dc_nopaints.Flush(callback_2.GetCallback()));
634    CHECK_CALLBACK_BEHAVIOR(callback_2);
635    ASSERT_EQ(PP_ERROR_INPROGRESS, callback_2.result());
636  }
637  callback_1.WaitForResult(rv);
638  ASSERT_EQ(PP_OK, callback_1.result());
639
640  PASS();
641}
642
643void TestGraphics2D::DidChangeView(const pp::View& view) {
644  if (post_quit_on_view_changed_) {
645    post_quit_on_view_changed_ = false;
646    is_view_changed_ = true;
647    testing_interface_->QuitMessageLoop(instance_->pp_instance());
648  }
649}
650
651void TestGraphics2D::ResetViewChangedState() {
652  is_view_changed_ = false;
653}
654
655bool TestGraphics2D::WaitUntilViewChanged() {
656  // Run a nested message loop. It will exit either on ViewChanged or if the
657  // timeout happens.
658
659  // If view changed before we have chance to run message loop, return directly.
660  if (is_view_changed_)
661    return true;
662
663  post_quit_on_view_changed_ = true;
664  testing_interface_->RunMessageLoop(instance_->pp_instance());
665  post_quit_on_view_changed_ = false;
666
667  return is_view_changed_;
668}
669
670std::string TestGraphics2D::TestFlushOffscreenUpdate() {
671  // Tests that callback of offscreen updates should be delayed.
672  const PP_Time kFlushDelaySec = 1. / 30;  // 30 fps
673  const int w = 80, h = 80;
674  pp::Graphics2D dc(instance_, pp::Size(w, h), true);
675  ASSERT_FALSE(dc.is_null());
676  ASSERT_TRUE(instance_->BindGraphics(dc));
677
678  // Squeeze from top until bottom half of plugin is out of screen.
679  ResetViewChangedState();
680  instance_->EvalScript(
681      "var big = document.createElement('div');"
682      "var offset = "
683      "    window.innerHeight - plugin.offsetTop - plugin.offsetHeight / 2;"
684      "big.setAttribute('id', 'big-div');"
685      "big.setAttribute('style', 'height: ' + offset + '; width: 100%;');"
686      "document.body.insertBefore(big, document.body.firstChild);");
687  ASSERT_TRUE(WaitUntilViewChanged());
688
689  // Allocate a red image chunk
690  pp::ImageData chunk(instance_, PP_IMAGEDATAFORMAT_RGBA_PREMUL,
691                      pp::Size(w/8, h/8), true);
692  ASSERT_FALSE(chunk.is_null());
693  const uint32_t kRed = 0xff0000ff;
694  FillRectInImage(&chunk, pp::Rect(chunk.size()), kRed);
695
696  // Paint a invisable chunk, expecting Flush to invoke callback slowly.
697  dc.PaintImageData(chunk, pp::Point(0, h*0.75));
698
699  PP_Time begin = pp::Module::Get()->core()->GetTime();
700  ASSERT_SUBTEST_SUCCESS(FlushAndWaitForDone(&dc));
701  PP_Time actual_time_elapsed = pp::Module::Get()->core()->GetTime() - begin;
702  // Expect actual_time_elapsed >= kFlushDelaySec, but loose a bit to avoid
703  // precision issue.
704  ASSERT_GE(actual_time_elapsed, kFlushDelaySec * 0.9);
705
706  // Remove the padding on the top since test cases here isn't independent.
707  instance_->EvalScript(
708      "var big = document.getElementById('big-div');"
709      "big.parentNode.removeChild(big);");
710  ResetViewChangedState();
711  ASSERT_TRUE(WaitUntilViewChanged());
712
713  PASS();
714}
715
716std::string TestGraphics2D::TestDev() {
717  // Tests GetScale/SetScale via the Graphics2D_Dev C++ wrapper
718  const int w = 20, h = 16;
719  const float scale = 1.0f/2.0f;
720  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
721  ASSERT_FALSE(dc.is_null());
722  ASSERT_EQ(1.0f, dc.GetScale());
723  ASSERT_TRUE(dc.SetScale(scale));
724  ASSERT_EQ(scale, dc.GetScale());
725  // Try setting a few invalid scale factors. Ensure that we catch these errors
726  // and don't change the actual scale
727  ASSERT_FALSE(dc.SetScale(-1.0f));
728  ASSERT_FALSE(dc.SetScale(0.0f));
729  ASSERT_EQ(scale, dc.GetScale());
730
731  // Verify that the context has the specified number of pixels, despite the
732  // non-identity scale
733  PP_Size size;
734  size.width = -1;
735  size.height = -1;
736  PP_Bool is_always_opaque = PP_FALSE;
737  ASSERT_TRUE(graphics_2d_interface_->Describe(dc.pp_resource(), &size,
738                                               &is_always_opaque));
739  ASSERT_EQ(w, size.width);
740  ASSERT_EQ(h, size.height);
741  ASSERT_EQ(PP_FALSE, is_always_opaque);
742
743  PASS();
744}
745
746// This test makes sure that the out-of-process image data caching works as
747// expected. Doing ReplaceContents quickly should re-use the image data from
748// older ones.
749std::string TestGraphics2D::TestReplaceContentsCaching() {
750  // The cache is only active when running in the proxy, so skip it otherwise.
751  if (!testing_interface_->IsOutOfProcess())
752    PASS();
753
754  // Here we test resource IDs as a way to determine if the resource is being
755  // cached and re-used. This is non-optimal since it's entirely possible
756  // (and maybe better) for the proxy to return new resource IDs for the
757  // re-used objects. Howevever, our current implementation does this so it is
758  // an easy thing to check for.
759  //
760  // You could check for the shared memory pointers getting re-used, but the
761  // OS is very likely to use the same memory location for a newly-mapped image
762  // if one was deleted, meaning that it could pass even if the cache is broken.
763  // This would then require that we add some address-re-use-preventing code
764  // which would be tricky.
765  std::set<PP_Resource> resources;
766
767  pp::Size size(16, 16);
768  pp::Graphics2D dc(instance_, size, false);
769
770  // Do two replace contentses, adding the old resource IDs to our map.
771  PP_Resource imageres = ReplaceContentsAndReturnID(&dc, size);
772  ASSERT_TRUE(imageres);
773  resources.insert(imageres);
774  imageres = ReplaceContentsAndReturnID(&dc, size);
775  ASSERT_TRUE(imageres);
776  resources.insert(imageres);
777
778  // Now doing more replace contents should re-use older IDs if the cache is
779  // working.
780  imageres = ReplaceContentsAndReturnID(&dc, size);
781  ASSERT_TRUE(resources.find(imageres) != resources.end());
782  imageres = ReplaceContentsAndReturnID(&dc, size);
783  ASSERT_TRUE(resources.find(imageres) != resources.end());
784
785  PASS();
786}
787
788std::string TestGraphics2D::TestBindNull() {
789  // Binding a null resource is not an error, it should clear all bound
790  // resources. We can't easily test what resource is bound, but we can test
791  // that this doesn't throw an error.
792  ASSERT_TRUE(instance_->BindGraphics(pp::Graphics2D()));
793  ASSERT_TRUE(instance_->BindGraphics(pp::Graphics3D()));
794
795  const int w = 115, h = 117;
796  pp::Graphics2D dc(instance_, pp::Size(w, h), false);
797  ASSERT_FALSE(dc.is_null());
798  ASSERT_TRUE(instance_->BindGraphics(dc));
799
800  ASSERT_TRUE(instance_->BindGraphics(pp::Graphics2D()));
801  ASSERT_TRUE(instance_->BindGraphics(pp::Graphics3D()));
802
803  PASS();
804}
805
806