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