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