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