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