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