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