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