66d49b6e7549fae834c37dae940b964ede83f32d
[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 double simulatedMouseClickTimerDelay = .75;
71 static const double removeSnapshotTimerDelay = 1.5;
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(std::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, std::move(style));
210         renderSnapshottedPlugIn->updateSnapshot(m_snapshotImage);
211         return std::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, std::move(style));
219
220     if (isImageType())
221         return createRenderer<RenderImage>(*this, std::move(style));
222
223     return HTMLPlugInElement::createElementRenderer(std::move(style));
224 }
225
226 bool HTMLPlugInImageElement::willRecalcStyle(Style::Change)
227 {
228     // FIXME: There shoudn't be need to force render tree reconstruction here.
229     // It is only done because loading and load event dispatching is tied to render tree construction.
230     if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType() && (displayState() != DisplayingSnapshot))
231         setNeedsStyleRecalc(ReconstructRenderTree);
232     return true;
233 }
234
235 void HTMLPlugInImageElement::didAttachRenderers()
236 {
237     if (!isImageType()) {
238         RefPtr<HTMLPlugInImageElement> element = this;
239         Style::queuePostResolutionCallback([element]{
240             element->updateWidgetIfNecessary();
241         });
242         return;
243     }
244     if (!renderer() || useFallbackContent())
245         return;
246
247     // Image load might complete synchronously and cause us to re-enter.
248     RefPtr<HTMLPlugInImageElement> element = this;
249     Style::queuePostResolutionCallback([element]{
250         element->startLoadingImage();
251     });
252 }
253
254 void HTMLPlugInImageElement::willDetachRenderers()
255 {
256     // FIXME: Because of the insanity that is HTMLPlugInImageElement::willRecalcStyle,
257     // we can end up detaching during an attach() call, before we even have a
258     // renderer. In that case, don't mark the widget for update.
259     if (renderer() && !useFallbackContent()) {
260         // Update the widget the next time we attach (detaching destroys the plugin).
261         setNeedsWidgetUpdate(true);
262     }
263
264     HTMLPlugInElement::willDetachRenderers();
265 }
266
267 void HTMLPlugInImageElement::updateWidgetIfNecessary()
268 {
269     document().updateStyleIfNeeded();
270
271     if (!needsWidgetUpdate() || useFallbackContent() || isImageType())
272         return;
273
274     if (!renderEmbeddedObject() || renderEmbeddedObject()->isPluginUnavailable())
275         return;
276
277     updateWidget(CreateOnlyNonNetscapePlugins);
278 }
279
280 void HTMLPlugInImageElement::finishParsingChildren()
281 {
282     HTMLPlugInElement::finishParsingChildren();
283     if (useFallbackContent())
284         return;
285
286     setNeedsWidgetUpdate(true);
287     if (inDocument())
288         setNeedsStyleRecalc();
289 }
290
291 void HTMLPlugInImageElement::didMoveToNewDocument(Document* oldDocument)
292 {
293     if (m_needsDocumentActivationCallbacks) {
294         oldDocument->unregisterForPageCacheSuspensionCallbacks(this);
295         document().registerForPageCacheSuspensionCallbacks(this);
296     }
297
298     if (m_imageLoader)
299         m_imageLoader->elementDidMoveToNewDocument();
300
301     HTMLPlugInElement::didMoveToNewDocument(oldDocument);
302 }
303
304 void HTMLPlugInImageElement::documentWillSuspendForPageCache()
305 {
306     if (renderer())
307         Style::detachRenderTree(*this);
308
309     HTMLPlugInElement::documentWillSuspendForPageCache();
310 }
311
312 void HTMLPlugInImageElement::documentDidResumeFromPageCache()
313 {
314     setNeedsStyleRecalc(ReconstructRenderTree);
315
316     HTMLPlugInElement::documentDidResumeFromPageCache();
317 }
318
319 void HTMLPlugInImageElement::startLoadingImage()
320 {
321     if (!m_imageLoader)
322         m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
323     m_imageLoader->updateFromElement();
324 }
325
326 void HTMLPlugInImageElement::updateSnapshot(PassRefPtr<Image> image)
327 {
328     if (displayState() > DisplayingSnapshot)
329         return;
330
331     m_snapshotImage = image;
332
333     if (renderer()->isSnapshottedPlugIn()) {
334         toRenderSnapshottedPlugIn(renderer())->updateSnapshot(image);
335         return;
336     }
337
338     if (renderer()->isEmbeddedObject())
339         renderer()->repaint();
340 }
341
342 static DOMWrapperWorld& plugInImageElementIsolatedWorld()
343 {
344     static DOMWrapperWorld& isolatedWorld = *DOMWrapperWorld::create(JSDOMWindow::commonVM()).leakRef();
345     return isolatedWorld;
346 }
347
348 void HTMLPlugInImageElement::didAddUserAgentShadowRoot(ShadowRoot* root)
349 {
350     HTMLPlugInElement::didAddUserAgentShadowRoot(root);
351     if (displayState() >= PreparingPluginReplacement)
352         return;
353
354     Page* page = document().page();
355     if (!page)
356         return;
357
358     // Reset any author styles that may apply as we only want explicit
359     // styles defined in the injected user agents stylesheets to specify
360     // the look-and-feel of the snapshotted plug-in overlay. 
361     root->setResetStyleInheritance(true);
362     
363     String mimeType = loadedMimeType();
364
365     DOMWrapperWorld& isolatedWorld = plugInImageElementIsolatedWorld();
366     document().ensurePlugInsInjectedScript(isolatedWorld);
367
368     ScriptController& scriptController = page->mainFrame().script();
369     JSDOMGlobalObject* globalObject = JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(isolatedWorld));
370     JSC::ExecState* exec = globalObject->globalExec();
371
372     JSC::JSLockHolder lock(exec);
373
374     JSC::MarkedArgumentBuffer argList;
375     argList.append(toJS(exec, globalObject, root));
376     argList.append(jsString(exec, titleText(page, mimeType)));
377     argList.append(jsString(exec, subtitleText(page, mimeType)));
378     
379     // This parameter determines whether or not the snapshot overlay should always be visible over the plugin snapshot.
380     // If no snapshot was found then we want the overlay to be visible.
381     argList.append(JSC::jsBoolean(!m_snapshotImage));
382
383     // It is expected the JS file provides a createOverlay(shadowRoot, title, subtitle) function.
384     JSC::JSObject* overlay = globalObject->get(exec, JSC::Identifier(exec, "createOverlay")).toObject(exec);
385     JSC::CallData callData;
386     JSC::CallType callType = overlay->methodTable()->getCallData(overlay, callData);
387     if (callType == JSC::CallTypeNone)
388         return;
389
390     JSC::call(exec, overlay, callType, callData, globalObject, argList);
391 }
392
393 bool HTMLPlugInImageElement::partOfSnapshotOverlay(Node* node)
394 {
395     DEPRECATED_DEFINE_STATIC_LOCAL(AtomicString, selector, (".snapshot-overlay", AtomicString::ConstructFromLiteral));
396     RefPtr<Element> snapshotLabel = ensureUserAgentShadowRoot().querySelector(selector, ASSERT_NO_EXCEPTION);
397     return node && snapshotLabel && (node == snapshotLabel.get() || node->isDescendantOf(snapshotLabel.get()));
398 }
399
400 void HTMLPlugInImageElement::removeSnapshotTimerFired(Timer<HTMLPlugInImageElement>&)
401 {
402     m_snapshotImage = nullptr;
403     m_isRestartedPlugin = false;
404     setNeedsStyleRecalc(SyntheticStyleChange);
405     if (renderer())
406         renderer()->repaint();
407 }
408
409 static void addPlugInsFromNodeListMatchingPlugInOrigin(HTMLPlugInImageElementList& plugInList, PassRefPtr<NodeList> collection, const String& plugInOrigin, const String& mimeType)
410 {
411     for (unsigned i = 0, length = collection->length(); i < length; i++) {
412         Node* node = collection->item(i);
413         if (node->isPluginElement()) {
414             HTMLPlugInElement* plugInElement = toHTMLPlugInElement(node);
415             if (plugInElement->isPlugInImageElement()) {
416                 HTMLPlugInImageElement* plugInImageElement = toHTMLPlugInImageElement(node);
417                 const URL& loadedURL = plugInImageElement->loadedUrl();
418                 String otherMimeType = plugInImageElement->loadedMimeType();
419                 if (plugInOrigin == loadedURL.host() && mimeType == otherMimeType)
420                     plugInList.append(plugInImageElement);
421             }
422         }
423     }
424 }
425
426 void HTMLPlugInImageElement::restartSimilarPlugIns()
427 {
428     // Restart any other snapshotted plugins in the page with the same origin. Note that they
429     // may be in different frames, so traverse from the top of the document.
430
431     String plugInOrigin = m_loadedUrl.host();
432     String mimeType = loadedMimeType();
433     HTMLPlugInImageElementList similarPlugins;
434
435     if (!document().page())
436         return;
437
438     for (Frame* frame = &document().page()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
439         if (!frame->loader().subframeLoader().containsPlugins())
440             continue;
441         
442         if (!frame->document())
443             continue;
444
445         RefPtr<NodeList> plugIns = frame->document()->getElementsByTagName(embedTag.localName());
446         if (plugIns)
447             addPlugInsFromNodeListMatchingPlugInOrigin(similarPlugins, plugIns, plugInOrigin, mimeType);
448
449         plugIns = frame->document()->getElementsByTagName(objectTag.localName());
450         if (plugIns)
451             addPlugInsFromNodeListMatchingPlugInOrigin(similarPlugins, plugIns, plugInOrigin, mimeType);
452     }
453
454     for (size_t i = 0, length = similarPlugins.size(); i < length; ++i) {
455         HTMLPlugInImageElement* plugInToRestart = similarPlugins[i].get();
456         if (plugInToRestart->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
457             LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart);
458             plugInToRestart->restartSnapshottedPlugIn();
459         }
460         plugInToRestart->m_snapshotDecision = NeverSnapshot;
461     }
462 }
463
464 void HTMLPlugInImageElement::userDidClickSnapshot(PassRefPtr<MouseEvent> event, bool forwardEvent)
465 {
466     if (forwardEvent)
467         m_pendingClickEventFromSnapshot = event;
468
469     String plugInOrigin = m_loadedUrl.host();
470     if (document().page() && !SchemeRegistry::shouldTreatURLSchemeAsLocal(document().page()->mainFrame().document()->baseURL().protocol()) && document().page()->settings().autostartOriginPlugInSnapshottingEnabled())
471         document().page()->plugInClient()->didStartFromOrigin(document().page()->mainFrame().document()->baseURL().host(), plugInOrigin, loadedMimeType(), document().page()->sessionID());
472
473     LOG(Plugins, "%p User clicked on snapshotted plug-in. Restart.", this);
474     restartSnapshottedPlugIn();
475     if (forwardEvent)
476         setDisplayState(RestartingWithPendingMouseClick);
477     restartSimilarPlugIns();
478 }
479
480 void HTMLPlugInImageElement::setIsPrimarySnapshottedPlugIn(bool isPrimarySnapshottedPlugIn)
481 {
482     if (!document().page() || !document().page()->settings().primaryPlugInSnapshotDetectionEnabled() || document().page()->settings().snapshotAllPlugIns())
483         return;
484
485     if (isPrimarySnapshottedPlugIn) {
486         if (m_plugInWasCreated) {
487             LOG(Plugins, "%p Plug-in was detected as the primary element in the page. Restart.", this);
488             restartSnapshottedPlugIn();
489             restartSimilarPlugIns();
490         } else {
491             LOG(Plugins, "%p Plug-in was detected as the primary element in the page, but is not yet created. Will restart later.", this);
492             m_deferredPromotionToPrimaryPlugIn = true;
493         }
494     }
495 }
496
497 void HTMLPlugInImageElement::restartSnapshottedPlugIn()
498 {
499     if (displayState() >= RestartingWithPendingMouseClick)
500         return;
501
502     setDisplayState(Restarting);
503     setNeedsStyleRecalc(ReconstructRenderTree);
504 }
505
506 void HTMLPlugInImageElement::dispatchPendingMouseClick()
507 {
508     ASSERT(!m_simulatedMouseClickTimer.isActive());
509     m_simulatedMouseClickTimer.restart();
510 }
511
512 void HTMLPlugInImageElement::simulatedMouseClickTimerFired(DeferrableOneShotTimer<HTMLPlugInImageElement>&)
513 {
514     ASSERT(displayState() == RestartingWithPendingMouseClick);
515     ASSERT(m_pendingClickEventFromSnapshot);
516
517     setDisplayState(Playing);
518     dispatchSimulatedClick(m_pendingClickEventFromSnapshot.get(), SendMouseOverUpDownEvents, DoNotShowPressedLook);
519
520     m_pendingClickEventFromSnapshot = nullptr;
521 }
522
523 static bool documentHadRecentUserGesture(Document& document)
524 {
525     double lastKnownUserGestureTimestamp = document.lastHandledUserGestureTimestamp();
526
527     if (document.frame() != &document.page()->mainFrame() && document.page()->mainFrame().document())
528         lastKnownUserGestureTimestamp = std::max(lastKnownUserGestureTimestamp, document.page()->mainFrame().document()->lastHandledUserGestureTimestamp());
529
530     if (monotonicallyIncreasingTime() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold)
531         return true;
532
533     return false;
534 }
535
536 void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
537 {
538     if (!m_needsCheckForSizeChange || m_snapshotDecision != MaySnapshotWhenResized || documentHadRecentUserGesture(document()))
539         return;
540
541     m_needsCheckForSizeChange = false;
542     LayoutRect contentBoxRect = toRenderBox(renderer())->contentBoxRect();
543     int contentWidth = contentBoxRect.width();
544     int contentHeight = contentBoxRect.height();
545
546     if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold)
547         return;
548
549     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);
550     setDisplayState(WaitingForSnapshot);
551     m_snapshotDecision = Snapshotted;
552     Widget* widget = pluginWidget();
553     if (widget && widget->isPluginViewBase())
554         toPluginViewBase(widget)->beginSnapshottingRunningPlugin();
555 }
556
557 static inline bool is100Percent(Length length)
558 {
559     return length.isPercentNotCalculated() && length.percent() == 100;
560 }
561     
562 static inline bool isSmallerThanTinySizingThreshold(const RenderEmbeddedObject& renderer)
563 {
564     LayoutRect contentRect = renderer.contentBoxRect();
565     return contentRect.width() <= sizingTinyDimensionThreshold || contentRect.height() <= sizingTinyDimensionThreshold;
566 }
567     
568 bool HTMLPlugInImageElement::isTopLevelFullPagePlugin(const RenderEmbeddedObject& renderer) const
569 {
570     Frame& frame = *document().frame();
571     if (!frame.isMainFrame())
572         return false;
573     
574     auto& style = renderer.style();
575     IntSize visibleSize = frame.view()->visibleSize();
576     LayoutRect contentRect = renderer.contentBoxRect();
577     int contentWidth = contentRect.width();
578     int contentHeight = contentRect.height();
579     return is100Percent(style.width()) && is100Percent(style.height()) && contentWidth * contentHeight > visibleSize.area() * sizingFullPageAreaRatioThreshold;
580 }
581     
582 void HTMLPlugInImageElement::checkSnapshotStatus()
583 {
584     if (!renderer()->isSnapshottedPlugIn()) {
585         if (displayState() == Playing)
586             checkSizeChangeForSnapshotting();
587         return;
588     }
589     
590     // 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.
591     if (!document().page()->settings().snapshotAllPlugIns() && displayState() <= DisplayingSnapshot && !m_plugInDimensionsSpecified) {
592         RenderSnapshottedPlugIn& renderer = toRenderSnapshottedPlugIn(*this->renderer());
593         if (!renderer.style().logicalWidth().isSpecified() && !renderer.style().logicalHeight().isSpecified())
594             return;
595         
596         m_plugInDimensionsSpecified = true;
597         if (isTopLevelFullPagePlugin(renderer)) {
598             m_snapshotDecision = NeverSnapshot;
599             restartSnapshottedPlugIn();
600         } else if (isSmallerThanTinySizingThreshold(renderer)) {
601             m_snapshotDecision = MaySnapshotWhenResized;
602             restartSnapshottedPlugIn();
603         }
604         return;
605     }
606     
607     // Notify the shadow root that the size changed so that we may update the overlay layout.
608     ensureUserAgentShadowRoot().dispatchEvent(Event::create(eventNames().resizeEvent, true, false));
609 }
610     
611 void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const URL& url)
612 {
613     LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
614     LOG(Plugins, "   Actual URL: %s", url.string().utf8().data());
615     LOG(Plugins, "   MIME type: %s", loadedMimeType().utf8().data());
616
617     m_loadedUrl = url;
618     m_plugInWasCreated = false;
619     m_deferredPromotionToPrimaryPlugIn = false;
620
621     if (!document().page() || !document().page()->settings().plugInSnapshottingEnabled()) {
622         m_snapshotDecision = NeverSnapshot;
623         return;
624     }
625
626     if (displayState() == Restarting) {
627         LOG(Plugins, "%p Plug-in is explicitly restarting", this);
628         m_snapshotDecision = NeverSnapshot;
629         setDisplayState(Playing);
630         return;
631     }
632
633     if (displayState() == RestartingWithPendingMouseClick) {
634         LOG(Plugins, "%p Plug-in is explicitly restarting but also waiting for a click", this);
635         m_snapshotDecision = NeverSnapshot;
636         return;
637     }
638
639     if (m_snapshotDecision == NeverSnapshot) {
640         LOG(Plugins, "%p Plug-in is blessed, allow it to start", this);
641         return;
642     }
643
644     bool inMainFrame = document().frame()->isMainFrame();
645
646     if (document().isPluginDocument() && inMainFrame) {
647         LOG(Plugins, "%p Plug-in document in main frame", this);
648         m_snapshotDecision = NeverSnapshot;
649         return;
650     }
651
652     if (ScriptController::processingUserGesture()) {
653         LOG(Plugins, "%p Script is currently processing user gesture, set to play", this);
654         m_snapshotDecision = NeverSnapshot;
655         return;
656     }
657
658     if (m_createdDuringUserGesture) {
659         LOG(Plugins, "%p Plug-in was created when processing user gesture, set to play", this);
660         m_snapshotDecision = NeverSnapshot;
661         return;
662     }
663
664     if (documentHadRecentUserGesture(document())) {
665         LOG(Plugins, "%p Plug-in was created shortly after a user gesture, set to play", this);
666         m_snapshotDecision = NeverSnapshot;
667         return;
668     }
669
670     if (document().page()->settings().snapshotAllPlugIns()) {
671         LOG(Plugins, "%p Plug-in forced to snapshot by user preference", this);
672         m_snapshotDecision = Snapshotted;
673         setDisplayState(WaitingForSnapshot);
674         return;
675     }
676
677     if (document().page()->settings().autostartOriginPlugInSnapshottingEnabled() && document().page()->plugInClient() && document().page()->plugInClient()->shouldAutoStartFromOrigin(document().page()->mainFrame().document()->baseURL().host(), url.host(), loadedMimeType())) {
678         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());
679         m_snapshotDecision = NeverSnapshot;
680         return;
681     }
682
683     if (m_loadedUrl.isEmpty() && !loadedMimeType().isEmpty()) {
684         LOG(Plugins, "%p Plug-in has no src URL but does have a valid mime type %s, set to play", this, loadedMimeType().utf8().data());
685         m_snapshotDecision = MaySnapshotWhenContentIsSet;
686         return;
687     }
688
689     if (!SchemeRegistry::shouldTreatURLSchemeAsLocal(m_loadedUrl.protocol()) && !m_loadedUrl.host().isEmpty() && m_loadedUrl.host() == document().page()->mainFrame().document()->baseURL().host()) {
690         LOG(Plugins, "%p Plug-in is served from page's domain, set to play", this);
691         m_snapshotDecision = NeverSnapshot;
692         return;
693     }
694     
695     auto& renderer = toRenderEmbeddedObject(*this->renderer());
696     LayoutRect contentRect = renderer.contentBoxRect();
697     int contentWidth = contentRect.width();
698     int contentHeight = contentRect.height();
699     
700     m_plugInDimensionsSpecified = renderer.style().logicalWidth().isSpecified() || renderer.style().logicalHeight().isSpecified();
701     
702     if (isTopLevelFullPagePlugin(renderer)) {
703         LOG(Plugins, "%p Plug-in is top level full page, set to play", this);
704         m_snapshotDecision = NeverSnapshot;
705         return;
706     }
707
708     if (isSmallerThanTinySizingThreshold(renderer)) {
709         LOG(Plugins, "%p Plug-in is very small %dx%d, set to play", this, contentWidth, contentHeight);
710         m_sizeWhenSnapshotted = IntSize(contentWidth, contentHeight);
711         m_snapshotDecision = MaySnapshotWhenResized;
712         return;
713     }
714
715     if (!document().page()->plugInClient()) {
716         LOG(Plugins, "%p There is no plug-in client. Set to wait for snapshot", this);
717         m_snapshotDecision = NeverSnapshot;
718         setDisplayState(WaitingForSnapshot);
719         return;
720     }
721
722     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);
723     m_snapshotDecision = Snapshotted;
724     setDisplayState(WaitingForSnapshot);
725 }
726
727 void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget* widget)
728 {
729     m_plugInWasCreated = true;
730
731     if (widget->isPluginViewBase() && toPluginViewBase(widget)->shouldAlwaysAutoStart()) {
732         LOG(Plugins, "%p Plug-in should auto-start, set to play", this);
733         m_snapshotDecision = NeverSnapshot;
734         setDisplayState(Playing);
735         return;
736     }
737
738     if (m_deferredPromotionToPrimaryPlugIn) {
739         LOG(Plugins, "%p Plug-in was created, previously deferred promotion to primary. Will promote", this);
740         setIsPrimarySnapshottedPlugIn(true);
741         m_deferredPromotionToPrimaryPlugIn = false;
742     }
743 }
744
745 void HTMLPlugInImageElement::defaultEventHandler(Event* event)
746 {
747     RenderElement* r = renderer();
748     if (r && r->isEmbeddedObject()) {
749         if (isPlugInImageElement() && displayState() == WaitingForSnapshot && event->isMouseEvent() && event->type() == eventNames().clickEvent) {
750             MouseEvent* mouseEvent = toMouseEvent(event);
751             if (mouseEvent->button() == LeftButton) {
752                 userDidClickSnapshot(mouseEvent, true);
753                 event->setDefaultHandled();
754                 return;
755             }
756         }
757     }
758     HTMLPlugInElement::defaultEventHandler(event);
759 }
760
761 bool HTMLPlugInImageElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
762 {
763     if (HTMLPlugInElement::requestObject(url, mimeType, paramNames, paramValues))
764         return true;
765     
766     SubframeLoader& loader = document().frame()->loader().subframeLoader();
767     return loader.requestObject(*this, url, getNameAttribute(), mimeType, paramNames, paramValues);
768 }
769
770 } // namespace WebCore