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