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