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)
15 typedef NPCocoaEvent WindowlessPluginTestEvent;
16 #else
17 typedef NPEvent WindowlessPluginTestEvent;
18 #endif
19
20 namespace NPAPIClient {
21
22 namespace {
23
24 // Remember the first plugin instance for tests involving multiple instances.
25 WindowlessPluginTest* g_other_instance = NULL;
26
OnFinishTest(void * data)27 void OnFinishTest(void* data) {
28 static_cast<WindowlessPluginTest*>(data)->SignalTestCompleted();
29 }
30
IsPaintEvent(WindowlessPluginTestEvent * np_event)31 bool 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
IsMouseUpEvent(WindowlessPluginTestEvent * np_event)42 bool 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)
IsWindowActivationEvent(WindowlessPluginTestEvent * np_event)54 bool IsWindowActivationEvent(WindowlessPluginTestEvent* np_event) {
55 return np_event->type == NPCocoaEventWindowFocusChanged &&
56 np_event->data.focus.hasFocus;
57 }
58 #endif
59
60 } // namespace
61
WindowlessPluginTest(NPP id,NPNetscapeFuncs * host_functions)62 WindowlessPluginTest::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
IsWindowless() const70 bool WindowlessPluginTest::IsWindowless() const {
71 return true;
72 }
73
New(uint16 mode,int16 argc,const char * argn[],const char * argv[],NPSavedData * saved)74 NPError 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
HandleEvent(void * event)88 int16 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
ExecuteScript(NPNetscapeFuncs * browser,NPP id,const std::string & script,NPVariant * result)162 NPError 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
ExecuteScriptDeleteInPaint(NPNetscapeFuncs * browser)184 void 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
ExecuteScriptResizeInPaint(NPNetscapeFuncs * browser)192 void WindowlessPluginTest::ExecuteScriptResizeInPaint(
193 NPNetscapeFuncs* browser) {
194 ExecuteScript(browser, id(), "ResizePluginWithinScript();", NULL);
195 }
196
MultipleInstanceSyncCalls(NPNetscapeFuncs * browser)197 void 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)
StringForPoint(int x,int y)207 std::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
ConvertPoint(NPNetscapeFuncs * browser)217 void 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