Move URL from WebCore to WTF
[WebKit-https.git] / Source / WebCore / html / HTMLPlugInImageElement.cpp
1 /*
2  * Copyright (C) 2008-2017 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 "LocalizedStrings.h"
37 #include "Logging.h"
38 #include "MouseEvent.h"
39 #include "Page.h"
40 #include "PlatformMouseEvent.h"
41 #include "PlugInClient.h"
42 #include "PluginViewBase.h"
43 #include "RenderImage.h"
44 #include "RenderSnapshottedPlugIn.h"
45 #include "RenderTreeUpdater.h"
46 #include "SchemeRegistry.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 // We don't use m_url, as it may not be the final URL that the object loads, depending on <param> values.
158 bool HTMLPlugInImageElement::allowedToLoadFrameURL(const String& url)
159 {
160     URL completeURL = document().completeURL(url);
161     if (contentFrame() && WTF::protocolIsJavaScript(completeURL) && !document().securityOrigin().canAccess(contentDocument()->securityOrigin()))
162         return false;
163     return document().frame()->isURLAllowed(completeURL);
164 }
165
166 // We don't use m_url, or m_serviceType as they may not be the final values
167 // that <object> uses depending on <param> values.
168 bool HTMLPlugInImageElement::wouldLoadAsPlugIn(const String& url, const String& serviceType)
169 {
170     ASSERT(document().frame());
171     URL completedURL;
172     if (!url.isEmpty())
173         completedURL = document().completeURL(url);
174     return document().frame()->loader().client().objectContentType(completedURL, serviceType) == ObjectContentType::PlugIn;
175 }
176
177 RenderPtr<RenderElement> HTMLPlugInImageElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition& insertionPosition)
178 {
179     ASSERT(document().pageCacheState() == Document::NotInPageCache);
180
181     if (displayState() >= PreparingPluginReplacement)
182         return HTMLPlugInElement::createElementRenderer(WTFMove(style), insertionPosition);
183
184     // Once a plug-in element creates its renderer, it needs to be told when the document goes
185     // inactive or reactivates so it can clear the renderer before going into the page cache.
186     if (!m_needsDocumentActivationCallbacks) {
187         m_needsDocumentActivationCallbacks = true;
188         document().registerForDocumentSuspensionCallbacks(this);
189     }
190
191     if (displayState() == DisplayingSnapshot) {
192         auto renderSnapshottedPlugIn = createRenderer<RenderSnapshottedPlugIn>(*this, WTFMove(style));
193         renderSnapshottedPlugIn->updateSnapshot(m_snapshotImage.get());
194         return WTFMove(renderSnapshottedPlugIn);
195     }
196
197     if (useFallbackContent())
198         return RenderElement::createFor(*this, WTFMove(style));
199
200     if (isImageType())
201         return createRenderer<RenderImage>(*this, WTFMove(style));
202
203     return HTMLPlugInElement::createElementRenderer(WTFMove(style), insertionPosition);
204 }
205
206 bool HTMLPlugInImageElement::childShouldCreateRenderer(const Node& child) const
207 {
208     if (is<RenderSnapshottedPlugIn>(renderer()) && !hasShadowRootParent(child))
209         return false;
210
211     return HTMLPlugInElement::childShouldCreateRenderer(child);
212 }
213
214 void HTMLPlugInImageElement::willRecalcStyle(Style::Change change)
215 {
216     // Make sure style recalcs scheduled by a child shadow tree don't trigger reconstruction and cause flicker.
217     if (change == Style::NoChange && styleValidity() == Style::Validity::Valid)
218         return;
219
220     // FIXME: There shoudn't be need to force render tree reconstruction here.
221     // It is only done because loading and load event dispatching is tied to render tree construction.
222     if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType() && displayState() != DisplayingSnapshot)
223         invalidateStyleAndRenderersForSubtree();
224 }
225
226 void HTMLPlugInImageElement::didRecalcStyle(Style::Change styleChange)
227 {
228     scheduleUpdateForAfterStyleResolution();
229
230     HTMLPlugInElement::didRecalcStyle(styleChange);
231 }
232
233 void HTMLPlugInImageElement::didAttachRenderers()
234 {
235     m_needsWidgetUpdate = true;
236     scheduleUpdateForAfterStyleResolution();
237
238     // Update the RenderImageResource of the associated RenderImage.
239     if (m_imageLoader && is<RenderImage>(renderer())) {
240         auto& renderImageResource = downcast<RenderImage>(*renderer()).imageResource();
241         if (!renderImageResource.cachedImage())
242             renderImageResource.setCachedImage(m_imageLoader->image());
243     }
244
245     HTMLPlugInElement::didAttachRenderers();
246 }
247
248 void HTMLPlugInImageElement::willDetachRenderers()
249 {
250     auto widget = makeRefPtr(pluginWidget(PluginLoadingPolicy::DoNotLoad));
251     if (is<PluginViewBase>(widget))
252         downcast<PluginViewBase>(*widget).willDetachRenderer();
253
254     HTMLPlugInElement::willDetachRenderers();
255 }
256
257 void HTMLPlugInImageElement::scheduleUpdateForAfterStyleResolution()
258 {
259     if (m_hasUpdateScheduledForAfterStyleResolution)
260         return;
261
262     document().incrementLoadEventDelayCount();
263
264     m_hasUpdateScheduledForAfterStyleResolution = true;
265
266     Style::queuePostResolutionCallback([protectedThis = makeRef(*this)] {
267         protectedThis->updateAfterStyleResolution();
268     });
269 }
270
271 void HTMLPlugInImageElement::updateAfterStyleResolution()
272 {
273     m_hasUpdateScheduledForAfterStyleResolution = false;
274
275     // Do this after style resolution, since the image or widget load might complete synchronously
276     // and cause us to re-enter otherwise. Also, we can't really answer the question "do I have a renderer"
277     // accurately until after style resolution.
278
279     if (renderer() && !useFallbackContent()) {
280         if (isImageType()) {
281             if (!m_imageLoader)
282                 m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
283             if (m_needsImageReload)
284                 m_imageLoader->updateFromElementIgnoringPreviousError();
285             else
286                 m_imageLoader->updateFromElement();
287         } else {
288             if (needsWidgetUpdate() && renderEmbeddedObject() && !renderEmbeddedObject()->isPluginUnavailable())
289                 updateWidget(CreatePlugins::No);
290         }
291     }
292
293     // Either we reloaded the image just now, or we had some reason not to.
294     // Either way, clear the flag now, since we don't need to remember to try again.
295     m_needsImageReload = false;
296
297     document().decrementLoadEventDelayCount();
298 }
299
300 void HTMLPlugInImageElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
301 {
302     ASSERT_WITH_SECURITY_IMPLICATION(&document() == &newDocument);
303     if (m_needsDocumentActivationCallbacks) {
304         oldDocument.unregisterForDocumentSuspensionCallbacks(this);
305         newDocument.registerForDocumentSuspensionCallbacks(this);
306     }
307
308     if (m_imageLoader)
309         m_imageLoader->elementDidMoveToNewDocument();
310
311     if (m_hasUpdateScheduledForAfterStyleResolution) {
312         oldDocument.decrementLoadEventDelayCount();
313         newDocument.incrementLoadEventDelayCount();
314     }
315
316     HTMLPlugInElement::didMoveToNewDocument(oldDocument, newDocument);
317 }
318
319 void HTMLPlugInImageElement::prepareForDocumentSuspension()
320 {
321     if (renderer())
322         RenderTreeUpdater::tearDownRenderers(*this);
323
324     HTMLPlugInElement::prepareForDocumentSuspension();
325 }
326
327 void HTMLPlugInImageElement::resumeFromDocumentSuspension()
328 {
329     scheduleUpdateForAfterStyleResolution();
330     invalidateStyleAndRenderersForSubtree();
331
332     HTMLPlugInElement::resumeFromDocumentSuspension();
333 }
334
335 void HTMLPlugInImageElement::updateSnapshot(Image* image)
336 {
337     if (displayState() > DisplayingSnapshot)
338         return;
339
340     m_snapshotImage = image;
341
342     auto* renderer = this->renderer();
343     if (!renderer)
344         return;
345
346     if (is<RenderSnapshottedPlugIn>(*renderer)) {
347         downcast<RenderSnapshottedPlugIn>(*renderer).updateSnapshot(image);
348         return;
349     }
350
351     if (is<RenderEmbeddedObject>(*renderer))
352         renderer->repaint();
353 }
354
355 static DOMWrapperWorld& plugInImageElementIsolatedWorld()
356 {
357     static auto& isolatedWorld = DOMWrapperWorld::create(commonVM()).leakRef();
358     return isolatedWorld;
359 }
360
361 void HTMLPlugInImageElement::didAddUserAgentShadowRoot(ShadowRoot& root)
362 {
363     HTMLPlugInElement::didAddUserAgentShadowRoot(root);
364     if (displayState() >= PreparingPluginReplacement)
365         return;
366
367     auto* page = document().page();
368     if (!page)
369         return;
370
371     // Reset any author styles that may apply as we only want explicit
372     // styles defined in the injected user agents stylesheets to specify
373     // the look-and-feel of the snapshotted plug-in overlay. 
374     root.setResetStyleInheritance(true);
375     
376     String mimeType = serviceType();
377
378     auto& isolatedWorld = plugInImageElementIsolatedWorld();
379     document().ensurePlugInsInjectedScript(isolatedWorld);
380
381     auto& scriptController = document().frame()->script();
382     auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(isolatedWorld));
383
384     auto& vm = globalObject.vm();
385     JSC::JSLockHolder lock(vm);
386     auto scope = DECLARE_CATCH_SCOPE(vm);
387     auto& state = *globalObject.globalExec();
388
389     JSC::MarkedArgumentBuffer argList;
390     argList.append(toJS<IDLInterface<ShadowRoot>>(state, globalObject, root));
391     argList.append(toJS<IDLDOMString>(state, titleText(*page, mimeType)));
392     argList.append(toJS<IDLDOMString>(state, subtitleText(*page, mimeType)));
393     
394     // This parameter determines whether or not the snapshot overlay should always be visible over the plugin snapshot.
395     // If no snapshot was found then we want the overlay to be visible.
396     argList.append(toJS<IDLBoolean>(!m_snapshotImage));
397     ASSERT(!argList.hasOverflowed());
398
399     // It is expected the JS file provides a createOverlay(shadowRoot, title, subtitle) function.
400     auto* overlay = globalObject.get(&state, JSC::Identifier::fromString(&state, "createOverlay")).toObject(&state);
401     ASSERT(!overlay == !!scope.exception());
402     if (!overlay) {
403         scope.clearException();
404         return;
405     }
406     JSC::CallData callData;
407     auto callType = overlay->methodTable(vm)->getCallData(overlay, callData);
408     if (callType == JSC::CallType::None)
409         return;
410
411     call(&state, overlay, callType, callData, &globalObject, argList);
412     scope.clearException();
413 }
414
415 bool HTMLPlugInImageElement::partOfSnapshotOverlay(const EventTarget* target) const
416 {
417     static NeverDestroyed<AtomicString> selector(".snapshot-overlay", AtomicString::ConstructFromLiteral);
418     auto shadow = userAgentShadowRoot();
419     if (!shadow)
420         return false;
421     if (!is<Node>(target))
422         return false;
423     auto queryResult = shadow->querySelector(selector.get());
424     if (queryResult.hasException())
425         return false;
426     auto snapshotLabel = makeRefPtr(queryResult.releaseReturnValue());
427     return snapshotLabel && snapshotLabel->contains(downcast<Node>(target));
428 }
429
430 void HTMLPlugInImageElement::removeSnapshotTimerFired()
431 {
432     m_snapshotImage = nullptr;
433     m_isRestartedPlugin = false;
434     invalidateStyleAndLayerComposition();
435     if (renderer())
436         renderer()->repaint();
437 }
438
439 void HTMLPlugInImageElement::restartSimilarPlugIns()
440 {
441     // Restart any other snapshotted plugins in the page with the same origin. Note that they
442     // may be in different frames, so traverse from the top of the document.
443
444     auto plugInOrigin = m_loadedUrl.host();
445     String mimeType = serviceType();
446     Vector<Ref<HTMLPlugInImageElement>> similarPlugins;
447
448     if (!document().page())
449         return;
450
451     for (RefPtr<Frame> frame = &document().page()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
452         if (!frame->loader().subframeLoader().containsPlugins())
453             continue;
454         
455         if (!frame->document())
456             continue;
457
458         for (auto& element : descendantsOfType<HTMLPlugInImageElement>(*frame->document())) {
459             if (plugInOrigin == element.loadedUrl().host() && mimeType == element.serviceType())
460                 similarPlugins.append(element);
461         }
462     }
463
464     for (auto& plugInToRestart : similarPlugins) {
465         if (plugInToRestart->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
466             LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart.ptr());
467             plugInToRestart->restartSnapshottedPlugIn();
468         }
469         plugInToRestart->m_snapshotDecision = NeverSnapshot;
470     }
471 }
472
473 void HTMLPlugInImageElement::userDidClickSnapshot(MouseEvent& event, bool forwardEvent)
474 {
475     if (forwardEvent)
476         m_pendingClickEventFromSnapshot = &event;
477
478     auto plugInOrigin = m_loadedUrl.host();
479     if (document().page() && !SchemeRegistry::shouldTreatURLSchemeAsLocal(document().page()->mainFrame().document()->baseURL().protocol().toStringWithoutCopying()) && document().page()->settings().autostartOriginPlugInSnapshottingEnabled())
480         document().page()->plugInClient()->didStartFromOrigin(document().page()->mainFrame().document()->baseURL().host().toString(), plugInOrigin.toString(), serviceType(), document().page()->sessionID());
481
482     LOG(Plugins, "%p User clicked on snapshotted plug-in. Restart.", this);
483     restartSnapshottedPlugIn();
484     if (forwardEvent)
485         setDisplayState(RestartingWithPendingMouseClick);
486     restartSimilarPlugIns();
487 }
488
489 void HTMLPlugInImageElement::setIsPrimarySnapshottedPlugIn(bool isPrimarySnapshottedPlugIn)
490 {
491     if (!document().page() || !document().page()->settings().primaryPlugInSnapshotDetectionEnabled() || document().page()->settings().snapshotAllPlugIns())
492         return;
493
494     if (isPrimarySnapshottedPlugIn) {
495         if (m_plugInWasCreated) {
496             LOG(Plugins, "%p Plug-in was detected as the primary element in the page. Restart.", this);
497             restartSnapshottedPlugIn();
498             restartSimilarPlugIns();
499         } else {
500             LOG(Plugins, "%p Plug-in was detected as the primary element in the page, but is not yet created. Will restart later.", this);
501             m_deferredPromotionToPrimaryPlugIn = true;
502         }
503     }
504 }
505
506 void HTMLPlugInImageElement::restartSnapshottedPlugIn()
507 {
508     if (displayState() >= RestartingWithPendingMouseClick)
509         return;
510
511     setDisplayState(Restarting);
512     invalidateStyleAndRenderersForSubtree();
513 }
514
515 void HTMLPlugInImageElement::dispatchPendingMouseClick()
516 {
517     ASSERT(!m_simulatedMouseClickTimer.isActive());
518     m_simulatedMouseClickTimer.restart();
519 }
520
521 void HTMLPlugInImageElement::simulatedMouseClickTimerFired()
522 {
523     ASSERT(displayState() == RestartingWithPendingMouseClick);
524     ASSERT(m_pendingClickEventFromSnapshot);
525
526     setDisplayState(Playing);
527     dispatchSimulatedClick(m_pendingClickEventFromSnapshot.get(), SendMouseOverUpDownEvents, DoNotShowPressedLook);
528
529     m_pendingClickEventFromSnapshot = nullptr;
530 }
531
532 static bool documentHadRecentUserGesture(Document& document)
533 {
534     MonotonicTime lastKnownUserGestureTimestamp = document.lastHandledUserGestureTimestamp();
535     if (document.frame() != &document.page()->mainFrame() && document.page()->mainFrame().document())
536         lastKnownUserGestureTimestamp = std::max(lastKnownUserGestureTimestamp, document.page()->mainFrame().document()->lastHandledUserGestureTimestamp());
537
538     return MonotonicTime::now() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold;
539 }
540
541 void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
542 {
543     if (!m_needsCheckForSizeChange || m_snapshotDecision != MaySnapshotWhenResized || documentHadRecentUserGesture(document()))
544         return;
545
546     m_needsCheckForSizeChange = false;
547
548     auto contentBoxRect = downcast<RenderBox>(*renderer()).contentBoxRect();
549     int contentWidth = contentBoxRect.width();
550     int contentHeight = contentBoxRect.height();
551
552     if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold)
553         return;
554
555     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);
556     setDisplayState(WaitingForSnapshot);
557     m_snapshotDecision = Snapshotted;
558     auto widget = makeRefPtr(pluginWidget());
559     if (is<PluginViewBase>(widget))
560         downcast<PluginViewBase>(*widget).beginSnapshottingRunningPlugin();
561 }
562
563 static inline bool is100Percent(Length length)
564 {
565     return length.isPercent() && length.percent() == 100;
566 }
567     
568 static inline bool isSmallerThanTinySizingThreshold(const RenderEmbeddedObject& renderer)
569 {
570     auto contentRect = renderer.contentBoxRect();
571     return contentRect.width() <= sizingTinyDimensionThreshold || contentRect.height() <= sizingTinyDimensionThreshold;
572 }
573     
574 bool HTMLPlugInImageElement::isTopLevelFullPagePlugin(const RenderEmbeddedObject& renderer) const
575 {
576     ASSERT(document().frame());
577     auto& frame = *document().frame();
578     if (!frame.isMainFrame())
579         return false;
580     
581     auto& style = renderer.style();
582     auto visibleSize = frame.view()->visibleSize();
583     auto contentRect = renderer.contentBoxRect();
584     float contentWidth = contentRect.width();
585     float contentHeight = contentRect.height();
586     return is100Percent(style.width()) && is100Percent(style.height()) && contentWidth * contentHeight > visibleSize.area().unsafeGet() * sizingFullPageAreaRatioThreshold;
587 }
588
589 void HTMLPlugInImageElement::checkSnapshotStatus()
590 {
591     if (!is<RenderSnapshottedPlugIn>(*renderer())) {
592         if (displayState() == Playing)
593             checkSizeChangeForSnapshotting();
594         return;
595     }
596     
597     // 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.
598     if (!document().page()->settings().snapshotAllPlugIns() && displayState() <= DisplayingSnapshot && !m_plugInDimensionsSpecified) {
599         auto& renderer = downcast<RenderSnapshottedPlugIn>(*this->renderer());
600         if (!renderer.style().logicalWidth().isSpecified() && !renderer.style().logicalHeight().isSpecified())
601             return;
602         
603         m_plugInDimensionsSpecified = true;
604         if (isTopLevelFullPagePlugin(renderer)) {
605             m_snapshotDecision = NeverSnapshot;
606             restartSnapshottedPlugIn();
607         } else if (isSmallerThanTinySizingThreshold(renderer)) {
608             m_snapshotDecision = MaySnapshotWhenResized;
609             restartSnapshottedPlugIn();
610         }
611         return;
612     }
613     
614     // Notify the shadow root that the size changed so that we may update the overlay layout.
615     ensureUserAgentShadowRoot().dispatchEvent(Event::create(eventNames().resizeEvent, Event::CanBubble::Yes, Event::IsCancelable::No));
616 }
617     
618 void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const URL& url)
619 {
620     LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
621     LOG(Plugins, "   Actual URL: %s", url.string().utf8().data());
622     LOG(Plugins, "   MIME type: %s", serviceType().utf8().data());
623
624     m_loadedUrl = url;
625     m_plugInWasCreated = false;
626     m_deferredPromotionToPrimaryPlugIn = false;
627
628     if (!document().page() || !document().page()->settings().plugInSnapshottingEnabled()) {
629         m_snapshotDecision = NeverSnapshot;
630         return;
631     }
632
633     if (displayState() == Restarting) {
634         LOG(Plugins, "%p Plug-in is explicitly restarting", this);
635         m_snapshotDecision = NeverSnapshot;
636         setDisplayState(Playing);
637         return;
638     }
639
640     if (displayState() == RestartingWithPendingMouseClick) {
641         LOG(Plugins, "%p Plug-in is explicitly restarting but also waiting for a click", this);
642         m_snapshotDecision = NeverSnapshot;
643         return;
644     }
645
646     if (m_snapshotDecision == NeverSnapshot) {
647         LOG(Plugins, "%p Plug-in is blessed, allow it to start", this);
648         return;
649     }
650
651     bool inMainFrame = document().frame()->isMainFrame();
652
653     if (document().isPluginDocument() && inMainFrame) {
654         LOG(Plugins, "%p Plug-in document in main frame", this);
655         m_snapshotDecision = NeverSnapshot;
656         return;
657     }
658
659     if (UserGestureIndicator::processingUserGesture()) {
660         LOG(Plugins, "%p Script is currently processing user gesture, set to play", this);
661         m_snapshotDecision = NeverSnapshot;
662         return;
663     }
664
665     if (m_createdDuringUserGesture) {
666         LOG(Plugins, "%p Plug-in was created when processing user gesture, set to play", this);
667         m_snapshotDecision = NeverSnapshot;
668         return;
669     }
670
671     if (documentHadRecentUserGesture(document())) {
672         LOG(Plugins, "%p Plug-in was created shortly after a user gesture, set to play", this);
673         m_snapshotDecision = NeverSnapshot;
674         return;
675     }
676
677     if (document().page()->settings().snapshotAllPlugIns()) {
678         LOG(Plugins, "%p Plug-in forced to snapshot by user preference", this);
679         m_snapshotDecision = Snapshotted;
680         setDisplayState(WaitingForSnapshot);
681         return;
682     }
683
684     if (document().page()->settings().autostartOriginPlugInSnapshottingEnabled() && document().page()->plugInClient() && document().page()->plugInClient()->shouldAutoStartFromOrigin(document().page()->mainFrame().document()->baseURL().host().toString(), url.host().toString(), serviceType())) {
685         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());
686         m_snapshotDecision = NeverSnapshot;
687         return;
688     }
689
690     if (m_loadedUrl.isEmpty() && !serviceType().isEmpty()) {
691         LOG(Plugins, "%p Plug-in has no src URL but does have a valid mime type %s, set to play", this, serviceType().utf8().data());
692         m_snapshotDecision = MaySnapshotWhenContentIsSet;
693         return;
694     }
695
696     if (!SchemeRegistry::shouldTreatURLSchemeAsLocal(m_loadedUrl.protocol().toStringWithoutCopying()) && !m_loadedUrl.host().isEmpty() && m_loadedUrl.host() == document().page()->mainFrame().document()->baseURL().host()) {
697         LOG(Plugins, "%p Plug-in is served from page's domain, set to play", this);
698         m_snapshotDecision = NeverSnapshot;
699         return;
700     }
701     
702     auto& renderer = downcast<RenderEmbeddedObject>(*this->renderer());
703     auto contentRect = renderer.contentBoxRect();
704     int contentWidth = contentRect.width();
705     int contentHeight = contentRect.height();
706     
707     m_plugInDimensionsSpecified = renderer.style().logicalWidth().isSpecified() || renderer.style().logicalHeight().isSpecified();
708     
709     if (isTopLevelFullPagePlugin(renderer)) {
710         LOG(Plugins, "%p Plug-in is top level full page, set to play", this);
711         m_snapshotDecision = NeverSnapshot;
712         return;
713     }
714
715     if (isSmallerThanTinySizingThreshold(renderer)) {
716         LOG(Plugins, "%p Plug-in is very small %dx%d, set to play", this, contentWidth, contentHeight);
717         m_sizeWhenSnapshotted = IntSize(contentWidth, contentHeight);
718         m_snapshotDecision = MaySnapshotWhenResized;
719         return;
720     }
721
722     if (!document().page()->plugInClient()) {
723         LOG(Plugins, "%p There is no plug-in client. Set to wait for snapshot", this);
724         m_snapshotDecision = NeverSnapshot;
725         setDisplayState(WaitingForSnapshot);
726         return;
727     }
728
729     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);
730     m_snapshotDecision = Snapshotted;
731     setDisplayState(WaitingForSnapshot);
732 }
733
734 void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget& widget)
735 {
736     m_plugInWasCreated = true;
737
738     if (is<PluginViewBase>(widget) && downcast<PluginViewBase>(widget).shouldAlwaysAutoStart()) {
739         LOG(Plugins, "%p Plug-in should auto-start, set to play", this);
740         m_snapshotDecision = NeverSnapshot;
741         setDisplayState(Playing);
742         return;
743     }
744
745     if (m_deferredPromotionToPrimaryPlugIn) {
746         LOG(Plugins, "%p Plug-in was created, previously deferred promotion to primary. Will promote", this);
747         setIsPrimarySnapshottedPlugIn(true);
748         m_deferredPromotionToPrimaryPlugIn = false;
749     }
750 }
751
752 void HTMLPlugInImageElement::defaultEventHandler(Event& event)
753 {
754     if (is<RenderEmbeddedObject>(renderer()) && displayState() == WaitingForSnapshot && is<MouseEvent>(event) && event.type() == eventNames().clickEvent) {
755         auto& mouseEvent = downcast<MouseEvent>(event);
756         if (mouseEvent.button() == LeftButton) {
757             userDidClickSnapshot(mouseEvent, true);
758             mouseEvent.setDefaultHandled();
759             return;
760         }
761     }
762     HTMLPlugInElement::defaultEventHandler(event);
763 }
764
765 bool HTMLPlugInImageElement::allowedToLoadPluginContent(const String& url, const String& mimeType) const
766 {
767     // Elements in user agent show tree should load whatever the embedding document policy is.
768     if (isInUserAgentShadowTree())
769         return true;
770
771     URL completedURL;
772     if (!url.isEmpty())
773         completedURL = document().completeURL(url);
774
775     ASSERT(document().contentSecurityPolicy());
776     const ContentSecurityPolicy& contentSecurityPolicy = *document().contentSecurityPolicy();
777
778     contentSecurityPolicy.upgradeInsecureRequestIfNeeded(completedURL, ContentSecurityPolicy::InsecureRequestType::Load);
779
780     if (!contentSecurityPolicy.allowObjectFromSource(completedURL))
781         return false;
782
783     auto& declaredMimeType = document().isPluginDocument() && document().ownerElement() ?
784         document().ownerElement()->attributeWithoutSynchronization(HTMLNames::typeAttr) : attributeWithoutSynchronization(HTMLNames::typeAttr);
785     return contentSecurityPolicy.allowPluginType(mimeType, declaredMimeType, completedURL);
786 }
787
788 bool HTMLPlugInImageElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
789 {
790     ASSERT(document().frame());
791
792     if (url.isEmpty() && mimeType.isEmpty())
793         return false;
794
795     if (!allowedToLoadPluginContent(url, mimeType)) {
796         renderEmbeddedObject()->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
797         return false;
798     }
799
800     if (HTMLPlugInElement::requestObject(url, mimeType, paramNames, paramValues))
801         return true;
802     
803     return document().frame()->loader().subframeLoader().requestObject(*this, url, getNameAttribute(), mimeType, paramNames, paramValues);
804 }
805
806 void HTMLPlugInImageElement::updateImageLoaderWithNewURLSoon()
807 {
808     if (m_needsImageReload)
809         return;
810
811     m_needsImageReload = true;
812     scheduleUpdateForAfterStyleResolution();
813     invalidateStyle();
814 }
815
816 } // namespace WebCore