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