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