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