Plugins that resize might need to be snapshotted.
[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 "Frame.h"
27 #include "FrameLoader.h"
28 #include "FrameLoaderClient.h"
29 #include "FrameView.h"
30 #include "HTMLDivElement.h"
31 #include "HTMLImageLoader.h"
32 #include "Image.h"
33 #include "LocalizedStrings.h"
34 #include "Logging.h"
35 #include "MouseEvent.h"
36 #include "NodeList.h"
37 #include "NodeRenderStyle.h"
38 #include "NodeRenderingContext.h"
39 #include "Page.h"
40 #include "PlugInClient.h"
41 #include "PluginViewBase.h"
42 #include "RenderEmbeddedObject.h"
43 #include "RenderImage.h"
44 #include "RenderSnapshottedPlugIn.h"
45 #include "SchemeRegistry.h"
46 #include "ScriptController.h"
47 #include "SecurityOrigin.h"
48 #include "Settings.h"
49 #include "ShadowRoot.h"
50 #include "StyleResolver.h"
51 #include "Text.h"
52 #include <wtf/CurrentTime.h>
53
54 namespace WebCore {
55
56 using namespace HTMLNames;
57
58 typedef Vector<RefPtr<HTMLPlugInImageElement> > HTMLPlugInImageElementList;
59
60 static const int sizingTinyDimensionThreshold = 40;
61 static const int sizingSmallWidthThreshold = 250;
62 static const int sizingMediumWidthThreshold = 450;
63 static const int sizingMediumHeightThreshold = 300;
64 static const float sizingFullPageAreaRatioThreshold = 0.96;
65 static const float autostartSoonAfterUserGestureThreshold = 5.0;
66
67 // This delay should not exceed the snapshot delay in PluginView.cpp
68 static const double simulatedMouseClickTimerDelay = .75;
69 static const double removeSnapshotTimerDelay = 1.5;
70
71 HTMLPlugInImageElement::HTMLPlugInImageElement(const QualifiedName& tagName, Document* document, bool createdByParser, PreferPlugInsForImagesOption preferPlugInsForImagesOption)
72     : HTMLPlugInElement(tagName, document)
73     // m_needsWidgetUpdate(!createdByParser) allows HTMLObjectElement to delay
74     // widget updates until after all children are parsed.  For HTMLEmbedElement
75     // this delay is unnecessary, but it is simpler to make both classes share
76     // the same codepath in this class.
77     , m_needsWidgetUpdate(!createdByParser)
78     , m_shouldPreferPlugInsForImages(preferPlugInsForImagesOption == ShouldPreferPlugInsForImages)
79     , m_needsDocumentActivationCallbacks(false)
80     , m_simulatedMouseClickTimer(this, &HTMLPlugInImageElement::simulatedMouseClickTimerFired, simulatedMouseClickTimerDelay)
81     , m_swapRendererTimer(this, &HTMLPlugInImageElement::swapRendererTimerFired)
82     , m_removeSnapshotTimer(this, &HTMLPlugInImageElement::removeSnapshotTimerFired)
83     , m_createdDuringUserGesture(ScriptController::processingUserGesture())
84     , m_restartedPlugin(false)
85     , m_needsCheckForSizeChange(false)
86     , m_snapshotDecision(SnapshotNotYetDecided)
87 {
88     setHasCustomStyleCallbacks();
89 }
90
91 HTMLPlugInImageElement::~HTMLPlugInImageElement()
92 {
93     if (m_needsDocumentActivationCallbacks)
94         document()->unregisterForPageCacheSuspensionCallbacks(this);
95 }
96
97 void HTMLPlugInImageElement::setDisplayState(DisplayState state)
98 {
99 #if PLATFORM(MAC)
100     if (state == RestartingWithPendingMouseClick || state == Restarting) {
101         m_restartedPlugin = true;
102         m_snapshotDecision = NeverSnapshot;
103         if (displayState() == DisplayingSnapshot)
104             m_removeSnapshotTimer.startOneShot(removeSnapshotTimerDelay);
105     }
106 #endif
107
108     HTMLPlugInElement::setDisplayState(state);
109
110     if (state == DisplayingSnapshot)
111         m_swapRendererTimer.startOneShot(0);
112 }
113
114 RenderEmbeddedObject* HTMLPlugInImageElement::renderEmbeddedObject() const
115 {
116     // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers
117     // when using fallback content.
118     if (!renderer() || !renderer()->isEmbeddedObject())
119         return 0;
120     return toRenderEmbeddedObject(renderer());
121 }
122
123 bool HTMLPlugInImageElement::isImageType()
124 {
125     if (m_serviceType.isEmpty() && protocolIs(m_url, "data"))
126         m_serviceType = mimeTypeFromDataURL(m_url);
127
128     if (Frame* frame = document()->frame()) {
129         KURL completedURL = document()->completeURL(m_url);
130         return frame->loader()->client()->objectContentType(completedURL, m_serviceType, shouldPreferPlugInsForImages()) == ObjectContentImage;
131     }
132
133     return Image::supportsType(m_serviceType);
134 }
135
136 // We don't use m_url, as it may not be the final URL that the object loads,
137 // depending on <param> values.
138 bool HTMLPlugInImageElement::allowedToLoadFrameURL(const String& url)
139 {
140     KURL completeURL = document()->completeURL(url);
141
142     if (contentFrame() && protocolIsJavaScript(completeURL)
143         && !document()->securityOrigin()->canAccess(contentDocument()->securityOrigin()))
144         return false;
145
146     return document()->frame()->isURLAllowed(completeURL);
147 }
148
149 // We don't use m_url, or m_serviceType as they may not be the final values
150 // that <object> uses depending on <param> values.
151 bool HTMLPlugInImageElement::wouldLoadAsNetscapePlugin(const String& url, const String& serviceType)
152 {
153     ASSERT(document());
154     ASSERT(document()->frame());
155     KURL completedURL;
156     if (!url.isEmpty())
157         completedURL = document()->completeURL(url);
158
159     FrameLoader* frameLoader = document()->frame()->loader();
160     ASSERT(frameLoader);
161     if (frameLoader->client()->objectContentType(completedURL, serviceType, shouldPreferPlugInsForImages()) == ObjectContentNetscapePlugin)
162         return true;
163     return false;
164 }
165
166 RenderObject* HTMLPlugInImageElement::createRenderer(RenderArena* arena, RenderStyle* style)
167 {
168     // Once a PlugIn Element creates its renderer, it needs to be told when the Document goes
169     // inactive or reactivates so it can clear the renderer before going into the page cache.
170     if (!m_needsDocumentActivationCallbacks) {
171         m_needsDocumentActivationCallbacks = true;
172         document()->registerForPageCacheSuspensionCallbacks(this);
173     }
174
175     if (displayState() == DisplayingSnapshot) {
176         RenderSnapshottedPlugIn* renderSnapshottedPlugIn = new (arena) RenderSnapshottedPlugIn(this);
177         renderSnapshottedPlugIn->updateSnapshot(m_snapshotImage);
178         return renderSnapshottedPlugIn;
179     }
180
181     // Fallback content breaks the DOM->Renderer class relationship of this
182     // class and all superclasses because createObject won't necessarily
183     // return a RenderEmbeddedObject, RenderPart or even RenderWidget.
184     if (useFallbackContent())
185         return RenderObject::createObject(this, style);
186
187     if (isImageType()) {
188         RenderImage* image = new (arena) RenderImage(this);
189         image->setImageResource(RenderImageResource::create());
190         return image;
191     }
192
193     return new (arena) RenderEmbeddedObject(this);
194 }
195
196 bool HTMLPlugInImageElement::willRecalcStyle(StyleChange)
197 {
198     // FIXME: Why is this necessary?  Manual re-attach is almost always wrong.
199     if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType() && (displayState() != DisplayingSnapshot))
200         reattach();
201     return true;
202 }
203
204 void HTMLPlugInImageElement::attach()
205 {
206     PostAttachCallbackDisabler disabler(this);
207
208     bool isImage = isImageType();
209
210     if (!isImage)
211         queuePostAttachCallback(&HTMLPlugInImageElement::updateWidgetCallback, this);
212
213     HTMLPlugInElement::attach();
214
215     if (isImage && renderer() && !useFallbackContent()) {
216         if (!m_imageLoader)
217             m_imageLoader = adoptPtr(new HTMLImageLoader(this));
218         m_imageLoader->updateFromElement();
219     }
220 }
221
222 void HTMLPlugInImageElement::detach()
223 {
224     // FIXME: Because of the insanity that is HTMLPlugInImageElement::recalcStyle,
225     // we can end up detaching during an attach() call, before we even have a
226     // renderer.  In that case, don't mark the widget for update.
227     if (attached() && renderer() && !useFallbackContent())
228         // Update the widget the next time we attach (detaching destroys the plugin).
229         setNeedsWidgetUpdate(true);
230     HTMLPlugInElement::detach();
231 }
232
233 void HTMLPlugInImageElement::updateWidgetIfNecessary()
234 {
235     document()->updateStyleIfNeeded();
236
237     if (!needsWidgetUpdate() || useFallbackContent() || isImageType())
238         return;
239
240     if (!renderEmbeddedObject() || renderEmbeddedObject()->showsUnavailablePluginIndicator())
241         return;
242
243     updateWidget(CreateOnlyNonNetscapePlugins);
244 }
245
246 void HTMLPlugInImageElement::finishParsingChildren()
247 {
248     HTMLPlugInElement::finishParsingChildren();
249     if (useFallbackContent())
250         return;
251
252     setNeedsWidgetUpdate(true);
253     if (inDocument())
254         setNeedsStyleRecalc();
255 }
256
257 void HTMLPlugInImageElement::didMoveToNewDocument(Document* oldDocument)
258 {
259     if (m_needsDocumentActivationCallbacks) {
260         if (oldDocument)
261             oldDocument->unregisterForPageCacheSuspensionCallbacks(this);
262         document()->registerForPageCacheSuspensionCallbacks(this);
263     }
264
265     if (m_imageLoader)
266         m_imageLoader->elementDidMoveToNewDocument();
267     HTMLPlugInElement::didMoveToNewDocument(oldDocument);
268 }
269
270 void HTMLPlugInImageElement::documentWillSuspendForPageCache()
271 {
272     if (RenderStyle* renderStyle = this->renderStyle()) {
273         m_customStyleForPageCache = RenderStyle::clone(renderStyle);
274         m_customStyleForPageCache->setDisplay(NONE);
275         recalcStyle(Force);
276     }
277
278     HTMLPlugInElement::documentWillSuspendForPageCache();
279 }
280
281 void HTMLPlugInImageElement::documentDidResumeFromPageCache()
282 {
283     if (m_customStyleForPageCache) {
284         m_customStyleForPageCache = 0;
285         recalcStyle(Force);
286     }
287
288     HTMLPlugInElement::documentDidResumeFromPageCache();
289 }
290
291 PassRefPtr<RenderStyle> HTMLPlugInImageElement::customStyleForRenderer()
292 {
293     if (!m_customStyleForPageCache)
294         return document()->styleResolver()->styleForElement(this);
295     return m_customStyleForPageCache;
296 }
297
298 void HTMLPlugInImageElement::updateWidgetCallback(Node* n, unsigned)
299 {
300     static_cast<HTMLPlugInImageElement*>(n)->updateWidgetIfNecessary();
301 }
302
303 void HTMLPlugInImageElement::updateSnapshot(PassRefPtr<Image> image)
304 {
305     if (displayState() > DisplayingSnapshot)
306         return;
307
308     m_snapshotImage = image;
309
310     if (renderer()->isSnapshottedPlugIn()) {
311         toRenderSnapshottedPlugIn(renderer())->updateSnapshot(image);
312         return;
313     }
314
315     if (renderer()->isEmbeddedObject())
316         renderer()->repaint();
317 }
318
319 static AtomicString classNameForShadowRoot(const Node* node)
320 {
321     DEFINE_STATIC_LOCAL(const AtomicString, plugInTinySizeClassName, ("tiny", AtomicString::ConstructFromLiteral));
322     DEFINE_STATIC_LOCAL(const AtomicString, plugInSmallSizeClassName, ("small", AtomicString::ConstructFromLiteral));
323     DEFINE_STATIC_LOCAL(const AtomicString, plugInMediumSizeClassName, ("medium", AtomicString::ConstructFromLiteral));
324     DEFINE_STATIC_LOCAL(const AtomicString, plugInLargeSizeClassName, ("large", AtomicString::ConstructFromLiteral));
325
326     RenderBox* renderBox = static_cast<RenderBox*>(node->renderer());
327     LayoutUnit width = renderBox->contentWidth();
328     LayoutUnit height = renderBox->contentHeight();
329
330     if (width < sizingTinyDimensionThreshold || height < sizingTinyDimensionThreshold)
331         return plugInTinySizeClassName;
332
333     if (width < sizingSmallWidthThreshold)
334         return plugInSmallSizeClassName;
335
336     if (width < sizingMediumWidthThreshold || height < sizingMediumHeightThreshold)
337         return plugInMediumSizeClassName;
338
339     return plugInLargeSizeClassName;
340 }
341
342 void HTMLPlugInImageElement::checkSnapshotStatus()
343 {
344     if (!renderer()->isSnapshottedPlugIn()) {
345         if (displayState() == Playing)
346             checkSizeChangeForSnapshotting();
347         return;
348     }
349
350     ShadowRoot* root = userAgentShadowRoot();
351     if (!root)
352         return;
353
354     Element* shadowContainer = toElement(root->firstChild());
355     shadowContainer->setAttribute(classAttr, classNameForShadowRoot(this));
356 }
357
358 void HTMLPlugInImageElement::didAddUserAgentShadowRoot(ShadowRoot* root)
359 {
360     Document* doc = document();
361
362     m_shadowContainer = HTMLDivElement::create(doc);
363     m_shadowContainer->setPseudo(AtomicString("-webkit-snapshotted-plugin-content", AtomicString::ConstructFromLiteral));
364
365     RefPtr<Element> container = HTMLDivElement::create(doc);
366     container->setAttribute(classAttr, AtomicString("snapshot-container", AtomicString::ConstructFromLiteral));
367
368     RefPtr<Element> overlay = HTMLDivElement::create(doc);
369     overlay->setAttribute(classAttr, AtomicString("snapshot-overlay", AtomicString::ConstructFromLiteral));
370     container->appendChild(overlay, ASSERT_NO_EXCEPTION);
371
372     m_snapshotLabel = HTMLDivElement::create(doc);
373     m_snapshotLabel->setAttribute(classAttr, AtomicString("snapshot-label", AtomicString::ConstructFromLiteral));
374
375     String titleText = snapshottedPlugInLabelTitle();
376     String subtitleText = snapshottedPlugInLabelSubtitle();
377     if (document()->page()) {
378         String clientTitleText = document()->page()->chrome()->client()->plugInStartLabelTitle();
379         if (!clientTitleText.isEmpty())
380             titleText = clientTitleText;
381         String clientSubtitleText = document()->page()->chrome()->client()->plugInStartLabelSubtitle();
382         if (!clientSubtitleText.isEmpty())
383             subtitleText = clientSubtitleText;
384     }
385
386     RefPtr<Element> title = HTMLDivElement::create(doc);
387     title->setAttribute(classAttr, AtomicString("snapshot-title", AtomicString::ConstructFromLiteral));
388     title->appendChild(doc->createTextNode(titleText), ASSERT_NO_EXCEPTION);
389     m_snapshotLabel->appendChild(title, ASSERT_NO_EXCEPTION);
390
391     RefPtr<Element> subTitle = HTMLDivElement::create(doc);
392     subTitle->setAttribute(classAttr, AtomicString("snapshot-subtitle", AtomicString::ConstructFromLiteral));
393     subTitle->appendChild(doc->createTextNode(subtitleText), ASSERT_NO_EXCEPTION);
394     m_snapshotLabel->appendChild(subTitle, ASSERT_NO_EXCEPTION);
395
396     container->appendChild(m_snapshotLabel, ASSERT_NO_EXCEPTION);
397
398     // Make this into a button for accessibility clients.
399     String combinedText = titleText;
400     if (!combinedText.isEmpty() && !subtitleText.isEmpty())
401         combinedText.append(" ");
402     combinedText.append(subtitleText);
403     container->setAttribute(aria_labelAttr, combinedText);
404     container->setAttribute(roleAttr, "button");
405
406     m_shadowContainer->appendChild(container, ASSERT_NO_EXCEPTION);
407     root->appendChild(m_shadowContainer, ASSERT_NO_EXCEPTION);
408 }
409
410 bool HTMLPlugInImageElement::partOfSnapshotLabel(Node* node)
411 {
412     return node && (node == m_snapshotLabel.get() || node->isDescendantOf(m_snapshotLabel.get()));
413 }
414
415 void HTMLPlugInImageElement::swapRendererTimerFired(Timer<HTMLPlugInImageElement>*)
416 {
417     ASSERT(displayState() == DisplayingSnapshot);
418     if (userAgentShadowRoot())
419         return;
420
421     // Create a shadow root, which will trigger the code to add a snapshot container
422     // and reattach, thus making a new Renderer.
423     ensureUserAgentShadowRoot();
424 }
425
426 void HTMLPlugInImageElement::removeSnapshotTimerFired(Timer<HTMLPlugInImageElement>*)
427 {
428     m_snapshotImage = nullptr;
429     m_restartedPlugin = false;
430     if (renderer())
431         renderer()->repaint();
432 }
433
434 static void addPlugInsFromNodeListMatchingPlugInOrigin(HTMLPlugInImageElementList& plugInList, PassRefPtr<NodeList> collection, const String& plugInOrigin, const String& mimeType)
435 {
436     for (unsigned i = 0, length = collection->length(); i < length; i++) {
437         Node* node = collection->item(i);
438         if (node->isPluginElement()) {
439             HTMLPlugInElement* plugInElement = toHTMLPlugInElement(node);
440             if (plugInElement->isPlugInImageElement()) {
441                 HTMLPlugInImageElement* plugInImageElement = toHTMLPlugInImageElement(node);
442                 const KURL& loadedURL = plugInImageElement->loadedUrl();
443                 String otherMimeType = plugInImageElement->loadedMimeType();
444                 if (plugInOrigin == loadedURL.host() && mimeType == otherMimeType)
445                     plugInList.append(plugInImageElement);
446             }
447         }
448     }
449 }
450
451 void HTMLPlugInImageElement::restartSimilarPlugIns()
452 {
453     // Restart any other snapshotted plugins in the page with the same origin. Note that they
454     // may be in different frames, so traverse from the top of the document.
455
456     String plugInOrigin = m_loadedUrl.host();
457     String mimeType = loadedMimeType();
458     HTMLPlugInImageElementList similarPlugins;
459
460     if (!document()->page())
461         return;
462
463     for (Frame* frame = document()->page()->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
464         if (!frame->loader()->subframeLoader()->containsPlugins())
465             continue;
466         
467         if (!frame->document())
468             continue;
469
470         RefPtr<NodeList> plugIns = frame->document()->getElementsByTagName(embedTag.localName());
471         if (plugIns)
472             addPlugInsFromNodeListMatchingPlugInOrigin(similarPlugins, plugIns, plugInOrigin, mimeType);
473
474         plugIns = frame->document()->getElementsByTagName(objectTag.localName());
475         if (plugIns)
476             addPlugInsFromNodeListMatchingPlugInOrigin(similarPlugins, plugIns, plugInOrigin, mimeType);
477     }
478
479     for (size_t i = 0, length = similarPlugins.size(); i < length; ++i) {
480         HTMLPlugInImageElement* plugInToRestart = similarPlugins[i].get();
481         if (plugInToRestart->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
482             LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart);
483             plugInToRestart->setDisplayState(Playing);
484             plugInToRestart->restartSnapshottedPlugIn();
485         }
486         plugInToRestart->m_snapshotDecision = NeverSnapshot;
487     }
488 }
489
490 void HTMLPlugInImageElement::userDidClickSnapshot(PassRefPtr<MouseEvent> event, bool forwardEvent)
491 {
492     if (forwardEvent)
493         m_pendingClickEventFromSnapshot = event;
494
495     String plugInOrigin = m_loadedUrl.host();
496     if (document()->page() && !SchemeRegistry::shouldTreatURLSchemeAsLocal(document()->page()->mainFrame()->document()->baseURL().protocol()) && document()->page()->settings()->autostartOriginPlugInSnapshottingEnabled())
497         document()->page()->plugInClient()->didStartFromOrigin(document()->page()->mainFrame()->document()->baseURL().host(), plugInOrigin, loadedMimeType());
498
499     LOG(Plugins, "%p User clicked on snapshotted plug-in. Restart.", this);
500     restartSnapshottedPlugIn();
501     restartSimilarPlugIns();
502 }
503
504 void HTMLPlugInImageElement::setIsPrimarySnapshottedPlugIn(bool isPrimarySnapshottedPlugIn)
505 {
506     if (!document()->page() || !document()->page()->settings()->primaryPlugInSnapshotDetectionEnabled() || document()->page()->settings()->snapshotAllPlugIns())
507         return;
508
509     if (isPrimarySnapshottedPlugIn) {
510         LOG(Plugins, "%p Plug-in was detected as the primary element in the page. Restart.", this);
511         restartSnapshottedPlugIn();
512         restartSimilarPlugIns();
513     }
514 }
515
516 void HTMLPlugInImageElement::restartSnapshottedPlugIn()
517 {
518     if (displayState() != RestartingWithPendingMouseClick)
519         setDisplayState(Restarting);
520
521     reattach();
522 }
523
524 void HTMLPlugInImageElement::dispatchPendingMouseClick()
525 {
526     ASSERT(!m_simulatedMouseClickTimer.isActive());
527     m_simulatedMouseClickTimer.restart();
528 }
529
530 void HTMLPlugInImageElement::simulatedMouseClickTimerFired(DeferrableOneShotTimer<HTMLPlugInImageElement>*)
531 {
532     ASSERT(displayState() == RestartingWithPendingMouseClick);
533     ASSERT(m_pendingClickEventFromSnapshot);
534
535     dispatchSimulatedClick(m_pendingClickEventFromSnapshot.get(), SendMouseOverUpDownEvents, DoNotShowPressedLook);
536
537     setDisplayState(Playing);
538     m_pendingClickEventFromSnapshot = nullptr;
539 }
540
541 void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
542 {
543     if (!m_needsCheckForSizeChange || m_snapshotDecision != MaySnapshotWhenResized)
544         return;
545
546     m_needsCheckForSizeChange = false;
547     LayoutRect contentBoxRect = toRenderBox(renderer())->contentBoxRect();
548     int contentWidth = contentBoxRect.width();
549     int contentHeight = contentBoxRect.height();
550
551     if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold)
552         return;
553
554     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);
555     setDisplayState(WaitingForSnapshot);
556     m_snapshotDecision = Snapshotted;
557     Widget* widget = pluginWidget();
558     if (widget && widget->isPluginViewBase())
559         toPluginViewBase(widget)->beginSnapshottingRunningPlugin();
560 }
561
562 void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const KURL& url)
563 {
564     LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
565     LOG(Plugins, "   Loaded URL: %s", url.string().utf8().data());
566
567     m_loadedUrl = url;
568
569     if (!document()->page() || !document()->page()->settings()->plugInSnapshottingEnabled()) {
570         m_snapshotDecision = NeverSnapshot;
571         return;
572     }
573
574     if (displayState() == Restarting) {
575         LOG(Plugins, "%p Plug-in is explicitly restarting", this);
576         m_snapshotDecision = NeverSnapshot;
577         setDisplayState(Playing);
578         return;
579     }
580
581     if (displayState() == RestartingWithPendingMouseClick) {
582         LOG(Plugins, "%p Plug-in is explicitly restarting but also waiting for a click", this);
583         m_snapshotDecision = NeverSnapshot;
584         return;
585     }
586
587     if (m_snapshotDecision == NeverSnapshot) {
588         LOG(Plugins, "%p Plug-in is blessed, allow it to start", this);
589         return;
590     }
591     
592     bool inMainFrame = document()->frame() == document()->page()->mainFrame();
593
594     if (document()->isPluginDocument() && inMainFrame) {
595         LOG(Plugins, "%p Plug-in document in main frame", this);
596         m_snapshotDecision = NeverSnapshot;
597         return;
598     }
599
600     if (ScriptController::processingUserGesture()) {
601         LOG(Plugins, "%p Script is currently processing user gesture, set to play", this);
602         m_snapshotDecision = NeverSnapshot;
603         return;
604     }
605
606     if (m_createdDuringUserGesture) {
607         LOG(Plugins, "%p Plug-in was created when processing user gesture, set to play", this);
608         m_snapshotDecision = NeverSnapshot;
609         return;
610     }
611
612     double lastKnownUserGestureTimestamp = document()->lastHandledUserGestureTimestamp();
613     if (!inMainFrame && document()->page()->mainFrame() && document()->page()->mainFrame()->document())
614         lastKnownUserGestureTimestamp = std::max(lastKnownUserGestureTimestamp, document()->page()->mainFrame()->document()->lastHandledUserGestureTimestamp());
615     if (currentTime() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold) {
616         LOG(Plugins, "%p Plug-in was created shortly after a user gesture, set to play", this);
617         m_snapshotDecision = NeverSnapshot;
618         return;
619     }
620
621     if (document()->page()->settings()->snapshotAllPlugIns()) {
622         LOG(Plugins, "%p Plug-in forced to snapshot by user preference", this);
623         m_snapshotDecision = Snapshotted;
624         setDisplayState(WaitingForSnapshot);
625         return;
626     }
627
628     if (document()->page()->settings()->autostartOriginPlugInSnapshottingEnabled() && document()->page()->plugInClient()->shouldAutoStartFromOrigin(document()->page()->mainFrame()->document()->baseURL().host(), url.host(), loadedMimeType())) {
629         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());
630         m_snapshotDecision = NeverSnapshot;
631         return;
632     }
633
634     RenderBox* renderEmbeddedObject = toRenderBox(renderer());
635     Length styleWidth = renderEmbeddedObject->style()->width();
636     Length styleHeight = renderEmbeddedObject->style()->height();
637     LayoutRect contentBoxRect = renderEmbeddedObject->contentBoxRect();
638     int contentWidth = contentBoxRect.width();
639     int contentHeight = contentBoxRect.height();
640     int contentArea = contentWidth * contentHeight;
641     IntSize visibleViewSize = document()->frame()->view()->visibleSize();
642     int visibleArea = visibleViewSize.width() * visibleViewSize.height();
643
644     if (inMainFrame && styleWidth.isPercent() && (styleWidth.percent() == 100)
645         && styleHeight.isPercent() && (styleHeight.percent() == 100)
646         && (static_cast<float>(contentArea) / visibleArea > sizingFullPageAreaRatioThreshold)) {
647         LOG(Plugins, "%p Plug-in is top level full page, set to play", this);
648         m_snapshotDecision = NeverSnapshot;
649         return;
650     }
651
652     if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold) {
653         LOG(Plugins, "%p Plug-in is very small %dx%d, set to play", this, contentWidth, contentHeight);
654         m_sizeWhenSnapshotted = IntSize(contentBoxRect.width().toInt(), contentBoxRect.height().toInt());
655         m_snapshotDecision = MaySnapshotWhenResized;
656         return;
657     }
658
659     if (!document()->page()->plugInClient()) {
660         LOG(Plugins, "%p There is no plug-in client. Set to wait for snapshot", this);
661         m_snapshotDecision = NeverSnapshot;
662         setDisplayState(WaitingForSnapshot);
663         return;
664     }
665
666     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);
667     m_snapshotDecision = Snapshotted;
668     setDisplayState(WaitingForSnapshot);
669 }
670
671 void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget* widget)
672 {
673     if (!widget->isPluginViewBase() || !toPluginViewBase(widget)->shouldAlwaysAutoStart())
674         return;
675
676     LOG(Plugins, "%p Plug-in should auto-start, set to play", this);
677     m_snapshotDecision = NeverSnapshot;
678     setDisplayState(Playing);
679 }
680
681 } // namespace WebCore