| /* |
| * Copyright 2008, The Android Open Source Project |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "android_graphics.h" |
| #include "Document.h" |
| #include "Element.h" |
| #include "Frame.h" |
| #include "PluginPackage.h" |
| #include "PluginView.h" |
| #include "PluginWidgetAndroid.h" |
| #include "ScrollView.h" |
| #include "SkANP.h" |
| #include "SkFlipPixelRef.h" |
| #include "SkString.h" |
| #include "WebViewCore.h" |
| #include "jni_utility.h" |
| |
| #define DEBUG_VISIBLE_RECTS 1 // temporary debug printfs and fixes |
| |
| PluginWidgetAndroid::PluginWidgetAndroid(WebCore::PluginView* view) |
| : m_pluginView(view) { |
| m_flipPixelRef = NULL; |
| m_core = NULL; |
| m_drawingModel = kBitmap_ANPDrawingModel; |
| m_eventFlags = 0; |
| m_pluginWindow = NULL; |
| m_requestedVisibleRectCount = 0; |
| m_requestedFrameRect.setEmpty(); |
| m_visibleDocRect.setEmpty(); |
| m_pluginBounds.setEmpty(); |
| m_hasFocus = false; |
| m_isFullScreen = false; |
| m_zoomLevel = 0; |
| m_webkitPlugin = NULL; |
| m_embeddedView = NULL; |
| } |
| |
| PluginWidgetAndroid::~PluginWidgetAndroid() { |
| if (m_core) { |
| m_core->removePlugin(this); |
| if (m_embeddedView) { |
| m_core->destroySurface(m_embeddedView); |
| } |
| } |
| |
| // cleanup any remaining JNI References |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| if (m_webkitPlugin) { |
| env->DeleteGlobalRef(m_webkitPlugin); |
| } |
| if (m_embeddedView) { |
| env->DeleteGlobalRef(m_embeddedView); |
| } |
| |
| m_flipPixelRef->safeUnref(); |
| } |
| |
| void PluginWidgetAndroid::init(android::WebViewCore* core) { |
| m_core = core; |
| m_core->addPlugin(this); |
| } |
| |
| jobject PluginWidgetAndroid::getJavaPluginInstance() { |
| if (m_webkitPlugin == NULL && m_core != NULL) { |
| |
| jobject tempObj = m_core->createPluginJavaInstance(m_pluginView->plugin()->path(), |
| m_pluginView->instance()); |
| if (tempObj) { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| m_webkitPlugin = env->NewGlobalRef(tempObj); |
| } |
| } |
| return m_webkitPlugin; |
| } |
| |
| static SkBitmap::Config computeConfig(bool isTransparent) { |
| return isTransparent ? SkBitmap::kARGB_8888_Config |
| : SkBitmap::kRGB_565_Config; |
| } |
| |
| void PluginWidgetAndroid::setWindow(NPWindow* window, bool isTransparent) { |
| |
| // store the reference locally for easy lookup |
| m_pluginWindow = window; |
| |
| // make a copy of the previous bounds |
| SkIRect oldPluginBounds = m_pluginBounds; |
| |
| // keep a local copy of the plugin bounds because the m_pluginWindow pointer |
| // gets updated values prior to this method being called |
| m_pluginBounds.set(m_pluginWindow->x, m_pluginWindow->y, |
| m_pluginWindow->x + m_pluginWindow->width, |
| m_pluginWindow->y + m_pluginWindow->height); |
| |
| if (m_drawingModel == kSurface_ANPDrawingModel) { |
| |
| IntPoint docPoint = frameToDocumentCoords(window->x, window->y); |
| |
| // if the surface exists check for changes and update accordingly |
| if (m_embeddedView && m_pluginBounds != oldPluginBounds) { |
| |
| m_core->updateSurface(m_embeddedView, docPoint.x(), docPoint.y(), |
| window->width, window->height); |
| |
| // if the surface does not exist then create a new surface |
| } else if(!m_embeddedView) { |
| jobject tempObj = m_core->createSurface(getJavaPluginInstance(), |
| docPoint.x(), docPoint.y(), |
| window->width, window->height); |
| if (tempObj) { |
| JNIEnv* env = JSC::Bindings::getJNIEnv(); |
| m_embeddedView = env->NewGlobalRef(tempObj); |
| } |
| } |
| } else { |
| m_flipPixelRef->safeUnref(); |
| m_flipPixelRef = new SkFlipPixelRef(computeConfig(isTransparent), |
| window->width, window->height); |
| } |
| } |
| |
| bool PluginWidgetAndroid::setDrawingModel(ANPDrawingModel model) { |
| m_drawingModel = model; |
| return true; |
| } |
| |
| void PluginWidgetAndroid::localToDocumentCoords(SkIRect* rect) const { |
| if (m_pluginWindow) { |
| IntPoint pluginDocCoords = frameToDocumentCoords(m_pluginWindow->x, |
| m_pluginWindow->y); |
| rect->offset(pluginDocCoords.x(), pluginDocCoords.y()); |
| } |
| } |
| |
| bool PluginWidgetAndroid::isDirty(SkIRect* rect) const { |
| // nothing to report if we haven't had setWindow() called yet |
| if (NULL == m_flipPixelRef) { |
| return false; |
| } |
| |
| const SkRegion& dirty = m_flipPixelRef->dirtyRgn(); |
| if (dirty.isEmpty()) { |
| return false; |
| } else { |
| if (rect) { |
| *rect = dirty.getBounds(); |
| } |
| return true; |
| } |
| } |
| |
| void PluginWidgetAndroid::inval(const WebCore::IntRect& rect, |
| bool signalRedraw) { |
| // nothing to do if we haven't had setWindow() called yet. m_flipPixelRef |
| // will also be null if this is a Surface model. |
| if (NULL == m_flipPixelRef) { |
| return; |
| } |
| |
| m_flipPixelRef->inval(rect); |
| |
| if (signalRedraw && m_flipPixelRef->isDirty()) { |
| m_core->invalPlugin(this); |
| } |
| } |
| |
| void PluginWidgetAndroid::draw(SkCanvas* canvas) { |
| if (NULL == m_flipPixelRef || !m_flipPixelRef->isDirty()) { |
| return; |
| } |
| |
| SkAutoFlipUpdate update(m_flipPixelRef); |
| const SkBitmap& bitmap = update.bitmap(); |
| const SkRegion& dirty = update.dirty(); |
| |
| ANPEvent event; |
| SkANP::InitEvent(&event, kDraw_ANPEventType); |
| |
| event.data.draw.model = m_drawingModel; |
| SkANP::SetRect(&event.data.draw.clip, dirty.getBounds()); |
| |
| switch (m_drawingModel) { |
| case kBitmap_ANPDrawingModel: { |
| WebCore::PluginPackage* pkg = m_pluginView->plugin(); |
| NPP instance = m_pluginView->instance(); |
| |
| if (SkANP::SetBitmap(&event.data.draw.data.bitmap, |
| bitmap) && |
| pkg->pluginFuncs()->event(instance, &event)) { |
| |
| if (canvas && m_pluginWindow) { |
| SkBitmap bm(bitmap); |
| bm.setPixelRef(m_flipPixelRef); |
| canvas->drawBitmap(bm, SkIntToScalar(m_pluginWindow->x), |
| SkIntToScalar(m_pluginWindow->y), NULL); |
| } |
| } |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| bool PluginWidgetAndroid::sendEvent(const ANPEvent& evt) { |
| WebCore::PluginPackage* pkg = m_pluginView->plugin(); |
| NPP instance = m_pluginView->instance(); |
| // "missing" plugins won't have these |
| if (pkg && instance) { |
| |
| // keep track of whether or not the plugin currently has focus |
| if (evt.eventType == kLifecycle_ANPEventType) { |
| if (evt.data.lifecycle.action == kLoseFocus_ANPLifecycleAction) |
| m_hasFocus = false; |
| else if (evt.data.lifecycle.action == kGainFocus_ANPLifecycleAction) |
| m_hasFocus = true; |
| } |
| |
| // make a localCopy since the actual plugin may not respect its constness, |
| // and so we don't want our caller to have its param modified |
| ANPEvent localCopy = evt; |
| return pkg->pluginFuncs()->event(instance, &localCopy); |
| } |
| return false; |
| } |
| |
| void PluginWidgetAndroid::updateEventFlags(ANPEventFlags flags) { |
| |
| // if there are no differences then immediately return |
| if (m_eventFlags == flags) { |
| return; |
| } |
| |
| Document* doc = m_pluginView->getParentFrame()->document(); |
| if((m_eventFlags ^ flags) & kTouch_ANPEventFlag) { |
| if(flags & kTouch_ANPEventFlag) |
| doc->addTouchEventListener(m_pluginView->getElement()); |
| else |
| doc->removeTouchEventListener(m_pluginView->getElement()); |
| } |
| |
| m_eventFlags = flags; |
| } |
| |
| bool PluginWidgetAndroid::isAcceptingEvent(ANPEventFlag flag) { |
| return m_eventFlags & flag; |
| } |
| |
| void PluginWidgetAndroid::setVisibleScreen(const ANPRectI& visibleDocRect, float zoom) { |
| #if DEBUG_VISIBLE_RECTS |
| SkDebugf("%s (%d,%d,%d,%d)", __FUNCTION__, visibleDocRect.left, |
| visibleDocRect.top, visibleDocRect.right, visibleDocRect.bottom); |
| #endif |
| // TODO update the bitmap size based on the zoom? (for kBitmap_ANPDrawingModel) |
| |
| int oldScreenW = m_visibleDocRect.width(); |
| int oldScreenH = m_visibleDocRect.height(); |
| |
| m_visibleDocRect.set(visibleDocRect.left, visibleDocRect.top, |
| visibleDocRect.right, visibleDocRect.bottom); |
| |
| int newScreenW = m_visibleDocRect.width(); |
| int newScreenH = m_visibleDocRect.height(); |
| |
| if (oldScreenW != newScreenW || oldScreenH != newScreenH) |
| computeVisibleFrameRect(); |
| } |
| |
| void PluginWidgetAndroid::setVisibleRects(const ANPRectI rects[], int32_t count) { |
| #if DEBUG_VISIBLE_RECTS |
| SkDebugf("%s count=%d", __FUNCTION__, count); |
| #endif |
| // ensure the count does not exceed our allocated space |
| if (count > MAX_REQUESTED_RECTS) |
| count = MAX_REQUESTED_RECTS; |
| |
| // store the values in member variables |
| m_requestedVisibleRectCount = count; |
| memcpy(m_requestedVisibleRect, rects, count * sizeof(rects[0])); |
| |
| #if DEBUG_VISIBLE_RECTS // FIXME: this fixes bad data from the plugin |
| // take it out once plugin supplies better data |
| for (int index = 0; index < count; index++) { |
| SkDebugf("%s [%d](%d,%d,%d,%d)", __FUNCTION__, index, |
| m_requestedVisibleRect[index].left, |
| m_requestedVisibleRect[index].top, |
| m_requestedVisibleRect[index].right, |
| m_requestedVisibleRect[index].bottom); |
| if (m_requestedVisibleRect[index].left == |
| m_requestedVisibleRect[index].right) { |
| m_requestedVisibleRect[index].right += 1; |
| } |
| if (m_requestedVisibleRect[index].top == |
| m_requestedVisibleRect[index].bottom) { |
| m_requestedVisibleRect[index].bottom += 1; |
| } |
| } |
| #endif |
| computeVisibleFrameRect(); |
| } |
| |
| void PluginWidgetAndroid::computeVisibleFrameRect() { |
| |
| // ensure the visibleDocRect has been set (i.e. not equal to zero) |
| if (m_visibleDocRect.isEmpty() || !m_pluginWindow) |
| return; |
| |
| // create a rect that will contain as many of the rects that will fit on screen |
| SkIRect visibleRect; |
| visibleRect.setEmpty(); |
| |
| for (int counter = 0; counter < m_requestedVisibleRectCount; counter++) { |
| |
| ANPRectI* rect = &m_requestedVisibleRect[counter]; |
| |
| // create skia rect for easier manipulation and convert it to frame coordinates |
| SkIRect pluginRect; |
| pluginRect.set(rect->left, rect->top, rect->right, rect->bottom); |
| pluginRect.offset(m_pluginWindow->x, m_pluginWindow->y); |
| |
| // ensure the rect falls within the plugin's bounds |
| if (!m_pluginBounds.contains(pluginRect)) { |
| #if DEBUG_VISIBLE_RECTS |
| SkDebugf("%s (%d,%d,%d,%d) !contain (%d,%d,%d,%d)", __FUNCTION__, |
| m_pluginBounds.fLeft, m_pluginBounds.fTop, |
| m_pluginBounds.fRight, m_pluginBounds.fBottom, |
| pluginRect.fLeft, pluginRect.fTop, |
| pluginRect.fRight, pluginRect.fBottom); |
| // FIXME: assume that the desired outcome is to clamp to the container |
| pluginRect.intersect(m_pluginBounds); |
| #endif |
| continue; |
| } |
| // combine this new rect with the higher priority rects |
| pluginRect.join(visibleRect); |
| |
| // check to see if the new rect fits within the screen bounds. If this |
| // is the highest priority rect then attempt to center even if it doesn't |
| // fit on the screen. |
| if (counter > 0 && (m_visibleDocRect.width() < pluginRect.width() || |
| m_visibleDocRect.height() < pluginRect.height())) |
| break; |
| |
| // set the new visible rect |
| visibleRect = pluginRect; |
| } |
| |
| m_requestedFrameRect = visibleRect; |
| scrollToVisibleFrameRect(); |
| } |
| |
| void PluginWidgetAndroid::scrollToVisibleFrameRect() { |
| |
| if (!m_hasFocus || m_requestedFrameRect.isEmpty() || m_visibleDocRect.isEmpty()) { |
| #if DEBUG_VISIBLE_RECTS |
| SkDebugf("%s call m_hasFocus=%d m_requestedFrameRect.isEmpty()=%d" |
| " m_visibleDocRect.isEmpty()=%d", __FUNCTION__, m_hasFocus, |
| m_requestedFrameRect.isEmpty(), m_visibleDocRect.isEmpty()); |
| #endif |
| return; |
| } |
| // if the entire rect is already visible then we don't need to scroll, which |
| // requires converting the m_requestedFrameRect from frame to doc coordinates |
| IntPoint pluginDocPoint = frameToDocumentCoords(m_requestedFrameRect.fLeft, |
| m_requestedFrameRect.fTop); |
| SkIRect requestedDocRect; |
| requestedDocRect.set(pluginDocPoint.x(), pluginDocPoint.y(), |
| pluginDocPoint.x() + m_requestedFrameRect.width(), |
| pluginDocPoint.y() + m_requestedFrameRect.height()); |
| |
| if (m_visibleDocRect.contains(requestedDocRect)) |
| return; |
| |
| // find the center of the visibleRect in document coordinates |
| int rectCenterX = requestedDocRect.fLeft + requestedDocRect.width()/2; |
| int rectCenterY = requestedDocRect.fTop + requestedDocRect.height()/2; |
| |
| // find document coordinates for center of the visible screen |
| int screenCenterX = m_visibleDocRect.fLeft + m_visibleDocRect.width()/2; |
| int screenCenterY = m_visibleDocRect.fTop + m_visibleDocRect.height()/2; |
| |
| //compute the delta of the two points |
| int deltaX = rectCenterX - screenCenterX; |
| int deltaY = rectCenterY - screenCenterY; |
| |
| ScrollView* scrollView = m_pluginView->parent(); |
| android::WebViewCore* core = android::WebViewCore::getWebViewCore(scrollView); |
| #if DEBUG_VISIBLE_RECTS |
| SkDebugf("%s call scrollBy (%d,%d)", __FUNCTION__, deltaX, deltaY); |
| #endif |
| core->scrollBy(deltaX, deltaY, true); |
| } |
| |
| IntPoint PluginWidgetAndroid::frameToDocumentCoords(int frameX, int frameY) const { |
| IntPoint docPoint = IntPoint(frameX, frameY); |
| |
| const ScrollView* currentScrollView = m_pluginView->parent(); |
| if (currentScrollView) { |
| const ScrollView* parentScrollView = currentScrollView->parent(); |
| while (parentScrollView) { |
| |
| docPoint.move(currentScrollView->x(), currentScrollView->y()); |
| |
| currentScrollView = parentScrollView; |
| parentScrollView = parentScrollView->parent(); |
| } |
| } |
| |
| return docPoint; |
| } |
| |
| void PluginWidgetAndroid::requestFullScreen() { |
| if (m_isFullScreen || !m_webkitPlugin) { |
| return; |
| } |
| |
| m_core->showFullScreenPlugin(m_webkitPlugin, m_pluginView->instance()); |
| m_isFullScreen = true; |
| } |
| |
| void PluginWidgetAndroid::exitFullScreen(bool pluginInitiated) { |
| if (m_isFullScreen && pluginInitiated) { |
| m_core->hideFullScreenPlugin(); |
| } |
| |
| m_isFullScreen = false; |
| } |