1/* 2 * Copyright (C) 2009 Google Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31#include "config.h" 32#include "WebPluginContainerImpl.h" 33 34#include "Chrome.h" 35#include "ChromeClientImpl.h" 36#include "PluginLayerChromium.h" 37#include "WebClipboard.h" 38#include "WebCursorInfo.h" 39#include "WebDataSourceImpl.h" 40#include "WebElement.h" 41#include "WebInputEvent.h" 42#include "WebInputEventConversion.h" 43#include "WebKit.h" 44#include "WebKitClient.h" 45#include "WebPlugin.h" 46#include "WebRect.h" 47#include "WebString.h" 48#include "WebURL.h" 49#include "WebURLError.h" 50#include "WebURLRequest.h" 51#include "WebVector.h" 52#include "WebViewImpl.h" 53#include "WrappedResourceResponse.h" 54 55#include "EventNames.h" 56#include "FocusController.h" 57#include "FormState.h" 58#include "Frame.h" 59#include "FrameLoadRequest.h" 60#include "FrameView.h" 61#include "GraphicsContext.h" 62#include "HostWindow.h" 63#include "HTMLFormElement.h" 64#include "HTMLNames.h" 65#include "HTMLPlugInElement.h" 66#include "IFrameShimSupport.h" 67#include "KeyboardCodes.h" 68#include "KeyboardEvent.h" 69#include "MouseEvent.h" 70#include "Page.h" 71#include "RenderBox.h" 72#include "ScrollView.h" 73#include "UserGestureIndicator.h" 74#include "WheelEvent.h" 75 76#if WEBKIT_USING_SKIA 77#include "PlatformContextSkia.h" 78#endif 79 80using namespace WebCore; 81 82namespace WebKit { 83 84// Public methods -------------------------------------------------------------- 85 86void WebPluginContainerImpl::setFrameRect(const IntRect& frameRect) 87{ 88 Widget::setFrameRect(frameRect); 89 reportGeometry(); 90} 91 92void WebPluginContainerImpl::paint(GraphicsContext* gc, const IntRect& damageRect) 93{ 94 if (gc->paintingDisabled()) 95 return; 96 97 if (!parent()) 98 return; 99 100 // Don't paint anything if the plugin doesn't intersect the damage rect. 101 if (!frameRect().intersects(damageRect)) 102 return; 103 104 gc->save(); 105 106 ASSERT(parent()->isFrameView()); 107 ScrollView* view = parent(); 108 109 // The plugin is positioned in window coordinates, so it needs to be painted 110 // in window coordinates. 111 IntPoint origin = view->windowToContents(IntPoint(0, 0)); 112 gc->translate(static_cast<float>(origin.x()), static_cast<float>(origin.y())); 113 114#if WEBKIT_USING_SKIA 115 WebCanvas* canvas = gc->platformContext()->canvas(); 116#elif WEBKIT_USING_CG 117 WebCanvas* canvas = gc->platformContext(); 118#endif 119 120 IntRect windowRect = 121 IntRect(view->contentsToWindow(damageRect.location()), damageRect.size()); 122 m_webPlugin->paint(canvas, windowRect); 123 124 gc->restore(); 125} 126 127void WebPluginContainerImpl::invalidateRect(const IntRect& rect) 128{ 129 if (!parent()) 130 return; 131 132 RenderBox* renderer = toRenderBox(m_element->renderer()); 133 134 IntRect dirtyRect = rect; 135 dirtyRect.move(renderer->borderLeft() + renderer->paddingLeft(), 136 renderer->borderTop() + renderer->paddingTop()); 137 renderer->repaintRectangle(dirtyRect); 138} 139 140void WebPluginContainerImpl::setFocus(bool focused) 141{ 142 Widget::setFocus(focused); 143 m_webPlugin->updateFocus(focused); 144} 145 146void WebPluginContainerImpl::show() 147{ 148 setSelfVisible(true); 149 m_webPlugin->updateVisibility(true); 150 151 Widget::show(); 152} 153 154void WebPluginContainerImpl::hide() 155{ 156 setSelfVisible(false); 157 m_webPlugin->updateVisibility(false); 158 159 Widget::hide(); 160} 161 162void WebPluginContainerImpl::handleEvent(Event* event) 163{ 164 if (!m_webPlugin->acceptsInputEvents()) 165 return; 166 167 RefPtr<WebPluginContainerImpl> protector(this); 168 // The events we pass are defined at: 169 // http://devedge-temp.mozilla.org/library/manuals/2002/plugin/1.0/structures5.html#1000000 170 // Don't take the documentation as truth, however. There are many cases 171 // where mozilla behaves differently than the spec. 172 if (event->isMouseEvent()) 173 handleMouseEvent(static_cast<MouseEvent*>(event)); 174 else if (event->isWheelEvent()) 175 handleWheelEvent(static_cast<WheelEvent*>(event)); 176 else if (event->isKeyboardEvent()) 177 handleKeyboardEvent(static_cast<KeyboardEvent*>(event)); 178 179 // FIXME: it would be cleaner if Widget::handleEvent returned true/false and 180 // HTMLPluginElement called setDefaultHandled or defaultEventHandler. 181 if (!event->defaultHandled()) 182 m_element->Node::defaultEventHandler(event); 183} 184 185void WebPluginContainerImpl::frameRectsChanged() 186{ 187 Widget::frameRectsChanged(); 188 reportGeometry(); 189} 190 191void WebPluginContainerImpl::widgetPositionsUpdated() 192{ 193 Widget::widgetPositionsUpdated(); 194 reportGeometry(); 195} 196 197void WebPluginContainerImpl::setParentVisible(bool parentVisible) 198{ 199 // We override this function to make sure that geometry updates are sent 200 // over to the plugin. For e.g. when a plugin is instantiated it does not 201 // have a valid parent. As a result the first geometry update from webkit 202 // is ignored. This function is called when the plugin eventually gets a 203 // parent. 204 205 if (isParentVisible() == parentVisible) 206 return; // No change. 207 208 Widget::setParentVisible(parentVisible); 209 if (!isSelfVisible()) 210 return; // This widget has explicitely been marked as not visible. 211 212 m_webPlugin->updateVisibility(isVisible()); 213} 214 215void WebPluginContainerImpl::setParent(ScrollView* view) 216{ 217 // We override this function so that if the plugin is windowed, we can call 218 // NPP_SetWindow at the first possible moment. This ensures that 219 // NPP_SetWindow is called before the manual load data is sent to a plugin. 220 // If this order is reversed, Flash won't load videos. 221 222 Widget::setParent(view); 223 if (view) 224 reportGeometry(); 225} 226 227bool WebPluginContainerImpl::supportsPaginatedPrint() const 228{ 229 return m_webPlugin->supportsPaginatedPrint(); 230} 231 232int WebPluginContainerImpl::printBegin(const IntRect& printableArea, 233 int printerDPI) const 234{ 235 return m_webPlugin->printBegin(printableArea, printerDPI); 236} 237 238bool WebPluginContainerImpl::printPage(int pageNumber, 239 WebCore::GraphicsContext* gc) 240{ 241 gc->save(); 242#if WEBKIT_USING_SKIA 243 WebCanvas* canvas = gc->platformContext()->canvas(); 244#elif WEBKIT_USING_CG 245 WebCanvas* canvas = gc->platformContext(); 246#endif 247 bool ret = m_webPlugin->printPage(pageNumber, canvas); 248 gc->restore(); 249 return ret; 250} 251 252void WebPluginContainerImpl::printEnd() 253{ 254 return m_webPlugin->printEnd(); 255} 256 257void WebPluginContainerImpl::copy() 258{ 259 if (!m_webPlugin->hasSelection()) 260 return; 261 262 webKitClient()->clipboard()->writeHTML(m_webPlugin->selectionAsMarkup(), WebURL(), m_webPlugin->selectionAsText(), false); 263} 264 265WebElement WebPluginContainerImpl::element() 266{ 267 return WebElement(m_element); 268} 269 270void WebPluginContainerImpl::invalidate() 271{ 272 Widget::invalidate(); 273} 274 275void WebPluginContainerImpl::invalidateRect(const WebRect& rect) 276{ 277 invalidateRect(static_cast<IntRect>(rect)); 278} 279 280void WebPluginContainerImpl::scrollRect(int dx, int dy, const WebRect& rect) 281{ 282 Widget* parentWidget = parent(); 283 if (parentWidget->isFrameView()) { 284 FrameView* parentFrameView = static_cast<FrameView*>(parentWidget); 285 if (!parentFrameView->isOverlapped()) { 286 IntRect damageRect = convertToContainingWindow(static_cast<IntRect>(rect)); 287 IntSize scrollDelta(dx, dy); 288 // scroll() only uses the second rectangle, clipRect, and ignores the first 289 // rectangle. 290 parent()->hostWindow()->scroll(scrollDelta, damageRect, damageRect); 291 return; 292 } 293 } 294 295 // Use slow scrolling instead. 296 invalidateRect(rect); 297} 298 299void WebPluginContainerImpl::reportGeometry() 300{ 301 if (!parent()) 302 return; 303 304 IntRect windowRect, clipRect; 305 Vector<IntRect> cutOutRects; 306 calculateGeometry(frameRect(), windowRect, clipRect, cutOutRects); 307 308 m_webPlugin->updateGeometry(windowRect, clipRect, cutOutRects, isVisible()); 309} 310 311void WebPluginContainerImpl::setBackingTextureId(unsigned id) 312{ 313#if USE(ACCELERATED_COMPOSITING) 314 unsigned currId = m_platformLayer->textureId(); 315 if (currId == id) 316 return; 317 318 m_platformLayer->setTextureId(id); 319 // If anyone of the IDs is zero we need to switch between hardware 320 // and software compositing. This is done by triggering a style recalc 321 // on the container element. 322 if (!(currId * id)) 323 m_element->setNeedsStyleRecalc(WebCore::SyntheticStyleChange); 324#endif 325} 326 327void WebPluginContainerImpl::commitBackingTexture() 328{ 329#if USE(ACCELERATED_COMPOSITING) 330 if (platformLayer()) 331 platformLayer()->setNeedsDisplay(); 332#endif 333} 334 335void WebPluginContainerImpl::clearScriptObjects() 336{ 337 Frame* frame = m_element->document()->frame(); 338 if (!frame) 339 return; 340 frame->script()->cleanupScriptObjectsForPlugin(this); 341} 342 343NPObject* WebPluginContainerImpl::scriptableObjectForElement() 344{ 345 return m_element->getNPObject(); 346} 347 348WebString WebPluginContainerImpl::executeScriptURL(const WebURL& url, bool popupsAllowed) 349{ 350 Frame* frame = m_element->document()->frame(); 351 if (!frame) 352 return WebString(); 353 354 const KURL& kurl = url; 355 ASSERT(kurl.protocolIs("javascript")); 356 357 String script = decodeURLEscapeSequences( 358 kurl.string().substring(strlen("javascript:"))); 359 360 ScriptValue result = frame->script()->executeScript(script, popupsAllowed); 361 362 // Failure is reported as a null string. 363 String resultStr; 364 result.getString(resultStr); 365 return resultStr; 366} 367 368void WebPluginContainerImpl::loadFrameRequest( 369 const WebURLRequest& request, const WebString& target, bool notifyNeeded, void* notifyData) 370{ 371 Frame* frame = m_element->document()->frame(); 372 if (!frame) 373 return; // FIXME: send a notification in this case? 374 375 if (notifyNeeded) { 376 // FIXME: This is a bit of hack to allow us to observe completion of 377 // our frame request. It would be better to evolve FrameLoader to 378 // support a completion callback instead. 379 WebPluginLoadObserver* observer = 380 new WebPluginLoadObserver(this, request.url(), notifyData); 381 m_pluginLoadObservers.append(observer); 382 WebDataSourceImpl::setNextPluginLoadObserver(observer); 383 } 384 385 FrameLoadRequest frameRequest(frame->document()->securityOrigin(), 386 request.toResourceRequest(), target); 387 388 UserGestureIndicator gestureIndicator(request.hasUserGesture() ? 389 DefinitelyProcessingUserGesture : DefinitelyNotProcessingUserGesture); 390 391 frame->loader()->loadFrameRequest( 392 frameRequest, 393 false, // lock history 394 false, // lock back forward list 395 0, // event 396 0, // form state 397 SendReferrer); 398} 399 400void WebPluginContainerImpl::zoomLevelChanged(double zoomLevel) 401{ 402 WebViewImpl* view = WebViewImpl::fromPage(m_element->document()->frame()->page()); 403 view->fullFramePluginZoomLevelChanged(zoomLevel); 404} 405 406void WebPluginContainerImpl::didReceiveResponse(const ResourceResponse& response) 407{ 408 // Make sure that the plugin receives window geometry before data, or else 409 // plugins misbehave. 410 frameRectsChanged(); 411 412 WrappedResourceResponse urlResponse(response); 413 m_webPlugin->didReceiveResponse(urlResponse); 414} 415 416void WebPluginContainerImpl::didReceiveData(const char *data, int dataLength) 417{ 418 m_webPlugin->didReceiveData(data, dataLength); 419} 420 421void WebPluginContainerImpl::didFinishLoading() 422{ 423 m_webPlugin->didFinishLoading(); 424} 425 426void WebPluginContainerImpl::didFailLoading(const ResourceError& error) 427{ 428 m_webPlugin->didFailLoading(error); 429} 430 431NPObject* WebPluginContainerImpl::scriptableObject() 432{ 433 return m_webPlugin->scriptableObject(); 434} 435 436void WebPluginContainerImpl::willDestroyPluginLoadObserver(WebPluginLoadObserver* observer) 437{ 438 size_t pos = m_pluginLoadObservers.find(observer); 439 if (pos == notFound) 440 return; 441 m_pluginLoadObservers.remove(pos); 442} 443 444#if USE(ACCELERATED_COMPOSITING) 445WebCore::LayerChromium* WebPluginContainerImpl::platformLayer() const 446{ 447 return m_platformLayer->textureId() ? m_platformLayer.get() : 0; 448} 449#endif 450 451// Private methods ------------------------------------------------------------- 452 453WebPluginContainerImpl::WebPluginContainerImpl(WebCore::HTMLPlugInElement* element, WebPlugin* webPlugin) 454 : WebCore::PluginViewBase(0) 455 , m_element(element) 456 , m_webPlugin(webPlugin) 457#if USE(ACCELERATED_COMPOSITING) 458 , m_platformLayer(PluginLayerChromium::create(0)) 459#endif 460{ 461} 462 463WebPluginContainerImpl::~WebPluginContainerImpl() 464{ 465 for (size_t i = 0; i < m_pluginLoadObservers.size(); ++i) 466 m_pluginLoadObservers[i]->clearPluginContainer(); 467 m_webPlugin->destroy(); 468} 469 470void WebPluginContainerImpl::handleMouseEvent(MouseEvent* event) 471{ 472 ASSERT(parent()->isFrameView()); 473 474 // We cache the parent FrameView here as the plugin widget could be deleted 475 // in the call to HandleEvent. See http://b/issue?id=1362948 476 FrameView* parentView = static_cast<FrameView*>(parent()); 477 478 WebMouseEventBuilder webEvent(this, *event); 479 if (webEvent.type == WebInputEvent::Undefined) 480 return; 481 482 if (event->type() == eventNames().mousedownEvent) { 483 Frame* containingFrame = parentView->frame(); 484 if (Page* currentPage = containingFrame->page()) 485 currentPage->focusController()->setFocusedNode(m_element, containingFrame); 486 else 487 containingFrame->document()->setFocusedNode(m_element); 488 } 489 490 WebCursorInfo cursorInfo; 491 if (m_webPlugin->handleInputEvent(webEvent, cursorInfo)) 492 event->setDefaultHandled(); 493 494 // A windowless plugin can change the cursor in response to a mouse move 495 // event. We need to reflect the changed cursor in the frame view as the 496 // mouse is moved in the boundaries of the windowless plugin. 497 Page* page = parentView->frame()->page(); 498 if (!page) 499 return; 500 ChromeClientImpl* chromeClient = 501 static_cast<ChromeClientImpl*>(page->chrome()->client()); 502 chromeClient->setCursorForPlugin(cursorInfo); 503} 504 505void WebPluginContainerImpl::handleWheelEvent(WheelEvent* event) 506{ 507 WebMouseWheelEventBuilder webEvent(this, *event); 508 if (webEvent.type == WebInputEvent::Undefined) 509 return; 510 511 WebCursorInfo cursorInfo; 512 if (m_webPlugin->handleInputEvent(webEvent, cursorInfo)) 513 event->setDefaultHandled(); 514} 515 516void WebPluginContainerImpl::handleKeyboardEvent(KeyboardEvent* event) 517{ 518 WebKeyboardEventBuilder webEvent(*event); 519 if (webEvent.type == WebInputEvent::Undefined) 520 return; 521 522 if (webEvent.type == WebInputEvent::KeyDown) { 523#if defined(OS_MACOSX) 524 if (webEvent.modifiers == WebInputEvent::MetaKey 525#else 526 if (webEvent.modifiers == WebInputEvent::ControlKey 527#endif 528 && webEvent.windowsKeyCode == VKEY_C 529 // Only copy if there's a selection, so that we only ever do this 530 // for Pepper plugins that support copying. Windowless NPAPI 531 // plugins will get the event as before. 532 && m_webPlugin->hasSelection()) { 533 copy(); 534 event->setDefaultHandled(); 535 return; 536 } 537 } 538 539 const WebInputEvent* currentInputEvent = WebViewImpl::currentInputEvent(); 540 541 // Copy stashed info over, and only copy here in order not to interfere 542 // the ctrl-c logic above. 543 if (currentInputEvent 544 && WebInputEvent::isKeyboardEventType(currentInputEvent->type)) { 545 webEvent.modifiers |= currentInputEvent->modifiers & 546 (WebInputEvent::CapsLockOn | WebInputEvent::NumLockOn); 547 } 548 549 WebCursorInfo cursorInfo; 550 if (m_webPlugin->handleInputEvent(webEvent, cursorInfo)) 551 event->setDefaultHandled(); 552} 553 554void WebPluginContainerImpl::calculateGeometry(const IntRect& frameRect, 555 IntRect& windowRect, 556 IntRect& clipRect, 557 Vector<IntRect>& cutOutRects) 558{ 559 windowRect = IntRect( 560 parent()->contentsToWindow(frameRect.location()), frameRect.size()); 561 562 // Calculate a clip-rect so that we don't overlap the scrollbars, etc. 563 clipRect = windowClipRect(); 564 clipRect.move(-windowRect.x(), -windowRect.y()); 565 566 getPluginOcclusions(m_element, this->parent(), frameRect, cutOutRects); 567 // Convert to the plugin position. 568 for (size_t i = 0; i < cutOutRects.size(); i++) 569 cutOutRects[i].move(-frameRect.x(), -frameRect.y()); 570} 571 572WebCore::IntRect WebPluginContainerImpl::windowClipRect() const 573{ 574 // Start by clipping to our bounds. 575 IntRect clipRect = 576 convertToContainingWindow(IntRect(0, 0, width(), height())); 577 578 // document()->renderer() can be 0 when we receive messages from the 579 // plugins while we are destroying a frame. 580 if (m_element->renderer()->document()->renderer()) { 581 // Take our element and get the clip rect from the enclosing layer and 582 // frame view. 583 RenderLayer* layer = m_element->renderer()->enclosingLayer(); 584 clipRect.intersect( 585 m_element->document()->view()->windowClipRectForLayer(layer, true)); 586 } 587 588 return clipRect; 589} 590 591} // namespace WebKit 592