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 // 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 
     27 void OnFinishTest(void* data) {
     28   static_cast<WindowlessPluginTest*>(data)->SignalTestCompleted();
     29 }
     30 
     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 
     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)
     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 
     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 
     70 bool WindowlessPluginTest::IsWindowless() const {
     71   return true;
     72 }
     73 
     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 
     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 
    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 
    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 
    192 void WindowlessPluginTest::ExecuteScriptResizeInPaint(
    193     NPNetscapeFuncs* browser) {
    194   ExecuteScript(browser, id(), "ResizePluginWithinScript();", NULL);
    195 }
    196 
    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)
    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 
    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