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