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