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