EWS should have a way to retry a patch
[WebKit-https.git] / Source / WebCore / html / HTMLPlugInImageElement.cpp
1 /*
2  * Copyright (C) 2008-2019 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 "CommonVM.h"
27 #include "ContentSecurityPolicy.h"
28 #include "EventNames.h"
29 #include "Frame.h"
30 #include "FrameLoaderClient.h"
31 #include "HTMLImageLoader.h"
32 #include "JSDOMConvertBoolean.h"
33 #include "JSDOMConvertInterface.h"
34 #include "JSDOMConvertStrings.h"
35 #include "JSShadowRoot.h"
36 #include "LegacySchemeRegistry.h"
37 #include "LocalizedStrings.h"
38 #include "Logging.h"
39 #include "MouseEvent.h"
40 #include "Page.h"
41 #include "PlatformMouseEvent.h"
42 #include "PlugInClient.h"
43 #include "PluginViewBase.h"
44 #include "RenderImage.h"
45 #include "RenderSnapshottedPlugIn.h"
46 #include "RenderTreeUpdater.h"
47 #include "ScriptController.h"
48 #include "SecurityOrigin.h"
49 #include "Settings.h"
50 #include "ShadowRoot.h"
51 #include "StyleTreeResolver.h"
52 #include "SubframeLoader.h"
53 #include "TypedElementDescendantIterator.h"
54 #include "UserGestureIndicator.h"
55 #include <JavaScriptCore/CatchScope.h>
56 #include <wtf/IsoMallocInlines.h>
57
58 namespace WebCore {
59
60 WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLPlugInImageElement);
61
62 static const int sizingTinyDimensionThreshold = 40;
63 static const float sizingFullPageAreaRatioThreshold = 0.96;
64 static const Seconds autostartSoonAfterUserGestureThreshold = 5_s;
65
66 // This delay should not exceed the snapshot delay in PluginView.cpp
67 static const Seconds simulatedMouseClickTimerDelay { 750_ms };
68
69 #if PLATFORM(COCOA)
70 static const Seconds removeSnapshotTimerDelay { 1500_ms };
71 #endif
72
73 static const String titleText(Page& page, const String& mimeType)
74 {
75     if (mimeType.isEmpty())
76         return snapshottedPlugInLabelTitle();
77
78     // FIXME: It's not consistent to get a string from the page's chrome client, but then cache it globally.
79     // If it's global, it should come from elsewhere. If it's per-page then it should be cached per page.
80     static NeverDestroyed<HashMap<String, String>> mimeTypeToLabelTitleMap;
81     return mimeTypeToLabelTitleMap.get().ensure(mimeType, [&] {
82         auto title = page.chrome().client().plugInStartLabelTitle(mimeType);
83         if (!title.isEmpty())
84             return title;
85         return snapshottedPlugInLabelTitle();
86     }).iterator->value;
87 };
88
89 static const String subtitleText(Page& page, const String& mimeType)
90 {
91     if (mimeType.isEmpty())
92         return snapshottedPlugInLabelSubtitle();
93
94     // FIXME: It's not consistent to get a string from the page's chrome client, but then cache it globally.
95     // If it's global, it should come from elsewhere. If it's per-page then it should be cached per page.
96     static NeverDestroyed<HashMap<String, String>> mimeTypeToLabelSubtitleMap;
97     return mimeTypeToLabelSubtitleMap.get().ensure(mimeType, [&] {
98         auto subtitle = page.chrome().client().plugInStartLabelSubtitle(mimeType);
99         if (!subtitle.isEmpty())
100             return subtitle;
101         return snapshottedPlugInLabelSubtitle();
102     }).iterator->value;
103 };
104
105 HTMLPlugInImageElement::HTMLPlugInImageElement(const QualifiedName& tagName, Document& document)
106     : HTMLPlugInElement(tagName, document)
107     , m_simulatedMouseClickTimer(*this, &HTMLPlugInImageElement::simulatedMouseClickTimerFired, simulatedMouseClickTimerDelay)
108     , m_removeSnapshotTimer(*this, &HTMLPlugInImageElement::removeSnapshotTimerFired)
109     , m_createdDuringUserGesture(UserGestureIndicator::processingUserGesture())
110 {
111     setHasCustomStyleResolveCallbacks();
112 }
113
114 void HTMLPlugInImageElement::finishCreating()
115 {
116     scheduleUpdateForAfterStyleResolution();
117 }
118
119 HTMLPlugInImageElement::~HTMLPlugInImageElement()
120 {
121     if (m_needsDocumentActivationCallbacks)
122         document().unregisterForDocumentSuspensionCallbacks(*this);
123 }
124
125 void HTMLPlugInImageElement::setDisplayState(DisplayState state)
126 {
127 #if PLATFORM(COCOA)
128     if (state == RestartingWithPendingMouseClick || state == Restarting) {
129         m_isRestartedPlugin = true;
130         m_snapshotDecision = NeverSnapshot;
131         invalidateStyleAndLayerComposition();
132         if (displayState() == DisplayingSnapshot)
133             m_removeSnapshotTimer.startOneShot(removeSnapshotTimerDelay);
134     }
135 #endif
136
137     HTMLPlugInElement::setDisplayState(state);
138 }
139
140 RenderEmbeddedObject* HTMLPlugInImageElement::renderEmbeddedObject() const
141 {
142     // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers when using fallback content.
143     return is<RenderEmbeddedObject>(renderer()) ? downcast<RenderEmbeddedObject>(renderer()) : nullptr;
144 }
145
146 bool HTMLPlugInImageElement::isImageType()
147 {
148     if (m_serviceType.isEmpty() && protocolIs(m_url, "data"))
149         m_serviceType = mimeTypeFromDataURL(m_url);
150
151     if (auto frame = makeRefPtr(document().frame()))
152         return frame->loader().client().objectContentType(document().completeURL(m_url), m_serviceType) == ObjectContentType::Image;
153
154     return Image::supportsType(m_serviceType);
155 }
156
157 bool HTMLPlugInImageElement::canLoadURL(const String& relativeURL) const
158 {
159     return canLoadURL(document().completeURL(relativeURL));
160 }
161
162 // Note that unlike HTMLFrameElementBase::canLoadURL this uses SecurityOrigin::canAccess.
163 bool HTMLPlugInImageElement::canLoadURL(const URL& completeURL) const
164 {
165     if (WTF::protocolIsJavaScript(completeURL)) {
166         RefPtr<Document> contentDocument = this->contentDocument();
167         if (contentDocument && !document().securityOrigin().canAccess(contentDocument->securityOrigin()))
168             return false;
169     }
170
171     return !isProhibitedSelfReference(completeURL);
172 }
173
174 // We don't use m_url, or m_serviceType as they may not be the final values
175 // that <object> uses depending on <param> values.
176 bool HTMLPlugInImageElement::wouldLoadAsPlugIn(const String& relativeURL, const String& serviceType)
177 {
178     ASSERT(document().frame());
179     URL completedURL;
180     if (!relativeURL.isEmpty())
181         completedURL = document().completeURL(relativeURL);
182     return document().frame()->loader().client().objectContentType(completedURL, serviceType) == ObjectContentType::PlugIn;
183 }
184
185 RenderPtr<RenderElement> HTMLPlugInImageElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition& insertionPosition)
186 {
187     ASSERT(document().backForwardCacheState() == Document::NotInBackForwardCache);
188
189     if (displayState() >= PreparingPluginReplacement)
190         return HTMLPlugInElement::createElementRenderer(WTFMove(style), insertionPosition);
191
192     // Once a plug-in element creates its renderer, it needs to be told when the document goes
193     // inactive or reactivates so it can clear the renderer before going into the back/forward cache.
194     if (!m_needsDocumentActivationCallbacks) {
195         m_needsDocumentActivationCallbacks = true;
196         document().registerForDocumentSuspensionCallbacks(*this);
197     }
198
199     if (displayState() == DisplayingSnapshot) {
200         auto renderSnapshottedPlugIn = createRenderer<RenderSnapshottedPlugIn>(*this, WTFMove(style));
201         renderSnapshottedPlugIn->updateSnapshot(m_snapshotImage.get());
202         return renderSnapshottedPlugIn;
203     }
204
205     if (useFallbackContent())
206         return RenderElement::createFor(*this, WTFMove(style));
207
208     if (isImageType())
209         return createRenderer<RenderImage>(*this, WTFMove(style));
210
211     return HTMLPlugInElement::createElementRenderer(WTFMove(style), insertionPosition);
212 }
213
214 bool HTMLPlugInImageElement::childShouldCreateRenderer(const Node& child) const
215 {
216     if (is<RenderSnapshottedPlugIn>(renderer()) && !hasShadowRootParent(child))
217         return false;
218
219     return HTMLPlugInElement::childShouldCreateRenderer(child);
220 }
221
222 void HTMLPlugInImageElement::willRecalcStyle(Style::Change change)
223 {
224     // Make sure style recalcs scheduled by a child shadow tree don't trigger reconstruction and cause flicker.
225     if (change == Style::NoChange && styleValidity() == Style::Validity::Valid)
226         return;
227
228     // FIXME: There shoudn't be need to force render tree reconstruction here.
229     // It is only done because loading and load event dispatching is tied to render tree construction.
230     if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType() && displayState() != DisplayingSnapshot)
231         invalidateStyleAndRenderersForSubtree();
232 }
233
234 void HTMLPlugInImageElement::didRecalcStyle(Style::Change styleChange)
235 {
236     scheduleUpdateForAfterStyleResolution();
237
238     HTMLPlugInElement::didRecalcStyle(styleChange);
239 }
240
241 void HTMLPlugInImageElement::didAttachRenderers()
242 {
243     m_needsWidgetUpdate = true;
244     scheduleUpdateForAfterStyleResolution();
245
246     // Update the RenderImageResource of the associated RenderImage.
247     if (m_imageLoader && is<RenderImage>(renderer())) {
248         auto& renderImageResource = downcast<RenderImage>(*renderer()).imageResource();
249         if (!renderImageResource.cachedImage())
250             renderImageResource.setCachedImage(m_imageLoader->image());
251     }
252
253     HTMLPlugInElement::didAttachRenderers();
254 }
255
256 void HTMLPlugInImageElement::willDetachRenderers()
257 {
258     auto widget = makeRefPtr(pluginWidget(PluginLoadingPolicy::DoNotLoad));
259     if (is<PluginViewBase>(widget))
260         downcast<PluginViewBase>(*widget).willDetachRenderer();
261
262     HTMLPlugInElement::willDetachRenderers();
263 }
264
265 void HTMLPlugInImageElement::scheduleUpdateForAfterStyleResolution()
266 {
267     if (m_hasUpdateScheduledForAfterStyleResolution)
268         return;
269
270     document().incrementLoadEventDelayCount();
271
272     m_hasUpdateScheduledForAfterStyleResolution = true;
273
274     Style::queuePostResolutionCallback([protectedThis = makeRef(*this)] {
275         protectedThis->updateAfterStyleResolution();
276     });
277 }
278
279 void HTMLPlugInImageElement::updateAfterStyleResolution()
280 {
281     m_hasUpdateScheduledForAfterStyleResolution = false;
282
283     // Do this after style resolution, since the image or widget load might complete synchronously
284     // and cause us to re-enter otherwise. Also, we can't really answer the question "do I have a renderer"
285     // accurately until after style resolution.
286
287     if (renderer() && !useFallbackContent()) {
288         if (isImageType()) {
289             if (!m_imageLoader)
290                 m_imageLoader = makeUnique<HTMLImageLoader>(*this);
291             if (m_needsImageReload)
292                 m_imageLoader->updateFromElementIgnoringPreviousError();
293             else
294                 m_imageLoader->updateFromElement();
295         } else {
296             if (needsWidgetUpdate() && renderEmbeddedObject() && !renderEmbeddedObject()->isPluginUnavailable())
297                 updateWidget(CreatePlugins::No);
298         }
299     }
300
301     // Either we reloaded the image just now, or we had some reason not to.
302     // Either way, clear the flag now, since we don't need to remember to try again.
303     m_needsImageReload = false;
304
305     document().decrementLoadEventDelayCount();
306 }
307
308 void HTMLPlugInImageElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
309 {
310     ASSERT_WITH_SECURITY_IMPLICATION(&document() == &newDocument);
311     if (m_needsDocumentActivationCallbacks) {
312         oldDocument.unregisterForDocumentSuspensionCallbacks(*this);
313         newDocument.registerForDocumentSuspensionCallbacks(*this);
314     }
315
316     if (m_imageLoader)
317         m_imageLoader->elementDidMoveToNewDocument();
318
319     if (m_hasUpdateScheduledForAfterStyleResolution) {
320         oldDocument.decrementLoadEventDelayCount();
321         newDocument.incrementLoadEventDelayCount();
322     }
323
324     HTMLPlugInElement::didMoveToNewDocument(oldDocument, newDocument);
325 }
326
327 void HTMLPlugInImageElement::prepareForDocumentSuspension()
328 {
329     if (renderer())
330         RenderTreeUpdater::tearDownRenderers(*this);
331
332     HTMLPlugInElement::prepareForDocumentSuspension();
333 }
334
335 void HTMLPlugInImageElement::resumeFromDocumentSuspension()
336 {
337     scheduleUpdateForAfterStyleResolution();
338     invalidateStyleAndRenderersForSubtree();
339
340     HTMLPlugInElement::resumeFromDocumentSuspension();
341 }
342
343 void HTMLPlugInImageElement::updateSnapshot(Image* image)
344 {
345     if (displayState() > DisplayingSnapshot)
346         return;
347
348     m_snapshotImage = image;
349
350     auto* renderer = this->renderer();
351     if (!renderer)
352         return;
353
354     if (is<RenderSnapshottedPlugIn>(*renderer)) {
355         downcast<RenderSnapshottedPlugIn>(*renderer).updateSnapshot(image);
356         return;
357     }
358
359     if (is<RenderEmbeddedObject>(*renderer))
360         renderer->repaint();
361 }
362
363 static DOMWrapperWorld& plugInImageElementIsolatedWorld()
364 {
365     static auto& isolatedWorld = DOMWrapperWorld::create(commonVM()).leakRef();
366     return isolatedWorld;
367 }
368
369 void HTMLPlugInImageElement::didAddUserAgentShadowRoot(ShadowRoot& root)
370 {
371     HTMLPlugInElement::didAddUserAgentShadowRoot(root);
372     if (displayState() >= PreparingPluginReplacement)
373         return;
374
375     auto* page = document().page();
376     if (!page)
377         return;
378
379     // Reset any author styles that may apply as we only want explicit
380     // styles defined in the injected user agents stylesheets to specify
381     // the look-and-feel of the snapshotted plug-in overlay. 
382     root.setResetStyleInheritance(true);
383     
384     String mimeType = serviceType();
385
386     auto& isolatedWorld = plugInImageElementIsolatedWorld();
387     document().ensurePlugInsInjectedScript(isolatedWorld);
388
389     auto& scriptController = document().frame()->script();
390     auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(isolatedWorld));
391
392     auto& vm = globalObject.vm();
393     JSC::JSLockHolder lock(vm);
394     auto scope = DECLARE_CATCH_SCOPE(vm);
395     auto& state = *globalObject.globalExec();
396
397     JSC::MarkedArgumentBuffer argList;
398     argList.append(toJS<IDLInterface<ShadowRoot>>(state, globalObject, root));
399     argList.append(toJS<IDLDOMString>(state, titleText(*page, mimeType)));
400     argList.append(toJS<IDLDOMString>(state, subtitleText(*page, mimeType)));
401     
402     // This parameter determines whether or not the snapshot overlay should always be visible over the plugin snapshot.
403     // If no snapshot was found then we want the overlay to be visible.
404     argList.append(toJS<IDLBoolean>(!m_snapshotImage));
405     ASSERT(!argList.hasOverflowed());
406
407     // It is expected the JS file provides a createOverlay(shadowRoot, title, subtitle) function.
408     auto* overlay = globalObject.get(&state, JSC::Identifier::fromString(vm, "createOverlay")).toObject(&state);
409     ASSERT(!overlay == !!scope.exception());
410     if (!overlay) {
411         scope.clearException();
412         return;
413     }
414     JSC::CallData callData;
415     auto callType = overlay->methodTable(vm)->getCallData(overlay, callData);
416     if (callType == JSC::CallType::None)
417         return;
418
419     call(&state, overlay, callType, callData, &globalObject, argList);
420     scope.clearException();
421 }
422
423 bool HTMLPlugInImageElement::partOfSnapshotOverlay(const EventTarget* target) const
424 {
425     static NeverDestroyed<AtomString> selector(".snapshot-overlay", AtomString::ConstructFromLiteral);
426     auto shadow = userAgentShadowRoot();
427     if (!shadow)
428         return false;
429     if (!is<Node>(target))
430         return false;
431     auto queryResult = shadow->querySelector(selector.get());
432     if (queryResult.hasException())
433         return false;
434     auto snapshotLabel = makeRefPtr(queryResult.releaseReturnValue());
435     return snapshotLabel && snapshotLabel->contains(downcast<Node>(target));
436 }
437
438 void HTMLPlugInImageElement::removeSnapshotTimerFired()
439 {
440     m_snapshotImage = nullptr;
441     m_isRestartedPlugin = false;
442     invalidateStyleAndLayerComposition();
443     if (renderer())
444         renderer()->repaint();
445 }
446
447 void HTMLPlugInImageElement::restartSimilarPlugIns()
448 {
449     // Restart any other snapshotted plugins in the page with the same origin. Note that they
450     // may be in different frames, so traverse from the top of the document.
451
452     auto plugInOrigin = m_loadedUrl.host();
453     String mimeType = serviceType();
454     Vector<Ref<HTMLPlugInImageElement>> similarPlugins;
455
456     if (!document().page())
457         return;
458
459     for (RefPtr<Frame> frame = &document().page()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
460         if (!frame->loader().subframeLoader().containsPlugins())
461             continue;
462         
463         if (!frame->document())
464             continue;
465
466         for (auto& element : descendantsOfType<HTMLPlugInImageElement>(*frame->document())) {
467             if (plugInOrigin == element.loadedUrl().host() && mimeType == element.serviceType())
468                 similarPlugins.append(element);
469         }
470     }
471
472     for (auto& plugInToRestart : similarPlugins) {
473         if (plugInToRestart->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
474             LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart.ptr());
475             plugInToRestart->restartSnapshottedPlugIn();
476         }
477         plugInToRestart->m_snapshotDecision = NeverSnapshot;
478     }
479 }
480
481 void HTMLPlugInImageElement::userDidClickSnapshot(MouseEvent& event, bool forwardEvent)
482 {
483     if (forwardEvent)
484         m_pendingClickEventFromSnapshot = &event;
485
486     auto plugInOrigin = m_loadedUrl.host();
487     if (document().page() && !LegacySchemeRegistry::shouldTreatURLSchemeAsLocal(document().page()->mainFrame().document()->baseURL().protocol().toStringWithoutCopying()) && document().page()->settings().autostartOriginPlugInSnapshottingEnabled())
488         document().page()->plugInClient()->didStartFromOrigin(document().page()->mainFrame().document()->baseURL().host().toString(), plugInOrigin.toString(), serviceType());
489
490     LOG(Plugins, "%p User clicked on snapshotted plug-in. Restart.", this);
491     restartSnapshottedPlugIn();
492     if (forwardEvent)
493         setDisplayState(RestartingWithPendingMouseClick);
494     restartSimilarPlugIns();
495 }
496
497 void HTMLPlugInImageElement::setIsPrimarySnapshottedPlugIn(bool isPrimarySnapshottedPlugIn)
498 {
499     if (!document().page() || !document().page()->settings().primaryPlugInSnapshotDetectionEnabled() || document().page()->settings().snapshotAllPlugIns())
500         return;
501
502     if (isPrimarySnapshottedPlugIn) {
503         if (m_plugInWasCreated) {
504             LOG(Plugins, "%p Plug-in was detected as the primary element in the page. Restart.", this);
505             restartSnapshottedPlugIn();
506             restartSimilarPlugIns();
507         } else {
508             LOG(Plugins, "%p Plug-in was detected as the primary element in the page, but is not yet created. Will restart later.", this);
509             m_deferredPromotionToPrimaryPlugIn = true;
510         }
511     }
512 }
513
514 void HTMLPlugInImageElement::restartSnapshottedPlugIn()
515 {
516     if (displayState() >= RestartingWithPendingMouseClick)
517         return;
518
519     setDisplayState(Restarting);
520     invalidateStyleAndRenderersForSubtree();
521 }
522
523 void HTMLPlugInImageElement::dispatchPendingMouseClick()
524 {
525     ASSERT(!m_simulatedMouseClickTimer.isActive());
526     m_simulatedMouseClickTimer.restart();
527 }
528
529 void HTMLPlugInImageElement::simulatedMouseClickTimerFired()
530 {
531     ASSERT(displayState() == RestartingWithPendingMouseClick);
532     ASSERT(m_pendingClickEventFromSnapshot);
533
534     setDisplayState(Playing);
535     dispatchSimulatedClick(m_pendingClickEventFromSnapshot.get(), SendMouseOverUpDownEvents, DoNotShowPressedLook);
536
537     m_pendingClickEventFromSnapshot = nullptr;
538 }
539
540 static bool documentHadRecentUserGesture(Document& document)
541 {
542     MonotonicTime lastKnownUserGestureTimestamp = document.lastHandledUserGestureTimestamp();
543     if (document.frame() != &document.page()->mainFrame() && document.page()->mainFrame().document())
544         lastKnownUserGestureTimestamp = std::max(lastKnownUserGestureTimestamp, document.page()->mainFrame().document()->lastHandledUserGestureTimestamp());
545
546     return MonotonicTime::now() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold;
547 }
548
549 void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
550 {
551     if (!m_needsCheckForSizeChange || m_snapshotDecision != MaySnapshotWhenResized || documentHadRecentUserGesture(document()))
552         return;
553
554     m_needsCheckForSizeChange = false;
555
556     auto 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     auto widget = makeRefPtr(pluginWidget());
567     if (is<PluginViewBase>(widget))
568         downcast<PluginViewBase>(*widget).beginSnapshottingRunningPlugin();
569 }
570
571 static inline bool is100Percent(Length length)
572 {
573     return length.isPercent() && length.percent() == 100;
574 }
575     
576 static inline bool isSmallerThanTinySizingThreshold(const RenderEmbeddedObject& renderer)
577 {
578     auto contentRect = renderer.contentBoxRect();
579     return contentRect.width() <= sizingTinyDimensionThreshold || contentRect.height() <= sizingTinyDimensionThreshold;
580 }
581     
582 bool HTMLPlugInImageElement::isTopLevelFullPagePlugin(const RenderEmbeddedObject& renderer) const
583 {
584     ASSERT(document().frame());
585     auto& frame = *document().frame();
586     if (!frame.isMainFrame())
587         return false;
588     
589     auto& style = renderer.style();
590     auto visibleSize = frame.view()->visibleSize();
591     auto contentRect = renderer.contentBoxRect();
592     float contentWidth = contentRect.width();
593     float contentHeight = contentRect.height();
594     return is100Percent(style.width()) && is100Percent(style.height()) && contentWidth * contentHeight > visibleSize.area().unsafeGet() * 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         auto& 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, Event::CanBubble::Yes, Event::IsCancelable::No));
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", serviceType().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 (UserGestureIndicator::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().toString(), url.host().toString(), serviceType())) {
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() && !serviceType().isEmpty()) {
699         LOG(Plugins, "%p Plug-in has no src URL but does have a valid mime type %s, set to play", this, serviceType().utf8().data());
700         m_snapshotDecision = MaySnapshotWhenContentIsSet;
701         return;
702     }
703
704     if (!LegacySchemeRegistry::shouldTreatURLSchemeAsLocal(m_loadedUrl.protocol().toStringWithoutCopying()) && !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     auto 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     if (is<RenderEmbeddedObject>(renderer()) && displayState() == WaitingForSnapshot && is<MouseEvent>(event) && event.type() == eventNames().clickEvent) {
763         auto& mouseEvent = downcast<MouseEvent>(event);
764         if (mouseEvent.button() == LeftButton) {
765             userDidClickSnapshot(mouseEvent, true);
766             mouseEvent.setDefaultHandled();
767             return;
768         }
769     }
770     HTMLPlugInElement::defaultEventHandler(event);
771 }
772
773 bool HTMLPlugInImageElement::canLoadPlugInContent(const String& relativeURL, const String& mimeType) const
774 {
775     // Elements in user agent show tree should load whatever the embedding document policy is.
776     if (isInUserAgentShadowTree())
777         return true;
778
779     URL completedURL;
780     if (!relativeURL.isEmpty())
781         completedURL = document().completeURL(relativeURL);
782
783     ASSERT(document().contentSecurityPolicy());
784     const ContentSecurityPolicy& contentSecurityPolicy = *document().contentSecurityPolicy();
785
786     contentSecurityPolicy.upgradeInsecureRequestIfNeeded(completedURL, ContentSecurityPolicy::InsecureRequestType::Load);
787
788     if (!contentSecurityPolicy.allowObjectFromSource(completedURL))
789         return false;
790
791     auto& declaredMimeType = document().isPluginDocument() && document().ownerElement() ?
792         document().ownerElement()->attributeWithoutSynchronization(HTMLNames::typeAttr) : attributeWithoutSynchronization(HTMLNames::typeAttr);
793     return contentSecurityPolicy.allowPluginType(mimeType, declaredMimeType, completedURL);
794 }
795
796 bool HTMLPlugInImageElement::requestObject(const String& relativeURL, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
797 {
798     ASSERT(document().frame());
799
800     if (relativeURL.isEmpty() && mimeType.isEmpty())
801         return false;
802
803     if (!canLoadPlugInContent(relativeURL, mimeType)) {
804         renderEmbeddedObject()->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
805         return false;
806     }
807
808     if (HTMLPlugInElement::requestObject(relativeURL, mimeType, paramNames, paramValues))
809         return true;
810     
811     return document().frame()->loader().subframeLoader().requestObject(*this, relativeURL, getNameAttribute(), mimeType, paramNames, paramValues);
812 }
813
814 void HTMLPlugInImageElement::updateImageLoaderWithNewURLSoon()
815 {
816     if (m_needsImageReload)
817         return;
818
819     m_needsImageReload = true;
820     scheduleUpdateForAfterStyleResolution();
821     invalidateStyle();
822 }
823
824 } // namespace WebCore