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