CTTE Timer and DeferrableOneShotTimer
[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 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 "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(MAC)
59 #include "QuickTimePluginReplacement.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 = pluginWidget() && pluginWidget()->isPluginViewBase() ? toPluginViewBase(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.clear();
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.clear();
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() 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 0;
166     }
167
168     RenderWidget* renderWidget = renderWidgetForJSBindings();
169     if (!renderWidget)
170         return 0;
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 (!renderer || !renderer->isWidget())
210         return;
211
212     if (renderer->isEmbeddedObject()) {
213         if (toRenderEmbeddedObject(renderer)->isPluginUnavailable()) {
214             toRenderEmbeddedObject(renderer)->handleUnavailablePluginIndicatorEvent(event);
215             return;
216         }
217
218         if (toRenderEmbeddedObject(renderer)->isSnapshottedPlugIn() && displayState() < Restarting) {
219             toRenderSnapshottedPlugIn(renderer)->handleEvent(event);
220             HTMLFrameOwnerElement::defaultEventHandler(event);
221             return;
222         }
223
224         if (displayState() < Playing)
225             return;
226     }
227
228     RefPtr<Widget> widget = toRenderWidget(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 (!widget || !widget->isPluginViewBase())
245         return false;
246
247     return toPluginViewBase(widget)->supportsKeyboardFocus();
248 }
249
250 bool HTMLPlugInElement::isPluginElement() const
251 {
252     return true;
253 }
254
255 bool HTMLPlugInElement::supportsFocus() const
256 {
257     if (HTMLFrameOwnerElement::supportsFocus())
258         return true;
259
260     if (useFallbackContent() || !renderer() || !renderer()->isEmbeddedObject())
261         return false;
262     return !toRenderEmbeddedObject(renderer())->isPluginUnavailable();
263 }
264
265 #if ENABLE(NETSCAPE_PLUGIN_API)
266
267 NPObject* HTMLPlugInElement::getNPObject()
268 {
269     ASSERT(document().frame());
270     if (!m_NPObject)
271         m_NPObject = document().frame()->script().createScriptObjectForPluginElement(this);
272     return m_NPObject;
273 }
274
275 #endif /* ENABLE(NETSCAPE_PLUGIN_API) */
276
277 RenderPtr<RenderElement> HTMLPlugInElement::createElementRenderer(PassRef<RenderStyle> style)
278 {
279     if (m_pluginReplacement && m_pluginReplacement->willCreateRenderer())
280         return m_pluginReplacement->createElementRenderer(*this, std::move(style));
281
282     return createRenderer<RenderEmbeddedObject>(*this, std::move(style));
283 }
284
285 void HTMLPlugInElement::swapRendererTimerFired(Timer<HTMLPlugInElement>&)
286 {
287     ASSERT(displayState() == PreparingPluginReplacement || displayState() == DisplayingSnapshot);
288     if (userAgentShadowRoot())
289         return;
290     
291     // Create a shadow root, which will trigger the code to add a snapshot container
292     // and reattach, thus making a new Renderer.
293     ensureUserAgentShadowRoot();
294 }
295
296 void HTMLPlugInElement::setDisplayState(DisplayState state)
297 {
298     m_displayState = state;
299     
300     if ((state == DisplayingSnapshot || displayState() == PreparingPluginReplacement) && !m_swapRendererTimer.isActive())
301         m_swapRendererTimer.startOneShot(0);
302 }
303
304 void HTMLPlugInElement::didAddUserAgentShadowRoot(ShadowRoot* root)
305 {
306     if (!m_pluginReplacement || !document().page() || displayState() != PreparingPluginReplacement)
307         return;
308     
309     root->setResetStyleInheritance(true);
310     if (m_pluginReplacement->installReplacement(root)) {
311         setDisplayState(DisplayingPluginReplacement);
312         setNeedsStyleRecalc(ReconstructRenderTree);
313     }
314 }
315
316 #if PLATFORM(MAC)
317 static void registrar(const ReplacementPlugin&);
318 #endif
319
320 static Vector<ReplacementPlugin*>& registeredPluginReplacements()
321 {
322     DEFINE_STATIC_LOCAL(Vector<ReplacementPlugin*>, registeredReplacements, ());
323     static bool enginesQueried = false;
324     
325     if (enginesQueried)
326         return registeredReplacements;
327     enginesQueried = true;
328
329 #if PLATFORM(MAC)
330     QuickTimePluginReplacement::registerPluginReplacement(registrar);
331 #endif
332     
333     return registeredReplacements;
334 }
335
336 #if PLATFORM(MAC)
337 static void registrar(const ReplacementPlugin& replacement)
338 {
339     registeredPluginReplacements().append(new ReplacementPlugin(replacement));
340 }
341 #endif
342
343 static ReplacementPlugin* pluginReplacementForType(const URL& url, const String& mimeType)
344 {
345     Vector<ReplacementPlugin*>& replacements = registeredPluginReplacements();
346     if (replacements.isEmpty())
347         return nullptr;
348
349     String extension;
350     String lastPathComponent = url.lastPathComponent();
351     size_t dotOffset = lastPathComponent.reverseFind('.');
352     if (dotOffset != notFound)
353         extension = lastPathComponent.substring(dotOffset + 1);
354
355     String type = mimeType;
356     if (type.isEmpty() && url.protocolIsData())
357         type = mimeTypeFromDataURL(url.string());
358     
359     if (type.isEmpty() && !extension.isEmpty()) {
360         for (size_t i = 0; i < replacements.size(); i++)
361             if (replacements[i]->supportsFileExtension(extension))
362                 return replacements[i];
363     }
364     
365     if (type.isEmpty()) {
366         if (extension.isEmpty())
367             return nullptr;
368         type = MIMETypeRegistry::getMediaMIMETypeForExtension(extension);
369     }
370
371     if (type.isEmpty())
372         return nullptr;
373
374     for (unsigned i = 0; i < replacements.size(); i++)
375         if (replacements[i]->supportsType(type))
376             return replacements[i];
377
378     return nullptr;
379 }
380
381 bool HTMLPlugInElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
382 {
383     if (!RuntimeEnabledFeatures::sharedFeatures().pluginReplacementEnabled())
384         return false;
385
386     if (m_pluginReplacement)
387         return true;
388
389     URL completedURL;
390     if (!url.isEmpty())
391         completedURL = document().completeURL(url);
392
393     ReplacementPlugin* replacement = pluginReplacementForType(completedURL, mimeType);
394     if (!replacement)
395         return false;
396
397     LOG(Plugins, "%p - Found plug-in replacement for %s.", this, completedURL.string().utf8().data());
398
399     m_pluginReplacement = replacement->create(*this, paramNames, paramValues);
400     setDisplayState(PreparingPluginReplacement);
401     return true;
402 }
403
404 JSC::JSObject* HTMLPlugInElement::scriptObjectForPluginReplacement()
405 {
406     if (m_pluginReplacement)
407         return m_pluginReplacement->scriptObject();
408     return nullptr;
409 }
410
411 }