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#define STRSAFE_NO_DEPRECATE
6
7#include "content/test/plugin/plugin_windowless_test.h"
8
9#include "base/strings/string_number_conversions.h"
10#include "base/strings/string_util.h"
11#include "content/test/plugin/plugin_client.h"
12
13#if defined(TOOLKIT_GTK)
14#include <gdk/gdkx.h>
15#endif
16
17// NPEvent does not exist on the Mac.
18#if defined(OS_MACOSX)
19typedef NPCocoaEvent WindowlessPluginTestEvent;
20#else
21typedef NPEvent WindowlessPluginTestEvent;
22#endif
23
24namespace NPAPIClient {
25
26namespace {
27
28// Remember the first plugin instance for tests involving multiple instances.
29WindowlessPluginTest* g_other_instance = NULL;
30
31void OnFinishTest(void* data) {
32  static_cast<WindowlessPluginTest*>(data)->SignalTestCompleted();
33}
34
35bool IsPaintEvent(WindowlessPluginTestEvent* np_event) {
36#if defined(OS_WIN)
37  return np_event->event == WM_PAINT;
38#elif defined(OS_MACOSX)
39  return np_event->type == NPCocoaEventDrawRect;
40#elif defined(TOOLKIT_GTK)
41  return np_event->type == GraphicsExpose;
42#else
43  NOTIMPLEMENTED();
44  return false;
45#endif
46}
47
48bool IsMouseUpEvent(WindowlessPluginTestEvent* np_event) {
49#if defined(OS_WIN)
50  return np_event->event == WM_LBUTTONUP;
51#elif defined(OS_MACOSX)
52  return np_event->type == NPCocoaEventMouseUp;
53#else
54  NOTIMPLEMENTED();
55  return false;
56#endif
57}
58
59#if defined(OS_MACOSX)
60bool IsWindowActivationEvent(WindowlessPluginTestEvent* np_event) {
61  return np_event->type == NPCocoaEventWindowFocusChanged &&
62         np_event->data.focus.hasFocus;
63}
64#endif
65
66}  // namespace
67
68WindowlessPluginTest::WindowlessPluginTest(NPP id,
69                                           NPNetscapeFuncs *host_functions)
70    : PluginTest(id, host_functions),
71      paint_counter_(0) {
72  if (!g_other_instance)
73    g_other_instance = this;
74}
75
76bool WindowlessPluginTest::IsWindowless() const {
77  return true;
78}
79
80NPError WindowlessPluginTest::New(uint16 mode, int16 argc,
81                                 const char* argn[], const char* argv[],
82                                 NPSavedData* saved) {
83  NPError error = PluginTest::New(mode, argc, argn, argv, saved);
84
85  if (test_name() == "invoke_js_function_on_create") {
86    ExecuteScript(
87        NPAPIClient::PluginClient::HostFunctions(), g_other_instance->id(),
88        "PluginCreated();", NULL);
89  }
90
91  return error;
92}
93
94int16 WindowlessPluginTest::HandleEvent(void* event) {
95  NPNetscapeFuncs* browser = NPAPIClient::PluginClient::HostFunctions();
96
97  NPBool supports_windowless = 0;
98  NPError result = browser->getvalue(id(), NPNVSupportsWindowless,
99                                     &supports_windowless);
100  if ((result != NPERR_NO_ERROR) || (!supports_windowless)) {
101    SetError("Failed to read NPNVSupportsWindowless value");
102    SignalTestCompleted();
103    return PluginTest::HandleEvent(event);
104  }
105
106  WindowlessPluginTestEvent* np_event =
107      reinterpret_cast<WindowlessPluginTestEvent*>(event);
108  if (IsPaintEvent(np_event)) {
109    paint_counter_++;
110#if defined(OS_WIN)
111    HDC paint_dc = reinterpret_cast<HDC>(np_event->wParam);
112    if (paint_dc == NULL) {
113      SetError("Invalid Window DC passed to HandleEvent for WM_PAINT");
114      SignalTestCompleted();
115      return NPERR_GENERIC_ERROR;
116    }
117
118    HRGN clipping_region = CreateRectRgn(0, 0, 0, 0);
119    if (!GetClipRgn(paint_dc, clipping_region)) {
120      SetError("No clipping region set in window DC");
121      DeleteObject(clipping_region);
122      SignalTestCompleted();
123      return NPERR_GENERIC_ERROR;
124    }
125
126    DeleteObject(clipping_region);
127#endif
128
129    if (test_name() == "execute_script_delete_in_paint") {
130      ExecuteScriptDeleteInPaint(browser);
131    } else if (test_name() == "multiple_instances_sync_calls") {
132      MultipleInstanceSyncCalls(browser);
133    } else if (test_name() == "resize_during_paint") {
134      if (paint_counter_ == 1) {
135        // So that we get another paint later.
136        browser->invalidaterect(id(), NULL);
137      } else if (paint_counter_ == 2) {
138        // Do this in the second paint since that's asynchronous. The first
139        // paint will always be synchronous (since the renderer process doesn't
140        // have a cache of the plugin yet). If we try calling NPN_Evaluate while
141        // WebKit is painting, it will assert since style recalc is happening
142        // during painting.
143        ExecuteScriptResizeInPaint(browser);
144
145        // So that we can exit the test after the message loop is unrolled.
146        browser->pluginthreadasynccall(id(), OnFinishTest, this);
147      }
148    }
149#if defined(OS_MACOSX)
150  } else if (IsWindowActivationEvent(np_event) &&
151             test_name() == "convert_point") {
152      ConvertPoint(browser);
153#endif
154  } else if (IsMouseUpEvent(np_event) &&
155             test_name() == "execute_script_delete_in_mouse_up") {
156    ExecuteScript(browser, id(), "DeletePluginWithinScript();", NULL);
157    SignalTestCompleted();
158  } else if (IsMouseUpEvent(np_event) &&
159             test_name() == "delete_frame_test") {
160    ExecuteScript(
161        browser, id(),
162        "parent.document.getElementById('frame').outerHTML = ''", NULL);
163  }
164  // If this test failed, then we'd have crashed by now.
165  return PluginTest::HandleEvent(event);
166}
167
168NPError WindowlessPluginTest::ExecuteScript(NPNetscapeFuncs* browser, NPP id,
169    const std::string& script, NPVariant* result) {
170  std::string script_url = "javascript:";
171  script_url += script;
172
173  size_t script_length = script_url.length();
174  if (script_length != static_cast<uint32_t>(script_length)) {
175    return NPERR_GENERIC_ERROR;
176  }
177
178  NPString script_string = { script_url.c_str(),
179                             static_cast<uint32_t>(script_length) };
180  NPObject *window_obj = NULL;
181  browser->getvalue(id, NPNVWindowNPObject, &window_obj);
182
183  NPVariant unused_result;
184  if (!result)
185    result = &unused_result;
186
187  return browser->evaluate(id, window_obj, &script_string, result);
188}
189
190void WindowlessPluginTest::ExecuteScriptDeleteInPaint(
191    NPNetscapeFuncs* browser) {
192  const NPUTF8* urlString = "javascript:DeletePluginWithinScript()";
193  const NPUTF8* targetString = NULL;
194  browser->geturl(id(), urlString, targetString);
195  SignalTestCompleted();
196}
197
198void WindowlessPluginTest::ExecuteScriptResizeInPaint(
199    NPNetscapeFuncs* browser) {
200  ExecuteScript(browser, id(), "ResizePluginWithinScript();", NULL);
201}
202
203void WindowlessPluginTest::MultipleInstanceSyncCalls(NPNetscapeFuncs* browser) {
204  if (this == g_other_instance)
205    return;
206
207  DCHECK(g_other_instance);
208  ExecuteScript(browser, g_other_instance->id(), "TestCallback();", NULL);
209  SignalTestCompleted();
210}
211
212#if defined(OS_MACOSX)
213std::string StringForPoint(int x, int y) {
214  std::string point_string("(");
215  point_string.append(base::IntToString(x));
216  point_string.append(", ");
217  point_string.append(base::IntToString(y));
218  point_string.append(")");
219  return point_string;
220}
221#endif
222
223void WindowlessPluginTest::ConvertPoint(NPNetscapeFuncs* browser) {
224#if defined(OS_MACOSX)
225  // First, just sanity-test that round trips work.
226  NPCoordinateSpace spaces[] = { NPCoordinateSpacePlugin,
227                                 NPCoordinateSpaceWindow,
228                                 NPCoordinateSpaceFlippedWindow,
229                                 NPCoordinateSpaceScreen,
230                                 NPCoordinateSpaceFlippedScreen };
231  for (unsigned int i = 0; i < arraysize(spaces); ++i) {
232    for (unsigned int j = 0; j < arraysize(spaces); ++j) {
233      double x, y, round_trip_x, round_trip_y;
234      if (!(browser->convertpoint(id(), 0, 0, spaces[i], &x, &y, spaces[j])) ||
235          !(browser->convertpoint(id(), x, y, spaces[j], &round_trip_x,
236                                  &round_trip_y, spaces[i]))) {
237        SetError("Conversion failed");
238        SignalTestCompleted();
239        return;
240      }
241      if (i != j && x == 0 && y == 0) {
242        SetError("Converting a coordinate should change it");
243        SignalTestCompleted();
244        return;
245      }
246      if (round_trip_x != 0 || round_trip_y != 0) {
247        SetError("Round-trip conversion should return the original point");
248        SignalTestCompleted();
249        return;
250      }
251    }
252  }
253
254  // Now, more extensive testing on a single point.
255  double screen_x, screen_y;
256  browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin,
257                        &screen_x, &screen_y, NPCoordinateSpaceScreen);
258  double flipped_screen_x, flipped_screen_y;
259  browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin,
260                        &flipped_screen_x, &flipped_screen_y,
261                        NPCoordinateSpaceFlippedScreen);
262  double window_x, window_y;
263  browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin,
264                        &window_x, &window_y, NPCoordinateSpaceWindow);
265  double flipped_window_x, flipped_window_y;
266  browser->convertpoint(id(), 0, 0, NPCoordinateSpacePlugin,
267                        &flipped_window_x, &flipped_window_y,
268                        NPCoordinateSpaceFlippedWindow);
269
270  CGRect main_display_bounds = CGDisplayBounds(CGMainDisplayID());
271
272  // Check that all the coordinates are right. The constants below are based on
273  // the window frame set in the UI test and the content offset in the test
274  // html. Y-coordinates are not checked exactly so that the test is robust
275  // against toolbar changes, info and bookmark bar visibility, etc.
276  const int kWindowHeight = 400;
277  const int kWindowXOrigin = 50;
278  const int kWindowYOrigin = 50;
279  const int kPluginXContentOffset = 50;
280  const int kPluginYContentOffset = 50;
281  const int kChromeYTolerance = 200;
282
283  std::string error_string;
284  if (screen_x != flipped_screen_x)
285    error_string = "Flipping screen coordinates shouldn't change x";
286  else if (flipped_screen_y != main_display_bounds.size.height - screen_y)
287    error_string = "Flipped screen coordinates should be flipped vertically";
288  else if (screen_x != kWindowXOrigin + kPluginXContentOffset)
289    error_string = "Screen x location is wrong";
290  else if (flipped_screen_y < kWindowYOrigin + kPluginYContentOffset ||
291           flipped_screen_y > kWindowYOrigin + kPluginYContentOffset +
292                              kChromeYTolerance)
293    error_string = "Screen y location is wrong";
294  else if (window_x != flipped_window_x)
295    error_string = "Flipping window coordinates shouldn't change x";
296  else if (flipped_window_y != kWindowHeight - window_y)
297    error_string = "Flipped window coordinates should be flipped vertically";
298  else if (window_x != kPluginXContentOffset)
299    error_string = "Window x location is wrong";
300  else if (flipped_window_y < kPluginYContentOffset ||
301           flipped_window_y > kPluginYContentOffset + kChromeYTolerance)
302    error_string = "Window y location is wrong";
303
304  if (!error_string.empty()) {
305    error_string.append(" - ");
306    error_string.append(StringForPoint(screen_x, screen_y));
307    error_string.append(" - ");
308    error_string.append(StringForPoint(flipped_screen_x, flipped_screen_y));
309    error_string.append(" - ");
310    error_string.append(StringForPoint(window_x, window_y));
311    error_string.append(" - ");
312    error_string.append(StringForPoint(flipped_window_x, flipped_window_y));
313    SetError(error_string);
314  }
315#else
316  SetError("Unimplemented");
317#endif
318  SignalTestCompleted();
319}
320
321}  // namespace NPAPIClient
322