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