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