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