3a819db2110cba4614eb063324ce4e4461b4775a
[WebKit-https.git] / Source / WebCore / 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, 2014 Apple 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 "HTMLPlugInElement.h"
25
26 #include "Attribute.h"
27 #include "BridgeJSC.h"
28 #include "Chrome.h"
29 #include "ChromeClient.h"
30 #include "CSSPropertyNames.h"
31 #include "Document.h"
32 #include "Event.h"
33 #include "EventHandler.h"
34 #include "Frame.h"
35 #include "FrameLoader.h"
36 #include "FrameTree.h"
37 #include "HTMLNames.h"
38 #include "Logging.h"
39 #include "MIMETypeRegistry.h"
40 #include "Page.h"
41 #include "PluginData.h"
42 #include "PluginReplacement.h"
43 #include "PluginViewBase.h"
44 #include "RenderEmbeddedObject.h"
45 #include "RenderSnapshottedPlugIn.h"
46 #include "RenderWidget.h"
47 #include "RuntimeEnabledFeatures.h"
48 #include "ScriptController.h"
49 #include "Settings.h"
50 #include "ShadowRoot.h"
51 #include "SubframeLoader.h"
52 #include "Widget.h"
53
54 #if ENABLE(NETSCAPE_PLUGIN_API)
55 #include "npruntime_impl.h"
56 #endif
57
58 #if PLATFORM(COCOA)
59 #include "QuickTimePluginReplacement.h"
60 #include "YouTubePluginReplacement.h"
61 #endif
62
63 namespace WebCore {
64
65 using namespace HTMLNames;
66
67 HTMLPlugInElement::HTMLPlugInElement(const QualifiedName& tagName, Document& document)
68     : HTMLFrameOwnerElement(tagName, document)
69     , m_inBeforeLoadEventHandler(false)
70     , m_swapRendererTimer(this, &HTMLPlugInElement::swapRendererTimerFired)
71 #if ENABLE(NETSCAPE_PLUGIN_API)
72     , m_NPObject(0)
73 #endif
74     , m_isCapturingMouseEvents(false)
75     , m_displayState(Playing)
76 {
77     setHasCustomStyleResolveCallbacks();
78 }
79
80 HTMLPlugInElement::~HTMLPlugInElement()
81 {
82     ASSERT(!m_instance); // cleared in detach()
83
84 #if ENABLE(NETSCAPE_PLUGIN_API)
85     if (m_NPObject) {
86         _NPN_ReleaseObject(m_NPObject);
87         m_NPObject = 0;
88     }
89 #endif
90 }
91
92 bool HTMLPlugInElement::canProcessDrag() const
93 {
94     const PluginViewBase* plugin = is<PluginViewBase>(pluginWidget()) ? downcast<PluginViewBase>(pluginWidget()) : nullptr;
95     return plugin ? plugin->canProcessDrag() : false;
96 }
97
98 bool HTMLPlugInElement::willRespondToMouseClickEvents()
99 {
100     if (isDisabledFormControl())
101         return false;
102     auto renderer = this->renderer();
103     return renderer && renderer->isWidget();
104 }
105
106 void HTMLPlugInElement::willDetachRenderers()
107 {
108     m_instance.clear();
109
110     if (m_isCapturingMouseEvents) {
111         if (Frame* frame = document().frame())
112             frame->eventHandler().setCapturingMouseEventsElement(nullptr);
113         m_isCapturingMouseEvents = false;
114     }
115
116 #if ENABLE(NETSCAPE_PLUGIN_API)
117     if (m_NPObject) {
118         _NPN_ReleaseObject(m_NPObject);
119         m_NPObject = 0;
120     }
121 #endif
122 }
123
124 void HTMLPlugInElement::resetInstance()
125 {
126     m_instance.clear();
127 }
128
129 PassRefPtr<JSC::Bindings::Instance> HTMLPlugInElement::getInstance()
130 {
131     Frame* frame = document().frame();
132     if (!frame)
133         return 0;
134
135     // If the host dynamically turns off JavaScript (or Java) we will still return
136     // the cached allocated Bindings::Instance.  Not supporting this edge-case is OK.
137     if (m_instance)
138         return m_instance;
139
140     if (Widget* widget = pluginWidget())
141         m_instance = frame->script().createScriptInstanceForWidget(widget);
142
143     return m_instance;
144 }
145
146 bool HTMLPlugInElement::guardedDispatchBeforeLoadEvent(const String& sourceURL)
147 {
148     // FIXME: Our current plug-in loading design can't guarantee the following
149     // assertion is true, since plug-in loading can be initiated during layout,
150     // and synchronous layout can be initiated in a beforeload event handler!
151     // See <http://webkit.org/b/71264>.
152     // ASSERT(!m_inBeforeLoadEventHandler);
153     m_inBeforeLoadEventHandler = true;
154     // static_cast is used to avoid a compile error since dispatchBeforeLoadEvent
155     // is intentionally undefined on this class.
156     bool beforeLoadAllowedLoad = static_cast<HTMLFrameOwnerElement*>(this)->dispatchBeforeLoadEvent(sourceURL);
157     m_inBeforeLoadEventHandler = false;
158     return beforeLoadAllowedLoad;
159 }
160
161 Widget* HTMLPlugInElement::pluginWidget(PluginLoadingPolicy loadPolicy) const
162 {
163     if (m_inBeforeLoadEventHandler) {
164         // The plug-in hasn't loaded yet, and it makes no sense to try to load if beforeload handler happened to touch the plug-in element.
165         // That would recursively call beforeload for the same element.
166         return nullptr;
167     }
168
169     RenderWidget* renderWidget = loadPolicy == PluginLoadingPolicy::Load ? renderWidgetLoadingPlugin() : this->renderWidget();
170     if (!renderWidget)
171         return nullptr;
172
173     return renderWidget->widget();
174 }
175
176 bool HTMLPlugInElement::isPresentationAttribute(const QualifiedName& name) const
177 {
178     if (name == widthAttr || name == heightAttr || name == vspaceAttr || name == hspaceAttr || name == alignAttr)
179         return true;
180     return HTMLFrameOwnerElement::isPresentationAttribute(name);
181 }
182
183 void HTMLPlugInElement::collectStyleForPresentationAttribute(const QualifiedName& name, const AtomicString& value, MutableStyleProperties& style)
184 {
185     if (name == widthAttr)
186         addHTMLLengthToStyle(style, CSSPropertyWidth, value);
187     else if (name == heightAttr)
188         addHTMLLengthToStyle(style, CSSPropertyHeight, value);
189     else if (name == vspaceAttr) {
190         addHTMLLengthToStyle(style, CSSPropertyMarginTop, value);
191         addHTMLLengthToStyle(style, CSSPropertyMarginBottom, value);
192     } else if (name == hspaceAttr) {
193         addHTMLLengthToStyle(style, CSSPropertyMarginLeft, value);
194         addHTMLLengthToStyle(style, CSSPropertyMarginRight, value);
195     } else if (name == alignAttr)
196         applyAlignmentAttributeToStyle(value, style);
197     else
198         HTMLFrameOwnerElement::collectStyleForPresentationAttribute(name, value, style);
199 }
200
201 void HTMLPlugInElement::defaultEventHandler(Event* event)
202 {
203     // Firefox seems to use a fake event listener to dispatch events to plug-in (tested with mouse events only).
204     // This is observable via different order of events - in Firefox, event listeners specified in HTML attributes fires first, then an event
205     // gets dispatched to plug-in, and only then other event listeners fire. Hopefully, this difference does not matter in practice.
206
207     // FIXME: Mouse down and scroll events are passed down to plug-in via custom code in EventHandler; these code paths should be united.
208
209     auto renderer = this->renderer();
210     if (!is<RenderWidget>(renderer))
211         return;
212
213     if (is<RenderEmbeddedObject>(*renderer)) {
214         if (downcast<RenderEmbeddedObject>(*renderer).isPluginUnavailable()) {
215             downcast<RenderEmbeddedObject>(*renderer).handleUnavailablePluginIndicatorEvent(event);
216             return;
217         }
218
219         if (is<RenderSnapshottedPlugIn>(*renderer) && displayState() < Restarting) {
220             downcast<RenderSnapshottedPlugIn>(*renderer).handleEvent(event);
221             HTMLFrameOwnerElement::defaultEventHandler(event);
222             return;
223         }
224
225         if (displayState() < Playing)
226             return;
227     }
228
229     RefPtr<Widget> widget = downcast<RenderWidget>(*renderer).widget();
230     if (!widget)
231         return;
232     widget->handleEvent(event);
233     if (event->defaultHandled())
234         return;
235     HTMLFrameOwnerElement::defaultEventHandler(event);
236 }
237
238 bool HTMLPlugInElement::isKeyboardFocusable(KeyboardEvent*) const
239 {
240     // FIXME: Why is this check needed?
241     if (!document().page())
242         return false;
243
244     Widget* widget = pluginWidget();
245     if (!is<PluginViewBase>(widget))
246         return false;
247
248     return downcast<PluginViewBase>(*widget).supportsKeyboardFocus();
249 }
250
251 bool HTMLPlugInElement::isPluginElement() const
252 {
253     return true;
254 }
255
256 bool HTMLPlugInElement::isUserObservable() const
257 {
258     // No widget - can't be anything to see or hear here.
259     Widget* widget = pluginWidget(PluginLoadingPolicy::DoNotLoad);
260     if (!is<PluginViewBase>(widget))
261         return false;
262
263     PluginViewBase& pluginView = downcast<PluginViewBase>(*widget);
264
265     // If audio is playing (or might be) then the plugin is detectable.
266     if (pluginView.audioHardwareActivity() != AudioHardwareActivityType::IsInactive)
267         return true;
268
269     // If the plugin is visible and not vanishingly small in either dimension it is detectable.
270     return pluginView.isVisible() && pluginView.width() > 2 && pluginView.height() > 2;
271 }
272
273 bool HTMLPlugInElement::supportsFocus() const
274 {
275     if (HTMLFrameOwnerElement::supportsFocus())
276         return true;
277
278     if (useFallbackContent() || !renderer() || !renderer()->isEmbeddedObject())
279         return false;
280     return !toRenderEmbeddedObject(renderer())->isPluginUnavailable();
281 }
282
283 #if ENABLE(NETSCAPE_PLUGIN_API)
284
285 NPObject* HTMLPlugInElement::getNPObject()
286 {
287     ASSERT(document().frame());
288     if (!m_NPObject)
289         m_NPObject = document().frame()->script().createScriptObjectForPluginElement(this);
290     return m_NPObject;
291 }
292
293 #endif /* ENABLE(NETSCAPE_PLUGIN_API) */
294
295 RenderPtr<RenderElement> HTMLPlugInElement::createElementRenderer(PassRef<RenderStyle> style)
296 {
297     if (m_pluginReplacement && m_pluginReplacement->willCreateRenderer())
298         return m_pluginReplacement->createElementRenderer(*this, WTF::move(style));
299
300     return createRenderer<RenderEmbeddedObject>(*this, WTF::move(style));
301 }
302
303 void HTMLPlugInElement::swapRendererTimerFired(Timer<HTMLPlugInElement>&)
304 {
305     ASSERT(displayState() == PreparingPluginReplacement || displayState() == DisplayingSnapshot);
306     if (userAgentShadowRoot())
307         return;
308     
309     // Create a shadow root, which will trigger the code to add a snapshot container
310     // and reattach, thus making a new Renderer.
311     ensureUserAgentShadowRoot();
312 }
313
314 void HTMLPlugInElement::setDisplayState(DisplayState state)
315 {
316     m_displayState = state;
317     
318     if ((state == DisplayingSnapshot || displayState() == PreparingPluginReplacement) && !m_swapRendererTimer.isActive())
319         m_swapRendererTimer.startOneShot(0);
320 }
321
322 void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot* root)
323 {
324     if (!m_pluginReplacement || !document().page() || displayState() != PreparingPluginReplacement)
325         return;
326     
327     root->setResetStyleInheritance(true);
328     if (m_pluginReplacement->installReplacement(root)) {
329         setDisplayState(DisplayingPluginReplacement);
330         setNeedsStyleRecalc(ReconstructRenderTree);
331     }
332 }
333
334 #if PLATFORM(COCOA)
335 static void registrar(const ReplacementPlugin&);
336 #endif
337
338 static Vector<ReplacementPlugin*>& registeredPluginReplacements()
339 {
340     DEPRECATED_DEFINE_STATIC_LOCAL(Vector<ReplacementPlugin*>, registeredReplacements, ());
341     static bool enginesQueried = false;
342     
343     if (enginesQueried)
344         return registeredReplacements;
345     enginesQueried = true;
346
347 #if PLATFORM(COCOA)
348     QuickTimePluginReplacement::registerPluginReplacement(registrar);
349     YouTubePluginReplacement::registerPluginReplacement(registrar);
350 #endif
351     
352     return registeredReplacements;
353 }
354
355 #if PLATFORM(COCOA)
356 static void registrar(const ReplacementPlugin& replacement)
357 {
358     registeredPluginReplacements().append(new ReplacementPlugin(replacement));
359 }
360 #endif
361
362 static ReplacementPlugin* pluginReplacementForType(const URL& url, const String& mimeType)
363 {
364     Vector<ReplacementPlugin*>& replacements = registeredPluginReplacements();
365     if (replacements.isEmpty())
366         return nullptr;
367
368     String extension;
369     String lastPathComponent = url.lastPathComponent();
370     size_t dotOffset = lastPathComponent.reverseFind('.');
371     if (dotOffset != notFound)
372         extension = lastPathComponent.substring(dotOffset + 1);
373
374     String type = mimeType;
375     if (type.isEmpty() && url.protocolIsData())
376         type = mimeTypeFromDataURL(url.string());
377     
378     if (type.isEmpty() && !extension.isEmpty()) {
379         for (auto* replacement : replacements) {
380             if (replacement->supportsFileExtension(extension) && replacement->supportsURL(url))
381                 return replacement;
382         }
383     }
384     
385     if (type.isEmpty()) {
386         if (extension.isEmpty())
387             return nullptr;
388         type = MIMETypeRegistry::getMediaMIMETypeForExtension(extension);
389     }
390
391     if (type.isEmpty())
392         return nullptr;
393
394     for (auto* replacement : replacements) {
395         if (replacement->supportsType(type) && replacement->supportsURL(url))
396             return replacement;
397     }
398
399     return nullptr;
400 }
401
402 bool HTMLPlugInElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
403 {
404     if (!RuntimeEnabledFeatures::sharedFeatures().pluginReplacementEnabled())
405         return false;
406
407     if (m_pluginReplacement)
408         return true;
409
410     URL completedURL;
411     if (!url.isEmpty())
412         completedURL = document().completeURL(url);
413
414     ReplacementPlugin* replacement = pluginReplacementForType(completedURL, mimeType);
415     if (!replacement)
416         return false;
417
418     LOG(Plugins, "%p - Found plug-in replacement for %s.", this, completedURL.string().utf8().data());
419
420     m_pluginReplacement = replacement->create(*this, paramNames, paramValues);
421     setDisplayState(PreparingPluginReplacement);
422     return true;
423 }
424
425 JSC::JSObject* HTMLPlugInElement::scriptObjectForPluginReplacement()
426 {
427     if (m_pluginReplacement)
428         return m_pluginReplacement->scriptObject();
429     return nullptr;
430 }
431
432 }