09bc1656e86ceb4445601d09bb470a72cebc0c9c
[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();
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(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 }
406
407 bool HTMLPlugInImageElement::partOfSnapshotOverlay(const Node* node) const
408 {
409     DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, selector, (".snapshot-overlay", AtomicString::ConstructFromLiteral));
410     ShadowRoot* shadow = userAgentShadowRoot();
411     if (!shadow)
412         return false;
413     RefPtr<Element> snapshotLabel = shadow->querySelector(selector, ASSERT_NO_EXCEPTION);
414     return node && snapshotLabel && (node == snapshotLabel.get() || node->isDescendantOf(snapshotLabel.get()));
415 }
416
417 void HTMLPlugInImageElement::removeSnapshotTimerFired()
418 {
419     m_snapshotImage = nullptr;
420     m_isRestartedPlugin = false;
421     setNeedsStyleRecalc(SyntheticStyleChange);
422     if (renderer())
423         renderer()->repaint();
424 }
425
426 static void addPlugInsFromNodeListMatchingPlugInOrigin(HTMLPlugInImageElementList& plugInList, PassRefPtr<NodeList> collection, const String& plugInOrigin, const String& mimeType)
427 {
428     for (unsigned i = 0, length = collection->length(); i < length; i++) {
429         Node* node = collection->item(i);
430         if (is<HTMLPlugInImageElement>(*node)) {
431             HTMLPlugInImageElement& plugInImageElement = downcast<HTMLPlugInImageElement>(*node);
432             const URL& loadedURL = plugInImageElement.loadedUrl();
433             String otherMimeType = plugInImageElement.loadedMimeType();
434             if (plugInOrigin == loadedURL.host() && mimeType == otherMimeType)
435                 plugInList.append(&plugInImageElement);
436         }
437     }
438 }
439
440 void HTMLPlugInImageElement::restartSimilarPlugIns()
441 {
442     // Restart any other snapshotted plugins in the page with the same origin. Note that they
443     // may be in different frames, so traverse from the top of the document.
444
445     String plugInOrigin = m_loadedUrl.host();
446     String mimeType = loadedMimeType();
447     HTMLPlugInImageElementList similarPlugins;
448
449     if (!document().page())
450         return;
451
452     for (Frame* frame = &document().page()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
453         if (!frame->loader().subframeLoader().containsPlugins())
454             continue;
455         
456         if (!frame->document())
457             continue;
458
459         RefPtr<NodeList> plugIns = frame->document()->getElementsByTagName(embedTag.localName());
460         if (plugIns)
461             addPlugInsFromNodeListMatchingPlugInOrigin(similarPlugins, plugIns, plugInOrigin, mimeType);
462
463         plugIns = frame->document()->getElementsByTagName(objectTag.localName());
464         if (plugIns)
465             addPlugInsFromNodeListMatchingPlugInOrigin(similarPlugins, plugIns, plugInOrigin, mimeType);
466     }
467
468     for (size_t i = 0, length = similarPlugins.size(); i < length; ++i) {
469         HTMLPlugInImageElement* plugInToRestart = similarPlugins[i].get();
470         if (plugInToRestart->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
471             LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart);
472             plugInToRestart->restartSnapshottedPlugIn();
473         }
474         plugInToRestart->m_snapshotDecision = NeverSnapshot;
475     }
476 }
477
478 void HTMLPlugInImageElement::userDidClickSnapshot(PassRefPtr<MouseEvent> event, bool forwardEvent)
479 {
480     if (forwardEvent)
481         m_pendingClickEventFromSnapshot = event;
482
483     String plugInOrigin = m_loadedUrl.host();
484     if (document().page() && !SchemeRegistry::shouldTreatURLSchemeAsLocal(document().page()->mainFrame().document()->baseURL().protocol()) && document().page()->settings().autostartOriginPlugInSnapshottingEnabled())
485         document().page()->plugInClient()->didStartFromOrigin(document().page()->mainFrame().document()->baseURL().host(), plugInOrigin, loadedMimeType(), document().page()->sessionID());
486
487     LOG(Plugins, "%p User clicked on snapshotted plug-in. Restart.", this);
488     restartSnapshottedPlugIn();
489     if (forwardEvent)
490         setDisplayState(RestartingWithPendingMouseClick);
491     restartSimilarPlugIns();
492 }
493
494 void HTMLPlugInImageElement::setIsPrimarySnapshottedPlugIn(bool isPrimarySnapshottedPlugIn)
495 {
496     if (!document().page() || !document().page()->settings().primaryPlugInSnapshotDetectionEnabled() || document().page()->settings().snapshotAllPlugIns())
497         return;
498
499     if (isPrimarySnapshottedPlugIn) {
500         if (m_plugInWasCreated) {
501             LOG(Plugins, "%p Plug-in was detected as the primary element in the page. Restart.", this);
502             restartSnapshottedPlugIn();
503             restartSimilarPlugIns();
504         } else {
505             LOG(Plugins, "%p Plug-in was detected as the primary element in the page, but is not yet created. Will restart later.", this);
506             m_deferredPromotionToPrimaryPlugIn = true;
507         }
508     }
509 }
510
511 void HTMLPlugInImageElement::restartSnapshottedPlugIn()
512 {
513     if (displayState() >= RestartingWithPendingMouseClick)
514         return;
515
516     setDisplayState(Restarting);
517     setNeedsStyleRecalc(ReconstructRenderTree);
518 }
519
520 void HTMLPlugInImageElement::dispatchPendingMouseClick()
521 {
522     ASSERT(!m_simulatedMouseClickTimer.isActive());
523     m_simulatedMouseClickTimer.restart();
524 }
525
526 void HTMLPlugInImageElement::simulatedMouseClickTimerFired()
527 {
528     ASSERT(displayState() == RestartingWithPendingMouseClick);
529     ASSERT(m_pendingClickEventFromSnapshot);
530
531     setDisplayState(Playing);
532     dispatchSimulatedClick(m_pendingClickEventFromSnapshot.get(), SendMouseOverUpDownEvents, DoNotShowPressedLook);
533
534     m_pendingClickEventFromSnapshot = nullptr;
535 }
536
537 static bool documentHadRecentUserGesture(Document& document)
538 {
539     double lastKnownUserGestureTimestamp = document.lastHandledUserGestureTimestamp();
540
541     if (document.frame() != &document.page()->mainFrame() && document.page()->mainFrame().document())
542         lastKnownUserGestureTimestamp = std::max(lastKnownUserGestureTimestamp, document.page()->mainFrame().document()->lastHandledUserGestureTimestamp());
543
544     if (monotonicallyIncreasingTime() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold)
545         return true;
546
547     return false;
548 }
549
550 void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
551 {
552     if (!m_needsCheckForSizeChange || m_snapshotDecision != MaySnapshotWhenResized || documentHadRecentUserGesture(document()))
553         return;
554
555     m_needsCheckForSizeChange = false;
556     LayoutRect contentBoxRect = downcast<RenderBox>(*renderer()).contentBoxRect();
557     int contentWidth = contentBoxRect.width();
558     int contentHeight = contentBoxRect.height();
559
560     if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold)
561         return;
562
563     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);
564     setDisplayState(WaitingForSnapshot);
565     m_snapshotDecision = Snapshotted;
566     Widget* widget = pluginWidget();
567     if (is<PluginViewBase>(widget))
568         downcast<PluginViewBase>(*widget).beginSnapshottingRunningPlugin();
569 }
570
571 static inline bool is100Percent(Length length)
572 {
573     return length.isPercentNotCalculated() && length.percent() == 100;
574 }
575     
576 static inline bool isSmallerThanTinySizingThreshold(const RenderEmbeddedObject& renderer)
577 {
578     LayoutRect contentRect = renderer.contentBoxRect();
579     return contentRect.width() <= sizingTinyDimensionThreshold || contentRect.height() <= sizingTinyDimensionThreshold;
580 }
581     
582 bool HTMLPlugInImageElement::isTopLevelFullPagePlugin(const RenderEmbeddedObject& renderer) const
583 {
584     Frame& frame = *document().frame();
585     if (!frame.isMainFrame())
586         return false;
587     
588     auto& style = renderer.style();
589     IntSize visibleSize = frame.view()->visibleSize();
590     LayoutRect contentRect = renderer.contentBoxRect();
591     int contentWidth = contentRect.width();
592     int contentHeight = contentRect.height();
593     return is100Percent(style.width()) && is100Percent(style.height()) && contentWidth * contentHeight > visibleSize.area() * sizingFullPageAreaRatioThreshold;
594 }
595     
596 void HTMLPlugInImageElement::checkSnapshotStatus()
597 {
598     if (!is<RenderSnapshottedPlugIn>(*renderer())) {
599         if (displayState() == Playing)
600             checkSizeChangeForSnapshotting();
601         return;
602     }
603     
604     // 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.
605     if (!document().page()->settings().snapshotAllPlugIns() && displayState() <= DisplayingSnapshot && !m_plugInDimensionsSpecified) {
606         RenderSnapshottedPlugIn& renderer = downcast<RenderSnapshottedPlugIn>(*this->renderer());
607         if (!renderer.style().logicalWidth().isSpecified() && !renderer.style().logicalHeight().isSpecified())
608             return;
609         
610         m_plugInDimensionsSpecified = true;
611         if (isTopLevelFullPagePlugin(renderer)) {
612             m_snapshotDecision = NeverSnapshot;
613             restartSnapshottedPlugIn();
614         } else if (isSmallerThanTinySizingThreshold(renderer)) {
615             m_snapshotDecision = MaySnapshotWhenResized;
616             restartSnapshottedPlugIn();
617         }
618         return;
619     }
620     
621     // Notify the shadow root that the size changed so that we may update the overlay layout.
622     ensureUserAgentShadowRoot().dispatchEvent(Event::create(eventNames().resizeEvent, true, false));
623 }
624     
625 void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const URL& url)
626 {
627     LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
628     LOG(Plugins, "   Actual URL: %s", url.string().utf8().data());
629     LOG(Plugins, "   MIME type: %s", loadedMimeType().utf8().data());
630
631     m_loadedUrl = url;
632     m_plugInWasCreated = false;
633     m_deferredPromotionToPrimaryPlugIn = false;
634
635     if (!document().page() || !document().page()->settings().plugInSnapshottingEnabled()) {
636         m_snapshotDecision = NeverSnapshot;
637         return;
638     }
639
640     if (displayState() == Restarting) {
641         LOG(Plugins, "%p Plug-in is explicitly restarting", this);
642         m_snapshotDecision = NeverSnapshot;
643         setDisplayState(Playing);
644         return;
645     }
646
647     if (displayState() == RestartingWithPendingMouseClick) {
648         LOG(Plugins, "%p Plug-in is explicitly restarting but also waiting for a click", this);
649         m_snapshotDecision = NeverSnapshot;
650         return;
651     }
652
653     if (m_snapshotDecision == NeverSnapshot) {
654         LOG(Plugins, "%p Plug-in is blessed, allow it to start", this);
655         return;
656     }
657
658     bool inMainFrame = document().frame()->isMainFrame();
659
660     if (document().isPluginDocument() && inMainFrame) {
661         LOG(Plugins, "%p Plug-in document in main frame", this);
662         m_snapshotDecision = NeverSnapshot;
663         return;
664     }
665
666     if (ScriptController::processingUserGesture()) {
667         LOG(Plugins, "%p Script is currently processing user gesture, set to play", this);
668         m_snapshotDecision = NeverSnapshot;
669         return;
670     }
671
672     if (m_createdDuringUserGesture) {
673         LOG(Plugins, "%p Plug-in was created when processing user gesture, set to play", this);
674         m_snapshotDecision = NeverSnapshot;
675         return;
676     }
677
678     if (documentHadRecentUserGesture(document())) {
679         LOG(Plugins, "%p Plug-in was created shortly after a user gesture, set to play", this);
680         m_snapshotDecision = NeverSnapshot;
681         return;
682     }
683
684     if (document().page()->settings().snapshotAllPlugIns()) {
685         LOG(Plugins, "%p Plug-in forced to snapshot by user preference", this);
686         m_snapshotDecision = Snapshotted;
687         setDisplayState(WaitingForSnapshot);
688         return;
689     }
690
691     if (document().page()->settings().autostartOriginPlugInSnapshottingEnabled() && document().page()->plugInClient() && document().page()->plugInClient()->shouldAutoStartFromOrigin(document().page()->mainFrame().document()->baseURL().host(), url.host(), loadedMimeType())) {
692         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());
693         m_snapshotDecision = NeverSnapshot;
694         return;
695     }
696
697     if (m_loadedUrl.isEmpty() && !loadedMimeType().isEmpty()) {
698         LOG(Plugins, "%p Plug-in has no src URL but does have a valid mime type %s, set to play", this, loadedMimeType().utf8().data());
699         m_snapshotDecision = MaySnapshotWhenContentIsSet;
700         return;
701     }
702
703     if (!SchemeRegistry::shouldTreatURLSchemeAsLocal(m_loadedUrl.protocol()) && !m_loadedUrl.host().isEmpty() && m_loadedUrl.host() == document().page()->mainFrame().document()->baseURL().host()) {
704         LOG(Plugins, "%p Plug-in is served from page's domain, set to play", this);
705         m_snapshotDecision = NeverSnapshot;
706         return;
707     }
708     
709     auto& renderer = downcast<RenderEmbeddedObject>(*this->renderer());
710     LayoutRect contentRect = renderer.contentBoxRect();
711     int contentWidth = contentRect.width();
712     int contentHeight = contentRect.height();
713     
714     m_plugInDimensionsSpecified = renderer.style().logicalWidth().isSpecified() || renderer.style().logicalHeight().isSpecified();
715     
716     if (isTopLevelFullPagePlugin(renderer)) {
717         LOG(Plugins, "%p Plug-in is top level full page, set to play", this);
718         m_snapshotDecision = NeverSnapshot;
719         return;
720     }
721
722     if (isSmallerThanTinySizingThreshold(renderer)) {
723         LOG(Plugins, "%p Plug-in is very small %dx%d, set to play", this, contentWidth, contentHeight);
724         m_sizeWhenSnapshotted = IntSize(contentWidth, contentHeight);
725         m_snapshotDecision = MaySnapshotWhenResized;
726         return;
727     }
728
729     if (!document().page()->plugInClient()) {
730         LOG(Plugins, "%p There is no plug-in client. Set to wait for snapshot", this);
731         m_snapshotDecision = NeverSnapshot;
732         setDisplayState(WaitingForSnapshot);
733         return;
734     }
735
736     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);
737     m_snapshotDecision = Snapshotted;
738     setDisplayState(WaitingForSnapshot);
739 }
740
741 void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget& widget)
742 {
743     m_plugInWasCreated = true;
744
745     if (is<PluginViewBase>(widget) && downcast<PluginViewBase>(widget).shouldAlwaysAutoStart()) {
746         LOG(Plugins, "%p Plug-in should auto-start, set to play", this);
747         m_snapshotDecision = NeverSnapshot;
748         setDisplayState(Playing);
749         return;
750     }
751
752     if (m_deferredPromotionToPrimaryPlugIn) {
753         LOG(Plugins, "%p Plug-in was created, previously deferred promotion to primary. Will promote", this);
754         setIsPrimarySnapshottedPlugIn(true);
755         m_deferredPromotionToPrimaryPlugIn = false;
756     }
757 }
758
759 void HTMLPlugInImageElement::defaultEventHandler(Event* event)
760 {
761     RenderElement* r = renderer();
762     if (r && r->isEmbeddedObject()) {
763         if (displayState() == WaitingForSnapshot && is<MouseEvent>(*event) && event->type() == eventNames().clickEvent) {
764             MouseEvent& mouseEvent = downcast<MouseEvent>(*event);
765             if (mouseEvent.button() == LeftButton) {
766                 userDidClickSnapshot(&mouseEvent, true);
767                 mouseEvent.setDefaultHandled();
768                 return;
769             }
770         }
771     }
772     HTMLPlugInElement::defaultEventHandler(event);
773 }
774
775 bool HTMLPlugInImageElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
776 {
777     if (HTMLPlugInElement::requestObject(url, mimeType, paramNames, paramValues))
778         return true;
779     
780     SubframeLoader& loader = document().frame()->loader().subframeLoader();
781     return loader.requestObject(*this, url, getNameAttribute(), mimeType, paramNames, paramValues);
782 }
783
784 } // namespace WebCore