1/** 2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org) 3 * (C) 1999 Antti Koivisto (koivisto@kde.org) 4 * (C) 2000 Stefan Schimanski (1Stein@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 56namespace blink { 57 58using namespace HTMLNames; 59 60HTMLPlugInElement::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 76HTMLPlugInElement::~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 87void HTMLPlugInElement::trace(Visitor* visitor) 88{ 89 visitor->trace(m_imageLoader); 90 HTMLFrameOwnerElement::trace(visitor); 91} 92 93bool HTMLPlugInElement::canProcessDrag() const 94{ 95 return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->canProcessDrag(); 96} 97 98bool HTMLPlugInElement::willRespondToMouseClickEvents() 99{ 100 if (isDisabledFormControl()) 101 return false; 102 RenderObject* r = renderer(); 103 return r && (r->isEmbeddedObject() || r->isWidget()); 104} 105 106void HTMLPlugInElement::removeAllEventListeners() 107{ 108 HTMLFrameOwnerElement::removeAllEventListeners(); 109 if (RenderWidget* renderer = existingRenderWidget()) { 110 if (Widget* widget = renderer->widget()) 111 widget->eventListenersRemoved(); 112 } 113} 114 115void HTMLPlugInElement::didMoveToNewDocument(Document& oldDocument) 116{ 117 if (m_imageLoader) 118 m_imageLoader->elementDidMoveToNewDocument(); 119 HTMLFrameOwnerElement::didMoveToNewDocument(oldDocument); 120} 121 122void 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 144void HTMLPlugInElement::updateWidget() 145{ 146 RefPtrWillBeRawPtr<HTMLPlugInElement> protector(this); 147 updateWidgetInternal(); 148 if (m_isDelayingLoadEvent) { 149 m_isDelayingLoadEvent = false; 150 document().decrementLoadEventDelayCount(); 151 } 152} 153 154void 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 169void 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 184bool HTMLPlugInElement::shouldAccelerate() const 185{ 186 if (Widget* widget = ownedWidget()) 187 return widget->isPluginView() && toPluginView(widget)->platformLayer(); 188 return false; 189} 190 191void 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 228RenderObject* 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 248void 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 255void HTMLPlugInElement::finishParsingChildren() 256{ 257 HTMLFrameOwnerElement::finishParsingChildren(); 258 if (useFallbackContent()) 259 return; 260 261 setNeedsWidgetUpdate(true); 262 if (inDocument()) 263 setNeedsStyleRecalc(SubtreeStyleChange); 264} 265 266void HTMLPlugInElement::resetInstance() 267{ 268 m_pluginWrapper.clear(); 269} 270 271SharedPersistent<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 294Widget* HTMLPlugInElement::pluginWidget() const 295{ 296 if (RenderWidget* renderWidget = renderWidgetForJSBindings()) 297 return renderWidget->widget(); 298 return 0; 299} 300 301bool 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 308void 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 327void 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 355RenderWidget* 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 364bool HTMLPlugInElement::isKeyboardFocusable() const 365{ 366 if (!document().isActive()) 367 return false; 368 return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->supportsKeyboardFocus(); 369} 370 371bool HTMLPlugInElement::hasCustomFocusLogic() const 372{ 373 return !hasAuthorShadowRoot(); 374} 375 376bool HTMLPlugInElement::isPluginElement() const 377{ 378 return true; 379} 380 381bool 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 391NPObject* 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 399bool 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 412RenderEmbeddedObject* 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. 423bool 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. 434bool 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 443bool 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 470bool 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 515bool 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 535void 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 543bool 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 573void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot&) 574{ 575 userAgentShadowRoot()->appendChild(HTMLContentElement::create(document())); 576} 577 578void HTMLPlugInElement::willAddFirstAuthorShadowRoot() 579{ 580 lazyReattachIfAttached(); 581} 582 583bool HTMLPlugInElement::hasFallbackContent() const 584{ 585 return false; 586} 587 588bool HTMLPlugInElement::useFallbackContent() const 589{ 590 return hasAuthorShadowRoot(); 591} 592 593void HTMLPlugInElement::setUsePlaceholderContent(bool use) 594{ 595 if (use != m_usePlaceholderContent) { 596 m_usePlaceholderContent = use; 597 lazyReattachIfAttached(); 598 } 599} 600 601} 602