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