Remove code in HTMLObjectElement attribute parsing that forces style resolution and...
[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
381     // It is expected the JS file provides a createOverlay(shadowRoot, title, subtitle) function.
382     auto* overlay = globalObject.get(&state, JSC::Identifier::fromString(&state, "createOverlay")).toObject(&state);
383     ASSERT(!overlay == !!scope.exception());
384     if (!overlay) {
385         scope.clearException();
386         return;
387     }
388     JSC::CallData callData;
389     auto callType = overlay->methodTable()->getCallData(overlay, callData);
390     if (callType == JSC::CallType::None)
391         return;
392
393     call(&state, overlay, callType, callData, &globalObject, argList);
394     scope.clearException();
395 }
396
397 bool HTMLPlugInImageElement::partOfSnapshotOverlay(const Node* node) const
398 {
399     static NeverDestroyed<AtomicString> selector(".snapshot-overlay", AtomicString::ConstructFromLiteral);
400     auto* shadow = userAgentShadowRoot();
401     if (!shadow)
402         return false;
403     if (!node)
404         return false;
405     auto queryResult = shadow->querySelector(selector.get());
406     if (queryResult.hasException())
407         return false;
408     auto* snapshotLabel = queryResult.releaseReturnValue();
409     return snapshotLabel && snapshotLabel->contains(node);
410 }
411
412 void HTMLPlugInImageElement::removeSnapshotTimerFired()
413 {
414     m_snapshotImage = nullptr;
415     m_isRestartedPlugin = false;
416     invalidateStyleAndLayerComposition();
417     if (renderer())
418         renderer()->repaint();
419 }
420
421 void HTMLPlugInImageElement::restartSimilarPlugIns()
422 {
423     // Restart any other snapshotted plugins in the page with the same origin. Note that they
424     // may be in different frames, so traverse from the top of the document.
425
426     String plugInOrigin = m_loadedUrl.host();
427     String mimeType = loadedMimeType();
428     Vector<Ref<HTMLPlugInImageElement>> similarPlugins;
429
430     if (!document().page())
431         return;
432
433     for (Frame* frame = &document().page()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
434         if (!frame->loader().subframeLoader().containsPlugins())
435             continue;
436         
437         if (!frame->document())
438             continue;
439
440         for (auto& element : descendantsOfType<HTMLPlugInImageElement>(*frame->document())) {
441             if (plugInOrigin == element.loadedUrl().host() && mimeType == element.loadedMimeType())
442                 similarPlugins.append(element);
443         }
444     }
445
446     for (auto& plugInToRestart : similarPlugins) {
447         if (plugInToRestart->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
448             LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart.ptr());
449             plugInToRestart->restartSnapshottedPlugIn();
450         }
451         plugInToRestart->m_snapshotDecision = NeverSnapshot;
452     }
453 }
454
455 void HTMLPlugInImageElement::userDidClickSnapshot(MouseEvent& event, bool forwardEvent)
456 {
457     if (forwardEvent)
458         m_pendingClickEventFromSnapshot = &event;
459
460     String plugInOrigin = m_loadedUrl.host();
461     if (document().page() && !SchemeRegistry::shouldTreatURLSchemeAsLocal(document().page()->mainFrame().document()->baseURL().protocol().toStringWithoutCopying()) && document().page()->settings().autostartOriginPlugInSnapshottingEnabled())
462         document().page()->plugInClient()->didStartFromOrigin(document().page()->mainFrame().document()->baseURL().host(), plugInOrigin, loadedMimeType(), document().page()->sessionID());
463
464     LOG(Plugins, "%p User clicked on snapshotted plug-in. Restart.", this);
465     restartSnapshottedPlugIn();
466     if (forwardEvent)
467         setDisplayState(RestartingWithPendingMouseClick);
468     restartSimilarPlugIns();
469 }
470
471 void HTMLPlugInImageElement::setIsPrimarySnapshottedPlugIn(bool isPrimarySnapshottedPlugIn)
472 {
473     if (!document().page() || !document().page()->settings().primaryPlugInSnapshotDetectionEnabled() || document().page()->settings().snapshotAllPlugIns())
474         return;
475
476     if (isPrimarySnapshottedPlugIn) {
477         if (m_plugInWasCreated) {
478             LOG(Plugins, "%p Plug-in was detected as the primary element in the page. Restart.", this);
479             restartSnapshottedPlugIn();
480             restartSimilarPlugIns();
481         } else {
482             LOG(Plugins, "%p Plug-in was detected as the primary element in the page, but is not yet created. Will restart later.", this);
483             m_deferredPromotionToPrimaryPlugIn = true;
484         }
485     }
486 }
487
488 void HTMLPlugInImageElement::restartSnapshottedPlugIn()
489 {
490     if (displayState() >= RestartingWithPendingMouseClick)
491         return;
492
493     setDisplayState(Restarting);
494     invalidateStyleAndRenderersForSubtree();
495 }
496
497 void HTMLPlugInImageElement::dispatchPendingMouseClick()
498 {
499     ASSERT(!m_simulatedMouseClickTimer.isActive());
500     m_simulatedMouseClickTimer.restart();
501 }
502
503 void HTMLPlugInImageElement::simulatedMouseClickTimerFired()
504 {
505     ASSERT(displayState() == RestartingWithPendingMouseClick);
506     ASSERT(m_pendingClickEventFromSnapshot);
507
508     setDisplayState(Playing);
509     dispatchSimulatedClick(m_pendingClickEventFromSnapshot.get(), SendMouseOverUpDownEvents, DoNotShowPressedLook);
510
511     m_pendingClickEventFromSnapshot = nullptr;
512 }
513
514 static bool documentHadRecentUserGesture(Document& document)
515 {
516     MonotonicTime lastKnownUserGestureTimestamp = document.lastHandledUserGestureTimestamp();
517     if (document.frame() != &document.page()->mainFrame() && document.page()->mainFrame().document())
518         lastKnownUserGestureTimestamp = std::max(lastKnownUserGestureTimestamp, document.page()->mainFrame().document()->lastHandledUserGestureTimestamp());
519
520     return MonotonicTime::now() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold;
521 }
522
523 void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
524 {
525     if (!m_needsCheckForSizeChange || m_snapshotDecision != MaySnapshotWhenResized || documentHadRecentUserGesture(document()))
526         return;
527
528     m_needsCheckForSizeChange = false;
529
530     auto contentBoxRect = downcast<RenderBox>(*renderer()).contentBoxRect();
531     int contentWidth = contentBoxRect.width();
532     int contentHeight = contentBoxRect.height();
533
534     if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold)
535         return;
536
537     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);
538     setDisplayState(WaitingForSnapshot);
539     m_snapshotDecision = Snapshotted;
540     auto* widget = pluginWidget();
541     if (is<PluginViewBase>(widget))
542         downcast<PluginViewBase>(*widget).beginSnapshottingRunningPlugin();
543 }
544
545 static inline bool is100Percent(Length length)
546 {
547     return length.isPercent() && length.percent() == 100;
548 }
549     
550 static inline bool isSmallerThanTinySizingThreshold(const RenderEmbeddedObject& renderer)
551 {
552     auto contentRect = renderer.contentBoxRect();
553     return contentRect.width() <= sizingTinyDimensionThreshold || contentRect.height() <= sizingTinyDimensionThreshold;
554 }
555     
556 bool HTMLPlugInImageElement::isTopLevelFullPagePlugin(const RenderEmbeddedObject& renderer) const
557 {
558     ASSERT(document().frame());
559     auto& frame = *document().frame();
560     if (!frame.isMainFrame())
561         return false;
562     
563     auto& style = renderer.style();
564     auto visibleSize = frame.view()->visibleSize();
565     auto contentRect = renderer.contentBoxRect();
566     float contentWidth = contentRect.width();
567     float contentHeight = contentRect.height();
568     return is100Percent(style.width()) && is100Percent(style.height()) && contentWidth * contentHeight > visibleSize.area().unsafeGet() * sizingFullPageAreaRatioThreshold;
569 }
570
571 void HTMLPlugInImageElement::checkSnapshotStatus()
572 {
573     if (!is<RenderSnapshottedPlugIn>(*renderer())) {
574         if (displayState() == Playing)
575             checkSizeChangeForSnapshotting();
576         return;
577     }
578     
579     // 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.
580     if (!document().page()->settings().snapshotAllPlugIns() && displayState() <= DisplayingSnapshot && !m_plugInDimensionsSpecified) {
581         auto& renderer = downcast<RenderSnapshottedPlugIn>(*this->renderer());
582         if (!renderer.style().logicalWidth().isSpecified() && !renderer.style().logicalHeight().isSpecified())
583             return;
584         
585         m_plugInDimensionsSpecified = true;
586         if (isTopLevelFullPagePlugin(renderer)) {
587             m_snapshotDecision = NeverSnapshot;
588             restartSnapshottedPlugIn();
589         } else if (isSmallerThanTinySizingThreshold(renderer)) {
590             m_snapshotDecision = MaySnapshotWhenResized;
591             restartSnapshottedPlugIn();
592         }
593         return;
594     }
595     
596     // Notify the shadow root that the size changed so that we may update the overlay layout.
597     ensureUserAgentShadowRoot().dispatchEvent(Event::create(eventNames().resizeEvent, true, false));
598 }
599     
600 void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const URL& url)
601 {
602     LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
603     LOG(Plugins, "   Actual URL: %s", url.string().utf8().data());
604     LOG(Plugins, "   MIME type: %s", loadedMimeType().utf8().data());
605
606     m_loadedUrl = url;
607     m_plugInWasCreated = false;
608     m_deferredPromotionToPrimaryPlugIn = false;
609
610     if (!document().page() || !document().page()->settings().plugInSnapshottingEnabled()) {
611         m_snapshotDecision = NeverSnapshot;
612         return;
613     }
614
615     if (displayState() == Restarting) {
616         LOG(Plugins, "%p Plug-in is explicitly restarting", this);
617         m_snapshotDecision = NeverSnapshot;
618         setDisplayState(Playing);
619         return;
620     }
621
622     if (displayState() == RestartingWithPendingMouseClick) {
623         LOG(Plugins, "%p Plug-in is explicitly restarting but also waiting for a click", this);
624         m_snapshotDecision = NeverSnapshot;
625         return;
626     }
627
628     if (m_snapshotDecision == NeverSnapshot) {
629         LOG(Plugins, "%p Plug-in is blessed, allow it to start", this);
630         return;
631     }
632
633     bool inMainFrame = document().frame()->isMainFrame();
634
635     if (document().isPluginDocument() && inMainFrame) {
636         LOG(Plugins, "%p Plug-in document in main frame", this);
637         m_snapshotDecision = NeverSnapshot;
638         return;
639     }
640
641     if (ScriptController::processingUserGesture()) {
642         LOG(Plugins, "%p Script is currently processing user gesture, set to play", this);
643         m_snapshotDecision = NeverSnapshot;
644         return;
645     }
646
647     if (m_createdDuringUserGesture) {
648         LOG(Plugins, "%p Plug-in was created when processing user gesture, set to play", this);
649         m_snapshotDecision = NeverSnapshot;
650         return;
651     }
652
653     if (documentHadRecentUserGesture(document())) {
654         LOG(Plugins, "%p Plug-in was created shortly after a user gesture, set to play", this);
655         m_snapshotDecision = NeverSnapshot;
656         return;
657     }
658
659     if (document().page()->settings().snapshotAllPlugIns()) {
660         LOG(Plugins, "%p Plug-in forced to snapshot by user preference", this);
661         m_snapshotDecision = Snapshotted;
662         setDisplayState(WaitingForSnapshot);
663         return;
664     }
665
666     if (document().page()->settings().autostartOriginPlugInSnapshottingEnabled() && document().page()->plugInClient() && document().page()->plugInClient()->shouldAutoStartFromOrigin(document().page()->mainFrame().document()->baseURL().host(), url.host(), loadedMimeType())) {
667         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());
668         m_snapshotDecision = NeverSnapshot;
669         return;
670     }
671
672     if (m_loadedUrl.isEmpty() && !loadedMimeType().isEmpty()) {
673         LOG(Plugins, "%p Plug-in has no src URL but does have a valid mime type %s, set to play", this, loadedMimeType().utf8().data());
674         m_snapshotDecision = MaySnapshotWhenContentIsSet;
675         return;
676     }
677
678     if (!SchemeRegistry::shouldTreatURLSchemeAsLocal(m_loadedUrl.protocol().toStringWithoutCopying()) && !m_loadedUrl.host().isEmpty() && m_loadedUrl.host() == document().page()->mainFrame().document()->baseURL().host()) {
679         LOG(Plugins, "%p Plug-in is served from page's domain, set to play", this);
680         m_snapshotDecision = NeverSnapshot;
681         return;
682     }
683     
684     auto& renderer = downcast<RenderEmbeddedObject>(*this->renderer());
685     auto contentRect = renderer.contentBoxRect();
686     int contentWidth = contentRect.width();
687     int contentHeight = contentRect.height();
688     
689     m_plugInDimensionsSpecified = renderer.style().logicalWidth().isSpecified() || renderer.style().logicalHeight().isSpecified();
690     
691     if (isTopLevelFullPagePlugin(renderer)) {
692         LOG(Plugins, "%p Plug-in is top level full page, set to play", this);
693         m_snapshotDecision = NeverSnapshot;
694         return;
695     }
696
697     if (isSmallerThanTinySizingThreshold(renderer)) {
698         LOG(Plugins, "%p Plug-in is very small %dx%d, set to play", this, contentWidth, contentHeight);
699         m_sizeWhenSnapshotted = IntSize(contentWidth, contentHeight);
700         m_snapshotDecision = MaySnapshotWhenResized;
701         return;
702     }
703
704     if (!document().page()->plugInClient()) {
705         LOG(Plugins, "%p There is no plug-in client. Set to wait for snapshot", this);
706         m_snapshotDecision = NeverSnapshot;
707         setDisplayState(WaitingForSnapshot);
708         return;
709     }
710
711     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);
712     m_snapshotDecision = Snapshotted;
713     setDisplayState(WaitingForSnapshot);
714 }
715
716 void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget& widget)
717 {
718     m_plugInWasCreated = true;
719
720     if (is<PluginViewBase>(widget) && downcast<PluginViewBase>(widget).shouldAlwaysAutoStart()) {
721         LOG(Plugins, "%p Plug-in should auto-start, set to play", this);
722         m_snapshotDecision = NeverSnapshot;
723         setDisplayState(Playing);
724         return;
725     }
726
727     if (m_deferredPromotionToPrimaryPlugIn) {
728         LOG(Plugins, "%p Plug-in was created, previously deferred promotion to primary. Will promote", this);
729         setIsPrimarySnapshottedPlugIn(true);
730         m_deferredPromotionToPrimaryPlugIn = false;
731     }
732 }
733
734 void HTMLPlugInImageElement::defaultEventHandler(Event& event)
735 {
736     if (is<RenderEmbeddedObject>(renderer()) && displayState() == WaitingForSnapshot && is<MouseEvent>(event) && event.type() == eventNames().clickEvent) {
737         auto& mouseEvent = downcast<MouseEvent>(event);
738         if (mouseEvent.button() == LeftButton) {
739             userDidClickSnapshot(mouseEvent, true);
740             mouseEvent.setDefaultHandled();
741             return;
742         }
743     }
744     HTMLPlugInElement::defaultEventHandler(event);
745 }
746
747 bool HTMLPlugInImageElement::allowedToLoadPluginContent(const String& url, const String& mimeType) const
748 {
749     // Elements in user agent show tree should load whatever the embedding document policy is.
750     if (isInUserAgentShadowTree())
751         return true;
752
753     URL completedURL;
754     if (!url.isEmpty())
755         completedURL = document().completeURL(url);
756
757     ASSERT(document().contentSecurityPolicy());
758     const ContentSecurityPolicy& contentSecurityPolicy = *document().contentSecurityPolicy();
759
760     contentSecurityPolicy.upgradeInsecureRequestIfNeeded(completedURL, ContentSecurityPolicy::InsecureRequestType::Load);
761
762     if (!contentSecurityPolicy.allowObjectFromSource(completedURL))
763         return false;
764
765     auto& declaredMimeType = document().isPluginDocument() && document().ownerElement() ?
766         document().ownerElement()->attributeWithoutSynchronization(HTMLNames::typeAttr) : attributeWithoutSynchronization(HTMLNames::typeAttr);
767     return contentSecurityPolicy.allowPluginType(mimeType, declaredMimeType, completedURL);
768 }
769
770 bool HTMLPlugInImageElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
771 {
772     ASSERT(document().frame());
773
774     if (url.isEmpty() && mimeType.isEmpty())
775         return false;
776
777     if (!allowedToLoadPluginContent(url, mimeType)) {
778         renderEmbeddedObject()->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
779         return false;
780     }
781
782     if (HTMLPlugInElement::requestObject(url, mimeType, paramNames, paramValues))
783         return true;
784     
785     return document().frame()->loader().subframeLoader().requestObject(*this, url, getNameAttribute(), mimeType, paramNames, paramValues);
786 }
787
788 void HTMLPlugInImageElement::updateImageLoaderWithNewURLSoon()
789 {
790     if (m_needsImageReload)
791         return;
792
793     m_needsImageReload = true;
794     scheduleUpdateForAfterStyleResolution();
795     invalidateStyle();
796 }
797
798 } // namespace WebCore