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