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 "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
HTMLPlugInElement(const QualifiedName & tagName,Document & doc,bool createdByParser,PreferPlugInsForImagesOption preferPlugInsForImagesOption)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
~HTMLPlugInElement()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
canProcessDrag() const85 bool HTMLPlugInElement::canProcessDrag() const
86 {
87 return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->canProcessDrag();
88 }
89
willRespondToMouseClickEvents()90 bool HTMLPlugInElement::willRespondToMouseClickEvents()
91 {
92 if (isDisabledFormControl())
93 return false;
94 RenderObject* r = renderer();
95 return r && (r->isEmbeddedObject() || r->isWidget());
96 }
97
removeAllEventListeners()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
didMoveToNewDocument(Document & oldDocument)107 void HTMLPlugInElement::didMoveToNewDocument(Document& oldDocument)
108 {
109 if (m_imageLoader)
110 m_imageLoader->elementDidMoveToNewDocument();
111 HTMLFrameOwnerElement::didMoveToNewDocument(oldDocument);
112 }
113
attach(const AttachContext & context)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
updateWidget()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
detach(const AttachContext & context)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
createRenderer(RenderStyle * style)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
willRecalcStyle(StyleRecalcChange)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
finishParsingChildren()195 void HTMLPlugInElement::finishParsingChildren()
196 {
197 HTMLFrameOwnerElement::finishParsingChildren();
198 if (useFallbackContent())
199 return;
200
201 setNeedsWidgetUpdate(true);
202 if (inDocument())
203 setNeedsStyleRecalc();
204 }
205
resetInstance()206 void HTMLPlugInElement::resetInstance()
207 {
208 m_pluginWrapper.clear();
209 }
210
pluginWrapper()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
dispatchBeforeLoadEvent(const String & sourceURL)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
pluginWidget() const240 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
isPresentationAttribute(const QualifiedName & name) const254 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
collectStyleForPresentationAttribute(const QualifiedName & name,const AtomicString & value,MutableStylePropertySet * style)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
defaultEventHandler(Event * event)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
renderWidgetForJSBindings() const310 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
isKeyboardFocusable() const319 bool HTMLPlugInElement::isKeyboardFocusable() const
320 {
321 if (!document().isActive())
322 return false;
323 return pluginWidget() && pluginWidget()->isPluginView() && toPluginView(pluginWidget())->supportsKeyboardFocus();
324 }
325
hasCustomFocusLogic() const326 bool HTMLPlugInElement::hasCustomFocusLogic() const
327 {
328 return !hasAuthorShadowRoot();
329 }
330
isPluginElement() const331 bool HTMLPlugInElement::isPluginElement() const
332 {
333 return true;
334 }
335
rendererIsFocusable() const336 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
getNPObject()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
isImageType()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
loadedMimeType() const367 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
renderEmbeddedObject() const375 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.
allowedToLoadFrameURL(const String & url)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.
wouldLoadAsNetscapePlugin(const String & url,const String & serviceType)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
requestObject(const String & url,const String & mimeType,const Vector<String> & paramNames,const Vector<String> & paramValues)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
loadPlugin(const KURL & url,const String & mimeType,const Vector<String> & paramNames,const Vector<String> & paramValues,bool useFallback)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
shouldUsePlugin(const KURL & url,const String & mimeType,bool hasFallback,bool & useFallback)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
pluginIsLoadable(const KURL & url,const String & mimeType)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
didAddUserAgentShadowRoot(ShadowRoot &)515 void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot&)
516 {
517 userAgentShadowRoot()->appendChild(HTMLContentElement::create(document()));
518 }
519
didAddShadowRoot(ShadowRoot & root)520 void HTMLPlugInElement::didAddShadowRoot(ShadowRoot& root)
521 {
522 if (root.isOldestAuthorShadowRoot())
523 lazyReattachIfAttached();
524 }
525
useFallbackContent() const526 bool HTMLPlugInElement::useFallbackContent() const
527 {
528 return hasAuthorShadowRoot();
529 }
530
531 }
532