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