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