Replace some auto* with RefPtr within WebCore/html
[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-2017 Apple Inc. All rights reserved.
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 "CSSPropertyNames.h"
28 #include "Document.h"
29 #include "Event.h"
30 #include "EventHandler.h"
31 #include "Frame.h"
32 #include "FrameLoader.h"
33 #include "FrameTree.h"
34 #include "HTMLNames.h"
35 #include "HitTestResult.h"
36 #include "Logging.h"
37 #include "MIMETypeRegistry.h"
38 #include "Page.h"
39 #include "PluginData.h"
40 #include "PluginReplacement.h"
41 #include "PluginViewBase.h"
42 #include "RenderEmbeddedObject.h"
43 #include "RenderLayer.h"
44 #include "RenderSnapshottedPlugIn.h"
45 #include "RenderView.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     , m_isCapturingMouseEvents(false)
72     , m_displayState(Playing)
73 {
74     setHasCustomStyleResolveCallbacks();
75 }
76
77 HTMLPlugInElement::~HTMLPlugInElement()
78 {
79     ASSERT(!m_instance); // cleared in detach()
80 }
81
82 bool HTMLPlugInElement::canProcessDrag() const
83 {
84     const PluginViewBase* plugin = is<PluginViewBase>(pluginWidget()) ? downcast<PluginViewBase>(pluginWidget()) : nullptr;
85     return plugin ? plugin->canProcessDrag() : false;
86 }
87
88 bool HTMLPlugInElement::willRespondToMouseClickEvents()
89 {
90     if (isDisabledFormControl())
91         return false;
92     auto renderer = this->renderer();
93     return renderer && renderer->isWidget();
94 }
95
96 void HTMLPlugInElement::willDetachRenderers()
97 {
98     m_instance = nullptr;
99
100     if (m_isCapturingMouseEvents) {
101         if (RefPtr<Frame> frame = document().frame())
102             frame->eventHandler().setCapturingMouseEventsElement(nullptr);
103         m_isCapturingMouseEvents = false;
104     }
105 }
106
107 void HTMLPlugInElement::resetInstance()
108 {
109     m_instance = nullptr;
110 }
111
112 JSC::Bindings::Instance* HTMLPlugInElement::bindingsInstance()
113 {
114     auto frame = makeRefPtr(document().frame());
115     if (!frame)
116         return nullptr;
117
118     // If the host dynamically turns off JavaScript (or Java) we will still return
119     // the cached allocated Bindings::Instance.  Not supporting this edge-case is OK.
120
121     if (!m_instance) {
122         if (auto widget = makeRefPtr(pluginWidget()))
123             m_instance = frame->script().createScriptInstanceForWidget(widget.get());
124     }
125     return m_instance.get();
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     RefPtr<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     RefPtr<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_s);
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         invalidateStyleAndRenderersForSubtree();
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 // Return whether or not the replacement content for blocked plugins is accessible to the user.
403 bool HTMLPlugInElement::isReplacementObscured(const String& unavailabilityDescription)
404 {
405     if (!is<RenderEmbeddedObject>(renderer()))
406         return false;
407     Ref<HTMLPlugInElement> protectedThis(*this);
408     downcast<RenderEmbeddedObject>(*renderer()).setPluginUnavailabilityReasonWithDescription(RenderEmbeddedObject::InsecurePluginVersion, unavailabilityDescription);
409     bool replacementIsObscured = isReplacementObscured();
410     // hittest in isReplacementObscured() method could destroy the renderer. Let's refetch it.
411     if (is<RenderEmbeddedObject>(renderer()))
412         downcast<RenderEmbeddedObject>(*renderer()).setUnavailablePluginIndicatorIsHidden(replacementIsObscured);
413     return replacementIsObscured;
414 }
415
416 bool HTMLPlugInElement::isReplacementObscured()
417 {
418     // We should always start hit testing a clean tree.
419     if (document().view())
420         document().view()->updateLayoutAndStyleIfNeededRecursive();
421     // Check if style recalc/layout destroyed the associated renderer.
422     auto* renderView = document().topDocument().renderView();
423     if (!document().view() || !renderView)
424         return false;
425     if (!renderer() || !is<RenderEmbeddedObject>(*renderer()))
426         return false;
427     auto& pluginRenderer = downcast<RenderEmbeddedObject>(*renderer());
428     // Check the opacity of each layer containing the element or its ancestors.
429     float opacity = 1.0;
430     for (auto* layer = pluginRenderer.enclosingLayer(); layer; layer = layer->parent()) {
431         opacity *= layer->renderer().style().opacity();
432         if (opacity < 0.1)
433             return true;
434     }
435     // Calculate the absolute rect for the blocked plugin replacement text.
436     LayoutPoint absoluteLocation(pluginRenderer.absoluteBoundingBoxRect().location());
437     LayoutRect rect = pluginRenderer.unavailablePluginIndicatorBounds(absoluteLocation);
438     if (rect.isEmpty())
439         return true;
440     auto viewRect = document().view()->convertToRootView(snappedIntRect(rect));
441     auto x = viewRect.x();
442     auto y = viewRect.y();
443     auto width = viewRect.width();
444     auto height = viewRect.height();
445     // Hit test the center and near the corners of the replacement text to ensure
446     // it is visible and is not masked by other elements.
447     HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::IgnoreClipping | HitTestRequest::DisallowUserAgentShadowContent | HitTestRequest::AllowChildFrameContent);
448     HitTestResult result;
449     HitTestLocation location = LayoutPoint(x + width / 2, y + height / 2);
450     bool hit = renderView->hitTest(request, location, result);
451     if (!hit || result.innerNode() != &pluginRenderer.frameOwnerElement())
452         return true;
453
454     location = LayoutPoint(x, y);
455     hit = renderView->hitTest(request, location, result);
456     if (!hit || result.innerNode() != &pluginRenderer.frameOwnerElement())
457         return true;
458
459     location = LayoutPoint(x + width, y);
460     hit = renderView->hitTest(request, location, result);
461     if (!hit || result.innerNode() != &pluginRenderer.frameOwnerElement())
462         return true;
463
464     location = LayoutPoint(x + width, y + height);
465     hit = renderView->hitTest(request, location, result);
466     if (!hit || result.innerNode() != &pluginRenderer.frameOwnerElement())
467         return true;
468
469     location = LayoutPoint(x, y + height);
470     hit = renderView->hitTest(request, location, result);
471     if (!hit || result.innerNode() != &pluginRenderer.frameOwnerElement())
472         return true;
473     return false;
474 }
475
476 }