Home | History | Annotate | Download | only in plugins
      1 /*
      2  * Copyright 2008, The Android Open Source Project
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  *  * Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  *  * Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "PluginWidgetAndroid.h"
     28 
     29 #if ENABLE(TOUCH_EVENTS)
     30 #include "ChromeClient.h"
     31 #endif
     32 #include "Document.h"
     33 #include "Element.h"
     34 #include "Frame.h"
     35 #include "Page.h"
     36 #include "PluginPackage.h"
     37 #include "PluginView.h"
     38 #include "PluginWidgetAndroid.h"
     39 #include "ScrollView.h"
     40 #include "SkANP.h"
     41 #include "SkFlipPixelRef.h"
     42 #include "SkString.h"
     43 #include "SkTime.h"
     44 #include "WebViewCore.h"
     45 #include "android_graphics.h"
     46 #include <JNIUtility.h>
     47 
     48 // #define PLUGIN_DEBUG_LOCAL // controls the printing of log messages
     49 #define DEBUG_EVENTS 0 // logs event contents, return value, and processing time
     50 #define DEBUG_VISIBLE_RECTS 0 // temporary debug printfs and fixes
     51 
     52 // this include statement must follow the declaration of PLUGIN_DEBUG_LOCAL
     53 #include "PluginDebugAndroid.h"
     54 
     55 PluginWidgetAndroid::PluginWidgetAndroid(WebCore::PluginView* view)
     56         : m_pluginView(view) {
     57     m_flipPixelRef = NULL;
     58     m_core = NULL;
     59     m_drawingModel = kBitmap_ANPDrawingModel;
     60     m_eventFlags = 0;
     61     m_pluginWindow = NULL;
     62     m_requestedVisibleRectCount = 0;
     63     m_requestedVisibleRect.setEmpty();
     64     m_visibleDocRect.setEmpty();
     65     m_pluginBounds.setEmpty();
     66     m_hasFocus = false;
     67     m_isFullScreen = false;
     68     m_visible = true;
     69     m_zoomLevel = 0;
     70     m_embeddedView = NULL;
     71     m_embeddedViewAttached = false;
     72     m_acceptEvents = false;
     73     m_isSurfaceClippedOut = false;
     74 }
     75 
     76 PluginWidgetAndroid::~PluginWidgetAndroid() {
     77     PLUGIN_LOG("%p Deleting Plugin", m_pluginView->instance());
     78     m_acceptEvents = false;
     79     if (m_core) {
     80         m_core->removePlugin(this);
     81         if (m_isFullScreen) {
     82             exitFullScreen(true);
     83         }
     84         if (m_embeddedView) {
     85             m_core->destroySurface(m_embeddedView);
     86         }
     87     }
     88 
     89     // cleanup any remaining JNI References
     90     JNIEnv* env = JSC::Bindings::getJNIEnv();
     91     if (m_embeddedView) {
     92         env->DeleteGlobalRef(m_embeddedView);
     93     }
     94 
     95     m_flipPixelRef->safeUnref();
     96 }
     97 
     98 void PluginWidgetAndroid::init(android::WebViewCore* core) {
     99     m_core = core;
    100     m_core->addPlugin(this);
    101     m_acceptEvents = true;
    102     PLUGIN_LOG("%p Initialized Plugin", m_pluginView->instance());
    103 }
    104 
    105 static SkBitmap::Config computeConfig(bool isTransparent) {
    106     return isTransparent ? SkBitmap::kARGB_8888_Config
    107                          : SkBitmap::kRGB_565_Config;
    108 }
    109 
    110 void PluginWidgetAndroid::setWindow(NPWindow* window, bool isTransparent) {
    111 
    112     // store the reference locally for easy lookup
    113     m_pluginWindow = window;
    114 
    115     // make a copy of the previous bounds
    116     SkIRect oldPluginBounds = m_pluginBounds;
    117 
    118     // keep a local copy of the plugin bounds because the m_pluginWindow pointer
    119     // gets updated values prior to this method being called
    120     m_pluginBounds.set(m_pluginWindow->x, m_pluginWindow->y,
    121                        m_pluginWindow->x + m_pluginWindow->width,
    122                        m_pluginWindow->y + m_pluginWindow->height);
    123 
    124     PLUGIN_LOG("%p PluginBounds (%d,%d,%d,%d)", m_pluginView->instance(),
    125                m_pluginBounds.fLeft, m_pluginBounds.fTop,
    126                m_pluginBounds.fRight, m_pluginBounds.fBottom);
    127 
    128     layoutSurface(m_pluginBounds != oldPluginBounds);
    129 
    130     if (m_drawingModel != kSurface_ANPDrawingModel) {
    131         m_flipPixelRef->safeUnref();
    132         m_flipPixelRef = new SkFlipPixelRef(computeConfig(isTransparent),
    133                                             window->width, window->height);
    134     }
    135 }
    136 
    137 bool PluginWidgetAndroid::setDrawingModel(ANPDrawingModel model) {
    138     m_drawingModel = model;
    139     return true;
    140 }
    141 
    142 // returned rect is in the page coordinate
    143 bool PluginWidgetAndroid::isDirty(SkIRect* rect) const {
    144     // nothing to report if we haven't had setWindow() called yet
    145     if (NULL == m_flipPixelRef) {
    146         return false;
    147     }
    148 
    149     const SkRegion& dirty = m_flipPixelRef->dirtyRgn();
    150     if (dirty.isEmpty()) {
    151         return false;
    152     } else {
    153         if (rect) {
    154             *rect = dirty.getBounds();
    155             rect->offset(m_pluginWindow->x, m_pluginWindow->y);
    156         }
    157         return true;
    158     }
    159 }
    160 
    161 void PluginWidgetAndroid::inval(const WebCore::IntRect& rect,
    162                                 bool signalRedraw) {
    163     // nothing to do if we haven't had setWindow() called yet. m_flipPixelRef
    164     // will also be null if this is a Surface model.
    165     if (NULL == m_flipPixelRef) {
    166         return;
    167     }
    168 
    169     m_flipPixelRef->inval(rect);
    170 
    171     if (signalRedraw && m_flipPixelRef->isDirty()) {
    172         m_core->invalPlugin(this);
    173     }
    174 }
    175 
    176 void PluginWidgetAndroid::draw(SkCanvas* canvas) {
    177     if (NULL == m_flipPixelRef || !m_flipPixelRef->isDirty()) {
    178         return;
    179     }
    180 
    181     SkAutoFlipUpdate update(m_flipPixelRef);
    182     const SkBitmap& bitmap = update.bitmap();
    183     const SkRegion& dirty = update.dirty();
    184 
    185     ANPEvent    event;
    186     SkANP::InitEvent(&event, kDraw_ANPEventType);
    187 
    188     event.data.draw.model = m_drawingModel;
    189     SkANP::SetRect(&event.data.draw.clip, dirty.getBounds());
    190 
    191     switch (m_drawingModel) {
    192         case kBitmap_ANPDrawingModel: {
    193             WebCore::PluginPackage* pkg = m_pluginView->plugin();
    194             NPP instance = m_pluginView->instance();
    195 
    196             if (SkANP::SetBitmap(&event.data.draw.data.bitmap,
    197                                  bitmap) &&
    198                     pkg->pluginFuncs()->event(instance, &event)) {
    199 
    200                 if (canvas && m_pluginWindow) {
    201                     SkBitmap bm(bitmap);
    202                     bm.setPixelRef(m_flipPixelRef);
    203                     canvas->drawBitmap(bm, 0, 0);
    204                 }
    205             }
    206             break;
    207         }
    208         default:
    209             break;
    210     }
    211 }
    212 
    213 void PluginWidgetAndroid::setSurfaceClip(const SkIRect& clip) {
    214 
    215     if (m_drawingModel != kSurface_ANPDrawingModel)
    216         return;
    217 
    218     /* don't display surfaces that are either entirely clipped or only 1x1 in
    219        size. It appears that when an element is absolutely positioned and has
    220        been completely clipped in CSS that webkit still sends a clip of 1x1.
    221      */
    222     bool clippedOut = (clip.width() <= 1 && clip.height() <= 1);
    223     if(clippedOut != m_isSurfaceClippedOut) {
    224         m_isSurfaceClippedOut = clippedOut;
    225         layoutSurface();
    226     }
    227 }
    228 
    229 void PluginWidgetAndroid::layoutSurface(bool pluginBoundsChanged) {
    230 
    231     if (m_drawingModel != kSurface_ANPDrawingModel)
    232         return;
    233     if (!m_pluginWindow)
    234         return;
    235 
    236 
    237     bool displayPlugin = m_pluginView->isVisible() && !m_isSurfaceClippedOut;
    238     PLUGIN_LOG("%p DisplayPlugin[%d] visible=[%d] clipped=[%d]",
    239             m_pluginView->instance(), displayPlugin,
    240             m_pluginView->isVisible(), m_isSurfaceClippedOut);
    241 
    242     // if the surface does not exist then create a new surface
    243     if (!m_embeddedView && displayPlugin) {
    244 
    245         WebCore::PluginPackage* pkg = m_pluginView->plugin();
    246         NPP instance = m_pluginView->instance();
    247 
    248         jobject pluginSurface;
    249         pkg->pluginFuncs()->getvalue(instance, kJavaSurface_ANPGetValue,
    250                                      static_cast<void*>(&pluginSurface));
    251 
    252         jobject tempObj = m_core->addSurface(pluginSurface,
    253                 m_pluginWindow->x, m_pluginWindow->y,
    254                 m_pluginWindow->width, m_pluginWindow->height);
    255 
    256         if (tempObj) {
    257             JNIEnv* env = JSC::Bindings::getJNIEnv();
    258             m_embeddedView = env->NewGlobalRef(tempObj);
    259             m_embeddedViewAttached = true;
    260         }
    261     // if the view is unattached but visible then attach it
    262     } else if (m_embeddedView && !m_embeddedViewAttached && displayPlugin && !m_isFullScreen) {
    263         m_core->updateSurface(m_embeddedView, m_pluginWindow->x, m_pluginWindow->y,
    264                               m_pluginWindow->width, m_pluginWindow->height);
    265         m_embeddedViewAttached = true;
    266     // if the view is attached but invisible then remove it
    267     } else if (m_embeddedView && m_embeddedViewAttached && !displayPlugin) {
    268         m_core->destroySurface(m_embeddedView);
    269         m_embeddedViewAttached = false;
    270     // if the plugin's bounds have changed and it's visible then update it
    271     } else if (pluginBoundsChanged && displayPlugin && !m_isFullScreen) {
    272         m_core->updateSurface(m_embeddedView, m_pluginWindow->x, m_pluginWindow->y,
    273                               m_pluginWindow->width, m_pluginWindow->height);
    274 
    275     }
    276 }
    277 
    278 int16 PluginWidgetAndroid::sendEvent(const ANPEvent& evt) {
    279     if (!m_acceptEvents)
    280         return 0;
    281     WebCore::PluginPackage* pkg = m_pluginView->plugin();
    282     NPP instance = m_pluginView->instance();
    283     // "missing" plugins won't have these
    284     if (pkg && instance) {
    285 
    286         // if the plugin is gaining focus then update our state now to allow
    287         // the plugin's event handler to perform actions that require focus
    288         if (evt.eventType == kLifecycle_ANPEventType &&
    289                 evt.data.lifecycle.action == kGainFocus_ANPLifecycleAction) {
    290             m_hasFocus = true;
    291         }
    292 
    293 #if DEBUG_EVENTS
    294         SkMSec startTime = SkTime::GetMSecs();
    295 #endif
    296 
    297         // make a localCopy since the actual plugin may not respect its constness,
    298         // and so we don't want our caller to have its param modified
    299         ANPEvent localCopy = evt;
    300         int16 result = pkg->pluginFuncs()->event(instance, &localCopy);
    301 
    302 #if DEBUG_EVENTS
    303         SkMSec endTime = SkTime::GetMSecs();
    304         PLUGIN_LOG_EVENT(instance, &evt, result, endTime - startTime);
    305 #endif
    306 
    307         // if the plugin is losing focus then delay the update of our state
    308         // until after we notify the plugin and allow them to perform actions
    309         // that may require focus
    310         if (evt.eventType == kLifecycle_ANPEventType &&
    311                 evt.data.lifecycle.action == kLoseFocus_ANPLifecycleAction) {
    312             m_hasFocus = false;
    313         }
    314 
    315         return result;
    316     }
    317     return 0;
    318 }
    319 
    320 void PluginWidgetAndroid::updateEventFlags(ANPEventFlags flags) {
    321 
    322     // if there are no differences then immediately return
    323     if (m_eventFlags == flags) {
    324         return;
    325     }
    326 
    327     Document* doc = m_pluginView->parentFrame()->document();
    328 #if ENABLE(TOUCH_EVENTS)
    329     if((m_eventFlags ^ flags) & kTouch_ANPEventFlag) {
    330         if (flags & kTouch_ANPEventFlag)
    331            doc->addListenerTypeIfNeeded(eventNames().touchstartEvent);
    332     }
    333 #endif
    334 
    335     m_eventFlags = flags;
    336 }
    337 
    338 bool PluginWidgetAndroid::isAcceptingEvent(ANPEventFlag flag) {
    339     return m_eventFlags & flag;
    340 }
    341 
    342 void PluginWidgetAndroid::setVisibleScreen(const ANPRectI& visibleDocRect, float zoom) {
    343 #if DEBUG_VISIBLE_RECTS
    344     PLUGIN_LOG("%s (%d,%d,%d,%d)[%f]", __FUNCTION__, visibleDocRect.left,
    345             visibleDocRect.top, visibleDocRect.right,
    346             visibleDocRect.bottom, zoom);
    347 #endif
    348     // TODO update the bitmap size based on the zoom? (for kBitmap_ANPDrawingModel)
    349 
    350     int oldScreenW = m_visibleDocRect.width();
    351     int oldScreenH = m_visibleDocRect.height();
    352 
    353     // make local copies of the parameters
    354     m_zoomLevel = zoom;
    355     m_visibleDocRect.set(visibleDocRect.left,
    356                          visibleDocRect.top,
    357                          visibleDocRect.right,
    358                          visibleDocRect.bottom);
    359 
    360     int newScreenW = m_visibleDocRect.width();
    361     int newScreenH = m_visibleDocRect.height();
    362 
    363     // if the screen dimensions have changed by more than 5 pixels in either
    364     // direction then recompute the plugin's visible rectangle
    365     if (abs(oldScreenW - newScreenW) > 5 || abs(oldScreenH - newScreenH) > 5) {
    366         PLUGIN_LOG("%s VisibleDoc old=[%d,%d] new=[%d,%d] ", __FUNCTION__,
    367                    oldScreenW, oldScreenH, newScreenW, newScreenH);
    368         computeVisiblePluginRect();
    369     }
    370 
    371     bool visible = SkIRect::Intersects(m_visibleDocRect, m_pluginBounds);
    372     if(m_visible != visible) {
    373 
    374 #if DEBUG_VISIBLE_RECTS
    375         PLUGIN_LOG("%p changeVisiblity[%d] pluginBounds(%d,%d,%d,%d)",
    376                    m_pluginView->instance(), visible,
    377                    m_pluginBounds.fLeft, m_pluginBounds.fTop,
    378                    m_pluginBounds.fRight, m_pluginBounds.fBottom);
    379 #endif
    380 
    381         // change the visibility
    382         m_visible = visible;
    383         // send the event
    384         ANPEvent event;
    385         SkANP::InitEvent(&event, kLifecycle_ANPEventType);
    386         event.data.lifecycle.action = visible ? kOnScreen_ANPLifecycleAction
    387                                               : kOffScreen_ANPLifecycleAction;
    388         sendEvent(event);
    389     }
    390 }
    391 
    392 void PluginWidgetAndroid::setVisibleRects(const ANPRectI rects[], int32_t count) {
    393 #if DEBUG_VISIBLE_RECTS
    394     PLUGIN_LOG("%s count=%d", __FUNCTION__, count);
    395 #endif
    396     // ensure the count does not exceed our allocated space
    397     if (count > MAX_REQUESTED_RECTS)
    398         count = MAX_REQUESTED_RECTS;
    399 
    400     // store the values in member variables
    401     m_requestedVisibleRectCount = count;
    402     memcpy(m_requestedVisibleRects, rects, count * sizeof(rects[0]));
    403 
    404 #if DEBUG_VISIBLE_RECTS // FIXME: this fixes bad data from the plugin
    405     // take it out once plugin supplies better data
    406     for (int index = 0; index < count; index++) {
    407         PLUGIN_LOG("%s [%d](%d,%d,%d,%d)", __FUNCTION__, index,
    408             m_requestedVisibleRects[index].left,
    409             m_requestedVisibleRects[index].top,
    410             m_requestedVisibleRects[index].right,
    411             m_requestedVisibleRects[index].bottom);
    412         if (m_requestedVisibleRects[index].left ==
    413                 m_requestedVisibleRects[index].right) {
    414             m_requestedVisibleRects[index].right += 1;
    415         }
    416         if (m_requestedVisibleRects[index].top ==
    417                 m_requestedVisibleRects[index].bottom) {
    418             m_requestedVisibleRects[index].bottom += 1;
    419         }
    420     }
    421 #endif
    422     computeVisiblePluginRect();
    423 }
    424 
    425 void PluginWidgetAndroid::computeVisiblePluginRect() {
    426 
    427     // ensure the visibleDocRect has been set (i.e. not equal to zero)
    428     if (m_visibleDocRect.isEmpty() || !m_pluginWindow || m_requestedVisibleRectCount < 1)
    429         return;
    430 
    431     // create a rect that will contain as many of the rects that will fit on screen
    432     SkIRect visibleRect;
    433     visibleRect.setEmpty();
    434 
    435     for (int counter = 0; counter < m_requestedVisibleRectCount; counter++) {
    436 
    437         ANPRectI* rect = &m_requestedVisibleRects[counter];
    438 
    439         // create skia rect for easier manipulation and convert it to page coordinates
    440         SkIRect pluginRect;
    441         pluginRect.set(rect->left, rect->top, rect->right, rect->bottom);
    442         pluginRect.offset(m_pluginWindow->x, m_pluginWindow->y);
    443 
    444         // ensure the rect falls within the plugin's bounds
    445         if (!m_pluginBounds.contains(pluginRect)) {
    446 #if DEBUG_VISIBLE_RECTS
    447             PLUGIN_LOG("%s (%d,%d,%d,%d) !contain (%d,%d,%d,%d)", __FUNCTION__,
    448                        m_pluginBounds.fLeft, m_pluginBounds.fTop,
    449                        m_pluginBounds.fRight, m_pluginBounds.fBottom,
    450                        pluginRect.fLeft, pluginRect.fTop,
    451                        pluginRect.fRight, pluginRect.fBottom);
    452             // assume that the desired outcome is to clamp to the container
    453             if (pluginRect.intersect(m_pluginBounds)) {
    454                 visibleRect = pluginRect;
    455             }
    456 #endif
    457             continue;
    458         }
    459 
    460         // combine this new rect with the higher priority rects
    461         pluginRect.join(visibleRect);
    462 
    463         // check to see if the new rect could be made to fit within the screen
    464         // bounds. If this is the highest priority rect then attempt to center
    465         // even if it doesn't fit on the screen.
    466         if (counter > 0 && (m_visibleDocRect.width() < pluginRect.width() ||
    467                             m_visibleDocRect.height() < pluginRect.height()))
    468           break;
    469 
    470         // set the new visible rect
    471         visibleRect = pluginRect;
    472     }
    473 
    474     m_requestedVisibleRect = visibleRect;
    475     scrollToVisiblePluginRect();
    476 }
    477 
    478 void PluginWidgetAndroid::scrollToVisiblePluginRect() {
    479 
    480     if (!m_hasFocus || m_requestedVisibleRect.isEmpty() || m_visibleDocRect.isEmpty()) {
    481 #if DEBUG_VISIBLE_RECTS
    482         PLUGIN_LOG("%s call m_hasFocus=%d m_requestedVisibleRect.isEmpty()=%d"
    483                 " m_visibleDocRect.isEmpty()=%d", __FUNCTION__, m_hasFocus,
    484                 m_requestedVisibleRect.isEmpty(), m_visibleDocRect.isEmpty());
    485 #endif
    486         return;
    487     }
    488     // if the entire rect is already visible then we don't need to scroll
    489     if (m_visibleDocRect.contains(m_requestedVisibleRect))
    490         return;
    491 
    492     // find the center of the visibleRect in document coordinates
    493     int rectCenterX = m_requestedVisibleRect.fLeft + m_requestedVisibleRect.width()/2;
    494     int rectCenterY = m_requestedVisibleRect.fTop + m_requestedVisibleRect.height()/2;
    495 
    496     // find document coordinates for center of the visible screen
    497     int visibleDocCenterX = m_visibleDocRect.fLeft + m_visibleDocRect.width()/2;
    498     int visibleDocCenterY = m_visibleDocRect.fTop + m_visibleDocRect.height()/2;
    499 
    500     //compute the delta of the two points and scale to screen coordinates
    501     int deltaX = rectCenterX - visibleDocCenterX;
    502     int deltaY = rectCenterY - visibleDocCenterY;
    503 
    504     ScrollView* scrollView = m_pluginView->parent();
    505     android::WebViewCore* core = android::WebViewCore::getWebViewCore(scrollView);
    506 #if DEBUG_VISIBLE_RECTS
    507     PLUGIN_LOG("%s call scrollBy (%d,%d)", __FUNCTION__, deltaX, deltaY);
    508 #endif
    509     core->scrollBy(deltaX, deltaY, true);
    510 }
    511 
    512 void PluginWidgetAndroid::requestFullScreen() {
    513     if (m_isFullScreen || !m_embeddedView) {
    514         return;
    515     }
    516 
    517     // send event to notify plugin of full screen change
    518     ANPEvent event;
    519     SkANP::InitEvent(&event, kLifecycle_ANPEventType);
    520     event.data.lifecycle.action = kEnterFullScreen_ANPLifecycleAction;
    521     sendEvent(event);
    522 
    523     // remove the embedded surface from the view hierarchy
    524     m_core->destroySurface(m_embeddedView);
    525 
    526     // add the full screen view
    527     m_core->showFullScreenPlugin(m_embeddedView, m_pluginView->instance());
    528     m_isFullScreen = true;
    529 }
    530 
    531 void PluginWidgetAndroid::exitFullScreen(bool pluginInitiated) {
    532     if (!m_isFullScreen || !m_embeddedView) {
    533         return;
    534     }
    535 
    536     // remove the full screen surface from the view hierarchy
    537     if (pluginInitiated) {
    538         m_core->hideFullScreenPlugin();
    539     }
    540 
    541     // add the embedded view back
    542     m_core->updateSurface(m_embeddedView, m_pluginWindow->x, m_pluginWindow->y,
    543                           m_pluginWindow->width, m_pluginWindow->height);
    544 
    545     // send event to notify plugin of full screen change
    546     ANPEvent event;
    547     SkANP::InitEvent(&event, kLifecycle_ANPEventType);
    548     event.data.lifecycle.action = kExitFullScreen_ANPLifecycleAction;
    549     sendEvent(event);
    550 
    551     m_isFullScreen = false;
    552 }
    553 
    554 void PluginWidgetAndroid::requestCenterFitZoom() {
    555     m_core->centerFitRect(m_pluginWindow->x, m_pluginWindow->y,
    556             m_pluginWindow->width, m_pluginWindow->height);
    557 }
    558 
    559