0cf9d5910fce306672e1cf9043ae95541a7e378b
[WebKit-https.git] / Source / WebCore / html / HTMLPlugInImageElement.cpp
1 /*
2  * Copyright (C) 2008, 2011, 2012, 2014 Apple Inc. All rights reserved.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public License
15  * along with this library; see the file COPYING.LIB.  If not, write to
16  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  *
19  */
20
21 #include "config.h"
22 #include "HTMLPlugInImageElement.h"
23
24 #include "Chrome.h"
25 #include "ChromeClient.h"
26 #include "Event.h"
27 #include "EventHandler.h"
28 #include "Frame.h"
29 #include "FrameLoader.h"
30 #include "FrameLoaderClient.h"
31 #include "FrameView.h"
32 #include "HTMLImageLoader.h"
33 #include "JSDocumentFragment.h"
34 #include "LocalizedStrings.h"
35 #include "Logging.h"
36 #include "MainFrame.h"
37 #include "MouseEvent.h"
38 #include "NodeList.h"
39 #include "NodeRenderStyle.h"
40 #include "Page.h"
41 #include "PlugInClient.h"
42 #include "PluginViewBase.h"
43 #include "RenderEmbeddedObject.h"
44 #include "RenderImage.h"
45 #include "RenderSnapshottedPlugIn.h"
46 #include "SchemeRegistry.h"
47 #include "ScriptController.h"
48 #include "SecurityOrigin.h"
49 #include "Settings.h"
50 #include "ShadowRoot.h"
51 #include "StyleResolver.h"
52 #include "SubframeLoader.h"
53 #include <JavaScriptCore/APICast.h>
54 #include <JavaScriptCore/JSBase.h>
55 #include <wtf/HashMap.h>
56 #include <wtf/text/StringHash.h>
57
58 namespace WebCore {
59
60 using namespace HTMLNames;
61
62 typedef Vector<RefPtr<HTMLPlugInImageElement>> HTMLPlugInImageElementList;
63 typedef HashMap<String, String> MimeTypeToLocalizedStringMap;
64
65 static const int sizingTinyDimensionThreshold = 40;
66 static const float sizingFullPageAreaRatioThreshold = 0.96;
67 static const float autostartSoonAfterUserGestureThreshold = 5.0;
68
69 // This delay should not exceed the snapshot delay in PluginView.cpp
70 static const auto simulatedMouseClickTimerDelay = std::chrono::milliseconds { 750 };
71 static const auto removeSnapshotTimerDelay = std::chrono::milliseconds { 1500 };
72
73 static const String titleText(Page* page, String mimeType)
74 {
75     DEPRECATED_DEFINE_STATIC_LOCAL(MimeTypeToLocalizedStringMap, mimeTypeToLabelTitleMap, ());
76     String titleText = mimeTypeToLabelTitleMap.get(mimeType);
77     if (!titleText.isEmpty())
78         return titleText;
79
80     titleText = page->chrome().client().plugInStartLabelTitle(mimeType);
81     if (titleText.isEmpty())
82         titleText = snapshottedPlugInLabelTitle();
83     mimeTypeToLabelTitleMap.set(mimeType, titleText);
84     return titleText;
85 };
86
87 static const String subtitleText(Page* page, String mimeType)
88 {
89     DEPRECATED_DEFINE_STATIC_LOCAL(MimeTypeToLocalizedStringMap, mimeTypeToLabelSubtitleMap, ());
90     String subtitleText = mimeTypeToLabelSubtitleMap.get(mimeType);
91     if (!subtitleText.isEmpty())
92         return subtitleText;
93
94     subtitleText = page->chrome().client().plugInStartLabelSubtitle(mimeType);
95     if (subtitleText.isEmpty())
96         subtitleText = snapshottedPlugInLabelSubtitle();
97     mimeTypeToLabelSubtitleMap.set(mimeType, subtitleText);
98     return subtitleText;
99 };
100
101 HTMLPlugInImageElement::HTMLPlugInImageElement(const QualifiedName& tagName, Document& document, bool createdByParser, PreferPlugInsForImagesOption preferPlugInsForImagesOption)
102     : HTMLPlugInElement(tagName, document)
103     // m_needsWidgetUpdate(!createdByParser) allows HTMLObjectElement to delay
104     // widget updates until after all children are parsed.  For HTMLEmbedElement
105     // this delay is unnecessary, but it is simpler to make both classes share
106     // the same codepath in this class.
107     , m_needsWidgetUpdate(!createdByParser)
108     , m_shouldPreferPlugInsForImages(preferPlugInsForImagesOption == ShouldPreferPlugInsForImages)
109     , m_needsDocumentActivationCallbacks(false)
110     , m_simulatedMouseClickTimer(*this, &HTMLPlugInImageElement::simulatedMouseClickTimerFired, simulatedMouseClickTimerDelay)
111     , m_removeSnapshotTimer(*this, &HTMLPlugInImageElement::removeSnapshotTimerFired)
112     , m_createdDuringUserGesture(ScriptController::processingUserGesture())
113     , m_isRestartedPlugin(false)
114     , m_needsCheckForSizeChange(false)
115     , m_plugInWasCreated(false)
116     , m_deferredPromotionToPrimaryPlugIn(false)
117     , m_snapshotDecision(SnapshotNotYetDecided)
118     , m_plugInDimensionsSpecified(false)
119 {
120     setHasCustomStyleResolveCallbacks();
121 }
122
123 HTMLPlugInImageElement::~HTMLPlugInImageElement()
124 {
125     if (m_needsDocumentActivationCallbacks)
126         document().unregisterForPageCacheSuspensionCallbacks(this);
127 }
128
129 void HTMLPlugInImageElement::setDisplayState(DisplayState state)
130 {
131 #if PLATFORM(COCOA)
132     if (state == RestartingWithPendingMouseClick || state == Restarting) {
133         m_isRestartedPlugin = true;
134         m_snapshotDecision = NeverSnapshot;
135         setNeedsStyleRecalc(SyntheticStyleChange);
136         if (displayState() == DisplayingSnapshot)
137             m_removeSnapshotTimer.startOneShot(removeSnapshotTimerDelay);
138     }
139 #endif
140
141     HTMLPlugInElement::setDisplayState(state);
142 }
143
144 RenderEmbeddedObject* HTMLPlugInImageElement::renderEmbeddedObject() const
145 {
146     // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers
147     // when using fallback content.
148     return is<RenderEmbeddedObject>(renderer()) ? downcast<RenderEmbeddedObject>(renderer()) : nullptr;
149 }
150
151 bool HTMLPlugInImageElement::isImageType()
152 {
153     if (m_serviceType.isEmpty() && protocolIs(m_url, "data"))
154         m_serviceType = mimeTypeFromDataURL(m_url);
155
156     if (Frame* frame = document().frame()) {
157         URL completedURL = document().completeURL(m_url);
158         return frame->loader().client().objectContentType(completedURL, m_serviceType, shouldPreferPlugInsForImages()) == ObjectContentImage;
159     }
160
161     return Image::supportsType(m_serviceType);
162 }
163
164 // We don't use m_url, as it may not be the final URL that the object loads,
165 // depending on <param> values.
166 bool HTMLPlugInImageElement::allowedToLoadFrameURL(const String& url)
167 {
168     URL completeURL = document().completeURL(url);
169
170     if (contentFrame() && protocolIsJavaScript(completeURL)
171         && !document().securityOrigin()->canAccess(contentDocument()->securityOrigin()))
172         return false;
173
174     return document().frame()->isURLAllowed(completeURL);
175 }
176
177 // We don't use m_url, or m_serviceType as they may not be the final values
178 // that <object> uses depending on <param> values.
179 bool HTMLPlugInImageElement::wouldLoadAsNetscapePlugin(const String& url, const String& serviceType)
180 {
181     ASSERT(document().frame());
182     URL completedURL;
183     if (!url.isEmpty())
184         completedURL = document().completeURL(url);
185
186     FrameLoader& frameLoader = document().frame()->loader();
187     if (frameLoader.client().objectContentType(completedURL, serviceType, shouldPreferPlugInsForImages()) == ObjectContentNetscapePlugin)
188         return true;
189     return false;
190 }
191
192 RenderPtr<RenderElement> HTMLPlugInImageElement::createElementRenderer(Ref<RenderStyle>&& style)
193 {
194     ASSERT(!document().inPageCache());
195
196     if (displayState() >= PreparingPluginReplacement)
197         return HTMLPlugInElement::createElementRenderer(WTF::move(style));
198
199     // Once a PlugIn Element creates its renderer, it needs to be told when the Document goes
200     // inactive or reactivates so it can clear the renderer before going into the page cache.
201     if (!m_needsDocumentActivationCallbacks) {
202         m_needsDocumentActivationCallbacks = true;
203         document().registerForPageCacheSuspensionCallbacks(this);
204     }
205
206     if (displayState() == DisplayingSnapshot) {
207         auto renderSnapshottedPlugIn = createRenderer<RenderSnapshottedPlugIn>(*this, WTF::move(style));
208         renderSnapshottedPlugIn->updateSnapshot(m_snapshotImage);
209         return WTF::move(renderSnapshottedPlugIn);
210     }
211
212     // Fallback content breaks the DOM->Renderer class relationship of this
213     // class and all superclasses because createObject won't necessarily
214     // return a RenderEmbeddedObject or RenderWidget.
215     if (useFallbackContent())
216         return RenderElement::createFor(*this, WTF::move(style));
217
218     if (isImageType())
219         return createRenderer<RenderImage>(*this, WTF::move(style));
220
221     return HTMLPlugInElement::createElementRenderer(WTF::move(style));
222 }
223
224 bool HTMLPlugInImageElement::childShouldCreateRenderer(const Node& child) const
225 {
226     if (is<RenderSnapshottedPlugIn>(renderer()) && !hasShadowRootParent(child))
227         return false;
228
229     return HTMLPlugInElement::childShouldCreateRenderer(child);
230 }
231
232 bool HTMLPlugInImageElement::willRecalcStyle(Style::Change change)
233 {
234     // Make sure style recalcs scheduled by a child shadow tree don't trigger reconstruction and cause flicker.
235     if (change == Style::NoChange && styleChangeType() == NoStyleChange)
236         return true;
237
238     // FIXME: There shoudn't be need to force render tree reconstruction here.
239     // It is only done because loading and load event dispatching is tied to render tree construction.
240     if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType() && (displayState() != DisplayingSnapshot))
241         setNeedsStyleRecalc(ReconstructRenderTree);
242     return true;
243 }
244
245 void HTMLPlugInImageElement::didAttachRenderers()
246 {
247     if (!isImageType()) {
248         RefPtr<HTMLPlugInImageElement> element = this;
249         Style::queuePostResolutionCallback([element]{
250             element->updateWidgetIfNecessary();
251         });
252         return;
253     }
254     if (!renderer() || useFallbackContent())
255         return;
256
257     // Image load might complete synchronously and cause us to re-enter.
258     RefPtr<HTMLPlugInImageElement> element = this;
259     Style::queuePostResolutionCallback([element]{
260         element->startLoadingImage();
261     });
262 }
263
264 void HTMLPlugInImageElement::willDetachRenderers()
265 {
266     // FIXME: Because of the insanity that is HTMLPlugInImageElement::willRecalcStyle,
267     // we can end up detaching during an attach() call, before we even have a
268     // renderer. In that case, don't mark the widget for update.
269     if (renderer() && !useFallbackContent()) {
270         // Update the widget the next time we attach (detaching destroys the plugin).
271         setNeedsWidgetUpdate(true);
272     }
273
274     HTMLPlugInElement::willDetachRenderers();
275 }
276
277 void HTMLPlugInImageElement::updateWidgetIfNecessary()
278 {
279     document().updateStyleIfNeeded();
280
281     if (!needsWidgetUpdate() || useFallbackContent() || isImageType())
282         return;
283
284     if (!renderEmbeddedObject() || renderEmbeddedObject()->isPluginUnavailable())
285         return;
286
287     updateWidget(CreateOnlyNonNetscapePlugins);
288 }
289
290 void HTMLPlugInImageElement::finishParsingChildren()
291 {
292     HTMLPlugInElement::finishParsingChildren();
293     if (useFallbackContent())
294         return;
295
296     setNeedsWidgetUpdate(true);
297     if (inDocument())
298         setNeedsStyleRecalc();
299 }
300
301 void HTMLPlugInImageElement::didMoveToNewDocument(Document* oldDocument)
302 {
303     if (m_needsDocumentActivationCallbacks) {
304         oldDocument->unregisterForPageCacheSuspensionCallbacks(this);
305         document().registerForPageCacheSuspensionCallbacks(this);
306     }
307
308     if (m_imageLoader)
309         m_imageLoader->elementDidMoveToNewDocument(oldDocument);
310
311     HTMLPlugInElement::didMoveToNewDocument(oldDocument);
312 }
313
314 void HTMLPlugInImageElement::documentWillSuspendForPageCache()
315 {
316     if (renderer())
317         Style::detachRenderTree(*this);
318
319     HTMLPlugInElement::documentWillSuspendForPageCache();
320 }
321
322 void HTMLPlugInImageElement::documentDidResumeFromPageCache()
323 {
324     setNeedsStyleRecalc(ReconstructRenderTree);
325
326     HTMLPlugInElement::documentDidResumeFromPageCache();
327 }
328
329 void HTMLPlugInImageElement::startLoadingImage()
330 {
331     if (!m_imageLoader)
332         m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
333     m_imageLoader->updateFromElement();
334 }
335
336 void HTMLPlugInImageElement::updateSnapshot(PassRefPtr<Image> image)
337 {
338     if (displayState() > DisplayingSnapshot)
339         return;
340
341     m_snapshotImage = image;
342
343     if (!renderer())
344         return;
345     auto& renderer = *this->renderer();
346
347     if (is<RenderSnapshottedPlugIn>(renderer)) {
348         downcast<RenderSnapshottedPlugIn>(renderer).updateSnapshot(image);
349         return;
350     }
351
352     if (is<RenderEmbeddedObject>(renderer))
353         renderer.repaint();
354 }
355
356 static DOMWrapperWorld& plugInImageElementIsolatedWorld()
357 {
358     static DOMWrapperWorld& isolatedWorld = DOMWrapperWorld::create(JSDOMWindow::commonVM()).leakRef();
359     return isolatedWorld;
360 }
361
362 void HTMLPlugInImageElement::didAddUserAgentShadowRoot(ShadowRoot* root)
363 {
364     HTMLPlugInElement::didAddUserAgentShadowRoot(root);
365     if (displayState() >= PreparingPluginReplacement)
366         return;
367
368     Page* page = document().page();
369     if (!page)
370         return;
371
372     // Reset any author styles that may apply as we only want explicit
373     // styles defined in the injected user agents stylesheets to specify
374     // the look-and-feel of the snapshotted plug-in overlay. 
375     root->setResetStyleInheritance(true);
376     
377     String mimeType = loadedMimeType();
378
379     DOMWrapperWorld& isolatedWorld = plugInImageElementIsolatedWorld();
380     document().ensurePlugInsInjectedScript(isolatedWorld);
381
382     ScriptController& scriptController = page->mainFrame().script();
383     JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(isolatedWorld));
384     JSC::ExecState* exec = globalObject->globalExec();
385
386     JSC::JSLockHolder lock(exec);
387
388     JSC::MarkedArgumentBuffer argList;
389     argList.append(toJS(exec, globalObject, root));
390     argList.append(jsString(exec, titleText(page, mimeType)));
391     argList.append(jsString(exec, subtitleText(page, mimeType)));
392     
393     // This parameter determines whether or not the snapshot overlay should always be visible over the plugin snapshot.
394     // If no snapshot was found then we want the overlay to be visible.
395     argList.append(JSC::jsBoolean(!m_snapshotImage));
396
397     // It is expected the JS file provides a createOverlay(shadowRoot, title, subtitle) function.
398     JSC::JSObject* overlay = globalObject->get(exec, JSC::Identifier::fromString(exec, "createOverlay")).toObject(exec);
399     JSC::CallData callData;
400     JSC::CallType callType = overlay->methodTable()->getCallData(overlay, callData);
401     if (callType == JSC::CallTypeNone)
402         return;
403
404     JSC::call(exec, overlay, callType, callData, globalObject, argList);
405     exec->clearException();
406 }
407
408 bool HTMLPlugInImageElement::partOfSnapshotOverlay(const Node* node) const
409 {
410     DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, selector, (".snapshot-overlay", AtomicString::ConstructFromLiteral));
411     ShadowRoot* shadow = userAgentShadowRoot();
412     if (!shadow)
413         return false;
414     RefPtr<Element> snapshotLabel = shadow->querySelector(selector, ASSERT_NO_EXCEPTION);
415     return node && snapshotLabel && (node == snapshotLabel.get() || node->isDescendantOf(snapshotLabel.get()));
416 }
417
418 void HTMLPlugInImageElement::removeSnapshotTimerFired()
419 {
420     m_snapshotImage = nullptr;
421     m_isRestartedPlugin = false;
422     setNeedsStyleRecalc(SyntheticStyleChange);
423     if (renderer())
424         renderer()->repaint();
425 }
426
427 static void addPlugInsFromNodeListMatchingPlugInOrigin(HTMLPlugInImageElementList& plugInList, PassRefPtr<NodeList> collection, const String& plugInOrigin, const String& mimeType)
428 {
429     for (unsigned i = 0, length = collection->length(); i < length; i++) {
430         Node* node = collection->item(i);
431         if (is<HTMLPlugInImageElement>(*node)) {
432             HTMLPlugInImageElement& plugInImageElement = downcast<HTMLPlugInImageElement>(*node);
433             const URL& loadedURL = plugInImageElement.loadedUrl();
434             String otherMimeType = plugInImageElement.loadedMimeType();
435             if (plugInOrigin == loadedURL.host() && mimeType == otherMimeType)
436                 plugInList.append(&plugInImageElement);
437         }
438     }
439 }
440
441 void HTMLPlugInImageElement::restartSimilarPlugIns()
442 {
443     // Restart any other snapshotted plugins in the page with the same origin. Note that they
444     // may be in different frames, so traverse from the top of the document.
445
446     String plugInOrigin = m_loadedUrl.host();
447     String mimeType = loadedMimeType();
448     HTMLPlugInImageElementList similarPlugins;
449
450     if (!document().page())
451         return;
452
453     for (Frame* frame = &document().page()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
454         if (!frame->loader().subframeLoader().containsPlugins())
455             continue;
456         
457         if (!frame->document())
458             continue;
459
460         RefPtr<NodeList> plugIns = frame->document()->getElementsByTagName(embedTag.localName());
461         if (plugIns)
462             addPlugInsFromNodeListMatchingPlugInOrigin(similarPlugins, plugIns, plugInOrigin, mimeType);
463
464         plugIns = frame->document()->getElementsByTagName(objectTag.localName());
465         if (plugIns)
466             addPlugInsFromNodeListMatchingPlugInOrigin(similarPlugins, plugIns, plugInOrigin, mimeType);
467     }
468
469     for (size_t i = 0, length = similarPlugins.size(); i < length; ++i) {
470         HTMLPlugInImageElement* plugInToRestart = similarPlugins[i].get();
471         if (plugInToRestart->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
472             LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart);
473             plugInToRestart->restartSnapshottedPlugIn();
474         }
475         plugInToRestart->m_snapshotDecision = NeverSnapshot;
476     }
477 }
478
479 void HTMLPlugInImageElement::userDidClickSnapshot(PassRefPtr<MouseEvent> event, bool forwardEvent)
480 {
481     if (forwardEvent)
482         m_pendingClickEventFromSnapshot = event;
483
484     String plugInOrigin = m_loadedUrl.host();
485     if (document().page() && !SchemeRegistry::shouldTreatURLSchemeAsLocal(document().page()->mainFrame().document()->baseURL().protocol()) && document().page()->settings().autostartOriginPlugInSnapshottingEnabled())
486         document().page()->plugInClient()->didStartFromOrigin(document().page()->mainFrame().document()->baseURL().host(), plugInOrigin, loadedMimeType(), document().page()->sessionID());
487
488     LOG(Plugins, "%p User clicked on snapshotted plug-in. Restart.", this);
489     restartSnapshottedPlugIn();
490     if (forwardEvent)
491         setDisplayState(RestartingWithPendingMouseClick);
492     restartSimilarPlugIns();
493 }
494
495 void HTMLPlugInImageElement::setIsPrimarySnapshottedPlugIn(bool isPrimarySnapshottedPlugIn)
496 {
497     if (!document().page() || !document().page()->settings().primaryPlugInSnapshotDetectionEnabled() || document().page()->settings().snapshotAllPlugIns())
498         return;
499
500     if (isPrimarySnapshottedPlugIn) {
501         if (m_plugInWasCreated) {
502             LOG(Plugins, "%p Plug-in was detected as the primary element in the page. Restart.", this);
503             restartSnapshottedPlugIn();
504             restartSimilarPlugIns();
505         } else {
506             LOG(Plugins, "%p Plug-in was detected as the primary element in the page, but is not yet created. Will restart later.", this);
507             m_deferredPromotionToPrimaryPlugIn = true;
508         }
509     }
510 }
511
512 void HTMLPlugInImageElement::restartSnapshottedPlugIn()
513 {
514     if (displayState() >= RestartingWithPendingMouseClick)
515         return;
516
517     setDisplayState(Restarting);
518     setNeedsStyleRecalc(ReconstructRenderTree);
519 }
520
521 void HTMLPlugInImageElement::dispatchPendingMouseClick()
522 {
523     ASSERT(!m_simulatedMouseClickTimer.isActive());
524     m_simulatedMouseClickTimer.restart();
525 }
526
527 void HTMLPlugInImageElement::simulatedMouseClickTimerFired()
528 {
529     ASSERT(displayState() == RestartingWithPendingMouseClick);
530     ASSERT(m_pendingClickEventFromSnapshot);
531
532     setDisplayState(Playing);
533     dispatchSimulatedClick(m_pendingClickEventFromSnapshot.get(), SendMouseOverUpDownEvents, DoNotShowPressedLook);
534
535     m_pendingClickEventFromSnapshot = nullptr;
536 }
537
538 static bool documentHadRecentUserGesture(Document& document)
539 {
540     double lastKnownUserGestureTimestamp = document.lastHandledUserGestureTimestamp();
541
542     if (document.frame() != &document.page()->mainFrame() && document.page()->mainFrame().document())
543         lastKnownUserGestureTimestamp = std::max(lastKnownUserGestureTimestamp, document.page()->mainFrame().document()->lastHandledUserGestureTimestamp());
544
545     if (monotonicallyIncreasingTime() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold)
546         return true;
547
548     return false;
549 }
550
551 void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
552 {
553     if (!m_needsCheckForSizeChange || m_snapshotDecision != MaySnapshotWhenResized || documentHadRecentUserGesture(document()))
554         return;
555
556     m_needsCheckForSizeChange = false;
557     LayoutRect contentBoxRect = downcast<RenderBox>(*renderer()).contentBoxRect();
558     int contentWidth = contentBoxRect.width();
559     int contentHeight = contentBoxRect.height();
560
561     if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold)
562         return;
563
564     LOG(Plugins, "%p Plug-in originally avoided snapshotting because it was sized %dx%d. Now it is %dx%d. Tell it to snapshot.\n", this, m_sizeWhenSnapshotted.width(), m_sizeWhenSnapshotted.height(), contentWidth, contentHeight);
565     setDisplayState(WaitingForSnapshot);
566     m_snapshotDecision = Snapshotted;
567     Widget* widget = pluginWidget();
568     if (is<PluginViewBase>(widget))
569         downcast<PluginViewBase>(*widget).beginSnapshottingRunningPlugin();
570 }
571
572 static inline bool is100Percent(Length length)
573 {
574     return length.isPercentNotCalculated() && length.percent() == 100;
575 }
576     
577 static inline bool isSmallerThanTinySizingThreshold(const RenderEmbeddedObject& renderer)
578 {
579     LayoutRect contentRect = renderer.contentBoxRect();
580     return contentRect.width() <= sizingTinyDimensionThreshold || contentRect.height() <= sizingTinyDimensionThreshold;
581 }
582     
583 bool HTMLPlugInImageElement::isTopLevelFullPagePlugin(const RenderEmbeddedObject& renderer) const
584 {
585     Frame& frame = *document().frame();
586     if (!frame.isMainFrame())
587         return false;
588     
589     auto& style = renderer.style();
590     IntSize visibleSize = frame.view()->visibleSize();
591     LayoutRect contentRect = renderer.contentBoxRect();
592     int contentWidth = contentRect.width();
593     int contentHeight = contentRect.height();
594     return is100Percent(style.width()) && is100Percent(style.height()) && contentWidth * contentHeight > visibleSize.area() * sizingFullPageAreaRatioThreshold;
595 }
596     
597 void HTMLPlugInImageElement::checkSnapshotStatus()
598 {
599     if (!is<RenderSnapshottedPlugIn>(*renderer())) {
600         if (displayState() == Playing)
601             checkSizeChangeForSnapshotting();
602         return;
603     }
604     
605     // If width and height styles were previously not set and we've snapshotted the plugin we may need to restart the plugin so that its state can be updated appropriately.
606     if (!document().page()->settings().snapshotAllPlugIns() && displayState() <= DisplayingSnapshot && !m_plugInDimensionsSpecified) {
607         RenderSnapshottedPlugIn& renderer = downcast<RenderSnapshottedPlugIn>(*this->renderer());
608         if (!renderer.style().logicalWidth().isSpecified() && !renderer.style().logicalHeight().isSpecified())
609             return;
610         
611         m_plugInDimensionsSpecified = true;
612         if (isTopLevelFullPagePlugin(renderer)) {
613             m_snapshotDecision = NeverSnapshot;
614             restartSnapshottedPlugIn();
615         } else if (isSmallerThanTinySizingThreshold(renderer)) {
616             m_snapshotDecision = MaySnapshotWhenResized;
617             restartSnapshottedPlugIn();
618         }
619         return;
620     }
621     
622     // Notify the shadow root that the size changed so that we may update the overlay layout.
623     ensureUserAgentShadowRoot().dispatchEvent(Event::create(eventNames().resizeEvent, true, false));
624 }
625     
626 void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const URL& url)
627 {
628     LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
629     LOG(Plugins, "   Actual URL: %s", url.string().utf8().data());
630     LOG(Plugins, "   MIME type: %s", loadedMimeType().utf8().data());
631
632     m_loadedUrl = url;
633     m_plugInWasCreated = false;
634     m_deferredPromotionToPrimaryPlugIn = false;
635
636     if (!document().page() || !document().page()->settings().plugInSnapshottingEnabled()) {
637         m_snapshotDecision = NeverSnapshot;
638         return;
639     }
640
641     if (displayState() == Restarting) {
642         LOG(Plugins, "%p Plug-in is explicitly restarting", this);
643         m_snapshotDecision = NeverSnapshot;
644         setDisplayState(Playing);
645         return;
646     }
647
648     if (displayState() == RestartingWithPendingMouseClick) {
649         LOG(Plugins, "%p Plug-in is explicitly restarting but also waiting for a click", this);
650         m_snapshotDecision = NeverSnapshot;
651         return;
652     }
653
654     if (m_snapshotDecision == NeverSnapshot) {
655         LOG(Plugins, "%p Plug-in is blessed, allow it to start", this);
656         return;
657     }
658
659     bool inMainFrame = document().frame()->isMainFrame();
660
661     if (document().isPluginDocument() && inMainFrame) {
662         LOG(Plugins, "%p Plug-in document in main frame", this);
663         m_snapshotDecision = NeverSnapshot;
664         return;
665     }
666
667     if (ScriptController::processingUserGesture()) {
668         LOG(Plugins, "%p Script is currently processing user gesture, set to play", this);
669         m_snapshotDecision = NeverSnapshot;
670         return;
671     }
672
673     if (m_createdDuringUserGesture) {
674         LOG(Plugins, "%p Plug-in was created when processing user gesture, set to play", this);
675         m_snapshotDecision = NeverSnapshot;
676         return;
677     }
678
679     if (documentHadRecentUserGesture(document())) {
680         LOG(Plugins, "%p Plug-in was created shortly after a user gesture, set to play", this);
681         m_snapshotDecision = NeverSnapshot;
682         return;
683     }
684
685     if (document().page()->settings().snapshotAllPlugIns()) {
686         LOG(Plugins, "%p Plug-in forced to snapshot by user preference", this);
687         m_snapshotDecision = Snapshotted;
688         setDisplayState(WaitingForSnapshot);
689         return;
690     }
691
692     if (document().page()->settings().autostartOriginPlugInSnapshottingEnabled() && document().page()->plugInClient() && document().page()->plugInClient()->shouldAutoStartFromOrigin(document().page()->mainFrame().document()->baseURL().host(), url.host(), loadedMimeType())) {
693         LOG(Plugins, "%p Plug-in from (%s, %s) is marked to auto-start, set to play", this, document().page()->mainFrame().document()->baseURL().host().utf8().data(), url.host().utf8().data());
694         m_snapshotDecision = NeverSnapshot;
695         return;
696     }
697
698     if (m_loadedUrl.isEmpty() && !loadedMimeType().isEmpty()) {
699         LOG(Plugins, "%p Plug-in has no src URL but does have a valid mime type %s, set to play", this, loadedMimeType().utf8().data());
700         m_snapshotDecision = MaySnapshotWhenContentIsSet;
701         return;
702     }
703
704     if (!SchemeRegistry::shouldTreatURLSchemeAsLocal(m_loadedUrl.protocol()) && !m_loadedUrl.host().isEmpty() && m_loadedUrl.host() == document().page()->mainFrame().document()->baseURL().host()) {
705         LOG(Plugins, "%p Plug-in is served from page's domain, set to play", this);
706         m_snapshotDecision = NeverSnapshot;
707         return;
708     }
709     
710     auto& renderer = downcast<RenderEmbeddedObject>(*this->renderer());
711     LayoutRect contentRect = renderer.contentBoxRect();
712     int contentWidth = contentRect.width();
713     int contentHeight = contentRect.height();
714     
715     m_plugInDimensionsSpecified = renderer.style().logicalWidth().isSpecified() || renderer.style().logicalHeight().isSpecified();
716     
717     if (isTopLevelFullPagePlugin(renderer)) {
718         LOG(Plugins, "%p Plug-in is top level full page, set to play", this);
719         m_snapshotDecision = NeverSnapshot;
720         return;
721     }
722
723     if (isSmallerThanTinySizingThreshold(renderer)) {
724         LOG(Plugins, "%p Plug-in is very small %dx%d, set to play", this, contentWidth, contentHeight);
725         m_sizeWhenSnapshotted = IntSize(contentWidth, contentHeight);
726         m_snapshotDecision = MaySnapshotWhenResized;
727         return;
728     }
729
730     if (!document().page()->plugInClient()) {
731         LOG(Plugins, "%p There is no plug-in client. Set to wait for snapshot", this);
732         m_snapshotDecision = NeverSnapshot;
733         setDisplayState(WaitingForSnapshot);
734         return;
735     }
736
737     LOG(Plugins, "%p Plug-in from (%s, %s) is not auto-start, sized at %dx%d, set to wait for snapshot", this, document().topDocument().baseURL().host().utf8().data(), url.host().utf8().data(), contentWidth, contentHeight);
738     m_snapshotDecision = Snapshotted;
739     setDisplayState(WaitingForSnapshot);
740 }
741
742 void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget& widget)
743 {
744     m_plugInWasCreated = true;
745
746     if (is<PluginViewBase>(widget) && downcast<PluginViewBase>(widget).shouldAlwaysAutoStart()) {
747         LOG(Plugins, "%p Plug-in should auto-start, set to play", this);
748         m_snapshotDecision = NeverSnapshot;
749         setDisplayState(Playing);
750         return;
751     }
752
753     if (m_deferredPromotionToPrimaryPlugIn) {
754         LOG(Plugins, "%p Plug-in was created, previously deferred promotion to primary. Will promote", this);
755         setIsPrimarySnapshottedPlugIn(true);
756         m_deferredPromotionToPrimaryPlugIn = false;
757     }
758 }
759
760 void HTMLPlugInImageElement::defaultEventHandler(Event* event)
761 {
762     RenderElement* r = renderer();
763     if (r && r->isEmbeddedObject()) {
764         if (displayState() == WaitingForSnapshot && is<MouseEvent>(*event) && event->type() == eventNames().clickEvent) {
765             MouseEvent& mouseEvent = downcast<MouseEvent>(*event);
766             if (mouseEvent.button() == LeftButton) {
767                 userDidClickSnapshot(&mouseEvent, true);
768                 mouseEvent.setDefaultHandled();
769                 return;
770             }
771         }
772     }
773     HTMLPlugInElement::defaultEventHandler(event);
774 }
775
776 bool HTMLPlugInImageElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
777 {
778     if (HTMLPlugInElement::requestObject(url, mimeType, paramNames, paramValues))
779         return true;
780     
781     SubframeLoader& loader = document().frame()->loader().subframeLoader();
782     return loader.requestObject(*this, url, getNameAttribute(), mimeType, paramNames, paramValues);
783 }
784
785 } // namespace WebCore