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