1/*
2 * Copyright (C) 2008-2017 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 "CommonVM.h"
27#include "ContentSecurityPolicy.h"
28#include "EventNames.h"
29#include "Frame.h"
30#include "FrameLoaderClient.h"
31#include "HTMLImageLoader.h"
32#include "JSDOMConvertBoolean.h"
33#include "JSDOMConvertInterface.h"
34#include "JSDOMConvertStrings.h"
35#include "JSShadowRoot.h"
36#include "LocalizedStrings.h"
37#include "Logging.h"
38#include "MouseEvent.h"
39#include "Page.h"
40#include "PlatformMouseEvent.h"
41#include "PlugInClient.h"
42#include "PluginViewBase.h"
43#include "RenderImage.h"
44#include "RenderSnapshottedPlugIn.h"
45#include "RenderTreeUpdater.h"
46#include "SchemeRegistry.h"
47#include "ScriptController.h"
48#include "SecurityOrigin.h"
49#include "Settings.h"
50#include "ShadowRoot.h"
51#include "StyleTreeResolver.h"
52#include "SubframeLoader.h"
53#include "TypedElementDescendantIterator.h"
54#include "UserGestureIndicator.h"
55#include <JavaScriptCore/CatchScope.h>
56#include <wtf/IsoMallocInlines.h>
57
58namespace WebCore {
59
60WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLPlugInImageElement);
61
62static const int sizingTinyDimensionThreshold = 40;
63static const float sizingFullPageAreaRatioThreshold = 0.96;
64static const Seconds autostartSoonAfterUserGestureThreshold = 5_s;
65
66// This delay should not exceed the snapshot delay in PluginView.cpp
67static const Seconds simulatedMouseClickTimerDelay { 750_ms };
68
69#if PLATFORM(COCOA)
70static const Seconds removeSnapshotTimerDelay { 1500_ms };
71#endif
72
73static const String titleText(Page& page, const String& mimeType)
74{
75 if (mimeType.isEmpty())
76 return snapshottedPlugInLabelTitle();
77
78 // FIXME: It's not consistent to get a string from the page's chrome client, but then cache it globally.
79 // If it's global, it should come from elsewhere. If it's per-page then it should be cached per page.
80 static NeverDestroyed<HashMap<String, String>> mimeTypeToLabelTitleMap;
81 return mimeTypeToLabelTitleMap.get().ensure(mimeType, [&] {
82 auto title = page.chrome().client().plugInStartLabelTitle(mimeType);
83 if (!title.isEmpty())
84 return title;
85 return snapshottedPlugInLabelTitle();
86 }).iterator->value;
87};
88
89static const String subtitleText(Page& page, const String& mimeType)
90{
91 if (mimeType.isEmpty())
92 return snapshottedPlugInLabelSubtitle();
93
94 // FIXME: It's not consistent to get a string from the page's chrome client, but then cache it globally.
95 // If it's global, it should come from elsewhere. If it's per-page then it should be cached per page.
96 static NeverDestroyed<HashMap<String, String>> mimeTypeToLabelSubtitleMap;
97 return mimeTypeToLabelSubtitleMap.get().ensure(mimeType, [&] {
98 auto subtitle = page.chrome().client().plugInStartLabelSubtitle(mimeType);
99 if (!subtitle.isEmpty())
100 return subtitle;
101 return snapshottedPlugInLabelSubtitle();
102 }).iterator->value;
103};
104
105HTMLPlugInImageElement::HTMLPlugInImageElement(const QualifiedName& tagName, Document& document)
106 : HTMLPlugInElement(tagName, document)
107 , m_simulatedMouseClickTimer(*this, &HTMLPlugInImageElement::simulatedMouseClickTimerFired, simulatedMouseClickTimerDelay)
108 , m_removeSnapshotTimer(*this, &HTMLPlugInImageElement::removeSnapshotTimerFired)
109 , m_createdDuringUserGesture(UserGestureIndicator::processingUserGesture())
110{
111 setHasCustomStyleResolveCallbacks();
112}
113
114void HTMLPlugInImageElement::finishCreating()
115{
116 scheduleUpdateForAfterStyleResolution();
117}
118
119HTMLPlugInImageElement::~HTMLPlugInImageElement()
120{
121 if (m_needsDocumentActivationCallbacks)
122 document().unregisterForDocumentSuspensionCallbacks(*this);
123}
124
125void HTMLPlugInImageElement::setDisplayState(DisplayState state)
126{
127#if PLATFORM(COCOA)
128 if (state == RestartingWithPendingMouseClick || state == Restarting) {
129 m_isRestartedPlugin = true;
130 m_snapshotDecision = NeverSnapshot;
131 invalidateStyleAndLayerComposition();
132 if (displayState() == DisplayingSnapshot)
133 m_removeSnapshotTimer.startOneShot(removeSnapshotTimerDelay);
134 }
135#endif
136
137 HTMLPlugInElement::setDisplayState(state);
138}
139
140RenderEmbeddedObject* HTMLPlugInImageElement::renderEmbeddedObject() const
141{
142 // HTMLObjectElement and HTMLEmbedElement may return arbitrary renderers when using fallback content.
143 return is<RenderEmbeddedObject>(renderer()) ? downcast<RenderEmbeddedObject>(renderer()) : nullptr;
144}
145
146bool HTMLPlugInImageElement::isImageType()
147{
148 if (m_serviceType.isEmpty() && protocolIs(m_url, "data"))
149 m_serviceType = mimeTypeFromDataURL(m_url);
150
151 if (auto frame = makeRefPtr(document().frame()))
152 return frame->loader().client().objectContentType(document().completeURL(m_url), m_serviceType) == ObjectContentType::Image;
153
154 return Image::supportsType(m_serviceType);
155}
156
157// We don't use m_url, as it may not be the final URL that the object loads, depending on <param> values.
158bool HTMLPlugInImageElement::allowedToLoadFrameURL(const String& url)
159{
160 URL completeURL = document().completeURL(url);
161 if (contentFrame() && WTF::protocolIsJavaScript(completeURL) && !document().securityOrigin().canAccess(contentDocument()->securityOrigin()))
162 return false;
163 return document().frame()->isURLAllowed(completeURL);
164}
165
166// We don't use m_url, or m_serviceType as they may not be the final values
167// that <object> uses depending on <param> values.
168bool HTMLPlugInImageElement::wouldLoadAsPlugIn(const String& url, const String& serviceType)
169{
170 ASSERT(document().frame());
171 URL completedURL;
172 if (!url.isEmpty())
173 completedURL = document().completeURL(url);
174 return document().frame()->loader().client().objectContentType(completedURL, serviceType) == ObjectContentType::PlugIn;
175}
176
177RenderPtr<RenderElement> HTMLPlugInImageElement::createElementRenderer(RenderStyle&& style, const RenderTreePosition& insertionPosition)
178{
179 ASSERT(document().pageCacheState() == Document::NotInPageCache);
180
181 if (displayState() >= PreparingPluginReplacement)
182 return HTMLPlugInElement::createElementRenderer(WTFMove(style), insertionPosition);
183
184 // Once a plug-in element creates its renderer, it needs to be told when the document goes
185 // inactive or reactivates so it can clear the renderer before going into the page cache.
186 if (!m_needsDocumentActivationCallbacks) {
187 m_needsDocumentActivationCallbacks = true;
188 document().registerForDocumentSuspensionCallbacks(*this);
189 }
190
191 if (displayState() == DisplayingSnapshot) {
192 auto renderSnapshottedPlugIn = createRenderer<RenderSnapshottedPlugIn>(*this, WTFMove(style));
193 renderSnapshottedPlugIn->updateSnapshot(m_snapshotImage.get());
194 return renderSnapshottedPlugIn;
195 }
196
197 if (useFallbackContent())
198 return RenderElement::createFor(*this, WTFMove(style));
199
200 if (isImageType())
201 return createRenderer<RenderImage>(*this, WTFMove(style));
202
203 return HTMLPlugInElement::createElementRenderer(WTFMove(style), insertionPosition);
204}
205
206bool HTMLPlugInImageElement::childShouldCreateRenderer(const Node& child) const
207{
208 if (is<RenderSnapshottedPlugIn>(renderer()) && !hasShadowRootParent(child))
209 return false;
210
211 return HTMLPlugInElement::childShouldCreateRenderer(child);
212}
213
214void HTMLPlugInImageElement::willRecalcStyle(Style::Change change)
215{
216 // Make sure style recalcs scheduled by a child shadow tree don't trigger reconstruction and cause flicker.
217 if (change == Style::NoChange && styleValidity() == Style::Validity::Valid)
218 return;
219
220 // FIXME: There shoudn't be need to force render tree reconstruction here.
221 // It is only done because loading and load event dispatching is tied to render tree construction.
222 if (!useFallbackContent() && needsWidgetUpdate() && renderer() && !isImageType() && displayState() != DisplayingSnapshot)
223 invalidateStyleAndRenderersForSubtree();
224}
225
226void HTMLPlugInImageElement::didRecalcStyle(Style::Change styleChange)
227{
228 scheduleUpdateForAfterStyleResolution();
229
230 HTMLPlugInElement::didRecalcStyle(styleChange);
231}
232
233void HTMLPlugInImageElement::didAttachRenderers()
234{
235 m_needsWidgetUpdate = true;
236 scheduleUpdateForAfterStyleResolution();
237
238 // Update the RenderImageResource of the associated RenderImage.
239 if (m_imageLoader && is<RenderImage>(renderer())) {
240 auto& renderImageResource = downcast<RenderImage>(*renderer()).imageResource();
241 if (!renderImageResource.cachedImage())
242 renderImageResource.setCachedImage(m_imageLoader->image());
243 }
244
245 HTMLPlugInElement::didAttachRenderers();
246}
247
248void HTMLPlugInImageElement::willDetachRenderers()
249{
250 auto widget = makeRefPtr(pluginWidget(PluginLoadingPolicy::DoNotLoad));
251 if (is<PluginViewBase>(widget))
252 downcast<PluginViewBase>(*widget).willDetachRenderer();
253
254 HTMLPlugInElement::willDetachRenderers();
255}
256
257void HTMLPlugInImageElement::scheduleUpdateForAfterStyleResolution()
258{
259 if (m_hasUpdateScheduledForAfterStyleResolution)
260 return;
261
262 document().incrementLoadEventDelayCount();
263
264 m_hasUpdateScheduledForAfterStyleResolution = true;
265
266 Style::queuePostResolutionCallback([protectedThis = makeRef(*this)] {
267 protectedThis->updateAfterStyleResolution();
268 });
269}
270
271void HTMLPlugInImageElement::updateAfterStyleResolution()
272{
273 m_hasUpdateScheduledForAfterStyleResolution = false;
274
275 // Do this after style resolution, since the image or widget load might complete synchronously
276 // and cause us to re-enter otherwise. Also, we can't really answer the question "do I have a renderer"
277 // accurately until after style resolution.
278
279 if (renderer() && !useFallbackContent()) {
280 if (isImageType()) {
281 if (!m_imageLoader)
282 m_imageLoader = std::make_unique<HTMLImageLoader>(*this);
283 if (m_needsImageReload)
284 m_imageLoader->updateFromElementIgnoringPreviousError();
285 else
286 m_imageLoader->updateFromElement();
287 } else {
288 if (needsWidgetUpdate() && renderEmbeddedObject() && !renderEmbeddedObject()->isPluginUnavailable())
289 updateWidget(CreatePlugins::No);
290 }
291 }
292
293 // Either we reloaded the image just now, or we had some reason not to.
294 // Either way, clear the flag now, since we don't need to remember to try again.
295 m_needsImageReload = false;
296
297 document().decrementLoadEventDelayCount();
298}
299
300void HTMLPlugInImageElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument)
301{
302 ASSERT_WITH_SECURITY_IMPLICATION(&document() == &newDocument);
303 if (m_needsDocumentActivationCallbacks) {
304 oldDocument.unregisterForDocumentSuspensionCallbacks(*this);
305 newDocument.registerForDocumentSuspensionCallbacks(*this);
306 }
307
308 if (m_imageLoader)
309 m_imageLoader->elementDidMoveToNewDocument();
310
311 if (m_hasUpdateScheduledForAfterStyleResolution) {
312 oldDocument.decrementLoadEventDelayCount();
313 newDocument.incrementLoadEventDelayCount();
314 }
315
316 HTMLPlugInElement::didMoveToNewDocument(oldDocument, newDocument);
317}
318
319void HTMLPlugInImageElement::prepareForDocumentSuspension()
320{
321 if (renderer())
322 RenderTreeUpdater::tearDownRenderers(*this);
323
324 HTMLPlugInElement::prepareForDocumentSuspension();
325}
326
327void HTMLPlugInImageElement::resumeFromDocumentSuspension()
328{
329 scheduleUpdateForAfterStyleResolution();
330 invalidateStyleAndRenderersForSubtree();
331
332 HTMLPlugInElement::resumeFromDocumentSuspension();
333}
334
335void HTMLPlugInImageElement::updateSnapshot(Image* image)
336{
337 if (displayState() > DisplayingSnapshot)
338 return;
339
340 m_snapshotImage = image;
341
342 auto* renderer = this->renderer();
343 if (!renderer)
344 return;
345
346 if (is<RenderSnapshottedPlugIn>(*renderer)) {
347 downcast<RenderSnapshottedPlugIn>(*renderer).updateSnapshot(image);
348 return;
349 }
350
351 if (is<RenderEmbeddedObject>(*renderer))
352 renderer->repaint();
353}
354
355static DOMWrapperWorld& plugInImageElementIsolatedWorld()
356{
357 static auto& isolatedWorld = DOMWrapperWorld::create(commonVM()).leakRef();
358 return isolatedWorld;
359}
360
361void HTMLPlugInImageElement::didAddUserAgentShadowRoot(ShadowRoot& root)
362{
363 HTMLPlugInElement::didAddUserAgentShadowRoot(root);
364 if (displayState() >= PreparingPluginReplacement)
365 return;
366
367 auto* page = document().page();
368 if (!page)
369 return;
370
371 // Reset any author styles that may apply as we only want explicit
372 // styles defined in the injected user agents stylesheets to specify
373 // the look-and-feel of the snapshotted plug-in overlay.
374 root.setResetStyleInheritance(true);
375
376 String mimeType = serviceType();
377
378 auto& isolatedWorld = plugInImageElementIsolatedWorld();
379 document().ensurePlugInsInjectedScript(isolatedWorld);
380
381 auto& scriptController = document().frame()->script();
382 auto& globalObject = *JSC::jsCast<JSDOMGlobalObject*>(scriptController.globalObject(isolatedWorld));
383
384 auto& vm = globalObject.vm();
385 JSC::JSLockHolder lock(vm);
386 auto scope = DECLARE_CATCH_SCOPE(vm);
387 auto& state = *globalObject.globalExec();
388
389 JSC::MarkedArgumentBuffer argList;
390 argList.append(toJS<IDLInterface<ShadowRoot>>(state, globalObject, root));
391 argList.append(toJS<IDLDOMString>(state, titleText(*page, mimeType)));
392 argList.append(toJS<IDLDOMString>(state, subtitleText(*page, mimeType)));
393
394 // This parameter determines whether or not the snapshot overlay should always be visible over the plugin snapshot.
395 // If no snapshot was found then we want the overlay to be visible.
396 argList.append(toJS<IDLBoolean>(!m_snapshotImage));
397 ASSERT(!argList.hasOverflowed());
398
399 // It is expected the JS file provides a createOverlay(shadowRoot, title, subtitle) function.
400 auto* overlay = globalObject.get(&state, JSC::Identifier::fromString(&state, "createOverlay")).toObject(&state);
401 ASSERT(!overlay == !!scope.exception());
402 if (!overlay) {
403 scope.clearException();
404 return;
405 }
406 JSC::CallData callData;
407 auto callType = overlay->methodTable(vm)->getCallData(overlay, callData);
408 if (callType == JSC::CallType::None)
409 return;
410
411 call(&state, overlay, callType, callData, &globalObject, argList);
412 scope.clearException();
413}
414
415bool HTMLPlugInImageElement::partOfSnapshotOverlay(const EventTarget* target) const
416{
417 static NeverDestroyed<AtomString> selector(".snapshot-overlay", AtomString::ConstructFromLiteral);
418 auto shadow = userAgentShadowRoot();
419 if (!shadow)
420 return false;
421 if (!is<Node>(target))
422 return false;
423 auto queryResult = shadow->querySelector(selector.get());
424 if (queryResult.hasException())
425 return false;
426 auto snapshotLabel = makeRefPtr(queryResult.releaseReturnValue());
427 return snapshotLabel && snapshotLabel->contains(downcast<Node>(target));
428}
429
430void HTMLPlugInImageElement::removeSnapshotTimerFired()
431{
432 m_snapshotImage = nullptr;
433 m_isRestartedPlugin = false;
434 invalidateStyleAndLayerComposition();
435 if (renderer())
436 renderer()->repaint();
437}
438
439void HTMLPlugInImageElement::restartSimilarPlugIns()
440{
441 // Restart any other snapshotted plugins in the page with the same origin. Note that they
442 // may be in different frames, so traverse from the top of the document.
443
444 auto plugInOrigin = m_loadedUrl.host();
445 String mimeType = serviceType();
446 Vector<Ref<HTMLPlugInImageElement>> similarPlugins;
447
448 if (!document().page())
449 return;
450
451 for (RefPtr<Frame> frame = &document().page()->mainFrame(); frame; frame = frame->tree().traverseNext()) {
452 if (!frame->loader().subframeLoader().containsPlugins())
453 continue;
454
455 if (!frame->document())
456 continue;
457
458 for (auto& element : descendantsOfType<HTMLPlugInImageElement>(*frame->document())) {
459 if (plugInOrigin == element.loadedUrl().host() && mimeType == element.serviceType())
460 similarPlugins.append(element);
461 }
462 }
463
464 for (auto& plugInToRestart : similarPlugins) {
465 if (plugInToRestart->displayState() <= HTMLPlugInElement::DisplayingSnapshot) {
466 LOG(Plugins, "%p Plug-in looks similar to a restarted plug-in. Restart.", plugInToRestart.ptr());
467 plugInToRestart->restartSnapshottedPlugIn();
468 }
469 plugInToRestart->m_snapshotDecision = NeverSnapshot;
470 }
471}
472
473void HTMLPlugInImageElement::userDidClickSnapshot(MouseEvent& event, bool forwardEvent)
474{
475 if (forwardEvent)
476 m_pendingClickEventFromSnapshot = &event;
477
478 auto plugInOrigin = m_loadedUrl.host();
479 if (document().page() && !SchemeRegistry::shouldTreatURLSchemeAsLocal(document().page()->mainFrame().document()->baseURL().protocol().toStringWithoutCopying()) && document().page()->settings().autostartOriginPlugInSnapshottingEnabled())
480 document().page()->plugInClient()->didStartFromOrigin(document().page()->mainFrame().document()->baseURL().host().toString(), plugInOrigin.toString(), serviceType(), document().page()->sessionID());
481
482 LOG(Plugins, "%p User clicked on snapshotted plug-in. Restart.", this);
483 restartSnapshottedPlugIn();
484 if (forwardEvent)
485 setDisplayState(RestartingWithPendingMouseClick);
486 restartSimilarPlugIns();
487}
488
489void HTMLPlugInImageElement::setIsPrimarySnapshottedPlugIn(bool isPrimarySnapshottedPlugIn)
490{
491 if (!document().page() || !document().page()->settings().primaryPlugInSnapshotDetectionEnabled() || document().page()->settings().snapshotAllPlugIns())
492 return;
493
494 if (isPrimarySnapshottedPlugIn) {
495 if (m_plugInWasCreated) {
496 LOG(Plugins, "%p Plug-in was detected as the primary element in the page. Restart.", this);
497 restartSnapshottedPlugIn();
498 restartSimilarPlugIns();
499 } else {
500 LOG(Plugins, "%p Plug-in was detected as the primary element in the page, but is not yet created. Will restart later.", this);
501 m_deferredPromotionToPrimaryPlugIn = true;
502 }
503 }
504}
505
506void HTMLPlugInImageElement::restartSnapshottedPlugIn()
507{
508 if (displayState() >= RestartingWithPendingMouseClick)
509 return;
510
511 setDisplayState(Restarting);
512 invalidateStyleAndRenderersForSubtree();
513}
514
515void HTMLPlugInImageElement::dispatchPendingMouseClick()
516{
517 ASSERT(!m_simulatedMouseClickTimer.isActive());
518 m_simulatedMouseClickTimer.restart();
519}
520
521void HTMLPlugInImageElement::simulatedMouseClickTimerFired()
522{
523 ASSERT(displayState() == RestartingWithPendingMouseClick);
524 ASSERT(m_pendingClickEventFromSnapshot);
525
526 setDisplayState(Playing);
527 dispatchSimulatedClick(m_pendingClickEventFromSnapshot.get(), SendMouseOverUpDownEvents, DoNotShowPressedLook);
528
529 m_pendingClickEventFromSnapshot = nullptr;
530}
531
532static bool documentHadRecentUserGesture(Document& document)
533{
534 MonotonicTime lastKnownUserGestureTimestamp = document.lastHandledUserGestureTimestamp();
535 if (document.frame() != &document.page()->mainFrame() && document.page()->mainFrame().document())
536 lastKnownUserGestureTimestamp = std::max(lastKnownUserGestureTimestamp, document.page()->mainFrame().document()->lastHandledUserGestureTimestamp());
537
538 return MonotonicTime::now() - lastKnownUserGestureTimestamp < autostartSoonAfterUserGestureThreshold;
539}
540
541void HTMLPlugInImageElement::checkSizeChangeForSnapshotting()
542{
543 if (!m_needsCheckForSizeChange || m_snapshotDecision != MaySnapshotWhenResized || documentHadRecentUserGesture(document()))
544 return;
545
546 m_needsCheckForSizeChange = false;
547
548 auto contentBoxRect = downcast<RenderBox>(*renderer()).contentBoxRect();
549 int contentWidth = contentBoxRect.width();
550 int contentHeight = contentBoxRect.height();
551
552 if (contentWidth <= sizingTinyDimensionThreshold || contentHeight <= sizingTinyDimensionThreshold)
553 return;
554
555 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);
556 setDisplayState(WaitingForSnapshot);
557 m_snapshotDecision = Snapshotted;
558 auto widget = makeRefPtr(pluginWidget());
559 if (is<PluginViewBase>(widget))
560 downcast<PluginViewBase>(*widget).beginSnapshottingRunningPlugin();
561}
562
563static inline bool is100Percent(Length length)
564{
565 return length.isPercent() && length.percent() == 100;
566}
567
568static inline bool isSmallerThanTinySizingThreshold(const RenderEmbeddedObject& renderer)
569{
570 auto contentRect = renderer.contentBoxRect();
571 return contentRect.width() <= sizingTinyDimensionThreshold || contentRect.height() <= sizingTinyDimensionThreshold;
572}
573
574bool HTMLPlugInImageElement::isTopLevelFullPagePlugin(const RenderEmbeddedObject& renderer) const
575{
576 ASSERT(document().frame());
577 auto& frame = *document().frame();
578 if (!frame.isMainFrame())
579 return false;
580
581 auto& style = renderer.style();
582 auto visibleSize = frame.view()->visibleSize();
583 auto contentRect = renderer.contentBoxRect();
584 float contentWidth = contentRect.width();
585 float contentHeight = contentRect.height();
586 return is100Percent(style.width()) && is100Percent(style.height()) && contentWidth * contentHeight > visibleSize.area().unsafeGet() * sizingFullPageAreaRatioThreshold;
587}
588
589void HTMLPlugInImageElement::checkSnapshotStatus()
590{
591 if (!is<RenderSnapshottedPlugIn>(*renderer())) {
592 if (displayState() == Playing)
593 checkSizeChangeForSnapshotting();
594 return;
595 }
596
597 // 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.
598 if (!document().page()->settings().snapshotAllPlugIns() && displayState() <= DisplayingSnapshot && !m_plugInDimensionsSpecified) {
599 auto& renderer = downcast<RenderSnapshottedPlugIn>(*this->renderer());
600 if (!renderer.style().logicalWidth().isSpecified() && !renderer.style().logicalHeight().isSpecified())
601 return;
602
603 m_plugInDimensionsSpecified = true;
604 if (isTopLevelFullPagePlugin(renderer)) {
605 m_snapshotDecision = NeverSnapshot;
606 restartSnapshottedPlugIn();
607 } else if (isSmallerThanTinySizingThreshold(renderer)) {
608 m_snapshotDecision = MaySnapshotWhenResized;
609 restartSnapshottedPlugIn();
610 }
611 return;
612 }
613
614 // Notify the shadow root that the size changed so that we may update the overlay layout.
615 ensureUserAgentShadowRoot().dispatchEvent(Event::create(eventNames().resizeEvent, Event::CanBubble::Yes, Event::IsCancelable::No));
616}
617
618void HTMLPlugInImageElement::subframeLoaderWillCreatePlugIn(const URL& url)
619{
620 LOG(Plugins, "%p Plug-in URL: %s", this, m_url.utf8().data());
621 LOG(Plugins, " Actual URL: %s", url.string().utf8().data());
622 LOG(Plugins, " MIME type: %s", serviceType().utf8().data());
623
624 m_loadedUrl = url;
625 m_plugInWasCreated = false;
626 m_deferredPromotionToPrimaryPlugIn = false;
627
628 if (!document().page() || !document().page()->settings().plugInSnapshottingEnabled()) {
629 m_snapshotDecision = NeverSnapshot;
630 return;
631 }
632
633 if (displayState() == Restarting) {
634 LOG(Plugins, "%p Plug-in is explicitly restarting", this);
635 m_snapshotDecision = NeverSnapshot;
636 setDisplayState(Playing);
637 return;
638 }
639
640 if (displayState() == RestartingWithPendingMouseClick) {
641 LOG(Plugins, "%p Plug-in is explicitly restarting but also waiting for a click", this);
642 m_snapshotDecision = NeverSnapshot;
643 return;
644 }
645
646 if (m_snapshotDecision == NeverSnapshot) {
647 LOG(Plugins, "%p Plug-in is blessed, allow it to start", this);
648 return;
649 }
650
651 bool inMainFrame = document().frame()->isMainFrame();
652
653 if (document().isPluginDocument() && inMainFrame) {
654 LOG(Plugins, "%p Plug-in document in main frame", this);
655 m_snapshotDecision = NeverSnapshot;
656 return;
657 }
658
659 if (UserGestureIndicator::processingUserGesture()) {
660 LOG(Plugins, "%p Script is currently processing user gesture, set to play", this);
661 m_snapshotDecision = NeverSnapshot;
662 return;
663 }
664
665 if (m_createdDuringUserGesture) {
666 LOG(Plugins, "%p Plug-in was created when processing user gesture, set to play", this);
667 m_snapshotDecision = NeverSnapshot;
668 return;
669 }
670
671 if (documentHadRecentUserGesture(document())) {
672 LOG(Plugins, "%p Plug-in was created shortly after a user gesture, set to play", this);
673 m_snapshotDecision = NeverSnapshot;
674 return;
675 }
676
677 if (document().page()->settings().snapshotAllPlugIns()) {
678 LOG(Plugins, "%p Plug-in forced to snapshot by user preference", this);
679 m_snapshotDecision = Snapshotted;
680 setDisplayState(WaitingForSnapshot);
681 return;
682 }
683
684 if (document().page()->settings().autostartOriginPlugInSnapshottingEnabled() && document().page()->plugInClient() && document().page()->plugInClient()->shouldAutoStartFromOrigin(document().page()->mainFrame().document()->baseURL().host().toString(), url.host().toString(), serviceType())) {
685 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());
686 m_snapshotDecision = NeverSnapshot;
687 return;
688 }
689
690 if (m_loadedUrl.isEmpty() && !serviceType().isEmpty()) {
691 LOG(Plugins, "%p Plug-in has no src URL but does have a valid mime type %s, set to play", this, serviceType().utf8().data());
692 m_snapshotDecision = MaySnapshotWhenContentIsSet;
693 return;
694 }
695
696 if (!SchemeRegistry::shouldTreatURLSchemeAsLocal(m_loadedUrl.protocol().toStringWithoutCopying()) && !m_loadedUrl.host().isEmpty() && m_loadedUrl.host() == document().page()->mainFrame().document()->baseURL().host()) {
697 LOG(Plugins, "%p Plug-in is served from page's domain, set to play", this);
698 m_snapshotDecision = NeverSnapshot;
699 return;
700 }
701
702 auto& renderer = downcast<RenderEmbeddedObject>(*this->renderer());
703 auto contentRect = renderer.contentBoxRect();
704 int contentWidth = contentRect.width();
705 int contentHeight = contentRect.height();
706
707 m_plugInDimensionsSpecified = renderer.style().logicalWidth().isSpecified() || renderer.style().logicalHeight().isSpecified();
708
709 if (isTopLevelFullPagePlugin(renderer)) {
710 LOG(Plugins, "%p Plug-in is top level full page, set to play", this);
711 m_snapshotDecision = NeverSnapshot;
712 return;
713 }
714
715 if (isSmallerThanTinySizingThreshold(renderer)) {
716 LOG(Plugins, "%p Plug-in is very small %dx%d, set to play", this, contentWidth, contentHeight);
717 m_sizeWhenSnapshotted = IntSize(contentWidth, contentHeight);
718 m_snapshotDecision = MaySnapshotWhenResized;
719 return;
720 }
721
722 if (!document().page()->plugInClient()) {
723 LOG(Plugins, "%p There is no plug-in client. Set to wait for snapshot", this);
724 m_snapshotDecision = NeverSnapshot;
725 setDisplayState(WaitingForSnapshot);
726 return;
727 }
728
729 LOG(Plugins, "%p Plug-in from (%s, %s) is not auto-start, sized at %dx%d, set to wait for snapshot", this, document().topDocument().baseURL().host().utf8().data(), url.host().utf8().data(), contentWidth, contentHeight);
730 m_snapshotDecision = Snapshotted;
731 setDisplayState(WaitingForSnapshot);
732}
733
734void HTMLPlugInImageElement::subframeLoaderDidCreatePlugIn(const Widget& widget)
735{
736 m_plugInWasCreated = true;
737
738 if (is<PluginViewBase>(widget) && downcast<PluginViewBase>(widget).shouldAlwaysAutoStart()) {
739 LOG(Plugins, "%p Plug-in should auto-start, set to play", this);
740 m_snapshotDecision = NeverSnapshot;
741 setDisplayState(Playing);
742 return;
743 }
744
745 if (m_deferredPromotionToPrimaryPlugIn) {
746 LOG(Plugins, "%p Plug-in was created, previously deferred promotion to primary. Will promote", this);
747 setIsPrimarySnapshottedPlugIn(true);
748 m_deferredPromotionToPrimaryPlugIn = false;
749 }
750}
751
752void HTMLPlugInImageElement::defaultEventHandler(Event& event)
753{
754 if (is<RenderEmbeddedObject>(renderer()) && displayState() == WaitingForSnapshot && is<MouseEvent>(event) && event.type() == eventNames().clickEvent) {
755 auto& mouseEvent = downcast<MouseEvent>(event);
756 if (mouseEvent.button() == LeftButton) {
757 userDidClickSnapshot(mouseEvent, true);
758 mouseEvent.setDefaultHandled();
759 return;
760 }
761 }
762 HTMLPlugInElement::defaultEventHandler(event);
763}
764
765bool HTMLPlugInImageElement::allowedToLoadPluginContent(const String& url, const String& mimeType) const
766{
767 // Elements in user agent show tree should load whatever the embedding document policy is.
768 if (isInUserAgentShadowTree())
769 return true;
770
771 URL completedURL;
772 if (!url.isEmpty())
773 completedURL = document().completeURL(url);
774
775 ASSERT(document().contentSecurityPolicy());
776 const ContentSecurityPolicy& contentSecurityPolicy = *document().contentSecurityPolicy();
777
778 contentSecurityPolicy.upgradeInsecureRequestIfNeeded(completedURL, ContentSecurityPolicy::InsecureRequestType::Load);
779
780 if (!contentSecurityPolicy.allowObjectFromSource(completedURL))
781 return false;
782
783 auto& declaredMimeType = document().isPluginDocument() && document().ownerElement() ?
784 document().ownerElement()->attributeWithoutSynchronization(HTMLNames::typeAttr) : attributeWithoutSynchronization(HTMLNames::typeAttr);
785 return contentSecurityPolicy.allowPluginType(mimeType, declaredMimeType, completedURL);
786}
787
788bool HTMLPlugInImageElement::requestObject(const String& url, const String& mimeType, const Vector<String>& paramNames, const Vector<String>& paramValues)
789{
790 ASSERT(document().frame());
791
792 if (url.isEmpty() && mimeType.isEmpty())
793 return false;
794
795 if (!allowedToLoadPluginContent(url, mimeType)) {
796 renderEmbeddedObject()->setPluginUnavailabilityReason(RenderEmbeddedObject::PluginBlockedByContentSecurityPolicy);
797 return false;
798 }
799
800 if (HTMLPlugInElement::requestObject(url, mimeType, paramNames, paramValues))
801 return true;
802
803 return document().frame()->loader().subframeLoader().requestObject(*this, url, getNameAttribute(), mimeType, paramNames, paramValues);
804}
805
806void HTMLPlugInImageElement::updateImageLoaderWithNewURLSoon()
807{
808 if (m_needsImageReload)
809 return;
810
811 m_needsImageReload = true;
812 scheduleUpdateForAfterStyleResolution();
813 invalidateStyle();
814}
815
816} // namespace WebCore
817