1 /** 2 * Copyright (C) 1999 Lars Knoll (knoll (at) kde.org) 3 * (C) 1999 Antti Koivisto (koivisto (at) kde.org) 4 * (C) 2000 Stefan Schimanski (1Stein (at) gmx.de) 5 * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Library General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Library General Public License for more details. 16 * 17 * You should have received a copy of the GNU Library General Public License 18 * along with this library; see the file COPYING.LIB. If not, write to 19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20 * Boston, MA 02110-1301, USA. 21 */ 22 23 #include "config.h" 24 #include "core/html/HTMLPlugInElement.h" 25 26 #include "bindings/core/v8/ScriptController.h" 27 #include "bindings/core/v8/npruntime_impl.h" 28 #include "core/CSSPropertyNames.h" 29 #include "core/HTMLNames.h" 30 #include "core/dom/Document.h" 31 #include "core/dom/Node.h" 32 #include "core/dom/shadow/ShadowRoot.h" 33 #include "core/events/Event.h" 34 #include "core/frame/FrameView.h" 35 #include "core/frame/LocalFrame.h" 36 #include "core/frame/Settings.h" 37 #include "core/frame/csp/ContentSecurityPolicy.h" 38 #include "core/html/HTMLContentElement.h" 39 #include "core/html/HTMLImageLoader.h" 40 #include "core/html/PluginDocument.h" 41 #include "core/loader/FrameLoaderClient.h" 42 #include "core/page/EventHandler.h" 43 #include "core/page/Page.h" 44 #include "core/page/scrolling/ScrollingCoordinator.h" 45 #include "core/plugins/PluginView.h" 46 #include "core/rendering/RenderBlockFlow.h" 47 #include "core/rendering/RenderEmbeddedObject.h" 48 #include "core/rendering/RenderImage.h" 49 #include "core/rendering/RenderWidget.h" 50 #include "platform/Logging.h" 51 #include "platform/MIMETypeFromURL.h" 52 #include "platform/MIMETypeRegistry.h" 53 #include "platform/Widget.h" 54 #include "platform/plugins/PluginData.h" 55 56 namespace blink { 57 58 using namespace HTMLNames; 59 60 HTMLPlugInElement::HTMLPlugInElement(const QualifiedName& tagName, Document& doc, bool createdByParser, PreferPlugInsForImagesOption preferPlugInsForImagesOption) 61 : HTMLFrameOwnerElement(tagName, doc) 62 , m_isDelayingLoadEvent(false) 63 , m_NPObject(0) 64 , m_isCapturingMouseEvents(false) 65 // m_needsWidgetUpdate(!createdByParser) allows HTMLObjectElement to delay 66 // widget updates until after all children are parsed. For HTMLEmbedElement 67 // this delay is unnecessary, but it is simpler to make both classes share 68 // the same codepath in this class. 69 , m_needsWidgetUpdate(!createdByParser) 70 , m_shouldPreferPlugInsForImages(preferPlugInsForImagesOption == ShouldPreferPlugInsForImages) 71 , m_usePlaceholderContent(false) 72 { 73 setHasCustomStyleCallbacks(); 74 } 75 76 HTMLPlugInElement::~HTMLPlugInElement() 77 { 78 ASSERT(!m_pluginWrapper); // cleared in detach() 79 ASSERT(!m_isDelayingLoadEvent); 80 81 if (m_NPObject) { 82 _NPN_ReleaseObject(m_NPObject); 83 m_NPObject = 0; 84 } 85 } 86 87 void HTMLPlugInElement::trace(Visitor* visitor) 88 { 89 visitor->trace(m_imageLoader); 90 HTMLFrameOwnerElement::trace(visitor); 91 } 92 93 bool HTMLPlugInElement::canProcessDrag() const 94 { 95 return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->canProcessDrag(); 96 } 97 98 bool HTMLPlugInElement::willRespondToMouseClickEvents() 99 { 100 if (isDisabledFormControl()) 101 return false; 102 RenderObject* r = renderer(); 103 return r && (r->isEmbeddedObject() || r->isWidget()); 104 } 105 106 void HTMLPlugInElement::removeAllEventListeners() 107 { 108 HTMLFrameOwnerElement::removeAllEventListeners(); 109 if (RenderWidget* renderer = existingRenderWidget()) { 110 if (Widget* widget = renderer->widget()) 111 widget->eventListenersRemoved(); 112 } 113 } 114 115 void HTMLPlugInElement::didMoveToNewDocument(Document& oldDocument) 116 { 117 if (m_imageLoader) 118 m_imageLoader->elementDidMoveToNewDocument(); 119 HTMLFrameOwnerElement::didMoveToNewDocument(oldDocument); 120 } 121 122 void HTMLPlugInElement::attach(const AttachContext& context) 123 { 124 HTMLFrameOwnerElement::attach(context); 125 126 if (!renderer() || useFallbackContent()) 127 return; 128 129 if (isImageType()) { 130 if (!m_imageLoader) 131 m_imageLoader = HTMLImageLoader::create(this); 132 m_imageLoader->updateFromElement(); 133 } else if (needsWidgetUpdate() 134 && renderEmbeddedObject() 135 && !renderEmbeddedObject()->showsUnavailablePluginIndicator() 136 && !wouldLoadAsNetscapePlugin(m_url, m_serviceType) 137 && !m_isDelayingLoadEvent) { 138 m_isDelayingLoadEvent = true; 139 document().incrementLoadEventDelayCount(); 140 document().loadPluginsSoon(); 141 } 142 } 143 144 void HTMLPlugInElement::updateWidget() 145 { 146 RefPtrWillBeRawPtr<HTMLPlugInElement> protector(this); 147 updateWidgetInternal(); 148 if (m_isDelayingLoadEvent) { 149 m_isDelayingLoadEvent = false; 150 document().decrementLoadEventDelayCount(); 151 } 152 } 153 154 void HTMLPlugInElement::requestPluginCreationWithoutRendererIfPossible() 155 { 156 if (m_serviceType.isEmpty()) 157 return; 158 159 if (!document().frame() 160 || !document().frame()->loader().client()->canCreatePluginWithoutRenderer(m_serviceType)) 161 return; 162 163 if (renderer() && renderer()->isWidget()) 164 return; 165 166 createPluginWithoutRenderer(); 167 } 168 169 void HTMLPlugInElement::createPluginWithoutRenderer() 170 { 171 ASSERT(document().frame()->loader().client()->canCreatePluginWithoutRenderer(m_serviceType)); 172 173 KURL url; 174 Vector<String> paramNames; 175 Vector<String> paramValues; 176 177 paramNames.append("type"); 178 paramValues.append(m_serviceType); 179 180 bool useFallback = false; 181 loadPlugin(url, m_serviceType, paramNames, paramValues, useFallback, false); 182 } 183 184 bool HTMLPlugInElement::shouldAccelerate() const 185 { 186 if (Widget* widget = ownedWidget()) 187 return widget->isPluginView() && toPluginView(widget)->platformLayer(); 188 return false; 189 } 190 191 void HTMLPlugInElement::detach(const AttachContext& context) 192 { 193 // Update the widget the next time we attach (detaching destroys the plugin). 194 // FIXME: None of this "needsWidgetUpdate" related code looks right. 195 if (renderer() && !useFallbackContent()) 196 setNeedsWidgetUpdate(true); 197 if (m_isDelayingLoadEvent) { 198 m_isDelayingLoadEvent = false; 199 document().decrementLoadEventDelayCount(); 200 } 201 202 // Only try to persist a plugin widget we actually own. 203 Widget* plugin = ownedWidget(); 204 if (plugin && plugin->pluginShouldPersist()) 205 m_persistedPluginWidget = plugin; 206 #if ENABLE(OILPAN) 207 else if (plugin) 208 plugin->detach(); 209 #endif 210 resetInstance(); 211 // FIXME - is this next line necessary? 212 setWidget(nullptr); 213 214 if (m_isCapturingMouseEvents) { 215 if (LocalFrame* frame = document().frame()) 216 frame->eventHandler().setCapturingMouseEventsNode(nullptr); 217 m_isCapturingMouseEvents = false; 218 } 219 220 if (m_NPObject) { 221 _NPN_ReleaseObject(m_NPObject); 222 m_NPObject = 0; 223 } 224 225 HTMLFrameOwnerElement::detach(context); 226 } 227 228 RenderObject* HTMLPlugInElement::createRenderer(RenderStyle* style) 229 { 230 // Fallback content breaks the DOM->Renderer class relationship of this 231 // class and all superclasses because createObject won't necessarily 232 // return a RenderEmbeddedObject, RenderPart or even RenderWidget. 233 if (useFallbackContent()) 234 return RenderObject::createObject(this, style); 235 236 if (isImageType()) { 237 RenderImage* image = new RenderImage(this); 238 image->setImageResource(RenderImageResource::create()); 239 return image; 240 } 241 242 if (usePlaceholderContent()) 243 return new RenderBlockFlow(this); 244 245 return new RenderEmbeddedObject(this); 246 } 247 248 void HTMLPlugInElement::willRecalcStyle(StyleRecalcChange) 249 { 250 // FIXME: Why is this necessary? Manual re-attach is almost always wrong. 251 if (!useFallbackContent() && !usePlaceholderContent() && needsWidgetUpdate() && renderer() && !isImageType()) 252 reattach(); 253 } 254 255 void HTMLPlugInElement::finishParsingChildren() 256 { 257 HTMLFrameOwnerElement::finishParsingChildren(); 258 if (useFallbackContent()) 259 return; 260 261 setNeedsWidgetUpdate(true); 262 if (inDocument()) 263 setNeedsStyleRecalc(SubtreeStyleChange); 264 } 265 266 void HTMLPlugInElement::resetInstance() 267 { 268 m_pluginWrapper.clear(); 269 } 270 271 SharedPersistent<v8::Object>* HTMLPlugInElement::pluginWrapper() 272 { 273 LocalFrame* frame = document().frame(); 274 if (!frame) 275 return 0; 276 277 // If the host dynamically turns off JavaScript (or Java) we will still 278 // return the cached allocated Bindings::Instance. Not supporting this 279 // edge-case is OK. 280 if (!m_pluginWrapper) { 281 Widget* plugin; 282 283 if (m_persistedPluginWidget) 284 plugin = m_persistedPluginWidget.get(); 285 else 286 plugin = pluginWidget(); 287 288 if (plugin) 289 m_pluginWrapper = frame->script().createPluginWrapper(plugin); 290 } 291 return m_pluginWrapper.get(); 292 } 293 294 Widget* HTMLPlugInElement::pluginWidget() const 295 { 296 if (RenderWidget* renderWidget = renderWidgetForJSBindings()) 297 return renderWidget->widget(); 298 return 0; 299 } 300 301 bool HTMLPlugInElement::isPresentationAttribute(const QualifiedName& name) const 302 { 303 if (name == widthAttr || name == heightAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr) 304 return true; 305 return HTMLFrameOwnerElement::isPresentationAttribute(name); 306 } 307 308 void HTMLPlugInElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStylePropertySet* style) 309 { 310 if (name == widthAttr) { 311 addHTMLLengthToStyle(style, CSSPropertyWidth, value); 312 } else if (name == heightAttr) { 313 addHTMLLengthToStyle(style, CSSPropertyHeight, value); 314 } else if (name == vspaceAttr) { 315 addHTMLLengthToStyle(style, CSSPropertyMarginTop, value); 316 addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value); 317 } else if (name == hspaceAttr) { 318 addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value); 319 addHTMLLengthToStyle(style, CSSPropertyMarginRight, value); 320 } else if (name == alignAttr) { 321 applyAlignmentAttributeToStyle(value, style); 322 } else { 323 HTMLFrameOwnerElement::collectStyleForPresentationAttribute(name, value, style); 324 } 325 } 326 327 void HTMLPlugInElement::defaultEventHandler(Event* event) 328 { 329 // Firefox seems to use a fake event listener to dispatch events to plug-in 330 // (tested with mouse events only). This is observable via different order 331 // of events - in Firefox, event listeners specified in HTML attributes 332 // fires first, then an event gets dispatched to plug-in, and only then 333 // other event listeners fire. Hopefully, this difference does not matter in 334 // practice. 335 336 // FIXME: Mouse down and scroll events are passed down to plug-in via custom 337 // code in EventHandler; these code paths should be united. 338 339 RenderObject* r = renderer(); 340 if (!r || !r->isWidget()) 341 return; 342 if (r->isEmbeddedObject()) { 343 if (toRenderEmbeddedObject(r)->showsUnavailablePluginIndicator()) 344 return; 345 } 346 RefPtr<Widget> widget = toRenderWidget(r)->widget(); 347 if (!widget) 348 return; 349 widget->handleEvent(event); 350 if (event->defaultHandled()) 351 return; 352 HTMLFrameOwnerElement::defaultEventHandler(event); 353 } 354 355 RenderWidget* HTMLPlugInElement::renderWidgetForJSBindings() const 356 { 357 // Needs to load the plugin immediatedly because this function is called 358 // when JavaScript code accesses the plugin. 359 // FIXME: Check if dispatching events here is safe. 360 document().updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasksSynchronously); 361 return existingRenderWidget(); 362 } 363 364 bool HTMLPlugInElement::isKeyboardFocusable() const 365 { 366 if (!document().isActive()) 367 return false; 368 return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->supportsKeyboardFocus(); 369 } 370 371 bool HTMLPlugInElement::hasCustomFocusLogic() const 372 { 373 return !hasAuthorShadowRoot(); 374 } 375 376 bool HTMLPlugInElement::isPluginElement() const 377 { 378 return true; 379 } 380 381 bool HTMLPlugInElement::rendererIsFocusable() const 382 { 383 if (HTMLFrameOwnerElement::supportsFocus() && HTMLFrameOwnerElement::rendererIsFocusable()) 384 return true; 385 386 if (useFallbackContent() || !renderer() || !renderer()->isEmbeddedObject()) 387 return false; 388 return !toRenderEmbeddedObject(renderer())->showsUnavailablePluginIndicator(); 389 } 390 391 NPObject* HTMLPlugInElement::getNPObject() 392 { 393 ASSERT(document().frame()); 394 if (!m_NPObject) 395 m_NPObject = document().frame()->script().createScriptObjectForPluginElement(this); 396 return m_NPObject; 397 } 398 399 bool HTMLPlugInElement::isImageType() 400 { 401 if (m_serviceType.isEmpty() && protocolIs(m_url, "data")) 402 m_serviceType = mimeTypeFromDataURL(m_url); 403 404 if (LocalFrame* frame = document().frame()) { 405 KURL completedURL = document().completeURL(m_url); 406 return frame->loader().client()->objectContentType(completedURL, m_serviceType, shouldPreferPlugInsForImages()) == ObjectContentImage; 407 } 408 409 return Image::supportsType(m_serviceType); 410 } 411 412 RenderEmbeddedObject* HTMLPlugInElement::renderEmbeddedObject() const 413 { 414 // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers 415 // when using fallback content. 416 if (!renderer() || !renderer()->isEmbeddedObject()) 417 return 0; 418 return toRenderEmbeddedObject(renderer()); 419 } 420 421 // We don't use m_url, as it may not be the final URL that the object loads, 422 // depending on <param> values. 423 bool HTMLPlugInElement::allowedToLoadFrameURL(const String& url) 424 { 425 KURL completeURL = document().completeURL(url); 426 if (contentFrame() && protocolIsJavaScript(completeURL) 427 && !document().securityOrigin()->canAccess(contentDocument()->securityOrigin())) 428 return false; 429 return document().frame()->isURLAllowed(completeURL); 430 } 431 432 // We don't use m_url, or m_serviceType as they may not be the final values 433 // that <object> uses depending on <param> values. 434 bool HTMLPlugInElement::wouldLoadAsNetscapePlugin(const String& url, const String& serviceType) 435 { 436 ASSERT(document().frame()); 437 KURL completedURL; 438 if (!url.isEmpty()) 439 completedURL = document().completeURL(url); 440 return document().frame()->loader().client()->objectContentType(completedURL, serviceType, shouldPreferPlugInsForImages()) == ObjectContentNetscapePlugin; 441 } 442 443 bool HTMLPlugInElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues) 444 { 445 if (url.isEmpty() && mimeType.isEmpty()) 446 return false; 447 448 // FIXME: None of this code should use renderers! 449 RenderEmbeddedObject* renderer = renderEmbeddedObject(); 450 ASSERT(renderer); 451 if (!renderer) 452 return false; 453 454 KURL completedURL = document().completeURL(url); 455 if (!pluginIsLoadable(completedURL, mimeType)) 456 return false; 457 458 bool useFallback; 459 bool requireRenderer = true; 460 if (shouldUsePlugin(completedURL, mimeType, hasFallbackContent(), useFallback)) 461 return loadPlugin(completedURL, mimeType, paramNames, paramValues, useFallback, requireRenderer); 462 463 // If the plug-in element already contains a subframe, 464 // loadOrRedirectSubframe will re-use it. Otherwise, it will create a new 465 // frame and set it as the RenderPart's widget, causing what was previously 466 // in the widget to be torn down. 467 return loadOrRedirectSubframe(completedURL, getNameAttribute(), true); 468 } 469 470 bool HTMLPlugInElement::loadPlugin(const KURL& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues, bool useFallback, bool requireRenderer) 471 { 472 LocalFrame* frame = document().frame(); 473 474 if (!frame->loader().allowPlugins(AboutToInstantiatePlugin)) 475 return false; 476 477 RenderEmbeddedObject* renderer = renderEmbeddedObject(); 478 // FIXME: This code should not depend on renderer! 479 if ((!renderer && requireRenderer) || useFallback) 480 return false; 481 482 WTF_LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data()); 483 WTF_LOG(Plugins, " Loaded URL: %s", url.string().utf8().data()); 484 m_loadedUrl = url; 485 486 RefPtr<Widget> widget = m_persistedPluginWidget; 487 if (!widget) { 488 bool loadManually = document().isPluginDocument() && !document().containsPlugins(); 489 FrameLoaderClient::DetachedPluginPolicy policy = requireRenderer ? FrameLoaderClient::FailOnDetachedPlugin : FrameLoaderClient::AllowDetachedPlugin; 490 widget = frame->loader().client()->createPlugin(this, url, paramNames, paramValues, mimeType, loadManually, policy); 491 } 492 493 if (!widget) { 494 if (renderer && !renderer->showsUnavailablePluginIndicator()) 495 renderer->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginMissing); 496 return false; 497 } 498 499 if (renderer) { 500 setWidget(widget); 501 m_persistedPluginWidget = nullptr; 502 } else if (widget != m_persistedPluginWidget) { 503 m_persistedPluginWidget = widget; 504 } 505 document().setContainsPlugins(); 506 scheduleSVGFilterLayerUpdateHack(); 507 // Make sure any input event handlers introduced by the plugin are taken into account. 508 if (Page* page = document().frame()->page()) { 509 if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) 510 scrollingCoordinator->notifyLayoutUpdated(); 511 } 512 return true; 513 } 514 515 bool HTMLPlugInElement::shouldUsePlugin(const KURL& url, const String& mimeType, bool hasFallback, bool& useFallback) 516 { 517 // Allow other plug-ins to win over QuickTime because if the user has 518 // installed a plug-in that can handle TIFF (which QuickTime can also 519 // handle) they probably intended to override QT. 520 if (document().frame()->page() && (mimeType == "image/tiff" || mimeType == "image/tif" || mimeType == "image/x-tiff")) { 521 const PluginData* pluginData = document().frame()->page()->pluginData(); 522 String pluginName = pluginData ? pluginData->pluginNameForMimeType(mimeType) : String(); 523 if (!pluginName.isEmpty() && !pluginName.contains("QuickTime", false)) 524 return true; 525 } 526 527 ObjectContentType objectType = document().frame()->loader().client()->objectContentType(url, mimeType, shouldPreferPlugInsForImages()); 528 // If an object's content can't be handled and it has no fallback, let 529 // it be handled as a plugin to show the broken plugin icon. 530 useFallback = objectType == ObjectContentNone && hasFallback; 531 return objectType == ObjectContentNone || objectType == ObjectContentNetscapePlugin || objectType == ObjectContentOtherPlugin; 532 533 } 534 535 void HTMLPlugInElement::dispatchErrorEvent() 536 { 537 if (document().isPluginDocument() && document().ownerElement()) 538 document().ownerElement()->dispatchEvent(Event::create(EventTypeNames::error)); 539 else 540 dispatchEvent(Event::create(EventTypeNames::error)); 541 } 542 543 bool HTMLPlugInElement::pluginIsLoadable(const KURL& url, const String& mimeType) 544 { 545 LocalFrame* frame = document().frame(); 546 Settings* settings = frame->settings(); 547 if (!settings) 548 return false; 549 550 if (MIMETypeRegistry::isJavaAppletMIMEType(mimeType) && !settings->javaEnabled()) 551 return false; 552 553 if (document().isSandboxed(SandboxPlugins)) 554 return false; 555 556 if (!document().securityOrigin()->canDisplay(url)) { 557 FrameLoader::reportLocalLoadFailed(frame, url.string()); 558 return false; 559 } 560 561 AtomicString declaredMimeType = document().isPluginDocument() && document().ownerElement() ? 562 document().ownerElement()->fastGetAttribute(HTMLNames::typeAttr) : 563 fastGetAttribute(HTMLNames::typeAttr); 564 if (!document().contentSecurityPolicy()->allowObjectFromSource(url) 565 || !document().contentSecurityPolicy()->allowPluginType(mimeType, declaredMimeType, url)) { 566 renderEmbeddedObject()->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy); 567 return false; 568 } 569 570 return frame->loader().mixedContentChecker()->canRunInsecureContent(document().securityOrigin(), url); 571 } 572 573 void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot&) 574 { 575 userAgentShadowRoot()->appendChild(HTMLContentElement::create(document())); 576 } 577 578 void HTMLPlugInElement::willAddFirstAuthorShadowRoot() 579 { 580 lazyReattachIfAttached(); 581 } 582 583 bool HTMLPlugInElement::hasFallbackContent() const 584 { 585 return false; 586 } 587 588 bool HTMLPlugInElement::useFallbackContent() const 589 { 590 return hasAuthorShadowRoot(); 591 } 592 593 void HTMLPlugInElement::setUsePlaceholderContent(bool use) 594 { 595 if (use != m_usePlaceholderContent) { 596 m_usePlaceholderContent = use; 597 lazyReattachIfAttached(); 598 } 599 } 600 601 } 602