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