Home | History | Annotate | Download | only in plugin
      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)
     19 typedef NPCocoaEvent WindowlessPluginTestEvent;
     20 #else
     21 typedef NPEvent WindowlessPluginTestEvent;
     22 #endif
     23 
     24 namespace NPAPIClient {
     25 
     26 namespace {
     27 
     28 // Remember the first plugin instance for tests involving multiple instances.
     29 WindowlessPluginTest* g_other_instance = NULL;
     30 
     31 void OnFinishTest(void* data) {
     32   static_cast<WindowlessPluginTest*>(data)->SignalTestCompleted();
     33 }
     34 
     35 bool 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 
     48 bool 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)
     60 bool IsWindowActivationEvent(WindowlessPluginTestEvent* np_event) {
     61   return np_event->type == NPCocoaEventWindowFocusChanged &&
     62          np_event->data.focus.hasFocus;
     63 }
     64 #endif
     65 
     66 }  // namespace
     67 
     68 WindowlessPluginTest::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 
     76 bool WindowlessPluginTest::IsWindowless() const {
     77   return true;
     78 }
     79 
     80 NPError 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 
     94 int16 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 
    168 NPError 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 
    190 void 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 
    198 void WindowlessPluginTest::ExecuteScriptResizeInPaint(
    199     NPNetscapeFuncs* browser) {
    200   ExecuteScript(browser, id(), "ResizePluginWithinScript();", NULL);
    201 }
    202 
    203 void 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)
    213 std::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 
    223 void 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