forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			3522 lines
		
	
	
	
		
			116 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			3522 lines
		
	
	
	
		
			116 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| /*
 | |
|  * A base class implementing nsIObjectLoadingContent for use by
 | |
|  * various content nodes that want to provide plugin/document/image
 | |
|  * loading functionality (eg <embed>, <object>, etc).
 | |
|  */
 | |
| 
 | |
| // Interface headers
 | |
| #include "imgLoader.h"
 | |
| #include "nsIClassOfService.h"
 | |
| #include "nsIConsoleService.h"
 | |
| #include "nsIContent.h"
 | |
| #include "nsIContentInlines.h"
 | |
| #include "nsIDocShell.h"
 | |
| #include "mozilla/BasePrincipal.h"
 | |
| #include "mozilla/dom/BindContext.h"
 | |
| #include "mozilla/dom/Document.h"
 | |
| #include "nsIExternalProtocolHandler.h"
 | |
| #include "nsIInterfaceRequestorUtils.h"
 | |
| #include "nsIObjectFrame.h"
 | |
| #include "nsIOService.h"
 | |
| #include "nsIPermissionManager.h"
 | |
| #include "nsPluginHost.h"
 | |
| #include "nsPluginInstanceOwner.h"
 | |
| #include "nsJSNPRuntime.h"
 | |
| #include "nsINestedURI.h"
 | |
| #include "nsScriptSecurityManager.h"
 | |
| #include "nsIURILoader.h"
 | |
| #include "nsIURL.h"
 | |
| #include "nsIScriptChannel.h"
 | |
| #include "nsIBlocklistService.h"
 | |
| #include "nsIAsyncVerifyRedirectCallback.h"
 | |
| #include "nsIAppShell.h"
 | |
| #include "nsIXULRuntime.h"
 | |
| #include "nsIScriptError.h"
 | |
| 
 | |
| #include "nsError.h"
 | |
| 
 | |
| // Util headers
 | |
| #include "prenv.h"
 | |
| #include "mozilla/Logging.h"
 | |
| 
 | |
| #include "nsCURILoader.h"
 | |
| #include "nsContentPolicyUtils.h"
 | |
| #include "nsContentUtils.h"
 | |
| #include "nsDocShellCID.h"
 | |
| #include "nsGkAtoms.h"
 | |
| #include "nsThreadUtils.h"
 | |
| #include "nsNetUtil.h"
 | |
| #include "nsMimeTypes.h"
 | |
| #include "nsStyleUtil.h"
 | |
| #include "nsUnicharUtils.h"
 | |
| #include "mozilla/Preferences.h"
 | |
| #include "nsSandboxFlags.h"
 | |
| 
 | |
| // Concrete classes
 | |
| #include "nsFrameLoader.h"
 | |
| 
 | |
| #include "nsObjectLoadingContent.h"
 | |
| #include "mozAutoDocUpdate.h"
 | |
| #include "GeckoProfiler.h"
 | |
| #include "nsPluginFrame.h"
 | |
| #include "nsWrapperCacheInlines.h"
 | |
| #include "nsDOMJSUtils.h"
 | |
| 
 | |
| #include "nsWidgetsCID.h"
 | |
| #include "nsContentCID.h"
 | |
| #include "mozilla/BasicEvents.h"
 | |
| #include "mozilla/Components.h"
 | |
| #include "mozilla/LoadInfo.h"
 | |
| #include "mozilla/dom/BindingUtils.h"
 | |
| #include "mozilla/dom/Element.h"
 | |
| #include "mozilla/dom/Event.h"
 | |
| #include "mozilla/dom/ScriptSettings.h"
 | |
| #include "mozilla/dom/PluginCrashedEvent.h"
 | |
| #include "mozilla/AsyncEventDispatcher.h"
 | |
| #include "mozilla/EventDispatcher.h"
 | |
| #include "mozilla/EventStates.h"
 | |
| #include "mozilla/IMEStateManager.h"
 | |
| #include "mozilla/widget/IMEData.h"
 | |
| #include "mozilla/IntegerPrintfMacros.h"
 | |
| #include "mozilla/dom/HTMLObjectElementBinding.h"
 | |
| #include "mozilla/dom/HTMLEmbedElement.h"
 | |
| #include "mozilla/dom/HTMLObjectElement.h"
 | |
| #include "mozilla/dom/UserActivation.h"
 | |
| #include "mozilla/dom/nsCSPContext.h"
 | |
| #include "mozilla/net/UrlClassifierFeatureFactory.h"
 | |
| #include "mozilla/LoadInfo.h"
 | |
| #include "mozilla/PresShell.h"
 | |
| #include "mozilla/StaticPrefs_security.h"
 | |
| #include "nsChannelClassifier.h"
 | |
| #include "nsFocusManager.h"
 | |
| #include "ReferrerInfo.h"
 | |
| 
 | |
| #ifdef XP_WIN
 | |
| // Thanks so much, Microsoft! :(
 | |
| #  ifdef CreateEvent
 | |
| #    undef CreateEvent
 | |
| #  endif
 | |
| #endif  // XP_WIN
 | |
| 
 | |
| static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 | |
| 
 | |
| static const char kPrefYoutubeRewrite[] = "plugins.rewrite_youtube_embeds";
 | |
| static const char kPrefFavorFallbackMode[] = "plugins.favorfallback.mode";
 | |
| static const char kPrefFavorFallbackRules[] = "plugins.favorfallback.rules";
 | |
| 
 | |
| using namespace mozilla;
 | |
| using namespace mozilla::dom;
 | |
| using namespace mozilla::net;
 | |
| 
 | |
| static LogModule* GetObjectLog() {
 | |
|   static LazyLogModule sLog("objlc");
 | |
|   return sLog;
 | |
| }
 | |
| 
 | |
| #define LOG(args) MOZ_LOG(GetObjectLog(), mozilla::LogLevel::Debug, args)
 | |
| #define LOG_ENABLED() MOZ_LOG_TEST(GetObjectLog(), mozilla::LogLevel::Debug)
 | |
| 
 | |
| static bool IsFlashMIME(const nsACString& aMIMEType) {
 | |
|   return nsPluginHost::GetSpecialType(aMIMEType) ==
 | |
|          nsPluginHost::eSpecialType_Flash;
 | |
| }
 | |
| 
 | |
| static bool InActiveDocument(nsIContent* aContent) {
 | |
|   if (!aContent->IsInComposedDoc()) {
 | |
|     return false;
 | |
|   }
 | |
|   Document* doc = aContent->OwnerDoc();
 | |
|   return (doc && doc->IsActive());
 | |
| }
 | |
| 
 | |
| static bool IsPluginType(nsObjectLoadingContent::ObjectType type) {
 | |
|   return type == nsObjectLoadingContent::eType_Plugin ||
 | |
|          type == nsObjectLoadingContent::eType_FakePlugin;
 | |
| }
 | |
| 
 | |
| ///
 | |
| /// Runnables and helper classes
 | |
| ///
 | |
| 
 | |
| class nsAsyncInstantiateEvent : public Runnable {
 | |
|  public:
 | |
|   explicit nsAsyncInstantiateEvent(nsObjectLoadingContent* aContent)
 | |
|       : Runnable("nsAsyncInstantiateEvent"), mContent(aContent) {}
 | |
| 
 | |
|   ~nsAsyncInstantiateEvent() override = default;
 | |
| 
 | |
|   NS_IMETHOD Run() override;
 | |
| 
 | |
|  private:
 | |
|   nsCOMPtr<nsIObjectLoadingContent> mContent;
 | |
| };
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsAsyncInstantiateEvent::Run() {
 | |
|   nsObjectLoadingContent* objLC =
 | |
|       static_cast<nsObjectLoadingContent*>(mContent.get());
 | |
| 
 | |
|   // If objLC is no longer tracking this event, we've been canceled or
 | |
|   // superseded
 | |
|   if (objLC->mPendingInstantiateEvent != this) {
 | |
|     return NS_OK;
 | |
|   }
 | |
|   objLC->mPendingInstantiateEvent = nullptr;
 | |
| 
 | |
|   return objLC->SyncStartPluginInstance();
 | |
| }
 | |
| 
 | |
| // Checks to see if the content for a plugin instance should be unloaded
 | |
| // (outside an active document) or stopped (in a document but unrendered). This
 | |
| // is used to allow scripts to move a plugin around the document hierarchy
 | |
| // without re-instantiating it.
 | |
| class CheckPluginStopEvent : public Runnable {
 | |
|  public:
 | |
|   explicit CheckPluginStopEvent(nsObjectLoadingContent* aContent)
 | |
|       : Runnable("CheckPluginStopEvent"), mContent(aContent) {}
 | |
| 
 | |
|   ~CheckPluginStopEvent() override = default;
 | |
| 
 | |
|   NS_IMETHOD Run() override;
 | |
| 
 | |
|  private:
 | |
|   nsCOMPtr<nsIObjectLoadingContent> mContent;
 | |
| };
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| CheckPluginStopEvent::Run() {
 | |
|   nsObjectLoadingContent* objLC =
 | |
|       static_cast<nsObjectLoadingContent*>(mContent.get());
 | |
| 
 | |
|   // If objLC is no longer tracking this event, we've been canceled or
 | |
|   // superseded. We clear this before we finish - either by calling
 | |
|   // UnloadObject/StopPluginInstance, or directly if we took no action.
 | |
|   if (objLC->mPendingCheckPluginStopEvent != this) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // CheckPluginStopEvent is queued when we either lose our frame, are removed
 | |
|   // from the document, or the document goes inactive. To avoid stopping the
 | |
|   // plugin when script is reparenting us or layout is rebuilding, we wait until
 | |
|   // this event to decide to stop.
 | |
| 
 | |
|   nsCOMPtr<nsIContent> content =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(objLC));
 | |
|   if (!InActiveDocument(content)) {
 | |
|     LOG(("OBJLC [%p]: Unloading plugin outside of document", this));
 | |
|     objLC->StopPluginInstance();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (content->GetPrimaryFrame()) {
 | |
|     LOG(
 | |
|         ("OBJLC [%p]: CheckPluginStopEvent - in active document with frame"
 | |
|          ", no action",
 | |
|          this));
 | |
|     objLC->mPendingCheckPluginStopEvent = nullptr;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // In an active document, but still no frame. Flush layout to see if we can
 | |
|   // regain a frame now.
 | |
|   LOG(("OBJLC [%p]: CheckPluginStopEvent - No frame, flushing layout", this));
 | |
|   Document* composedDoc = content->GetComposedDoc();
 | |
|   if (composedDoc) {
 | |
|     composedDoc->FlushPendingNotifications(FlushType::Layout);
 | |
|     if (objLC->mPendingCheckPluginStopEvent != this) {
 | |
|       LOG(("OBJLC [%p]: CheckPluginStopEvent - superseded in layout flush",
 | |
|            this));
 | |
|       return NS_OK;
 | |
|     }
 | |
|     if (content->GetPrimaryFrame()) {
 | |
|       LOG(("OBJLC [%p]: CheckPluginStopEvent - frame gained in layout flush",
 | |
|            this));
 | |
|       objLC->mPendingCheckPluginStopEvent = nullptr;
 | |
|       return NS_OK;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Still no frame, suspend plugin. HasNewFrame will restart us when we
 | |
|   // become rendered again
 | |
|   LOG(("OBJLC [%p]: Stopping plugin that lost frame", this));
 | |
|   objLC->StopPluginInstance();
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Helper task for firing simple events
 | |
|  */
 | |
| class nsSimplePluginEvent : public Runnable {
 | |
|  public:
 | |
|   nsSimplePluginEvent(nsIContent* aTarget, const nsAString& aEvent)
 | |
|       : Runnable("nsSimplePluginEvent"),
 | |
|         mTarget(aTarget),
 | |
|         mDocument(aTarget->GetComposedDoc()),
 | |
|         mEvent(aEvent) {
 | |
|     MOZ_ASSERT(aTarget && mDocument);
 | |
|   }
 | |
| 
 | |
|   nsSimplePluginEvent(Document* aTarget, const nsAString& aEvent)
 | |
|       : mozilla::Runnable("nsSimplePluginEvent"),
 | |
|         mTarget(ToSupports(aTarget)),
 | |
|         mDocument(aTarget),
 | |
|         mEvent(aEvent) {
 | |
|     MOZ_ASSERT(aTarget);
 | |
|   }
 | |
| 
 | |
|   nsSimplePluginEvent(nsIContent* aTarget, Document* aDocument,
 | |
|                       const nsAString& aEvent)
 | |
|       : mozilla::Runnable("nsSimplePluginEvent"),
 | |
|         mTarget(aTarget),
 | |
|         mDocument(aDocument),
 | |
|         mEvent(aEvent) {
 | |
|     MOZ_ASSERT(aTarget && aDocument);
 | |
|   }
 | |
| 
 | |
|   ~nsSimplePluginEvent() override = default;
 | |
| 
 | |
|   NS_IMETHOD Run() override;
 | |
| 
 | |
|  private:
 | |
|   nsCOMPtr<nsISupports> mTarget;
 | |
|   nsCOMPtr<Document> mDocument;
 | |
|   nsString mEvent;
 | |
| };
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsSimplePluginEvent::Run() {
 | |
|   if (mDocument && mDocument->IsActive()) {
 | |
|     LOG(("OBJLC [%p]: nsSimplePluginEvent firing event \"%s\"", mTarget.get(),
 | |
|          NS_ConvertUTF16toUTF8(mEvent).get()));
 | |
|     nsContentUtils::DispatchTrustedEvent(mDocument, mTarget, mEvent,
 | |
|                                          CanBubble::eYes, Cancelable::eYes);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * A task for firing PluginCrashed DOM Events.
 | |
|  */
 | |
| class nsPluginCrashedEvent : public Runnable {
 | |
|  public:
 | |
|   nsCOMPtr<nsIContent> mContent;
 | |
|   nsString mPluginDumpID;
 | |
|   nsString mPluginName;
 | |
|   nsString mPluginFilename;
 | |
|   bool mSubmittedCrashReport;
 | |
| 
 | |
|   nsPluginCrashedEvent(nsIContent* aContent, const nsAString& aPluginDumpID,
 | |
|                        const nsAString& aPluginName,
 | |
|                        const nsAString& aPluginFilename,
 | |
|                        bool submittedCrashReport)
 | |
|       : Runnable("nsPluginCrashedEvent"),
 | |
|         mContent(aContent),
 | |
|         mPluginDumpID(aPluginDumpID),
 | |
|         mPluginName(aPluginName),
 | |
|         mPluginFilename(aPluginFilename),
 | |
|         mSubmittedCrashReport(submittedCrashReport) {}
 | |
| 
 | |
|   ~nsPluginCrashedEvent() override = default;
 | |
| 
 | |
|   NS_IMETHOD Run() override;
 | |
| };
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsPluginCrashedEvent::Run() {
 | |
|   LOG(("OBJLC [%p]: Firing plugin crashed event\n", mContent.get()));
 | |
| 
 | |
|   nsCOMPtr<Document> doc = mContent->GetComposedDoc();
 | |
|   if (!doc) {
 | |
|     NS_WARNING("Couldn't get document for PluginCrashed event!");
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   PluginCrashedEventInit init;
 | |
|   init.mPluginDumpID = mPluginDumpID;
 | |
|   init.mPluginName = mPluginName;
 | |
|   init.mPluginFilename = mPluginFilename;
 | |
|   init.mSubmittedCrashReport = mSubmittedCrashReport;
 | |
|   init.mBubbles = true;
 | |
|   init.mCancelable = true;
 | |
| 
 | |
|   RefPtr<PluginCrashedEvent> event =
 | |
|       PluginCrashedEvent::Constructor(doc, u"PluginCrashed"_ns, init);
 | |
| 
 | |
|   event->SetTrusted(true);
 | |
|   event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
 | |
| 
 | |
|   EventDispatcher::DispatchDOMEvent(mContent, nullptr, event, nullptr, nullptr);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // You can't take the address of bitfield members, so we have two separate
 | |
| // classes for these :-/
 | |
| 
 | |
| // Sets a object's mInstantiating bit to false when destroyed
 | |
| class AutoSetInstantiatingToFalse {
 | |
|  public:
 | |
|   explicit AutoSetInstantiatingToFalse(nsObjectLoadingContent* aContent)
 | |
|       : mContent(aContent) {}
 | |
|   ~AutoSetInstantiatingToFalse() { mContent->mInstantiating = false; }
 | |
| 
 | |
|  private:
 | |
|   nsObjectLoadingContent* mContent;
 | |
| };
 | |
| 
 | |
| // Sets a object's mInstantiating bit to false when destroyed
 | |
| class AutoSetLoadingToFalse {
 | |
|  public:
 | |
|   explicit AutoSetLoadingToFalse(nsObjectLoadingContent* aContent)
 | |
|       : mContent(aContent) {}
 | |
|   ~AutoSetLoadingToFalse() { mContent->mIsLoading = false; }
 | |
| 
 | |
|  private:
 | |
|   nsObjectLoadingContent* mContent;
 | |
| };
 | |
| 
 | |
| ///
 | |
| /// Helper functions
 | |
| ///
 | |
| 
 | |
| static bool IsSuccessfulRequest(nsIRequest* aRequest, nsresult* aStatus) {
 | |
|   nsresult rv = aRequest->GetStatus(aStatus);
 | |
|   if (NS_FAILED(rv) || NS_FAILED(*aStatus)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // This may still be an error page or somesuch
 | |
|   nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(aRequest));
 | |
|   if (httpChan) {
 | |
|     bool success;
 | |
|     rv = httpChan->GetRequestSucceeded(&success);
 | |
|     if (NS_FAILED(rv) || !success) {
 | |
|       return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Otherwise, the request is successful
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| static bool CanHandleURI(nsIURI* aURI) {
 | |
|   nsAutoCString scheme;
 | |
|   if (NS_FAILED(aURI->GetScheme(scheme))) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsIIOService* ios = nsContentUtils::GetIOService();
 | |
|   if (!ios) return false;
 | |
| 
 | |
|   nsCOMPtr<nsIProtocolHandler> handler;
 | |
|   ios->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
 | |
|   if (!handler) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIExternalProtocolHandler> extHandler = do_QueryInterface(handler);
 | |
|   // We can handle this URI if its protocol handler is not the external one
 | |
|   return extHandler == nullptr;
 | |
| }
 | |
| 
 | |
| // Helper for tedious URI equality syntax when one or both arguments may be
 | |
| // null and URIEquals(null, null) should be true
 | |
| static bool inline URIEquals(nsIURI* a, nsIURI* b) {
 | |
|   bool equal;
 | |
|   return (!a && !b) || (a && b && NS_SUCCEEDED(a->Equals(b, &equal)) && equal);
 | |
| }
 | |
| 
 | |
| static void GetExtensionFromURI(nsIURI* uri, nsCString& ext) {
 | |
|   nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
 | |
|   if (url) {
 | |
|     url->GetFileExtension(ext);
 | |
|   } else {
 | |
|     nsCString spec;
 | |
|     nsresult rv = uri->GetSpec(spec);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       // This means we could incorrectly think a plugin is not enabled for
 | |
|       // the URI when it is, but that's not so bad.
 | |
|       ext.Truncate();
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     int32_t offset = spec.RFindChar('.');
 | |
|     if (offset != kNotFound) {
 | |
|       ext = Substring(spec, offset + 1, spec.Length());
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Checks whether a plugin exists and is enabled for the extension
 | |
|  * in the given URI. The MIME type is returned in the mimeType out parameter.
 | |
|  */
 | |
| bool IsPluginEnabledByExtension(nsIURI* uri, nsCString& mimeType) {
 | |
|   nsAutoCString ext;
 | |
|   GetExtensionFromURI(uri, ext);
 | |
| 
 | |
|   if (ext.IsEmpty()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Disables any native PDF plugins, when internal PDF viewer is enabled.
 | |
|   if (ext.EqualsIgnoreCase("pdf") && nsContentUtils::IsPDFJSEnabled()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
 | |
| 
 | |
|   if (!pluginHost) {
 | |
|     MOZ_ASSERT_UNREACHABLE("No pluginhost");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return pluginHost->HavePluginForExtension(ext, mimeType);
 | |
| }
 | |
| 
 | |
| ///
 | |
| /// Member Functions
 | |
| ///
 | |
| 
 | |
| // Helper to queue a CheckPluginStopEvent for a OBJLC object
 | |
| void nsObjectLoadingContent::QueueCheckPluginStopEvent() {
 | |
|   nsCOMPtr<nsIRunnable> event = new CheckPluginStopEvent(this);
 | |
|   mPendingCheckPluginStopEvent = event;
 | |
| 
 | |
|   NS_DispatchToCurrentThread(event);
 | |
| }
 | |
| 
 | |
| // Tedious syntax to create a plugin stream listener with checks and put it in
 | |
| // mFinalListener
 | |
| bool nsObjectLoadingContent::MakePluginListener() {
 | |
|   if (!mInstanceOwner) {
 | |
|     MOZ_ASSERT_UNREACHABLE("expecting a spawned plugin");
 | |
|     return false;
 | |
|   }
 | |
|   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
 | |
|   if (!pluginHost) {
 | |
|     MOZ_ASSERT_UNREACHABLE("No pluginHost");
 | |
|     return false;
 | |
|   }
 | |
|   NS_ASSERTION(!mFinalListener, "overwriting a final listener");
 | |
|   nsresult rv;
 | |
|   RefPtr<nsNPAPIPluginInstance> inst = mInstanceOwner->GetInstance();
 | |
|   nsCOMPtr<nsIStreamListener> finalListener;
 | |
|   rv = pluginHost->NewPluginStreamListener(mURI, inst,
 | |
|                                            getter_AddRefs(finalListener));
 | |
|   NS_ENSURE_SUCCESS(rv, false);
 | |
|   mFinalListener = finalListener;
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| // Helper to spawn the frameloader.
 | |
| void nsObjectLoadingContent::SetupFrameLoader(int32_t aJSPluginId) {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   NS_ASSERTION(thisContent, "must be a content");
 | |
| 
 | |
|   mFrameLoader =
 | |
|       nsFrameLoader::Create(thisContent->AsElement(), mNetworkCreated);
 | |
|   MOZ_ASSERT(mFrameLoader, "nsFrameLoader::Create failed");
 | |
| }
 | |
| 
 | |
| // Helper to spawn the frameloader and return a pointer to its docshell.
 | |
| already_AddRefed<nsIDocShell> nsObjectLoadingContent::SetupDocShell(
 | |
|     nsIURI* aRecursionCheckURI) {
 | |
|   SetupFrameLoader(nsFakePluginTag::NOT_JSPLUGIN);
 | |
|   if (!mFrameLoader) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIDocShell> docShell;
 | |
| 
 | |
|   if (aRecursionCheckURI) {
 | |
|     nsresult rv = mFrameLoader->CheckForRecursiveLoad(aRecursionCheckURI);
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|       IgnoredErrorResult result;
 | |
|       docShell = mFrameLoader->GetDocShell(result);
 | |
|       if (result.Failed()) {
 | |
|         MOZ_ASSERT_UNREACHABLE("Could not get DocShell from mFrameLoader?");
 | |
|       }
 | |
|     } else {
 | |
|       LOG(("OBJLC [%p]: Aborting recursive load", this));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!docShell) {
 | |
|     mFrameLoader->Destroy();
 | |
|     mFrameLoader = nullptr;
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return docShell.forget();
 | |
| }
 | |
| 
 | |
| nsresult nsObjectLoadingContent::BindToTree(BindContext& aContext,
 | |
|                                             nsINode& aParent) {
 | |
|   nsImageLoadingContent::BindToTree(aContext, aParent);
 | |
|   // FIXME(emilio): Should probably use composed doc?
 | |
|   if (Document* doc = aContext.GetUncomposedDoc()) {
 | |
|     doc->AddPlugin(this);
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::UnbindFromTree(bool aNullParent) {
 | |
|   nsImageLoadingContent::UnbindFromTree(aNullParent);
 | |
| 
 | |
|   nsCOMPtr<Element> thisElement =
 | |
|       do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
 | |
|   MOZ_ASSERT(thisElement);
 | |
|   Document* ownerDoc = thisElement->OwnerDoc();
 | |
|   ownerDoc->RemovePlugin(this);
 | |
| 
 | |
|   /// XXX(johns): Do we want to somehow propogate the reparenting behavior to
 | |
|   ///             FakePlugin types as well?
 | |
|   if (mType == eType_Plugin && (mInstanceOwner || mInstantiating)) {
 | |
|     // we'll let the plugin continue to run at least until we get back to
 | |
|     // the event loop. If we get back to the event loop and the node
 | |
|     // has still not been added back to the document then we tear down the
 | |
|     // plugin
 | |
|     QueueCheckPluginStopEvent();
 | |
|   } else if (mType != eType_Image) {
 | |
|     // nsImageLoadingContent handles the image case.
 | |
|     // Reset state and clear pending events
 | |
|     /// XXX(johns): The implementation for GenericFrame notes that ideally we
 | |
|     ///             would keep the docshell around, but trash the frameloader
 | |
|     UnloadObject();
 | |
|   }
 | |
| 
 | |
|   // Unattach plugin problem UIWidget if any.
 | |
|   if (thisElement->IsInComposedDoc()) {
 | |
|     thisElement->NotifyUAWidgetTeardown();
 | |
|   }
 | |
| 
 | |
|   if (mType == eType_Plugin) {
 | |
|     Document* doc = thisElement->GetComposedDoc();
 | |
|     if (doc && doc->IsActive()) {
 | |
|       nsCOMPtr<nsIRunnable> ev =
 | |
|           new nsSimplePluginEvent(doc, u"PluginRemoved"_ns);
 | |
|       NS_DispatchToCurrentThread(ev);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsObjectLoadingContent::nsObjectLoadingContent()
 | |
|     : mType(eType_Loading),
 | |
|       mFallbackType(eFallbackAlternate),
 | |
|       mRunID(0),
 | |
|       mHasRunID(false),
 | |
|       mChannelLoaded(false),
 | |
|       mInstantiating(false),
 | |
|       mNetworkCreated(true),
 | |
|       mActivated(false),
 | |
|       mContentBlockingEnabled(false),
 | |
|       mSkipFakePlugins(false),
 | |
|       mIsStopping(false),
 | |
|       mIsLoading(false),
 | |
|       mScriptRequested(false),
 | |
|       mRewrittenYoutubeEmbed(false),
 | |
|       mPreferFallback(false),
 | |
|       mPreferFallbackKnown(false) {}
 | |
| 
 | |
| nsObjectLoadingContent::~nsObjectLoadingContent() {
 | |
|   // Should have been unbound from the tree at this point, and
 | |
|   // CheckPluginStopEvent keeps us alive
 | |
|   if (mFrameLoader) {
 | |
|     MOZ_ASSERT_UNREACHABLE(
 | |
|         "Should not be tearing down frame loaders at this point");
 | |
|     mFrameLoader->Destroy();
 | |
|   }
 | |
|   if (mInstanceOwner || mInstantiating) {
 | |
|     // This is especially bad as delayed stop will try to hold on to this
 | |
|     // object...
 | |
|     MOZ_ASSERT_UNREACHABLE(
 | |
|         "Should not be tearing down a plugin at this point!");
 | |
|     StopPluginInstance();
 | |
|   }
 | |
|   DestroyImageLoadingContent();
 | |
| }
 | |
| 
 | |
| nsresult nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading) {
 | |
|   if (mInstanceOwner || mType != eType_Plugin || (mIsLoading != aIsLoading) ||
 | |
|       mInstantiating) {
 | |
|     // If we hit this assertion it's probably because LoadObject re-entered :(
 | |
|     //
 | |
|     // XXX(johns): This hackiness will go away in bug 767635
 | |
|     NS_ASSERTION(mIsLoading || !aIsLoading,
 | |
|                  "aIsLoading should only be true inside LoadObject");
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   mInstantiating = true;
 | |
|   AutoSetInstantiatingToFalse autoInstantiating(this);
 | |
| 
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
| 
 | |
|   nsCOMPtr<Document> doc = thisContent->GetComposedDoc();
 | |
|   if (!doc || !InActiveDocument(thisContent)) {
 | |
|     NS_ERROR(
 | |
|         "Shouldn't be calling "
 | |
|         "InstantiatePluginInstance without an active document");
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   // Instantiating an instance can result in script execution, which
 | |
|   // can destroy this DOM object. Don't allow that for the scope
 | |
|   // of this method.
 | |
|   nsCOMPtr<nsIObjectLoadingContent> kungFuDeathGrip = this;
 | |
| 
 | |
|   // Flush layout so that the frame is created if possible and the plugin is
 | |
|   // initialized with the latest information.
 | |
|   doc->FlushPendingNotifications(FlushType::Layout);
 | |
|   // Flushing layout may have re-entered and loaded something underneath us
 | |
|   NS_ENSURE_TRUE(mInstantiating, NS_OK);
 | |
| 
 | |
|   if (!thisContent->GetPrimaryFrame()) {
 | |
|     LOG(("OBJLC [%p]: Not instantiating plugin with no frame", this));
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
 | |
| 
 | |
|   if (!pluginHost) {
 | |
|     MOZ_ASSERT_UNREACHABLE("No pluginhost");
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   // If you add early return(s), be sure to balance this call to
 | |
|   // appShell->SuspendNative() with additional call(s) to
 | |
|   // appShell->ReturnNative().
 | |
|   nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
 | |
|   if (appShell) {
 | |
|     appShell->SuspendNative();
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsPluginInstanceOwner> newOwner;
 | |
|   nsresult rv = pluginHost->InstantiatePluginInstance(
 | |
|       mContentType, mURI.get(), this, getter_AddRefs(newOwner));
 | |
| 
 | |
|   // XXX(johns): We don't suspend native inside stopping plugins...
 | |
|   if (appShell) {
 | |
|     appShell->ResumeNative();
 | |
|   }
 | |
| 
 | |
|   if (!mInstantiating || NS_FAILED(rv)) {
 | |
|     LOG(
 | |
|         ("OBJLC [%p]: Plugin instantiation failed or re-entered, "
 | |
|          "killing old instance",
 | |
|          this));
 | |
|     // XXX(johns): This needs to be de-duplicated with DoStopPlugin, but we
 | |
|     //             don't want to touch the protochain or delayed stop.
 | |
|     //             (Bug 767635)
 | |
|     if (newOwner) {
 | |
|       RefPtr<nsNPAPIPluginInstance> inst = newOwner->GetInstance();
 | |
|       newOwner->SetFrame(nullptr);
 | |
|       if (inst) {
 | |
|         pluginHost->StopPluginInstance(inst);
 | |
|       }
 | |
|       newOwner->Destroy();
 | |
|     }
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   mInstanceOwner = newOwner;
 | |
| 
 | |
|   if (mInstanceOwner) {
 | |
|     RefPtr<nsNPAPIPluginInstance> inst = mInstanceOwner->GetInstance();
 | |
| 
 | |
|     rv = inst->GetRunID(&mRunID);
 | |
|     mHasRunID = NS_SUCCEEDED(rv);
 | |
|   }
 | |
| 
 | |
|   // Ensure the frame did not change during instantiation re-entry (common).
 | |
|   // HasNewFrame would not have mInstanceOwner yet, so the new frame would be
 | |
|   // dangling. (Bug 854082)
 | |
|   nsIFrame* frame = thisContent->GetPrimaryFrame();
 | |
|   if (frame && mInstanceOwner) {
 | |
|     mInstanceOwner->SetFrame(static_cast<nsPluginFrame*>(frame));
 | |
| 
 | |
|     // Bug 870216 - Adobe Reader renders with incorrect dimensions until it gets
 | |
|     // a second SetWindow call. This is otherwise redundant.
 | |
|     mInstanceOwner->CallSetWindow();
 | |
|   }
 | |
| 
 | |
|   // Set up scripting interfaces.
 | |
|   NotifyContentObjectWrapper();
 | |
| 
 | |
|   RefPtr<nsNPAPIPluginInstance> pluginInstance = GetPluginInstance();
 | |
|   if (pluginInstance) {
 | |
|     nsCOMPtr<nsIPluginTag> pluginTag;
 | |
|     pluginHost->GetPluginTagForInstance(pluginInstance,
 | |
|                                         getter_AddRefs(pluginTag));
 | |
| 
 | |
|     uint32_t blockState = nsIBlocklistService::STATE_NOT_BLOCKED;
 | |
|     pluginTag->GetBlocklistState(&blockState);
 | |
|     if (blockState == nsIBlocklistService::STATE_OUTDATED) {
 | |
|       // Fire plugin outdated event if necessary
 | |
|       LOG(("OBJLC [%p]: Dispatching plugin outdated event for content\n",
 | |
|            this));
 | |
|       nsCOMPtr<nsIRunnable> ev =
 | |
|           new nsSimplePluginEvent(thisContent, u"PluginOutdated"_ns);
 | |
|       nsresult rv = NS_DispatchToCurrentThread(ev);
 | |
|       if (NS_FAILED(rv)) {
 | |
|         NS_WARNING("failed to dispatch nsSimplePluginEvent");
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // If we have a URI but didn't open a channel yet (eAllowPluginSkipChannel)
 | |
|     // or we did load with a channel but are re-instantiating, re-open the
 | |
|     // channel. OpenChannel() performs security checks, and this plugin has
 | |
|     // already passed content policy in LoadObject.
 | |
|     if ((mURI && !mChannelLoaded) || (mChannelLoaded && !aIsLoading)) {
 | |
|       NS_ASSERTION(!mChannel, "should not have an existing channel here");
 | |
|       // We intentionally ignore errors here, leaving it up to the plugin to
 | |
|       // deal with not having an initial stream.
 | |
|       OpenChannel();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIRunnable> ev =
 | |
|       new nsSimplePluginEvent(thisContent, doc, u"PluginInstantiated"_ns);
 | |
|   NS_DispatchToCurrentThread(ev);
 | |
| 
 | |
| #ifdef XP_MACOSX
 | |
|   HTMLObjectElement::HandlePluginInstantiated(thisContent->AsElement());
 | |
| #endif
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::GetPluginAttributes(
 | |
|     nsTArray<MozPluginParameter>& aAttributes) {
 | |
|   aAttributes = mCachedAttributes.Clone();
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::GetPluginParameters(
 | |
|     nsTArray<MozPluginParameter>& aParameters) {
 | |
|   aParameters = mCachedParameters.Clone();
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::GetNestedParams(
 | |
|     nsTArray<MozPluginParameter>& aParams) {
 | |
|   nsCOMPtr<Element> ourElement =
 | |
|       do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
 | |
| 
 | |
|   nsCOMPtr<nsIHTMLCollection> allParams;
 | |
|   constexpr auto xhtml_ns = u"http://www.w3.org/1999/xhtml"_ns;
 | |
|   ErrorResult rv;
 | |
|   allParams = ourElement->GetElementsByTagNameNS(xhtml_ns, u"param"_ns, rv);
 | |
|   if (rv.Failed()) {
 | |
|     return;
 | |
|   }
 | |
|   MOZ_ASSERT(allParams);
 | |
| 
 | |
|   uint32_t numAllParams = allParams->Length();
 | |
|   for (uint32_t i = 0; i < numAllParams; i++) {
 | |
|     RefPtr<Element> element = allParams->Item(i);
 | |
| 
 | |
|     nsAutoString name;
 | |
|     element->GetAttr(nsGkAtoms::name, name);
 | |
| 
 | |
|     if (name.IsEmpty()) continue;
 | |
| 
 | |
|     nsCOMPtr<nsIContent> parent = element->GetParent();
 | |
|     RefPtr<HTMLObjectElement> objectElement;
 | |
|     while (!objectElement && parent) {
 | |
|       objectElement = HTMLObjectElement::FromNode(parent);
 | |
|       parent = parent->GetParent();
 | |
|     }
 | |
| 
 | |
|     if (objectElement) {
 | |
|       parent = objectElement;
 | |
|     } else {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     if (parent == ourElement) {
 | |
|       MozPluginParameter param;
 | |
|       element->GetAttr(nsGkAtoms::name, param.mName);
 | |
|       element->GetAttr(nsGkAtoms::value, param.mValue);
 | |
| 
 | |
|       param.mName.Trim(" \n\r\t\b", true, true, false);
 | |
|       param.mValue.Trim(" \n\r\t\b", true, true, false);
 | |
| 
 | |
|       aParams.AppendElement(param);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsresult nsObjectLoadingContent::BuildParametersArray() {
 | |
|   if (mCachedAttributes.Length() || mCachedParameters.Length()) {
 | |
|     MOZ_ASSERT(false, "Parameters array should be empty.");
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<Element> element =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
| 
 | |
|   for (uint32_t i = 0; i != element->GetAttrCount(); i += 1) {
 | |
|     MozPluginParameter param;
 | |
|     const nsAttrName* attrName = element->GetAttrNameAt(i);
 | |
|     nsAtom* atom = attrName->LocalName();
 | |
|     element->GetAttr(attrName->NamespaceID(), atom, param.mValue);
 | |
|     atom->ToString(param.mName);
 | |
|     mCachedAttributes.AppendElement(param);
 | |
|   }
 | |
| 
 | |
|   nsAutoCString wmodeOverride;
 | |
|   Preferences::GetCString("plugins.force.wmode", wmodeOverride);
 | |
| 
 | |
|   for (uint32_t i = 0; i < mCachedAttributes.Length(); i++) {
 | |
|     if (!wmodeOverride.IsEmpty() &&
 | |
|         mCachedAttributes[i].mName.EqualsIgnoreCase("wmode")) {
 | |
|       CopyASCIItoUTF16(wmodeOverride, mCachedAttributes[i].mValue);
 | |
|       wmodeOverride.Truncate();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!wmodeOverride.IsEmpty()) {
 | |
|     MozPluginParameter param;
 | |
|     param.mName = u"wmode"_ns;
 | |
|     CopyASCIItoUTF16(wmodeOverride, param.mValue);
 | |
|     mCachedAttributes.AppendElement(param);
 | |
|   }
 | |
| 
 | |
|   // Some plugins were never written to understand the "data" attribute of the
 | |
|   // OBJECT tag. Real and WMP will not play unless they find a "src" attribute,
 | |
|   // see bug 152334. Nav 4.x would simply replace the "data" with "src". Because
 | |
|   // some plugins correctly look for "data", lets instead copy the "data"
 | |
|   // attribute and add another entry to the bottom of the array if there isn't
 | |
|   // already a "src" specified.
 | |
|   if (element->IsHTMLElement(nsGkAtoms::object) &&
 | |
|       !element->HasAttr(kNameSpaceID_None, nsGkAtoms::src)) {
 | |
|     MozPluginParameter param;
 | |
|     element->GetAttr(kNameSpaceID_None, nsGkAtoms::data, param.mValue);
 | |
|     if (!param.mValue.IsEmpty()) {
 | |
|       param.mName = u"SRC"_ns;
 | |
|       mCachedAttributes.AppendElement(param);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   GetNestedParams(mCachedParameters);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::NotifyOwnerDocumentActivityChanged() {
 | |
|   // XXX(johns): We cannot touch plugins or run arbitrary script from this call,
 | |
|   //             as Document is in a non-reentrant state.
 | |
| 
 | |
|   // If we have a plugin we want to queue an event to stop it unless we are
 | |
|   // moved into an active document before returning to the event loop.
 | |
|   if (mInstanceOwner || mInstantiating) {
 | |
|     QueueCheckPluginStopEvent();
 | |
|   }
 | |
|   nsImageLoadingContent::NotifyOwnerDocumentActivityChanged();
 | |
| }
 | |
| 
 | |
| // nsIRequestObserver
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::OnStartRequest(nsIRequest* aRequest) {
 | |
|   AUTO_PROFILER_LABEL("nsObjectLoadingContent::OnStartRequest", NETWORK);
 | |
| 
 | |
|   LOG(("OBJLC [%p]: Channel OnStartRequest", this));
 | |
| 
 | |
|   if (aRequest != mChannel || !aRequest) {
 | |
|     // happens when a new load starts before the previous one got here
 | |
|     return NS_BINDING_ABORTED;
 | |
|   }
 | |
| 
 | |
|   // If we already switched to type plugin, this channel can just be passed to
 | |
|   // the final listener.
 | |
|   if (mType == eType_Plugin) {
 | |
|     if (!mInstanceOwner) {
 | |
|       // We drop mChannel when stopping plugins, so something is wrong
 | |
|       MOZ_ASSERT_UNREACHABLE(
 | |
|           "Opened a channel in plugin mode, but don't have "
 | |
|           "a plugin");
 | |
|       return NS_BINDING_ABORTED;
 | |
|     }
 | |
|     if (MakePluginListener()) {
 | |
|       return mFinalListener->OnStartRequest(aRequest);
 | |
|     }
 | |
|     MOZ_ASSERT_UNREACHABLE(
 | |
|         "Failed to create PluginStreamListener, aborting "
 | |
|         "channel");
 | |
|     return NS_BINDING_ABORTED;
 | |
|   }
 | |
| 
 | |
|   // Otherwise we should be state loading, and call LoadObject with the channel
 | |
|   if (mType != eType_Loading) {
 | |
|     MOZ_ASSERT_UNREACHABLE("Should be type loading at this point");
 | |
|     return NS_BINDING_ABORTED;
 | |
|   }
 | |
|   NS_ASSERTION(!mChannelLoaded, "mChannelLoaded set already?");
 | |
|   NS_ASSERTION(!mFinalListener, "mFinalListener exists already?");
 | |
| 
 | |
|   mChannelLoaded = true;
 | |
| 
 | |
|   nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
 | |
|   NS_ASSERTION(chan, "Why is our request not a channel?");
 | |
| 
 | |
|   nsresult status = NS_OK;
 | |
|   bool success = IsSuccessfulRequest(aRequest, &status);
 | |
| 
 | |
|   if (status == NS_ERROR_BLOCKED_URI) {
 | |
|     nsCOMPtr<nsIConsoleService> console(
 | |
|         do_GetService("@mozilla.org/consoleservice;1"));
 | |
|     if (console) {
 | |
|       nsCOMPtr<nsIURI> uri;
 | |
|       chan->GetURI(getter_AddRefs(uri));
 | |
|       nsString message =
 | |
|           u"Blocking "_ns +
 | |
|           NS_ConvertASCIItoUTF16(uri->GetSpecOrDefault().get()) +
 | |
|           nsLiteralString(
 | |
|               u" since it was found on an internal Firefox blocklist.");
 | |
|       console->LogStringMessage(message.get());
 | |
|     }
 | |
|     mContentBlockingEnabled = true;
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
 | |
|     mContentBlockingEnabled = true;
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   if (!success) {
 | |
|     LOG(("OBJLC [%p]: OnStartRequest: Request failed\n", this));
 | |
|     // If the request fails, we still call LoadObject() to handle fallback
 | |
|     // content and notifying of failure. (mChannelLoaded && !mChannel) indicates
 | |
|     // the bad state.
 | |
|     mChannel = nullptr;
 | |
|     LoadObject(true, false);
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   return LoadObject(true, false, aRequest);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::OnStopRequest(nsIRequest* aRequest,
 | |
|                                       nsresult aStatusCode) {
 | |
|   AUTO_PROFILER_LABEL("nsObjectLoadingContent::OnStopRequest", NETWORK);
 | |
| 
 | |
|   // Handle object not loading error because source was a tracking URL (or
 | |
|   // fingerprinting, cryptomining, etc.).
 | |
|   // We make a note of this object node by including it in a dedicated
 | |
|   // array of blocked tracking nodes under its parent document.
 | |
|   if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aStatusCode)) {
 | |
|     nsCOMPtr<nsIContent> thisNode =
 | |
|         do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
 | |
|     if (thisNode && thisNode->IsInComposedDoc()) {
 | |
|       thisNode->GetComposedDoc()->AddBlockedNodeByClassifier(thisNode);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aRequest != mChannel) {
 | |
|     return NS_BINDING_ABORTED;
 | |
|   }
 | |
| 
 | |
|   mChannel = nullptr;
 | |
| 
 | |
|   if (mFinalListener) {
 | |
|     // This may re-enter in the case of plugin listeners
 | |
|     nsCOMPtr<nsIStreamListener> listenerGrip(mFinalListener);
 | |
|     mFinalListener = nullptr;
 | |
|     listenerGrip->OnStopRequest(aRequest, aStatusCode);
 | |
|   }
 | |
| 
 | |
|   // Return value doesn't matter
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // nsIStreamListener
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::OnDataAvailable(nsIRequest* aRequest,
 | |
|                                         nsIInputStream* aInputStream,
 | |
|                                         uint64_t aOffset, uint32_t aCount) {
 | |
|   if (aRequest != mChannel) {
 | |
|     return NS_BINDING_ABORTED;
 | |
|   }
 | |
| 
 | |
|   if (mFinalListener) {
 | |
|     // This may re-enter in the case of plugin listeners
 | |
|     nsCOMPtr<nsIStreamListener> listenerGrip(mFinalListener);
 | |
|     return listenerGrip->OnDataAvailable(aRequest, aInputStream, aOffset,
 | |
|                                          aCount);
 | |
|   }
 | |
| 
 | |
|   // We shouldn't have a connected channel with no final listener
 | |
|   MOZ_ASSERT_UNREACHABLE(
 | |
|       "Got data for channel with no connected final "
 | |
|       "listener");
 | |
|   mChannel = nullptr;
 | |
| 
 | |
|   return NS_ERROR_UNEXPECTED;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::PresetOpenerWindow(
 | |
|     const Nullable<WindowProxyHolder>& aOpenerWindow, ErrorResult& aRv) {
 | |
|   aRv.Throw(NS_ERROR_FAILURE);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::GetActualType(nsACString& aType) {
 | |
|   aType = mContentType;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::GetDisplayedType(uint32_t* aType) {
 | |
|   *aType = DisplayedType();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::HasNewFrame(nsIObjectFrame* aFrame) {
 | |
|   if (mType != eType_Plugin) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (!aFrame) {
 | |
|     // Lost our frame. If we aren't going to be getting a new frame, e.g. we've
 | |
|     // become display:none, we'll want to stop the plugin. Queue a
 | |
|     // CheckPluginStopEvent
 | |
|     if (mInstanceOwner || mInstantiating) {
 | |
|       if (mInstanceOwner) {
 | |
|         mInstanceOwner->SetFrame(nullptr);
 | |
|       }
 | |
|       QueueCheckPluginStopEvent();
 | |
|     }
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Have a new frame
 | |
| 
 | |
|   if (!mInstanceOwner) {
 | |
|     // We are successfully setup as type plugin, but have not spawned an
 | |
|     // instance due to a lack of a frame.
 | |
|     AsyncStartPluginInstance();
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Otherwise, we're just changing frames
 | |
|   // Set up relationship between instance owner and frame.
 | |
|   nsPluginFrame* objFrame = static_cast<nsPluginFrame*>(aFrame);
 | |
|   mInstanceOwner->SetFrame(objFrame);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsNPAPIPluginInstance* nsObjectLoadingContent::GetPluginInstance() {
 | |
|   if (!mInstanceOwner) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return mInstanceOwner->GetInstance();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::GetContentTypeForMIMEType(const nsACString& aMIMEType,
 | |
|                                                   uint32_t* aType) {
 | |
|   *aType = GetTypeOfContent(PromiseFlatCString(aMIMEType), false);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // nsIInterfaceRequestor
 | |
| // We use a shim class to implement this so that JS consumers still
 | |
| // see an interface requestor even though WebIDL bindings don't expose
 | |
| // that stuff.
 | |
| class ObjectInterfaceRequestorShim final : public nsIInterfaceRequestor,
 | |
|                                            public nsIChannelEventSink,
 | |
|                                            public nsIStreamListener {
 | |
|  public:
 | |
|   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | |
|   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ObjectInterfaceRequestorShim,
 | |
|                                            nsIInterfaceRequestor)
 | |
|   NS_DECL_NSIINTERFACEREQUESTOR
 | |
|   // RefPtr<nsObjectLoadingContent> fails due to ambiguous AddRef/Release,
 | |
|   // hence the ugly static cast :(
 | |
|   NS_FORWARD_NSICHANNELEVENTSINK(
 | |
|       static_cast<nsObjectLoadingContent*>(mContent.get())->)
 | |
|   NS_FORWARD_NSISTREAMLISTENER(
 | |
|       static_cast<nsObjectLoadingContent*>(mContent.get())->)
 | |
|   NS_FORWARD_NSIREQUESTOBSERVER(
 | |
|       static_cast<nsObjectLoadingContent*>(mContent.get())->)
 | |
| 
 | |
|   explicit ObjectInterfaceRequestorShim(nsIObjectLoadingContent* aContent)
 | |
|       : mContent(aContent) {}
 | |
| 
 | |
|  protected:
 | |
|   ~ObjectInterfaceRequestorShim() = default;
 | |
|   nsCOMPtr<nsIObjectLoadingContent> mContent;
 | |
| };
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTION(ObjectInterfaceRequestorShim, mContent)
 | |
| 
 | |
| NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ObjectInterfaceRequestorShim)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
 | |
|   NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
 | |
|   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
 | |
| NS_INTERFACE_MAP_END
 | |
| 
 | |
| NS_IMPL_CYCLE_COLLECTING_ADDREF(ObjectInterfaceRequestorShim)
 | |
| NS_IMPL_CYCLE_COLLECTING_RELEASE(ObjectInterfaceRequestorShim)
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| ObjectInterfaceRequestorShim::GetInterface(const nsIID& aIID, void** aResult) {
 | |
|   if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
 | |
|     nsIChannelEventSink* sink = this;
 | |
|     *aResult = sink;
 | |
|     NS_ADDREF(sink);
 | |
|     return NS_OK;
 | |
|   }
 | |
|   return NS_NOINTERFACE;
 | |
| }
 | |
| 
 | |
| // nsIChannelEventSink
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::AsyncOnChannelRedirect(
 | |
|     nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
 | |
|     nsIAsyncVerifyRedirectCallback* cb) {
 | |
|   // If we're already busy with a new load, or have no load at all,
 | |
|   // cancel the redirect.
 | |
|   if (!mChannel || aOldChannel != mChannel) {
 | |
|     return NS_BINDING_ABORTED;
 | |
|   }
 | |
| 
 | |
|   mChannel = aNewChannel;
 | |
|   cb->OnRedirectVerifyCallback(NS_OK);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // <public>
 | |
| EventStates nsObjectLoadingContent::ObjectState() const {
 | |
|   switch (mType) {
 | |
|     case eType_Loading:
 | |
|       return NS_EVENT_STATE_LOADING;
 | |
|     case eType_Image:
 | |
|       return ImageState();
 | |
|     case eType_Plugin:
 | |
|     case eType_FakePlugin:
 | |
|     case eType_Document:
 | |
|       // These are OK. If documents start to load successfully, they display
 | |
|       // something, and are thus not broken in this sense. The same goes for
 | |
|       // plugins.
 | |
|       return EventStates();
 | |
|     case eType_Null:
 | |
|       switch (mFallbackType) {
 | |
|         case eFallbackSuppressed:
 | |
|           return NS_EVENT_STATE_SUPPRESSED;
 | |
|         case eFallbackUserDisabled:
 | |
|           return NS_EVENT_STATE_USERDISABLED;
 | |
|         case eFallbackClickToPlay:
 | |
|         case eFallbackClickToPlayQuiet:
 | |
|           return NS_EVENT_STATE_TYPE_CLICK_TO_PLAY;
 | |
|         case eFallbackDisabled:
 | |
|           return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_HANDLER_DISABLED;
 | |
|         case eFallbackBlocklisted:
 | |
|           return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_HANDLER_BLOCKED;
 | |
|         case eFallbackCrashed:
 | |
|           return NS_EVENT_STATE_BROKEN | NS_EVENT_STATE_HANDLER_CRASHED;
 | |
|         case eFallbackUnsupported:
 | |
|         case eFallbackOutdated:
 | |
|         case eFallbackAlternate:
 | |
|           return NS_EVENT_STATE_BROKEN;
 | |
|         case eFallbackVulnerableUpdatable:
 | |
|           return NS_EVENT_STATE_VULNERABLE_UPDATABLE;
 | |
|         case eFallbackVulnerableNoUpdate:
 | |
|           return NS_EVENT_STATE_VULNERABLE_NO_UPDATE;
 | |
|       }
 | |
|   }
 | |
|   MOZ_ASSERT_UNREACHABLE("unknown type?");
 | |
|   return NS_EVENT_STATE_LOADING;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::MaybeRewriteYoutubeEmbed(nsIURI* aURI,
 | |
|                                                       nsIURI* aBaseURI,
 | |
|                                                       nsIURI** aOutURI) {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   NS_ASSERTION(thisContent, "Must be an instance of content");
 | |
| 
 | |
|   // We're only interested in switching out embed and object tags
 | |
|   if (!thisContent->NodeInfo()->Equals(nsGkAtoms::embed) &&
 | |
|       !thisContent->NodeInfo()->Equals(nsGkAtoms::object)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIEffectiveTLDService> tldService =
 | |
|       do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
 | |
|   // If we can't analyze the URL, just pass on through.
 | |
|   if (!tldService) {
 | |
|     NS_WARNING("Could not get TLD service!");
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString currentBaseDomain;
 | |
|   bool ok = NS_SUCCEEDED(tldService->GetBaseDomain(aURI, 0, currentBaseDomain));
 | |
|   if (!ok) {
 | |
|     // Data URIs (commonly used for things like svg embeds) won't parse
 | |
|     // correctly, so just fail silently here.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // See if URL is referencing youtube
 | |
|   if (!currentBaseDomain.EqualsLiteral("youtube.com") &&
 | |
|       !currentBaseDomain.EqualsLiteral("youtube-nocookie.com")) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We should only rewrite URLs with paths starting with "/v/", as we shouldn't
 | |
|   // touch object nodes with "/embed/" urls that already do that right thing.
 | |
|   nsAutoCString path;
 | |
|   aURI->GetPathQueryRef(path);
 | |
|   if (!StringBeginsWith(path, "/v/"_ns)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // See if requester is planning on using the JS API.
 | |
|   nsAutoCString uri;
 | |
|   nsresult rv = aURI->GetSpec(uri);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // Some YouTube urls have parameters in path components, e.g.
 | |
|   // http://youtube.com/embed/7LcUOEP7Brc&start=35. These URLs work with flash,
 | |
|   // but break iframe/object embedding. If this situation occurs with rewritten
 | |
|   // URLs, convert the parameters to query in order to make the video load
 | |
|   // correctly as an iframe. In either case, warn about it in the
 | |
|   // developer console.
 | |
|   int32_t ampIndex = uri.FindChar('&', 0);
 | |
|   bool replaceQuery = false;
 | |
|   if (ampIndex != -1) {
 | |
|     int32_t qmIndex = uri.FindChar('?', 0);
 | |
|     if (qmIndex == -1 || qmIndex > ampIndex) {
 | |
|       replaceQuery = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If we're pref'd off, return after telemetry has been logged.
 | |
|   if (!Preferences::GetBool(kPrefYoutubeRewrite)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   nsAutoString utf16OldURI = NS_ConvertUTF8toUTF16(uri);
 | |
|   // If we need to convert the URL, it means an ampersand comes first.
 | |
|   // Use the index we found earlier.
 | |
|   if (replaceQuery) {
 | |
|     // Replace question marks with ampersands.
 | |
|     uri.ReplaceChar('?', '&');
 | |
|     // Replace the first ampersand with a question mark.
 | |
|     uri.SetCharAt('?', ampIndex);
 | |
|   }
 | |
|   // Switch out video access url formats, which should possibly allow HTML5
 | |
|   // video loading.
 | |
|   uri.ReplaceSubstring("/v/"_ns, "/embed/"_ns);
 | |
|   nsAutoString utf16URI = NS_ConvertUTF8toUTF16(uri);
 | |
|   rv = nsContentUtils::NewURIWithDocumentCharset(
 | |
|       aOutURI, utf16URI, thisContent->OwnerDoc(), aBaseURI);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return;
 | |
|   }
 | |
|   AutoTArray<nsString, 2> params = {utf16OldURI, utf16URI};
 | |
|   const char* msgName;
 | |
|   // If there's no query to rewrite, just notify in the developer console
 | |
|   // that we're changing the embed.
 | |
|   if (!replaceQuery) {
 | |
|     msgName = "RewriteYouTubeEmbed";
 | |
|   } else {
 | |
|     msgName = "RewriteYouTubeEmbedPathParams";
 | |
|   }
 | |
|   nsContentUtils::ReportToConsole(
 | |
|       nsIScriptError::warningFlag, "Plugins"_ns, thisContent->OwnerDoc(),
 | |
|       nsContentUtils::eDOM_PROPERTIES, msgName, params);
 | |
| }
 | |
| 
 | |
| bool nsObjectLoadingContent::CheckLoadPolicy(int16_t* aContentPolicy) {
 | |
|   if (!aContentPolicy || !mURI) {
 | |
|     MOZ_ASSERT_UNREACHABLE("Doing it wrong");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   NS_ASSERTION(thisContent, "Must be an instance of content");
 | |
| 
 | |
|   Document* doc = thisContent->OwnerDoc();
 | |
| 
 | |
|   nsContentPolicyType contentPolicyType = GetContentPolicyType();
 | |
| 
 | |
|   nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
 | |
|       doc->NodePrincipal(),  // loading principal
 | |
|       doc->NodePrincipal(),  // triggering principal
 | |
|       thisContent, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
 | |
|       contentPolicyType);
 | |
| 
 | |
|   *aContentPolicy = nsIContentPolicy::ACCEPT;
 | |
|   nsresult rv = NS_CheckContentLoadPolicy(mURI, secCheckLoadInfo, mContentType,
 | |
|                                           aContentPolicy,
 | |
|                                           nsContentUtils::GetContentPolicy());
 | |
|   NS_ENSURE_SUCCESS(rv, false);
 | |
|   if (NS_CP_REJECTED(*aContentPolicy)) {
 | |
|     LOG(("OBJLC [%p]: Content policy denied load of %s", this,
 | |
|          mURI->GetSpecOrDefault().get()));
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| bool nsObjectLoadingContent::CheckProcessPolicy(int16_t* aContentPolicy) {
 | |
|   if (!aContentPolicy) {
 | |
|     MOZ_ASSERT_UNREACHABLE("Null out variable");
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   NS_ASSERTION(thisContent, "Must be an instance of content");
 | |
| 
 | |
|   Document* doc = thisContent->OwnerDoc();
 | |
| 
 | |
|   int32_t objectType;
 | |
|   switch (mType) {
 | |
|     case eType_Image:
 | |
|       objectType = nsIContentPolicy::TYPE_INTERNAL_IMAGE;
 | |
|       break;
 | |
|     case eType_Document:
 | |
|       objectType = nsIContentPolicy::TYPE_DOCUMENT;
 | |
|       break;
 | |
|     // FIXME Fake plugins look just like real plugins to CSP, should they use
 | |
|     // the fake plugin's handler URI and look like documents instead?
 | |
|     case eType_FakePlugin:
 | |
|     case eType_Plugin:
 | |
|       objectType = GetContentPolicyType();
 | |
|       break;
 | |
|     default:
 | |
|       MOZ_ASSERT_UNREACHABLE(
 | |
|           "Calling checkProcessPolicy with an unloadable "
 | |
|           "type");
 | |
|       return false;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
 | |
|       doc->NodePrincipal(),  // loading principal
 | |
|       doc->NodePrincipal(),  // triggering principal
 | |
|       thisContent, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
 | |
|       objectType);
 | |
| 
 | |
|   *aContentPolicy = nsIContentPolicy::ACCEPT;
 | |
|   nsresult rv = NS_CheckContentProcessPolicy(
 | |
|       mURI ? mURI : mBaseURI, secCheckLoadInfo, mContentType, aContentPolicy,
 | |
|       nsContentUtils::GetContentPolicy());
 | |
|   NS_ENSURE_SUCCESS(rv, false);
 | |
| 
 | |
|   if (NS_CP_REJECTED(*aContentPolicy)) {
 | |
|     LOG(("OBJLC [%p]: CheckContentProcessPolicy rejected load", this));
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| nsObjectLoadingContent::ParameterUpdateFlags
 | |
| nsObjectLoadingContent::UpdateObjectParameters() {
 | |
|   nsCOMPtr<Element> thisElement =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   MOZ_ASSERT(thisElement, "Must be an Element");
 | |
| 
 | |
|   uint32_t caps = GetCapabilities();
 | |
|   LOG(("OBJLC [%p]: Updating object parameters", this));
 | |
| 
 | |
|   nsresult rv;
 | |
|   nsAutoCString newMime;
 | |
|   nsAutoString typeAttr;
 | |
|   nsCOMPtr<nsIURI> newURI;
 | |
|   nsCOMPtr<nsIURI> newBaseURI;
 | |
|   ObjectType newType;
 | |
|   // Set if this state can't be used to load anything, forces eType_Null
 | |
|   bool stateInvalid = false;
 | |
|   // Indicates what parameters changed.
 | |
|   // eParamChannelChanged - means parameters that affect channel opening
 | |
|   //                        decisions changed
 | |
|   // eParamStateChanged -   means anything that affects what content we load
 | |
|   //                        changed, even if the channel we'd open remains the
 | |
|   //                        same.
 | |
|   //
 | |
|   // State changes outside of the channel parameters only matter if we've
 | |
|   // already opened a channel or tried to instantiate content, whereas channel
 | |
|   // parameter changes require re-opening the channel even if we haven't gotten
 | |
|   // that far.
 | |
|   nsObjectLoadingContent::ParameterUpdateFlags retval = eParamNoChange;
 | |
| 
 | |
|   ///
 | |
|   /// Initial MIME Type
 | |
|   ///
 | |
| 
 | |
|   if (caps & eFallbackIfClassIDPresent) {
 | |
|     nsAutoString classIDAttr;
 | |
|     thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::classid, classIDAttr);
 | |
|     // We don't support class ID plugin references, so we should always treat
 | |
|     // having class Ids as attributes as invalid, and fallback accordingly.
 | |
|     if (!classIDAttr.IsEmpty()) {
 | |
|       newMime.Truncate();
 | |
|       stateInvalid = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   ///
 | |
|   /// Codebase
 | |
|   ///
 | |
| 
 | |
|   nsAutoString codebaseStr;
 | |
|   nsIURI* docBaseURI = thisElement->GetBaseURI();
 | |
|   thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::codebase, codebaseStr);
 | |
| 
 | |
|   if (!codebaseStr.IsEmpty()) {
 | |
|     rv = nsContentUtils::NewURIWithDocumentCharset(
 | |
|         getter_AddRefs(newBaseURI), codebaseStr, thisElement->OwnerDoc(),
 | |
|         docBaseURI);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       // Malformed URI
 | |
|       LOG(
 | |
|           ("OBJLC [%p]: Could not parse plugin's codebase as a URI, "
 | |
|            "will use document baseURI instead",
 | |
|            this));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // If we failed to build a valid URI, use the document's base URI
 | |
|   if (!newBaseURI) {
 | |
|     newBaseURI = docBaseURI;
 | |
|   }
 | |
| 
 | |
|   nsAutoString rawTypeAttr;
 | |
|   thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::type, rawTypeAttr);
 | |
|   if (!rawTypeAttr.IsEmpty()) {
 | |
|     typeAttr = rawTypeAttr;
 | |
|     nsAutoString params;
 | |
|     nsAutoString mime;
 | |
|     nsContentUtils::SplitMimeType(rawTypeAttr, mime, params);
 | |
|     CopyUTF16toUTF8(mime, newMime);
 | |
|   }
 | |
| 
 | |
|   ///
 | |
|   /// URI
 | |
|   ///
 | |
| 
 | |
|   nsAutoString uriStr;
 | |
|   // Different elements keep this in various locations
 | |
|   if (thisElement->NodeInfo()->Equals(nsGkAtoms::object)) {
 | |
|     thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::data, uriStr);
 | |
|   } else if (thisElement->NodeInfo()->Equals(nsGkAtoms::embed)) {
 | |
|     thisElement->GetAttr(kNameSpaceID_None, nsGkAtoms::src, uriStr);
 | |
|   } else {
 | |
|     MOZ_ASSERT_UNREACHABLE("Unrecognized plugin-loading tag");
 | |
|   }
 | |
| 
 | |
|   mRewrittenYoutubeEmbed = false;
 | |
|   // Note that the baseURI changing could affect the newURI, even if uriStr did
 | |
|   // not change.
 | |
|   if (!uriStr.IsEmpty()) {
 | |
|     rv = nsContentUtils::NewURIWithDocumentCharset(
 | |
|         getter_AddRefs(newURI), uriStr, thisElement->OwnerDoc(), newBaseURI);
 | |
|     nsCOMPtr<nsIURI> rewrittenURI;
 | |
|     MaybeRewriteYoutubeEmbed(newURI, newBaseURI, getter_AddRefs(rewrittenURI));
 | |
|     if (rewrittenURI) {
 | |
|       newURI = rewrittenURI;
 | |
|       mRewrittenYoutubeEmbed = true;
 | |
|       newMime = "text/html"_ns;
 | |
|     }
 | |
| 
 | |
|     if (NS_FAILED(rv)) {
 | |
|       stateInvalid = true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // For eAllowPluginSkipChannel tags, if we have a non-plugin type, but can get
 | |
|   // a plugin type from the extension, prefer that to falling back to a channel.
 | |
|   if (!IsPluginType(GetTypeOfContent(newMime, mSkipFakePlugins)) && newURI &&
 | |
|       (caps & eAllowPluginSkipChannel) &&
 | |
|       IsPluginEnabledByExtension(newURI, newMime)) {
 | |
|     LOG(("OBJLC [%p]: Using extension as type hint (%s)", this, newMime.get()));
 | |
|   }
 | |
| 
 | |
|   ///
 | |
|   /// Check if the original (pre-channel) content-type or URI changed, and
 | |
|   /// record mOriginal{ContentType,URI}
 | |
|   ///
 | |
| 
 | |
|   if ((mOriginalContentType != newMime) || !URIEquals(mOriginalURI, newURI)) {
 | |
|     // These parameters changing requires re-opening the channel, so don't
 | |
|     // consider the currently-open channel below
 | |
|     // XXX(johns): Changing the mime type might change our decision on whether
 | |
|     //             or not we load a channel, so we count changes to it as a
 | |
|     //             channel parameter change for the sake of simplicity.
 | |
|     retval = (ParameterUpdateFlags)(retval | eParamChannelChanged);
 | |
|     LOG(("OBJLC [%p]: Channel parameters changed", this));
 | |
|   }
 | |
|   mOriginalContentType = newMime;
 | |
|   mOriginalURI = newURI;
 | |
| 
 | |
|   ///
 | |
|   /// If we have a channel, see if its MIME type should take precendence and
 | |
|   /// check the final (redirected) URL
 | |
|   ///
 | |
| 
 | |
|   // If we have a loaded channel and channel parameters did not change, use it
 | |
|   // to determine what we would load.
 | |
|   bool useChannel = mChannelLoaded && !(retval & eParamChannelChanged);
 | |
|   // If we have a channel and are type loading, as opposed to having an existing
 | |
|   // channel for a previous load.
 | |
|   bool newChannel = useChannel && mType == eType_Loading;
 | |
| 
 | |
|   if (newChannel && mChannel) {
 | |
|     nsCString channelType;
 | |
|     rv = mChannel->GetContentType(channelType);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       MOZ_ASSERT_UNREACHABLE("GetContentType failed");
 | |
|       stateInvalid = true;
 | |
|       channelType.Truncate();
 | |
|     }
 | |
| 
 | |
|     LOG(("OBJLC [%p]: Channel has a content type of %s", this,
 | |
|          channelType.get()));
 | |
| 
 | |
|     bool binaryChannelType = false;
 | |
|     if (channelType.EqualsASCII(APPLICATION_GUESS_FROM_EXT)) {
 | |
|       channelType = APPLICATION_OCTET_STREAM;
 | |
|       mChannel->SetContentType(channelType);
 | |
|       binaryChannelType = true;
 | |
|     } else if (channelType.EqualsASCII(APPLICATION_OCTET_STREAM) ||
 | |
|                channelType.EqualsASCII(BINARY_OCTET_STREAM)) {
 | |
|       binaryChannelType = true;
 | |
|     }
 | |
| 
 | |
|     // Channel can change our URI through redirection
 | |
|     rv = NS_GetFinalChannelURI(mChannel, getter_AddRefs(newURI));
 | |
|     if (NS_FAILED(rv)) {
 | |
|       MOZ_ASSERT_UNREACHABLE("NS_GetFinalChannelURI failure");
 | |
|       stateInvalid = true;
 | |
|     }
 | |
| 
 | |
|     ObjectType typeHint = newMime.IsEmpty()
 | |
|                               ? eType_Null
 | |
|                               : GetTypeOfContent(newMime, mSkipFakePlugins);
 | |
| 
 | |
|     //
 | |
|     // In order of preference:
 | |
|     //
 | |
|     // 1) Use our type hint if it matches a plugin
 | |
|     // 2) If we have eAllowPluginSkipChannel, use the uri file extension if
 | |
|     //    it matches a plugin
 | |
|     // 3) If the channel returns a binary stream type:
 | |
|     //    3a) If we have a type non-null non-document type hint, use that
 | |
|     //    3b) If the uri file extension matches a plugin type, use that
 | |
|     // 4) Use the channel type
 | |
| 
 | |
|     bool overrideChannelType = false;
 | |
|     if (IsPluginType(typeHint)) {
 | |
|       LOG(("OBJLC [%p]: Using plugin type hint in favor of any channel type",
 | |
|            this));
 | |
|       overrideChannelType = true;
 | |
|     } else if ((caps & eAllowPluginSkipChannel) &&
 | |
|                IsPluginEnabledByExtension(newURI, newMime)) {
 | |
|       LOG(
 | |
|           ("OBJLC [%p]: Using extension as type hint for "
 | |
|            "eAllowPluginSkipChannel tag (%s)",
 | |
|            this, newMime.get()));
 | |
|       overrideChannelType = true;
 | |
|     } else if (binaryChannelType && typeHint != eType_Null &&
 | |
|                typeHint != eType_Document) {
 | |
|       LOG(("OBJLC [%p]: Using type hint in favor of binary channel type",
 | |
|            this));
 | |
|       overrideChannelType = true;
 | |
|     } else if (binaryChannelType &&
 | |
|                IsPluginEnabledByExtension(newURI, newMime)) {
 | |
|       LOG(("OBJLC [%p]: Using extension as type hint for binary channel (%s)",
 | |
|            this, newMime.get()));
 | |
|       overrideChannelType = true;
 | |
|     }
 | |
| 
 | |
|     if (overrideChannelType) {
 | |
|       // Set the type we'll use for dispatch on the channel.  Otherwise we could
 | |
|       // end up trying to dispatch to a nsFrameLoader, which will complain that
 | |
|       // it couldn't find a way to handle application/octet-stream
 | |
|       nsAutoCString parsedMime, dummy;
 | |
|       NS_ParseResponseContentType(newMime, parsedMime, dummy);
 | |
|       if (!parsedMime.IsEmpty()) {
 | |
|         mChannel->SetContentType(parsedMime);
 | |
|       }
 | |
|     } else {
 | |
|       newMime = channelType;
 | |
|     }
 | |
|   } else if (newChannel) {
 | |
|     LOG(("OBJLC [%p]: We failed to open a channel, marking invalid", this));
 | |
|     stateInvalid = true;
 | |
|   }
 | |
| 
 | |
|   ///
 | |
|   /// Determine final type
 | |
|   ///
 | |
|   // In order of preference:
 | |
|   //  1) If we have attempted channel load, or set stateInvalid above, the type
 | |
|   //     is always null (fallback)
 | |
|   //  2) If we have a loaded channel, we grabbed its mimeType above, use that
 | |
|   //     type.
 | |
|   //  3) If we have a plugin type and no URI, use that type.
 | |
|   //  4) If we have a plugin type and eAllowPluginSkipChannel, use that type.
 | |
|   //  5) if we have a URI, set type to loading to indicate we'd need a channel
 | |
|   //     to proceed.
 | |
|   //  6) Otherwise, type null to indicate unloadable content (fallback)
 | |
|   //
 | |
| 
 | |
|   ObjectType newMime_Type = GetTypeOfContent(newMime, mSkipFakePlugins);
 | |
| 
 | |
|   if (stateInvalid) {
 | |
|     newType = eType_Null;
 | |
|     newMime.Truncate();
 | |
|   } else if (newChannel) {
 | |
|     // If newChannel is set above, we considered it in setting newMime
 | |
|     newType = newMime_Type;
 | |
|     LOG(("OBJLC [%p]: Using channel type", this));
 | |
|   } else if (((caps & eAllowPluginSkipChannel) || !newURI) &&
 | |
|              IsPluginType(newMime_Type)) {
 | |
|     newType = newMime_Type;
 | |
|     LOG(("OBJLC [%p]: Plugin type with no URI, skipping channel load", this));
 | |
|   } else if (newURI &&
 | |
|              (mOriginalContentType.IsEmpty() || newMime_Type != eType_Null)) {
 | |
|     // We could potentially load this if we opened a channel on mURI, indicate
 | |
|     // this by leaving type as loading.
 | |
|     //
 | |
|     // If a MIME type was requested in the tag, but we have decided to set load
 | |
|     // type to null, ignore (otherwise we'll default to document type loading).
 | |
|     newType = eType_Loading;
 | |
|   } else {
 | |
|     // Unloadable - no URI, and no plugin/MIME type. Non-plugin types (images,
 | |
|     // documents) always load with a channel.
 | |
|     newType = eType_Null;
 | |
|   }
 | |
| 
 | |
|   ///
 | |
|   /// Handle existing channels
 | |
|   ///
 | |
| 
 | |
|   if (useChannel && newType == eType_Loading) {
 | |
|     // We decided to use a channel, and also that the previous channel is still
 | |
|     // usable, so re-use the existing values.
 | |
|     newType = mType;
 | |
|     newMime = mContentType;
 | |
|     newURI = mURI;
 | |
|   } else if (useChannel && !newChannel) {
 | |
|     // We have an existing channel, but did not decide to use one.
 | |
|     retval = (ParameterUpdateFlags)(retval | eParamChannelChanged);
 | |
|     useChannel = false;
 | |
|   }
 | |
| 
 | |
|   ///
 | |
|   /// Update changed values
 | |
|   ///
 | |
| 
 | |
|   if (newType != mType) {
 | |
|     retval = (ParameterUpdateFlags)(retval | eParamStateChanged);
 | |
|     LOG(("OBJLC [%p]: Type changed from %u -> %u", this, mType, newType));
 | |
|     bool updateIMEState = (mType == eType_Loading && newType == eType_Plugin);
 | |
|     mType = newType;
 | |
|     // The IME manager needs to know if this is a plugin so it can adjust
 | |
|     // input handling to an appropriate mode for plugins.
 | |
|     nsFocusManager* fm = nsFocusManager::GetFocusManager();
 | |
|     nsCOMPtr<nsIContent> thisContent =
 | |
|         do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|     MOZ_ASSERT(thisContent, "should have content");
 | |
|     if (updateIMEState && thisContent && fm && fm->IsFocused(thisContent)) {
 | |
|       widget::IMEState state;
 | |
|       state.mEnabled = widget::IMEState::PLUGIN;
 | |
|       state.mOpen = widget::IMEState::DONT_CHANGE_OPEN_STATE;
 | |
|       IMEStateManager::UpdateIMEState(state, thisContent, nullptr);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (!URIEquals(mBaseURI, newBaseURI)) {
 | |
|     LOG(("OBJLC [%p]: Object effective baseURI changed", this));
 | |
|     mBaseURI = newBaseURI;
 | |
|   }
 | |
| 
 | |
|   if (!URIEquals(newURI, mURI)) {
 | |
|     retval = (ParameterUpdateFlags)(retval | eParamStateChanged);
 | |
|     LOG(("OBJLC [%p]: Object effective URI changed", this));
 | |
|     mURI = newURI;
 | |
|   }
 | |
| 
 | |
|   // We don't update content type when loading, as the type is not final and we
 | |
|   // don't want to superfluously change between mOriginalContentType ->
 | |
|   // mContentType when doing |obj.data = obj.data| with a channel and differing
 | |
|   // type.
 | |
|   if (mType != eType_Loading && mContentType != newMime) {
 | |
|     retval = (ParameterUpdateFlags)(retval | eParamStateChanged);
 | |
|     retval = (ParameterUpdateFlags)(retval | eParamContentTypeChanged);
 | |
|     LOG(("OBJLC [%p]: Object effective mime type changed (%s -> %s)", this,
 | |
|          mContentType.get(), newMime.get()));
 | |
|     mContentType = newMime;
 | |
|   }
 | |
| 
 | |
|   // If we decided to keep using info from an old channel, but also that state
 | |
|   // changed, we need to invalidate it.
 | |
|   if (useChannel && !newChannel && (retval & eParamStateChanged)) {
 | |
|     mType = eType_Loading;
 | |
|     retval = (ParameterUpdateFlags)(retval | eParamChannelChanged);
 | |
|   }
 | |
| 
 | |
|   return retval;
 | |
| }
 | |
| 
 | |
| // Used by PluginDocument to kick off our initial load from the already-opened
 | |
| // channel.
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::InitializeFromChannel(nsIRequest* aChannel) {
 | |
|   LOG(("OBJLC [%p] InitializeFromChannel: %p", this, aChannel));
 | |
|   if (mType != eType_Loading || mChannel) {
 | |
|     // We could technically call UnloadObject() here, if consumers have a valid
 | |
|     // reason for wanting to call this on an already-loaded tag.
 | |
|     MOZ_ASSERT_UNREACHABLE("Should not have begun loading at this point");
 | |
|     return NS_ERROR_UNEXPECTED;
 | |
|   }
 | |
| 
 | |
|   // Because we didn't open this channel from an initial LoadObject, we'll
 | |
|   // update our parameters now, so the OnStartRequest->LoadObject doesn't
 | |
|   // believe our src/type suddenly changed.
 | |
|   UpdateObjectParameters();
 | |
|   // But we always want to load from a channel, in this case.
 | |
|   mType = eType_Loading;
 | |
|   mChannel = do_QueryInterface(aChannel);
 | |
|   NS_ASSERTION(mChannel, "passed a request that is not a channel");
 | |
| 
 | |
|   // OnStartRequest will now see we have a channel in the loading state, and
 | |
|   // call into LoadObject. There's a possibility LoadObject will decide not to
 | |
|   // load anything from a channel - it will call CloseChannel() in that case.
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // Only OnStartRequest should be passing the channel parameter
 | |
| nsresult nsObjectLoadingContent::LoadObject(bool aNotify, bool aForceLoad) {
 | |
|   return LoadObject(aNotify, aForceLoad, nullptr);
 | |
| }
 | |
| 
 | |
| nsresult nsObjectLoadingContent::LoadObject(bool aNotify, bool aForceLoad,
 | |
|                                             nsIRequest* aLoadingChannel) {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   NS_ASSERTION(thisContent, "must be a content");
 | |
|   Document* doc = thisContent->OwnerDoc();
 | |
|   nsresult rv = NS_OK;
 | |
| 
 | |
|   // Per bug 1318303, if the parent document is not active, load the alternative
 | |
|   // and return.
 | |
|   if (!doc->IsCurrentActiveDocument()) {
 | |
|     // Since this can be triggered on change of attributes, make sure we've
 | |
|     // unloaded whatever is loaded first.
 | |
|     UnloadObject();
 | |
|     LoadFallback(eFallbackAlternate, false);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // XXX(johns): In these cases, we refuse to touch our content and just
 | |
|   //   remain unloaded, as per legacy behavior. It would make more sense to
 | |
|   //   load fallback content initially and refuse to ever change state again.
 | |
|   if (doc->IsBeingUsedAsImage() || doc->IsLoadedAsData()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   LOG(("OBJLC [%p]: LoadObject called, notify %u, forceload %u, channel %p",
 | |
|        this, aNotify, aForceLoad, aLoadingChannel));
 | |
| 
 | |
|   // We can't re-use an already open channel, but aForceLoad may make us try
 | |
|   // to load a plugin without any changes in channel state.
 | |
|   if (aForceLoad && mChannelLoaded) {
 | |
|     CloseChannel();
 | |
|     mChannelLoaded = false;
 | |
|   }
 | |
| 
 | |
|   // Save these for NotifyStateChanged();
 | |
|   EventStates oldState = ObjectState();
 | |
|   ObjectType oldType = mType;
 | |
| 
 | |
|   ParameterUpdateFlags stateChange = UpdateObjectParameters();
 | |
| 
 | |
|   if (!stateChange && !aForceLoad) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   ///
 | |
|   /// State has changed, unload existing content and attempt to load new type
 | |
|   ///
 | |
|   LOG(("OBJLC [%p]: LoadObject - plugin state changed (%u)", this,
 | |
|        stateChange));
 | |
| 
 | |
|   // Setup fallback info. We may also change type to fallback below in case of
 | |
|   // sanity/OOM/etc. errors. We default to showing alternate content
 | |
|   // NOTE LoadFallback can override this in some cases
 | |
|   FallbackType fallbackType = eFallbackAlternate;
 | |
| 
 | |
|   // If GetTypeOfContent(mContentType) is null we truly have no handler for the
 | |
|   // type -- otherwise, we have a handler but UpdateObjectParameters rejected
 | |
|   // the configuration for another reason (e.g. an embed tag with type
 | |
|   // "image/png" but no URI). Don't show a plugin error or unknown type error in
 | |
|   // the latter case.
 | |
|   if (mType == eType_Null &&
 | |
|       GetTypeOfContent(mContentType, mSkipFakePlugins) == eType_Null) {
 | |
|     fallbackType = eFallbackUnsupported;
 | |
|   }
 | |
| 
 | |
|   // Explicit user activation should reset if the object changes content types
 | |
|   if (mActivated && (stateChange & eParamContentTypeChanged)) {
 | |
|     LOG(("OBJLC [%p]: Content type changed, clearing activation state", this));
 | |
|     mActivated = false;
 | |
|   }
 | |
| 
 | |
|   // We synchronously start/stop plugin instances below, which may spin the
 | |
|   // event loop. Re-entering into the load is fine, but at that point the
 | |
|   // original load call needs to abort when unwinding
 | |
|   // NOTE this is located *after* the state change check, a subsequent load
 | |
|   //      with no subsequently changed state will be a no-op.
 | |
|   if (mIsLoading) {
 | |
|     LOG(("OBJLC [%p]: Re-entering into LoadObject", this));
 | |
|   }
 | |
|   mIsLoading = true;
 | |
|   AutoSetLoadingToFalse reentryCheck(this);
 | |
| 
 | |
|   // Unload existing content, keeping in mind stopping plugins might spin the
 | |
|   // event loop. Note that we check for still-open channels below
 | |
|   UnloadObject(false);  // Don't reset state
 | |
|   if (!mIsLoading) {
 | |
|     // The event loop must've spun and re-entered into LoadObject, which
 | |
|     // finished the load
 | |
|     LOG(("OBJLC [%p]: Re-entered into LoadObject, aborting outer load", this));
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Determine what's going on with our channel.
 | |
|   if (stateChange & eParamChannelChanged) {
 | |
|     // If the channel params changed, throw away the channel, but unset
 | |
|     // mChannelLoaded so we'll still try to open a new one for this load if
 | |
|     // necessary
 | |
|     CloseChannel();
 | |
|     mChannelLoaded = false;
 | |
|   } else if (mType == eType_Null && mChannel) {
 | |
|     // If we opened a channel but then failed to find a loadable state, throw it
 | |
|     // away. mChannelLoaded will indicate that we tried to load a channel at one
 | |
|     // point so we wont recurse
 | |
|     CloseChannel();
 | |
|   } else if (mType == eType_Loading && mChannel) {
 | |
|     // We're still waiting on a channel load, already opened one, and
 | |
|     // channel parameters didn't change
 | |
|     return NS_OK;
 | |
|   } else if (mChannelLoaded && mChannel != aLoadingChannel) {
 | |
|     // The only time we should have a loaded channel with a changed state is
 | |
|     // when the channel has just opened -- in which case this call should
 | |
|     // have originated from OnStartRequest
 | |
|     MOZ_ASSERT_UNREACHABLE(
 | |
|         "Loading with a channel, but state doesn't make sense");
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Security checks
 | |
|   //
 | |
| 
 | |
|   if (mType != eType_Null) {
 | |
|     bool allowLoad = true;
 | |
|     int16_t contentPolicy = nsIContentPolicy::ACCEPT;
 | |
|     // If mChannelLoaded is set we presumably already passed load policy
 | |
|     // If mType == eType_Loading then we call OpenChannel() which internally
 | |
|     // creates a new channel and calls asyncOpen() on that channel which
 | |
|     // then enforces content policy checks.
 | |
|     if (allowLoad && mURI && !mChannelLoaded && mType != eType_Loading) {
 | |
|       allowLoad = CheckLoadPolicy(&contentPolicy);
 | |
|     }
 | |
|     // If we're loading a type now, check ProcessPolicy. Note that we may check
 | |
|     // both now in the case of plugins whose type is determined before opening a
 | |
|     // channel.
 | |
|     if (allowLoad && mType != eType_Loading) {
 | |
|       allowLoad = CheckProcessPolicy(&contentPolicy);
 | |
|     }
 | |
| 
 | |
|     // Content policy implementations can mutate the DOM, check for re-entry
 | |
|     if (!mIsLoading) {
 | |
|       LOG(("OBJLC [%p]: We re-entered in content policy, leaving original load",
 | |
|            this));
 | |
|       return NS_OK;
 | |
|     }
 | |
| 
 | |
|     // Load denied, switch to fallback and set disabled/suppressed if applicable
 | |
|     if (!allowLoad) {
 | |
|       LOG(("OBJLC [%p]: Load denied by policy", this));
 | |
|       mType = eType_Null;
 | |
|       if (contentPolicy == nsIContentPolicy::REJECT_TYPE) {
 | |
|         fallbackType = eFallbackUserDisabled;
 | |
|       } else {
 | |
|         fallbackType = eFallbackSuppressed;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Don't allow view-source scheme.
 | |
|   // view-source is the only scheme to which this applies at the moment due to
 | |
|   // potential timing attacks to read data from cross-origin documents. If this
 | |
|   // widens we should add a protocol flag for whether the scheme is only allowed
 | |
|   // in top and use something like nsNetUtil::NS_URIChainHasFlags.
 | |
|   if (mType != eType_Null) {
 | |
|     nsCOMPtr<nsIURI> tempURI = mURI;
 | |
|     nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(tempURI);
 | |
|     while (nestedURI) {
 | |
|       // view-source should always be an nsINestedURI, loop and check the
 | |
|       // scheme on this and all inner URIs that are also nested URIs.
 | |
|       if (tempURI->SchemeIs("view-source")) {
 | |
|         LOG(("OBJLC [%p]: Blocking as effective URI has view-source scheme",
 | |
|              this));
 | |
|         mType = eType_Null;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       nestedURI->GetInnerURI(getter_AddRefs(tempURI));
 | |
|       nestedURI = do_QueryInterface(tempURI);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Items resolved as Image/Document are no candidates for content blocking,
 | |
|   // as well as invalid plugins (they will not have the mContentType set).
 | |
|   if ((mType == eType_Null || IsPluginType(mType)) && ShouldBlockContent()) {
 | |
|     LOG(("OBJLC [%p]: Enable content blocking", this));
 | |
|     mType = eType_Loading;
 | |
|   }
 | |
| 
 | |
|   // If we're a plugin but shouldn't start yet, load fallback with
 | |
|   // reason click-to-play instead. Items resolved as Image/Document
 | |
|   // will not be checked for previews, as well as invalid plugins
 | |
|   // (they will not have the mContentType set).
 | |
|   FallbackType clickToPlayReason;
 | |
|   if (!mActivated && IsPluginType(mType) && !ShouldPlay(clickToPlayReason)) {
 | |
|     LOG(("OBJLC [%p]: Marking plugin as click-to-play", this));
 | |
|     mType = eType_Null;
 | |
|     fallbackType = clickToPlayReason;
 | |
|   }
 | |
| 
 | |
|   if (!mActivated && IsPluginType(mType)) {
 | |
|     // Object passed ShouldPlay, so it should be considered
 | |
|     // activated until it changes content type
 | |
|     LOG(("OBJLC [%p]: Object implicitly activated", this));
 | |
|     mActivated = true;
 | |
|   }
 | |
| 
 | |
|   // Sanity check: We shouldn't have any loaded resources, pending events, or
 | |
|   // a final listener at this point
 | |
|   if (mFrameLoader || mPendingInstantiateEvent || mInstanceOwner ||
 | |
|       mPendingCheckPluginStopEvent || mFinalListener) {
 | |
|     MOZ_ASSERT_UNREACHABLE("Trying to load new plugin with existing content");
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // More sanity-checking:
 | |
|   // If mChannel is set, mChannelLoaded should be set, and vice-versa
 | |
|   if (mType != eType_Null && !!mChannel != mChannelLoaded) {
 | |
|     MOZ_ASSERT_UNREACHABLE("Trying to load with bad channel state");
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   ///
 | |
|   /// Attempt to load new type
 | |
|   ///
 | |
| 
 | |
|   // Cache the current attributes and parameters.
 | |
|   if (mType == eType_Plugin || mType == eType_Null) {
 | |
|     rv = BuildParametersArray();
 | |
|     NS_ENSURE_SUCCESS(rv, rv);
 | |
|   }
 | |
| 
 | |
|   // We don't set mFinalListener until OnStartRequest has been called, to
 | |
|   // prevent re-entry ugliness with CloseChannel()
 | |
|   nsCOMPtr<nsIStreamListener> finalListener;
 | |
|   // If we decide to synchronously spawn a plugin, we do it after firing
 | |
|   // notifications to avoid re-entry causing notifications to fire out of order.
 | |
|   bool doSpawnPlugin = false;
 | |
|   switch (mType) {
 | |
|     case eType_Image:
 | |
|       if (!mChannel) {
 | |
|         // We have a LoadImage() call, but UpdateObjectParameters requires a
 | |
|         // channel for images, so this is not a valid state.
 | |
|         MOZ_ASSERT_UNREACHABLE("Attempting to load image without a channel?");
 | |
|         rv = NS_ERROR_UNEXPECTED;
 | |
|         break;
 | |
|       }
 | |
|       rv = LoadImageWithChannel(mChannel, getter_AddRefs(finalListener));
 | |
|       // finalListener will receive OnStartRequest below
 | |
|       break;
 | |
|     case eType_Plugin: {
 | |
|       if (mChannel) {
 | |
|         // Force a sync state change now, we need the frame created
 | |
|         NotifyStateChanged(oldType, oldState, true, aNotify);
 | |
|         oldType = mType;
 | |
|         oldState = ObjectState();
 | |
| 
 | |
|         if (!thisContent->GetPrimaryFrame()) {
 | |
|           // We're un-rendered, and can't instantiate a plugin. HasNewFrame will
 | |
|           // re-start us when we can proceed.
 | |
|           LOG(("OBJLC [%p]: Aborting load - plugin-type, but no frame", this));
 | |
|           CloseChannel();
 | |
|           break;
 | |
|         }
 | |
| 
 | |
|         // We'll handle this below
 | |
|         doSpawnPlugin = true;
 | |
|       } else {
 | |
|         rv = AsyncStartPluginInstance();
 | |
|       }
 | |
|     } break;
 | |
|     case eType_Document: {
 | |
|       if (!mChannel) {
 | |
|         // We could mFrameLoader->LoadURI(mURI), but UpdateObjectParameters
 | |
|         // requires documents have a channel, so this is not a valid state.
 | |
|         MOZ_ASSERT_UNREACHABLE(
 | |
|             "Attempting to load a document without a "
 | |
|             "channel");
 | |
|         rv = NS_ERROR_FAILURE;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       nsCOMPtr<nsIDocShell> docShell = SetupDocShell(mURI);
 | |
|       if (!docShell) {
 | |
|         rv = NS_ERROR_FAILURE;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       // We're loading a document, so we have to set LOAD_DOCUMENT_URI
 | |
|       // (especially important for firing onload)
 | |
|       nsLoadFlags flags = 0;
 | |
|       mChannel->GetLoadFlags(&flags);
 | |
|       flags |= nsIChannel::LOAD_DOCUMENT_URI;
 | |
|       mChannel->SetLoadFlags(flags);
 | |
| 
 | |
|       nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(docShell));
 | |
|       NS_ASSERTION(req, "Docshell must be an ifreq");
 | |
| 
 | |
|       nsCOMPtr<nsIURILoader> uriLoader(components::URILoader::Service());
 | |
|       if (NS_WARN_IF(!uriLoader)) {
 | |
|         MOZ_ASSERT_UNREACHABLE("Failed to get uriLoader service");
 | |
|         mFrameLoader->Destroy();
 | |
|         mFrameLoader = nullptr;
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       rv = uriLoader->OpenChannel(mChannel, nsIURILoader::DONT_RETARGET, req,
 | |
|                                   getter_AddRefs(finalListener));
 | |
|       // finalListener will receive OnStartRequest below
 | |
|     } break;
 | |
|     case eType_Loading:
 | |
|       // If our type remains Loading, we need a channel to proceed
 | |
|       rv = OpenChannel();
 | |
|       if (NS_FAILED(rv)) {
 | |
|         LOG(("OBJLC [%p]: OpenChannel returned failure (%" PRIu32 ")", this,
 | |
|              static_cast<uint32_t>(rv)));
 | |
|       }
 | |
|       break;
 | |
|     case eType_Null:
 | |
|       // Handled below, silence compiler warnings
 | |
|       break;
 | |
|     case eType_FakePlugin:
 | |
|       // We're now in the process of removing FakePlugin. See bug 1529133.
 | |
|       MOZ_CRASH(
 | |
|           "Shouldn't reach here! This means there's a fakeplugin trying to be "
 | |
|           "loaded.");
 | |
|   }
 | |
| 
 | |
|   //
 | |
|   // Loaded, handle notifications and fallback
 | |
|   //
 | |
|   if (NS_FAILED(rv)) {
 | |
|     // If we failed in the loading hunk above, switch to fallback
 | |
|     LOG(("OBJLC [%p]: Loading failed, switching to fallback", this));
 | |
|     mType = eType_Null;
 | |
|   }
 | |
| 
 | |
|   // If we didn't load anything, handle switching to fallback state
 | |
|   if (mType == eType_Null) {
 | |
|     LOG(("OBJLC [%p]: Loading fallback, type %u", this, fallbackType));
 | |
|     NS_ASSERTION(!mFrameLoader && !mInstanceOwner,
 | |
|                  "switched to type null but also loaded something");
 | |
| 
 | |
|     // Don't fire error events if we're falling back to click-to-play; instead
 | |
|     // pretend like this is a really slow-loading plug-in instead.
 | |
|     if (fallbackType != eFallbackClickToPlay &&
 | |
|         fallbackType != eFallbackClickToPlayQuiet) {
 | |
|       MaybeFireErrorEvent();
 | |
|     }
 | |
| 
 | |
|     if (mChannel) {
 | |
|       // If we were loading with a channel but then failed over, throw it away
 | |
|       CloseChannel();
 | |
|     }
 | |
| 
 | |
|     // Don't try to initialize plugins or final listener below
 | |
|     doSpawnPlugin = false;
 | |
|     finalListener = nullptr;
 | |
| 
 | |
|     // Don't notify, as LoadFallback doesn't know of our previous state
 | |
|     // (so really this is just setting mFallbackType)
 | |
|     LoadFallback(fallbackType, false);
 | |
|   }
 | |
| 
 | |
|   // Notify of our final state
 | |
|   NotifyStateChanged(oldType, oldState, false, aNotify);
 | |
|   NS_ENSURE_TRUE(mIsLoading, NS_OK);
 | |
| 
 | |
|   //
 | |
|   // Spawning plugins and dispatching to the final listener may re-enter, so are
 | |
|   // delayed until after we fire a notification, to prevent missing
 | |
|   // notifications or firing them out of order.
 | |
|   //
 | |
|   // Note that we ensured that we entered into LoadObject() from
 | |
|   // ::OnStartRequest above when loading with a channel.
 | |
|   //
 | |
| 
 | |
|   rv = NS_OK;
 | |
|   if (doSpawnPlugin) {
 | |
|     rv = InstantiatePluginInstance(true);
 | |
|     NS_ENSURE_TRUE(mIsLoading, NS_OK);
 | |
|     // Create the final listener if we're loading with a channel. We can't do
 | |
|     // this in the loading block above as it requires an instance.
 | |
|     if (aLoadingChannel && NS_SUCCEEDED(rv)) {
 | |
|       if (NS_SUCCEEDED(rv) && MakePluginListener()) {
 | |
|         rv = mFinalListener->OnStartRequest(mChannel);
 | |
|         if (NS_FAILED(rv)) {
 | |
|           // Plugins can reject their initial stream, but continue to run.
 | |
|           CloseChannel();
 | |
|           NS_ENSURE_TRUE(mIsLoading, NS_OK);
 | |
|           rv = NS_OK;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
|   } else if (finalListener) {
 | |
|     NS_ASSERTION(mType != eType_Null && mType != eType_Loading,
 | |
|                  "We should not have a final listener with a non-loaded type");
 | |
|     mFinalListener = finalListener;
 | |
|     rv = finalListener->OnStartRequest(mChannel);
 | |
|   }
 | |
| 
 | |
|   if (NS_FAILED(rv) && mIsLoading) {
 | |
|     // Since we've already notified of our transition, we can just Unload and
 | |
|     // call LoadFallback (which will notify again)
 | |
|     mType = eType_Null;
 | |
|     UnloadObject(false);
 | |
|     NS_ENSURE_TRUE(mIsLoading, NS_OK);
 | |
|     CloseChannel();
 | |
|     LoadFallback(fallbackType, true);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| // This call can re-enter when dealing with plugin listeners
 | |
| nsresult nsObjectLoadingContent::CloseChannel() {
 | |
|   if (mChannel) {
 | |
|     LOG(("OBJLC [%p]: Closing channel\n", this));
 | |
|     // Null the values before potentially-reentering, and ensure they survive
 | |
|     // the call
 | |
|     nsCOMPtr<nsIChannel> channelGrip(mChannel);
 | |
|     nsCOMPtr<nsIStreamListener> listenerGrip(mFinalListener);
 | |
|     mChannel = nullptr;
 | |
|     mFinalListener = nullptr;
 | |
|     channelGrip->Cancel(NS_BINDING_ABORTED);
 | |
|     if (listenerGrip) {
 | |
|       // mFinalListener is only set by LoadObject after OnStartRequest, or
 | |
|       // by OnStartRequest in the case of late-opened plugin streams
 | |
|       listenerGrip->OnStopRequest(channelGrip, NS_BINDING_ABORTED);
 | |
|     }
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsresult nsObjectLoadingContent::OpenChannel() {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   NS_ASSERTION(thisContent, "must be a content");
 | |
|   Document* doc = thisContent->OwnerDoc();
 | |
|   NS_ASSERTION(doc, "No owner document?");
 | |
| 
 | |
|   nsresult rv;
 | |
|   mChannel = nullptr;
 | |
| 
 | |
|   // E.g. mms://
 | |
|   if (!mURI || !CanHandleURI(mURI)) {
 | |
|     return NS_ERROR_NOT_AVAILABLE;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsILoadGroup> group = doc->GetDocumentLoadGroup();
 | |
|   nsCOMPtr<nsIChannel> chan;
 | |
|   RefPtr<ObjectInterfaceRequestorShim> shim =
 | |
|       new ObjectInterfaceRequestorShim(this);
 | |
| 
 | |
|   bool inherit = nsContentUtils::ChannelShouldInheritPrincipal(
 | |
|       thisContent->NodePrincipal(), mURI,
 | |
|       true,    // aInheritForAboutBlank
 | |
|       false);  // aForceInherit
 | |
|   nsSecurityFlags securityFlags =
 | |
|       nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
 | |
| 
 | |
|   bool isURIUniqueOrigin =
 | |
|       StaticPrefs::security_data_uri_unique_opaque_origin() &&
 | |
|       mURI->SchemeIs("data");
 | |
| 
 | |
|   if (inherit && !isURIUniqueOrigin) {
 | |
|     securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
 | |
|   }
 | |
| 
 | |
|   nsContentPolicyType contentPolicyType = GetContentPolicyType();
 | |
| 
 | |
|   rv = NS_NewChannel(getter_AddRefs(chan), mURI, thisContent, securityFlags,
 | |
|                      contentPolicyType,
 | |
|                      nullptr,  // aPerformanceStorage
 | |
|                      group,    // aLoadGroup
 | |
|                      shim,     // aCallbacks
 | |
|                      nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
 | |
|                          nsIChannel::LOAD_BYPASS_SERVICE_WORKER |
 | |
|                          nsIRequest::LOAD_HTML_OBJECT_DATA,
 | |
|                      nullptr,  // aIoService
 | |
|                      doc->GetSandboxFlags());
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   if (inherit) {
 | |
|     nsCOMPtr<nsILoadInfo> loadinfo = chan->LoadInfo();
 | |
|     loadinfo->SetPrincipalToInherit(thisContent->NodePrincipal());
 | |
|   }
 | |
| 
 | |
|   // For object loads we store the CSP that potentially needs to
 | |
|   // be inherited, e.g. in case we are loading an opaque origin
 | |
|   // like a data: URI. The actual inheritance check happens within
 | |
|   // Document::InitCSP(). Please create an actual copy of the CSP
 | |
|   // (do not share the same reference) otherwise a Meta CSP of an
 | |
|   // opaque origin will incorrectly be propagated to the embedding
 | |
|   // document.
 | |
|   nsCOMPtr<nsIContentSecurityPolicy> csp = doc->GetCsp();
 | |
|   if (csp) {
 | |
|     RefPtr<nsCSPContext> cspToInherit = new nsCSPContext();
 | |
|     cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
 | |
|     nsCOMPtr<nsILoadInfo> loadinfo = chan->LoadInfo();
 | |
|     static_cast<LoadInfo*>(loadinfo.get())->SetCSPToInherit(cspToInherit);
 | |
|   }
 | |
| 
 | |
|   // Referrer
 | |
|   nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan));
 | |
|   if (httpChan) {
 | |
|     auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
 | |
| 
 | |
|     rv = httpChan->SetReferrerInfoWithoutClone(referrerInfo);
 | |
|     MOZ_ASSERT(NS_SUCCEEDED(rv));
 | |
| 
 | |
|     // Set the initiator type
 | |
|     nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChan));
 | |
|     if (timedChannel) {
 | |
|       timedChannel->SetInitiatorType(thisContent->LocalName());
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(httpChan));
 | |
|     if (cos && UserActivation::IsHandlingUserInput()) {
 | |
|       cos->AddClassFlags(nsIClassOfService::UrgentStart);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(chan);
 | |
|   if (scriptChannel) {
 | |
|     // Allow execution against our context if the principals match
 | |
|     scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
 | |
|   }
 | |
| 
 | |
|   // AsyncOpen can fail if a file does not exist.
 | |
|   rv = chan->AsyncOpen(shim);
 | |
|   NS_ENSURE_SUCCESS(rv, rv);
 | |
|   LOG(("OBJLC [%p]: Channel opened", this));
 | |
|   mChannel = chan;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| uint32_t nsObjectLoadingContent::GetCapabilities() const {
 | |
|   return eSupportImages | eSupportPlugins | eSupportDocuments;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::DestroyContent() {
 | |
|   if (mFrameLoader) {
 | |
|     mFrameLoader->Destroy();
 | |
|     mFrameLoader = nullptr;
 | |
|   }
 | |
| 
 | |
|   if (mInstanceOwner || mInstantiating) {
 | |
|     QueueCheckPluginStopEvent();
 | |
|   }
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| void nsObjectLoadingContent::Traverse(nsObjectLoadingContent* tmp,
 | |
|                                       nsCycleCollectionTraversalCallback& cb) {
 | |
|   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader);
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::UnloadObject(bool aResetState) {
 | |
|   // Don't notify in CancelImageRequests until we transition to a new loaded
 | |
|   // state
 | |
|   CancelImageRequests(false);
 | |
|   if (mFrameLoader) {
 | |
|     mFrameLoader->Destroy();
 | |
|     mFrameLoader = nullptr;
 | |
|   }
 | |
| 
 | |
|   if (aResetState) {
 | |
|     if (mType != eType_Plugin) {
 | |
|       // This can re-enter when dealing with plugins, and StopPluginInstance
 | |
|       // will handle it
 | |
|       CloseChannel();
 | |
|     }
 | |
|     mChannelLoaded = false;
 | |
|     mType = eType_Loading;
 | |
|     mURI = mOriginalURI = mBaseURI = nullptr;
 | |
|     mContentType.Truncate();
 | |
|     mOriginalContentType.Truncate();
 | |
|   }
 | |
| 
 | |
|   // InstantiatePluginInstance checks this after re-entrant calls and aborts if
 | |
|   // it was cleared from under it
 | |
|   mInstantiating = false;
 | |
| 
 | |
|   mScriptRequested = false;
 | |
| 
 | |
|   if (mIsStopping) {
 | |
|     // The protochain is normally thrown out after a plugin stops, but if we
 | |
|     // re-enter while stopping a plugin and try to load something new, we need
 | |
|     // to throw away the old protochain in the nested unload.
 | |
|     TeardownProtoChain();
 | |
|     mIsStopping = false;
 | |
|   }
 | |
| 
 | |
|   mCachedAttributes.Clear();
 | |
|   mCachedParameters.Clear();
 | |
| 
 | |
|   // This call should be last as it may re-enter
 | |
|   StopPluginInstance();
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::NotifyStateChanged(ObjectType aOldType,
 | |
|                                                 EventStates aOldState,
 | |
|                                                 bool aSync, bool aNotify) {
 | |
|   LOG(("OBJLC [%p]: Notifying about state change: (%u, %" PRIx64
 | |
|        ") -> (%u, %" PRIx64 ")"
 | |
|        " (sync %i, notify %i)",
 | |
|        this, aOldType, aOldState.GetInternalValue(), mType,
 | |
|        ObjectState().GetInternalValue(), aSync, aNotify));
 | |
| 
 | |
|   nsCOMPtr<dom::Element> thisEl =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   MOZ_ASSERT(thisEl, "must be an element");
 | |
| 
 | |
|   // XXX(johns): A good bit of the code below replicates UpdateState(true)
 | |
| 
 | |
|   // Unfortunately, we do some state changes without notifying
 | |
|   // (e.g. in Fallback when canceling image requests), so we have to
 | |
|   // manually notify object state changes.
 | |
|   thisEl->UpdateState(false);
 | |
| 
 | |
|   if (!aNotify) {
 | |
|     // We're done here
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   Document* doc = thisEl->GetComposedDoc();
 | |
|   if (!doc) {
 | |
|     return;  // Nothing to do
 | |
|   }
 | |
| 
 | |
|   EventStates newState = ObjectState();
 | |
| 
 | |
|   if (newState == aOldState && mType == aOldType) {
 | |
|     return;  // Also done.
 | |
|   }
 | |
| 
 | |
|   if (newState != aOldState) {
 | |
|     MOZ_ASSERT(thisEl->IsInComposedDoc(), "Something is confused");
 | |
|     // This will trigger frame construction
 | |
|     EventStates changedBits = aOldState ^ newState;
 | |
|     {
 | |
|       nsAutoScriptBlocker scriptBlocker;
 | |
|       doc->ContentStateChanged(thisEl, changedBits);
 | |
|     }
 | |
| 
 | |
|     // Create/destroy plugin problem UAWidget.
 | |
|     const EventStates pluginProblemState = NS_EVENT_STATE_HANDLER_BLOCKED |
 | |
|                                            NS_EVENT_STATE_HANDLER_CRASHED |
 | |
|                                            NS_EVENT_STATE_TYPE_CLICK_TO_PLAY |
 | |
|                                            NS_EVENT_STATE_VULNERABLE_UPDATABLE |
 | |
|                                            NS_EVENT_STATE_VULNERABLE_NO_UPDATE;
 | |
| 
 | |
|     bool hadProblemState = !(aOldState & pluginProblemState).IsEmpty();
 | |
|     bool hasProblemState = !(newState & pluginProblemState).IsEmpty();
 | |
| 
 | |
|     if (hadProblemState && !hasProblemState) {
 | |
|       thisEl->NotifyUAWidgetTeardown();
 | |
|     } else if (!hadProblemState && hasProblemState) {
 | |
|       thisEl->AttachAndSetUAShadowRoot();
 | |
|       thisEl->NotifyUAWidgetSetupOrChange();
 | |
|     }
 | |
|   } else if (aOldType != mType) {
 | |
|     // If our state changed, then we already recreated frames
 | |
|     // Otherwise, need to do that here
 | |
|     RefPtr<PresShell> presShell = doc->GetPresShell();
 | |
|     if (presShell) {
 | |
|       presShell->PostRecreateFramesFor(thisEl);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   if (aSync) {
 | |
|     MOZ_ASSERT(InActiveDocument(thisEl), "Something is confused");
 | |
|     // Make sure that frames are actually constructed immediately.
 | |
|     doc->FlushPendingNotifications(FlushType::Frames);
 | |
|   }
 | |
| }
 | |
| 
 | |
| nsObjectLoadingContent::ObjectType nsObjectLoadingContent::GetTypeOfContent(
 | |
|     const nsCString& aMIMEType, bool aNoFakePlugin) {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   NS_ASSERTION(thisContent, "must be a content");
 | |
| 
 | |
|   ObjectType type =
 | |
|       static_cast<ObjectType>(nsContentUtils::HtmlObjectContentTypeForMIMEType(
 | |
|           aMIMEType, aNoFakePlugin, thisContent));
 | |
| 
 | |
|   // Switch the result type to eType_Null ic the capability is not present.
 | |
|   uint32_t caps = GetCapabilities();
 | |
|   if (!(caps & eSupportImages) && type == eType_Image) {
 | |
|     type = eType_Null;
 | |
|   }
 | |
|   if (!(caps & eSupportDocuments) && type == eType_Document) {
 | |
|     type = eType_Null;
 | |
|   }
 | |
|   if (!(caps & eSupportPlugins) &&
 | |
|       (type == eType_Plugin || type == eType_FakePlugin)) {
 | |
|     type = eType_Null;
 | |
|   }
 | |
| 
 | |
|   return type;
 | |
| }
 | |
| 
 | |
| nsPluginFrame* nsObjectLoadingContent::GetExistingFrame() {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   nsIFrame* frame = thisContent->GetPrimaryFrame();
 | |
|   nsIObjectFrame* objFrame = do_QueryFrame(frame);
 | |
|   return static_cast<nsPluginFrame*>(objFrame);
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::CreateStaticClone(
 | |
|     nsObjectLoadingContent* aDest) const {
 | |
|   nsImageLoadingContent::CreateStaticImageClone(aDest);
 | |
| 
 | |
|   aDest->mType = mType;
 | |
|   nsObjectLoadingContent* thisObj = const_cast<nsObjectLoadingContent*>(this);
 | |
|   if (thisObj->mPrintFrame.IsAlive()) {
 | |
|     aDest->mPrintFrame = thisObj->mPrintFrame;
 | |
|   } else {
 | |
|     aDest->mPrintFrame = thisObj->GetExistingFrame();
 | |
|   }
 | |
| 
 | |
|   if (mFrameLoader) {
 | |
|     nsCOMPtr<nsIContent> content =
 | |
|         do_QueryInterface(static_cast<nsIImageLoadingContent*>(aDest));
 | |
|     Document* doc = content->OwnerDoc();
 | |
|     if (doc->IsStaticDocument()) {
 | |
|       doc->AddPendingFrameStaticClone(aDest, mFrameLoader);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::GetPrintFrame(nsIFrame** aFrame) {
 | |
|   *aFrame = mPrintFrame.GetFrame();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::PluginDestroyed() {
 | |
|   // Called when our plugin is destroyed from under us, usually when reloading
 | |
|   // plugins in plugin host. Invalidate instance owner / prototype but otherwise
 | |
|   // don't take any action.
 | |
|   TeardownProtoChain();
 | |
|   if (mInstanceOwner) {
 | |
|     mInstanceOwner->Destroy();
 | |
|     mInstanceOwner = nullptr;
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::PluginCrashed(nsIPluginTag* aPluginTag,
 | |
|                                       const nsAString& pluginDumpID,
 | |
|                                       bool submittedCrashReport) {
 | |
|   LOG(("OBJLC [%p]: Plugin Crashed, queuing crash event", this));
 | |
|   NS_ASSERTION(mType == eType_Plugin, "PluginCrashed at non-plugin type");
 | |
| 
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
| 
 | |
| #ifdef XP_MACOSX
 | |
|   HTMLObjectElement::HandlePluginCrashed(thisContent->AsElement());
 | |
| #endif
 | |
| 
 | |
|   PluginDestroyed();
 | |
| 
 | |
|   // Switch to fallback/crashed state, notify
 | |
|   LoadFallback(eFallbackCrashed, true);
 | |
| 
 | |
|   // send nsPluginCrashedEvent
 | |
| 
 | |
|   // Note that aPluginTag in invalidated after we're called, so copy
 | |
|   // out any data we need now.
 | |
|   nsAutoCString pluginName;
 | |
|   aPluginTag->GetName(pluginName);
 | |
|   nsAutoCString pluginFilename;
 | |
|   aPluginTag->GetFilename(pluginFilename);
 | |
| 
 | |
|   nsCOMPtr<nsIRunnable> ev = new nsPluginCrashedEvent(
 | |
|       thisContent, pluginDumpID, NS_ConvertUTF8toUTF16(pluginName),
 | |
|       NS_ConvertUTF8toUTF16(pluginFilename), submittedCrashReport);
 | |
|   nsresult rv = NS_DispatchToCurrentThread(ev);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     NS_WARNING("failed to dispatch nsPluginCrashedEvent");
 | |
|   }
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| nsNPAPIPluginInstance* nsObjectLoadingContent::ScriptRequestPluginInstance(
 | |
|     JSContext* aCx) {
 | |
|   // The below methods pull the cx off the stack, so make sure they match.
 | |
|   //
 | |
|   // NB: Sometimes there's a null cx on the stack, in which case |cx| is the
 | |
|   // safe JS context. But in that case, IsCallerChrome() will return true,
 | |
|   // so the ensuing expression is short-circuited.
 | |
|   // XXXbz the NB comment above doesn't really make sense.  At the moment, all
 | |
|   // the callers to this except maybe SetupProtoChain have a useful JSContext*
 | |
|   // that could be used for nsContentUtils::IsSystemCaller...  We do need to
 | |
|   // sort out what the SetupProtoChain callers look like.
 | |
|   MOZ_ASSERT_IF(nsContentUtils::GetCurrentJSContext(),
 | |
|                 aCx == nsContentUtils::GetCurrentJSContext());
 | |
|   // FIXME(emilio): Doesn't account for UA widgets, but probably doesn't matter?
 | |
|   bool callerIsContentJS = (nsContentUtils::GetCurrentJSContext() &&
 | |
|                             !nsContentUtils::IsCallerChrome());
 | |
| 
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
| 
 | |
|   // The first time content script attempts to access placeholder content, fire
 | |
|   // an event.  Fallback types >= eFallbackClickToPlay are plugin-replacement
 | |
|   // types, see header.
 | |
|   if (callerIsContentJS && !mScriptRequested && InActiveDocument(thisContent) &&
 | |
|       mType == eType_Null && mFallbackType >= eFallbackClickToPlay) {
 | |
|     nsCOMPtr<nsIRunnable> ev =
 | |
|         new nsSimplePluginEvent(thisContent, u"PluginScripted"_ns);
 | |
|     nsresult rv = NS_DispatchToCurrentThread(ev);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       MOZ_ASSERT_UNREACHABLE("failed to dispatch PluginScripted event");
 | |
|     }
 | |
|     mScriptRequested = true;
 | |
|   } else if (callerIsContentJS && mType == eType_Plugin && !mInstanceOwner &&
 | |
|              nsContentUtils::IsSafeToRunScript() &&
 | |
|              InActiveDocument(thisContent)) {
 | |
|     // If we're configured as a plugin in an active document and it's safe to
 | |
|     // run scripts right now, try spawning synchronously
 | |
|     SyncStartPluginInstance();
 | |
|   }
 | |
| 
 | |
|   if (mInstanceOwner) {
 | |
|     return mInstanceOwner->GetInstance();
 | |
|   }
 | |
| 
 | |
|   // Note that returning a null plugin is expected (and happens often)
 | |
|   return nullptr;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::SyncStartPluginInstance() {
 | |
|   NS_ASSERTION(
 | |
|       nsContentUtils::IsSafeToRunScript(),
 | |
|       "Must be able to run script in order to instantiate a plugin instance!");
 | |
| 
 | |
|   // Don't even attempt to start an instance unless the content is in
 | |
|   // the document and active
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   if (!InActiveDocument(thisContent)) {
 | |
|     return NS_ERROR_FAILURE;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIURI> kungFuURIGrip(mURI);
 | |
|   mozilla::Unused
 | |
|       << kungFuURIGrip;  // This URI is not referred to within this function
 | |
|   nsCString contentType(mContentType);
 | |
|   return InstantiatePluginInstance();
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::AsyncStartPluginInstance() {
 | |
|   // OK to have an instance already or a pending spawn.
 | |
|   if (mInstanceOwner || mPendingInstantiateEvent) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   Document* doc = thisContent->OwnerDoc();
 | |
|   if (doc->IsStaticDocument() || doc->IsBeingUsedAsImage()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   nsCOMPtr<nsIRunnable> event = new nsAsyncInstantiateEvent(this);
 | |
|   nsresult rv = NS_DispatchToCurrentThread(event);
 | |
|   if (NS_SUCCEEDED(rv)) {
 | |
|     // Track pending events
 | |
|     mPendingInstantiateEvent = event;
 | |
|   }
 | |
| 
 | |
|   return rv;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::GetSrcURI(nsIURI** aURI) {
 | |
|   NS_IF_ADDREF(*aURI = GetSrcURI());
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::LoadFallback(FallbackType aType, bool aNotify) {
 | |
|   EventStates oldState = ObjectState();
 | |
|   ObjectType oldType = mType;
 | |
| 
 | |
|   NS_ASSERTION(!mInstanceOwner && !mFrameLoader && !mChannel,
 | |
|                "LoadFallback called with loaded content");
 | |
| 
 | |
|   //
 | |
|   // Fixup mFallbackType
 | |
|   //
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   NS_ASSERTION(thisContent, "must be a content");
 | |
| 
 | |
|   if (!thisContent->IsHTMLElement() || mContentType.IsEmpty()) {
 | |
|     // Don't let custom fallback handlers run outside HTML, tags without a
 | |
|     // determined type should always just be alternate content
 | |
|     aType = eFallbackAlternate;
 | |
|   }
 | |
| 
 | |
|   // We'll set this to null no matter what now, doing it here means we'll load
 | |
|   // child embeds as we find them in the upcoming loop.
 | |
|   mType = eType_Null;
 | |
| 
 | |
|   bool thisIsObject = thisContent->IsHTMLElement(nsGkAtoms::object);
 | |
| 
 | |
|   // Do a depth-first traverse of node tree with the current element as root,
 | |
|   // looking for <embed> or <object> elements that might now need to load.
 | |
|   nsTArray<nsINodeList*> childNodes;
 | |
|   if (thisContent->IsHTMLElement(nsGkAtoms::object) &&
 | |
|       (aType == eFallbackUnsupported || aType == eFallbackDisabled ||
 | |
|        aType == eFallbackBlocklisted || aType == eFallbackAlternate)) {
 | |
|     for (nsIContent* child = thisContent->GetFirstChild(); child;) {
 | |
|       // When we advance to our next child, we don't want to traverse subtrees
 | |
|       // under descendant <object> and <embed> elements; those will handle
 | |
|       // those subtrees themselves if they end up falling back.
 | |
|       bool skipChildDescendants = false;
 | |
|       if (aType != eFallbackAlternate &&
 | |
|           !child->IsHTMLElement(nsGkAtoms::param) &&
 | |
|           nsStyleUtil::IsSignificantChild(child, false)) {
 | |
|         aType = eFallbackAlternate;
 | |
|       }
 | |
|       if (thisIsObject) {
 | |
|         if (auto embed = HTMLEmbedElement::FromNode(child)) {
 | |
|           embed->StartObjectLoad(true, true);
 | |
|           skipChildDescendants = true;
 | |
|         } else if (auto object = HTMLObjectElement::FromNode(child)) {
 | |
|           object->StartObjectLoad(true, true);
 | |
|           skipChildDescendants = true;
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       if (skipChildDescendants) {
 | |
|         child = child->GetNextNonChildNode(thisContent);
 | |
|       } else {
 | |
|         child = child->GetNextNode(thisContent);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   mFallbackType = aType;
 | |
| 
 | |
|   // Notify
 | |
|   if (!aNotify) {
 | |
|     return;  // done
 | |
|   }
 | |
| 
 | |
|   NotifyStateChanged(oldType, oldState, false, true);
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::DoStopPlugin(
 | |
|     nsPluginInstanceOwner* aInstanceOwner) {
 | |
|   // DoStopPlugin can process events -- There may be pending
 | |
|   // CheckPluginStopEvent events which can drop in underneath us and destroy the
 | |
|   // instance we are about to destroy. We prevent that with the mIsStopping
 | |
|   // flag.
 | |
|   if (mIsStopping) {
 | |
|     return;
 | |
|   }
 | |
|   mIsStopping = true;
 | |
| 
 | |
|   RefPtr<nsPluginInstanceOwner> kungFuDeathGrip(aInstanceOwner);
 | |
|   if (mType == eType_FakePlugin) {
 | |
|     if (mFrameLoader) {
 | |
|       mFrameLoader->Destroy();
 | |
|       mFrameLoader = nullptr;
 | |
|     }
 | |
|   } else {
 | |
|     RefPtr<nsNPAPIPluginInstance> inst = aInstanceOwner->GetInstance();
 | |
|     if (inst) {
 | |
| #if defined(XP_MACOSX)
 | |
|       aInstanceOwner->HidePluginWindow();
 | |
| #endif
 | |
| 
 | |
|       RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
 | |
|       NS_ASSERTION(pluginHost, "No plugin host?");
 | |
|       pluginHost->StopPluginInstance(inst);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   aInstanceOwner->Destroy();
 | |
| 
 | |
|   // If we re-enter in plugin teardown UnloadObject will tear down the
 | |
|   // protochain -- the current protochain could be from a new, unrelated, load.
 | |
|   if (!mIsStopping) {
 | |
|     LOG(("OBJLC [%p]: Re-entered in plugin teardown", this));
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   TeardownProtoChain();
 | |
|   mIsStopping = false;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::StopPluginInstance() {
 | |
|   AUTO_PROFILER_LABEL("nsObjectLoadingContent::StopPluginInstance", OTHER);
 | |
|   // Clear any pending events
 | |
|   mPendingInstantiateEvent = nullptr;
 | |
|   mPendingCheckPluginStopEvent = nullptr;
 | |
| 
 | |
|   // If we're currently instantiating, clearing this will cause
 | |
|   // InstantiatePluginInstance's re-entrance check to destroy the created plugin
 | |
|   mInstantiating = false;
 | |
| 
 | |
|   if (!mInstanceOwner) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (mChannel) {
 | |
|     // The plugin has already used data from this channel, we'll need to
 | |
|     // re-open it to handle instantiating again, even if we don't invalidate
 | |
|     // our loaded state.
 | |
|     /// XXX(johns): Except currently, we don't, just leaving re-opening channels
 | |
|     ///             to plugins...
 | |
|     LOG(("OBJLC [%p]: StopPluginInstance - Closing used channel", this));
 | |
|     CloseChannel();
 | |
|   }
 | |
| 
 | |
|   // We detach the instance owner's frame before destruction, but don't destroy
 | |
|   // the instance owner until the plugin is stopped.
 | |
|   mInstanceOwner->SetFrame(nullptr);
 | |
| 
 | |
|   RefPtr<nsPluginInstanceOwner> ownerGrip(mInstanceOwner);
 | |
|   mInstanceOwner = nullptr;
 | |
| 
 | |
|   // This can/will re-enter
 | |
|   DoStopPlugin(ownerGrip);
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::NotifyContentObjectWrapper() {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
| 
 | |
|   AutoJSAPI jsapi;
 | |
|   jsapi.Init();
 | |
|   JSContext* cx = jsapi.cx();
 | |
| 
 | |
|   JS::Rooted<JSObject*> obj(cx, thisContent->GetWrapper());
 | |
|   if (!obj) {
 | |
|     // Nothing to do here if there's no wrapper for mContent. The proto
 | |
|     // chain will be fixed appropriately when the wrapper is created.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   SetupProtoChain(cx, obj);
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::PlayPlugin(SystemCallerGuarantee,
 | |
|                                         ErrorResult& aRv) {
 | |
|   // This is a ChromeOnly method, so no need to check caller type here.
 | |
|   if (!mActivated) {
 | |
|     mActivated = true;
 | |
|     LOG(("OBJLC [%p]: Activated by user", this));
 | |
|   }
 | |
| 
 | |
|   // If we're in a click-to-play state, reload.
 | |
|   // Fallback types >= eFallbackClickToPlay are plugin-replacement types, see
 | |
|   // header
 | |
|   if (mType == eType_Null && mFallbackType >= eFallbackClickToPlay) {
 | |
|     aRv = LoadObject(true, true);
 | |
|   }
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::Reload(bool aClearActivation) {
 | |
|   if (aClearActivation) {
 | |
|     mActivated = false;
 | |
|     mSkipFakePlugins = false;
 | |
|   }
 | |
| 
 | |
|   return LoadObject(true, true);
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::GetActivated(bool* aActivated) {
 | |
|   *aActivated = Activated();
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| uint32_t nsObjectLoadingContent::DefaultFallbackType() {
 | |
|   FallbackType reason;
 | |
|   if (ShouldPlay(reason)) {
 | |
|     return PLUGIN_ACTIVE;
 | |
|   }
 | |
|   return reason;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::SkipFakePlugins() {
 | |
|   if (!nsContentUtils::IsCallerChrome()) return NS_ERROR_NOT_AVAILABLE;
 | |
| 
 | |
|   mSkipFakePlugins = true;
 | |
| 
 | |
|   // If we're showing a fake plugin now, reload
 | |
|   if (mType == eType_FakePlugin) {
 | |
|     return LoadObject(true, true);
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| uint32_t nsObjectLoadingContent::GetRunID(SystemCallerGuarantee,
 | |
|                                           ErrorResult& aRv) {
 | |
|   if (!mHasRunID) {
 | |
|     // The plugin instance must not have a run ID, so we must
 | |
|     // be running the plugin in-process.
 | |
|     aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 | |
|     return 0;
 | |
|   }
 | |
|   return mRunID;
 | |
| }
 | |
| 
 | |
| bool nsObjectLoadingContent::ShouldBlockContent() {
 | |
|   if (mContentBlockingEnabled && mURI && IsFlashMIME(mContentType) &&
 | |
|       StaticPrefs::browser_safebrowsing_blockedURIs_enabled()) {
 | |
|     return true;
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool nsObjectLoadingContent::ShouldPlay(FallbackType& aReason) {
 | |
|   nsresult rv;
 | |
| 
 | |
|   if (BrowserTabsRemoteAutostart() && XRE_IsParentProcess()) {
 | |
|     // We no longer support loading plugins in the parent process.
 | |
|     aReason = eFallbackDisabled;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
 | |
| 
 | |
|   // Order of checks:
 | |
|   // * Assume a default of click-to-play
 | |
|   // * If globally disabled, per-site permissions cannot override.
 | |
|   // * If blocklisted, override the reason with the blocklist reason
 | |
|   // * Check if the flash blocking status for this page denies flash from
 | |
|   // loading.
 | |
|   // * Check per-site permissions and follow those if specified.
 | |
|   // * Honor per-plugin disabled permission
 | |
|   // * Blocklisted plugins are forced to CtP
 | |
|   // * Check per-plugin permission and follow that.
 | |
| 
 | |
|   aReason = eFallbackClickToPlay;
 | |
| 
 | |
|   uint32_t enabledState = nsIPluginTag::STATE_DISABLED;
 | |
|   pluginHost->GetStateForType(mContentType, nsPluginHost::eExcludeNone,
 | |
|                               &enabledState);
 | |
|   if (nsIPluginTag::STATE_DISABLED == enabledState) {
 | |
|     aReason = eFallbackDisabled;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Before we check permissions, get the blocklist state of this plugin to set
 | |
|   // the fallback reason correctly. In the content process this will involve
 | |
|   // an ipc call to chrome.
 | |
|   uint32_t blocklistState = nsIBlocklistService::STATE_BLOCKED;
 | |
|   pluginHost->GetBlocklistStateForType(mContentType, nsPluginHost::eExcludeNone,
 | |
|                                        &blocklistState);
 | |
|   if (blocklistState == nsIBlocklistService::STATE_BLOCKED) {
 | |
|     // no override possible
 | |
|     aReason = eFallbackBlocklisted;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (blocklistState ==
 | |
|       nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE) {
 | |
|     aReason = eFallbackVulnerableUpdatable;
 | |
|   } else if (blocklistState ==
 | |
|              nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
 | |
|     aReason = eFallbackVulnerableNoUpdate;
 | |
|   }
 | |
| 
 | |
|   // Document and window lookup
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
 | |
|   MOZ_ASSERT(thisContent);
 | |
|   Document* ownerDoc = thisContent->OwnerDoc();
 | |
| 
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> window = ownerDoc->GetWindow();
 | |
|   if (!window) {
 | |
|     return false;
 | |
|   }
 | |
|   nsCOMPtr<nsPIDOMWindowOuter> topWindow = window->GetInProcessTop();
 | |
|   NS_ENSURE_TRUE(topWindow, false);
 | |
|   nsCOMPtr<Document> topDoc = topWindow->GetDoc();
 | |
|   NS_ENSURE_TRUE(topDoc, false);
 | |
| 
 | |
|   // Check the flash blocking status for this page (this applies to Flash only)
 | |
|   FlashClassification documentClassification = FlashClassification::Unknown;
 | |
|   if (IsFlashMIME(mContentType)) {
 | |
|     documentClassification = ownerDoc->DocumentFlashClassification();
 | |
|   }
 | |
|   if (documentClassification == FlashClassification::Denied) {
 | |
|     aReason = eFallbackSuppressed;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // Check the permission manager for permission based on the principal of
 | |
|   // the toplevel content.
 | |
|   nsCOMPtr<nsIPermissionManager> permissionManager =
 | |
|       services::GetPermissionManager();
 | |
|   NS_ENSURE_TRUE(permissionManager, false);
 | |
| 
 | |
|   // For now we always say that the system principal uses click-to-play since
 | |
|   // that maintains current behavior and we have tests that expect this.  What
 | |
|   // we really should do is disable plugins entirely in pages that use the
 | |
|   // system principal, i.e. in chrome pages. That way the click-to-play code
 | |
|   // here wouldn't matter at all. Bug 775301 is tracking this.
 | |
|   if (!topDoc->NodePrincipal()->IsSystemPrincipal()) {
 | |
|     nsAutoCString permissionString;
 | |
|     rv = pluginHost->GetPermissionStringForType(
 | |
|         mContentType, nsPluginHost::eExcludeNone, permissionString);
 | |
|     NS_ENSURE_SUCCESS(rv, false);
 | |
|     uint32_t permission;
 | |
|     rv = permissionManager->TestPermissionFromPrincipal(
 | |
|         topDoc->NodePrincipal(), permissionString, &permission);
 | |
|     NS_ENSURE_SUCCESS(rv, false);
 | |
|     switch (permission) {
 | |
|       case nsIPermissionManager::ALLOW_ACTION:
 | |
|         if (PreferFallback(false /* isPluginClickToPlay */)) {
 | |
|           aReason = eFallbackAlternate;
 | |
|           return false;
 | |
|         }
 | |
| 
 | |
|         return true;
 | |
|       case nsIPermissionManager::DENY_ACTION:
 | |
|         aReason = eFallbackDisabled;
 | |
|         return false;
 | |
|       case PLUGIN_PERMISSION_PROMPT_ACTION_QUIET:
 | |
|         if (PreferFallback(true /* isPluginClickToPlay */)) {
 | |
|           aReason = eFallbackAlternate;
 | |
|         } else {
 | |
|           aReason = eFallbackClickToPlayQuiet;
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|       case nsIPermissionManager::PROMPT_ACTION:
 | |
|         if (PreferFallback(true /* isPluginClickToPlay */)) {
 | |
|           // False is already returned in this case, but
 | |
|           // it's important to correctly set aReason too.
 | |
|           aReason = eFallbackAlternate;
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|       case nsIPermissionManager::UNKNOWN_ACTION:
 | |
|         break;
 | |
|       default:
 | |
|         MOZ_ASSERT(false);
 | |
|         return false;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // No site-specific permissions. Vulnerable plugins are automatically CtP
 | |
|   if (blocklistState ==
 | |
|           nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE ||
 | |
|       blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   if (PreferFallback(enabledState == nsIPluginTag::STATE_CLICKTOPLAY)) {
 | |
|     aReason = eFallbackAlternate;
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   // On the following switch we don't need to handle the case where
 | |
|   // documentClassification is FlashClassification::Denied because
 | |
|   // that's already handled above.
 | |
|   switch (enabledState) {
 | |
|     case nsIPluginTag::STATE_ENABLED:
 | |
|       return true;
 | |
|     case nsIPluginTag::STATE_CLICKTOPLAY:
 | |
|       if (documentClassification == FlashClassification::Allowed) {
 | |
|         return true;
 | |
|       }
 | |
|       return false;
 | |
|   }
 | |
|   MOZ_CRASH("Unexpected enabledState");
 | |
| }
 | |
| 
 | |
| bool nsObjectLoadingContent::FavorFallbackMode(bool aIsPluginClickToPlay) {
 | |
|   if (!IsFlashMIME(mContentType)) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsAutoCString prefString;
 | |
|   if (NS_SUCCEEDED(
 | |
|           Preferences::GetCString(kPrefFavorFallbackMode, prefString))) {
 | |
|     if (aIsPluginClickToPlay && prefString.EqualsLiteral("follow-ctp")) {
 | |
|       return true;
 | |
|     }
 | |
| 
 | |
|     if (prefString.EqualsLiteral("always")) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool nsObjectLoadingContent::HasGoodFallback() {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   NS_ASSERTION(thisContent, "must be a content");
 | |
| 
 | |
|   if (!thisContent->IsHTMLElement(nsGkAtoms::object) ||
 | |
|       mContentType.IsEmpty()) {
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   nsTArray<nsCString> rulesList;
 | |
|   nsAutoCString prefString;
 | |
|   if (NS_SUCCEEDED(
 | |
|           Preferences::GetCString(kPrefFavorFallbackRules, prefString))) {
 | |
|     ParseString(prefString, ',', rulesList);
 | |
|   }
 | |
| 
 | |
|   for (uint32_t i = 0; i < rulesList.Length(); ++i) {
 | |
|     // RULE "embed":
 | |
|     // Don't use fallback content if the object contains an <embed> inside its
 | |
|     // fallback content.
 | |
|     if (rulesList[i].EqualsLiteral("embed")) {
 | |
|       nsTArray<nsINodeList*> childNodes;
 | |
|       for (nsIContent* child = thisContent->GetFirstChild(); child;
 | |
|            child = child->GetNextNode(thisContent)) {
 | |
|         if (child->IsHTMLElement(nsGkAtoms::embed)) {
 | |
|           return false;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // RULE "video":
 | |
|     // Use fallback content if the object contains a <video> inside its
 | |
|     // fallback content.
 | |
|     if (rulesList[i].EqualsLiteral("video")) {
 | |
|       nsTArray<nsINodeList*> childNodes;
 | |
|       for (nsIContent* child = thisContent->GetFirstChild(); child;
 | |
|            child = child->GetNextNode(thisContent)) {
 | |
|         if (child->IsHTMLElement(nsGkAtoms::video)) {
 | |
|           return true;
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // RULE "nosrc":
 | |
|     // Use fallback content if the object has not specified an URI.
 | |
|     if (rulesList[i].EqualsLiteral("nosrc")) {
 | |
|       if (!mOriginalURI) {
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // RULE "adobelink":
 | |
|     // Don't use fallback content when it has a link to adobe's website.
 | |
|     if (rulesList[i].EqualsLiteral("adobelink")) {
 | |
|       nsTArray<nsINodeList*> childNodes;
 | |
|       for (nsIContent* child = thisContent->GetFirstChild(); child;
 | |
|            child = child->GetNextNode(thisContent)) {
 | |
|         if (child->IsHTMLElement(nsGkAtoms::a)) {
 | |
|           nsCOMPtr<nsIURI> href = child->GetHrefURI();
 | |
|           if (href) {
 | |
|             nsAutoCString asciiHost;
 | |
|             nsresult rv = href->GetAsciiHost(asciiHost);
 | |
|             if (NS_SUCCEEDED(rv) && !asciiHost.IsEmpty() &&
 | |
|                 (asciiHost.EqualsLiteral("adobe.com") ||
 | |
|                  StringEndsWith(asciiHost, ".adobe.com"_ns))) {
 | |
|               return false;
 | |
|             }
 | |
|           }
 | |
|         }
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // RULE "installinstructions":
 | |
|     // Don't use fallback content when the text content on the fallback appears
 | |
|     // to contain instructions to install or download Flash.
 | |
|     if (rulesList[i].EqualsLiteral("installinstructions")) {
 | |
|       nsAutoString textContent;
 | |
|       ErrorResult rv;
 | |
|       thisContent->GetTextContent(textContent, rv);
 | |
|       bool hasText =
 | |
|           !rv.Failed() &&
 | |
|           (CaseInsensitiveFindInReadable(u"Flash"_ns, textContent) ||
 | |
|            CaseInsensitiveFindInReadable(u"Install"_ns, textContent) ||
 | |
|            CaseInsensitiveFindInReadable(u"Download"_ns, textContent));
 | |
| 
 | |
|       if (hasText) {
 | |
|         return false;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // RULE "true":
 | |
|     // By having a rule that returns true, we can put it at the end of the rules
 | |
|     // list to change the default-to-false behavior to be default-to-true.
 | |
|     if (rulesList[i].EqualsLiteral("true")) {
 | |
|       return true;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool nsObjectLoadingContent::PreferFallback(bool aIsPluginClickToPlay) {
 | |
|   if (mPreferFallbackKnown) {
 | |
|     return mPreferFallback;
 | |
|   }
 | |
| 
 | |
|   mPreferFallbackKnown = true;
 | |
|   mPreferFallback =
 | |
|       FavorFallbackMode(aIsPluginClickToPlay) && HasGoodFallback();
 | |
|   return mPreferFallback;
 | |
| }
 | |
| 
 | |
| Document* nsObjectLoadingContent::GetContentDocument(
 | |
|     nsIPrincipal& aSubjectPrincipal) {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
| 
 | |
|   if (!thisContent->IsInComposedDoc()) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   Document* sub_doc = thisContent->OwnerDoc()->GetSubDocumentFor(thisContent);
 | |
|   if (!sub_doc) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   // Return null for cross-origin contentDocument.
 | |
|   if (!aSubjectPrincipal.SubsumesConsideringDomain(sub_doc->NodePrincipal())) {
 | |
|     return nullptr;
 | |
|   }
 | |
| 
 | |
|   return sub_doc;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::SetupProtoChain(JSContext* aCx,
 | |
|                                              JS::Handle<JSObject*> aObject) {
 | |
|   if (mType != eType_Plugin) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!nsContentUtils::IsSafeToRunScript()) {
 | |
|     RefPtr<SetupProtoChainRunner> runner = new SetupProtoChainRunner(this);
 | |
|     nsContentUtils::AddScriptRunner(runner);
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // We get called on random realms here for some reason
 | |
|   // (perhaps because WrapObject can happen on a random realm?)
 | |
|   // so make sure to enter the realm of aObject.
 | |
|   MOZ_ASSERT(aCx == nsContentUtils::GetCurrentJSContext());
 | |
| 
 | |
|   MOZ_ASSERT(IsDOMObject(aObject));
 | |
|   JSAutoRealm ar(aCx, aObject);
 | |
| 
 | |
|   RefPtr<nsNPAPIPluginInstance> pi = ScriptRequestPluginInstance(aCx);
 | |
| 
 | |
|   if (!pi) {
 | |
|     // No plugin around for this object.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   JS::Rooted<JSObject*> pi_obj(
 | |
|       aCx);  // XPConnect-wrapped peer object, when we get it.
 | |
|   JS::Rooted<JSObject*> pi_proto(aCx);  // 'pi.__proto__'
 | |
| 
 | |
|   nsresult rv = GetPluginJSObject(aCx, pi, &pi_obj, &pi_proto);
 | |
|   if (NS_FAILED(rv)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (!pi_obj) {
 | |
|     // Didn't get a plugin instance JSObject, nothing we can do then.
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   // If we got an xpconnect-wrapped plugin object, set obj's
 | |
|   // prototype's prototype to the scriptable plugin.
 | |
| 
 | |
|   JS::Handle<JSObject*> my_proto = GetDOMClass(aObject)->mGetProto(aCx);
 | |
|   MOZ_ASSERT(my_proto);
 | |
| 
 | |
|   // Set 'this.__proto__' to pi
 | |
|   if (!::JS_SetPrototype(aCx, aObject, pi_obj)) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   if (pi_proto && js::GetObjectClass(pi_proto) != js::ObjectClassPtr) {
 | |
|     // The plugin wrapper has a proto that's not Object.prototype, set
 | |
|     // 'pi.__proto__.__proto__' to the original 'this.__proto__'
 | |
|     if (pi_proto != my_proto && !::JS_SetPrototype(aCx, pi_proto, my_proto)) {
 | |
|       return;
 | |
|     }
 | |
|   } else {
 | |
|     // 'pi' didn't have a prototype, or pi's proto was
 | |
|     // 'Object.prototype' (i.e. pi is an NPRuntime wrapped JS object)
 | |
|     // set 'pi.__proto__' to the original 'this.__proto__'
 | |
|     if (!::JS_SetPrototype(aCx, pi_obj, my_proto)) {
 | |
|       return;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   // Before this proto dance the objects involved looked like this:
 | |
|   //
 | |
|   // this.__proto__.__proto__
 | |
|   //   ^      ^         ^
 | |
|   //   |      |         |__ Object.prototype
 | |
|   //   |      |
 | |
|   //   |      |__ WebIDL prototype (shared)
 | |
|   //   |
 | |
|   //   |__ WebIDL object
 | |
|   //
 | |
|   // pi.__proto__
 | |
|   // ^      ^
 | |
|   // |      |__ Object.prototype or some other object
 | |
|   // |
 | |
|   // |__ Plugin NPRuntime JS object wrapper
 | |
|   //
 | |
|   // Now, after the above prototype setup the prototype chain should
 | |
|   // look like this if pi.__proto__ was Object.prototype:
 | |
|   //
 | |
|   // this.__proto__.__proto__.__proto__
 | |
|   //   ^      ^         ^         ^
 | |
|   //   |      |         |         |__ Object.prototype
 | |
|   //   |      |         |
 | |
|   //   |      |         |__ WebIDL prototype (shared)
 | |
|   //   |      |
 | |
|   //   |      |__ Plugin NPRuntime JS object wrapper
 | |
|   //   |
 | |
|   //   |__ WebIDL object
 | |
|   //
 | |
|   // or like this if pi.__proto__ was some other object:
 | |
|   //
 | |
|   // this.__proto__.__proto__.__proto__.__proto__
 | |
|   //   ^      ^         ^         ^         ^
 | |
|   //   |      |         |         |         |__ Object.prototype
 | |
|   //   |      |         |         |
 | |
|   //   |      |         |         |__ WebIDL prototype (shared)
 | |
|   //   |      |         |
 | |
|   //   |      |         |__ old pi.__proto__
 | |
|   //   |      |
 | |
|   //   |      |__ Plugin NPRuntime JS object wrapper
 | |
|   //   |
 | |
|   //   |__ WebIDL object
 | |
|   //
 | |
| }
 | |
| 
 | |
| // static
 | |
| nsresult nsObjectLoadingContent::GetPluginJSObject(
 | |
|     JSContext* cx, nsNPAPIPluginInstance* plugin_inst,
 | |
|     JS::MutableHandle<JSObject*> plugin_obj,
 | |
|     JS::MutableHandle<JSObject*> plugin_proto) {
 | |
|   if (plugin_inst) {
 | |
|     plugin_inst->GetJSObject(cx, plugin_obj.address());
 | |
|     if (plugin_obj) {
 | |
|       if (!::JS_GetPrototype(cx, plugin_obj, plugin_proto)) {
 | |
|         return NS_ERROR_UNEXPECTED;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::TeardownProtoChain() {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
| 
 | |
|   NS_ENSURE_TRUE_VOID(thisContent->GetWrapper());
 | |
| 
 | |
|   // We don't init the AutoJSAPI with our wrapper because we don't want it
 | |
|   // reporting errors to our window's onerror listeners.
 | |
|   AutoJSAPI jsapi;
 | |
|   jsapi.Init();
 | |
|   JSContext* cx = jsapi.cx();
 | |
|   JS::Rooted<JSObject*> obj(cx, thisContent->GetWrapper());
 | |
|   MOZ_ASSERT(obj);
 | |
| 
 | |
|   JS::Rooted<JSObject*> proto(cx);
 | |
|   JSAutoRealm ar(cx, obj);
 | |
| 
 | |
|   // Loop over the DOM element's JS object prototype chain and remove
 | |
|   // all JS objects of the class sNPObjectJSWrapperClass
 | |
|   DebugOnly<bool> removed = false;
 | |
|   while (obj) {
 | |
|     if (!::JS_GetPrototype(cx, obj, &proto)) {
 | |
|       return;
 | |
|     }
 | |
|     if (!proto) {
 | |
|       break;
 | |
|     }
 | |
|     // Unwrap while checking the class - if the prototype is a wrapper for
 | |
|     // an NP object, that counts too.
 | |
|     if (nsNPObjWrapper::IsWrapper(js::UncheckedUnwrap(proto))) {
 | |
|       // We found an NPObject on the proto chain, get its prototype...
 | |
|       if (!::JS_GetPrototype(cx, proto, &proto)) {
 | |
|         return;
 | |
|       }
 | |
| 
 | |
|       MOZ_ASSERT(!removed, "more than one NPObject in prototype chain");
 | |
|       removed = true;
 | |
| 
 | |
|       // ... and pull it out of the chain.
 | |
|       ::JS_SetPrototype(cx, obj, proto);
 | |
|     }
 | |
| 
 | |
|     obj = proto;
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsObjectLoadingContent::DoResolve(
 | |
|     JSContext* aCx, JS::Handle<JSObject*> aObject, JS::Handle<jsid> aId,
 | |
|     JS::MutableHandle<JS::PropertyDescriptor> aDesc) {
 | |
|   // We don't resolve anything; we just try to make sure we're instantiated.
 | |
|   // This purposefully does not fire for chrome/xray resolves, see bug 967694
 | |
|   Unused << ScriptRequestPluginInstance(aCx);
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| /* static */
 | |
| bool nsObjectLoadingContent::MayResolve(jsid aId) {
 | |
|   // We can resolve anything, really.
 | |
|   return true;
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::GetOwnPropertyNames(
 | |
|     JSContext* aCx, JS::MutableHandleVector<jsid> /* unused */,
 | |
|     bool /* unused */, ErrorResult& aRv) {
 | |
|   // Just like DoResolve, just make sure we're instantiated.  That will do
 | |
|   // the work our Enumerate hook needs to do.  This purposefully does not fire
 | |
|   // for xray resolves, see bug 967694
 | |
|   Unused << ScriptRequestPluginInstance(aCx);
 | |
| }
 | |
| 
 | |
| void nsObjectLoadingContent::MaybeFireErrorEvent() {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
|   // Queue a task to fire an error event if we're an <object> element.  The
 | |
|   // queueing is important, since then we don't have to worry about reentry.
 | |
|   if (thisContent->IsHTMLElement(nsGkAtoms::object)) {
 | |
|     RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
 | |
|         new LoadBlockingAsyncEventDispatcher(
 | |
|             thisContent, u"error"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
 | |
|     loadBlockingAsyncDispatcher->PostDOMEvent();
 | |
|   }
 | |
| }
 | |
| 
 | |
| bool nsObjectLoadingContent::BlockEmbedOrObjectContentLoading() {
 | |
|   nsCOMPtr<nsIContent> thisContent =
 | |
|       do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 | |
| 
 | |
|   // Traverse up the node tree to see if we have any ancestors that may block us
 | |
|   // from loading
 | |
|   for (nsIContent* parent = thisContent->GetParent(); parent;
 | |
|        parent = parent->GetParent()) {
 | |
|     if (parent->IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio)) {
 | |
|       return true;
 | |
|     }
 | |
|     // If we have an ancestor that is an object with a source, it'll have an
 | |
|     // associated displayed type. If that type is not null, don't load content
 | |
|     // for the embed.
 | |
|     if (HTMLObjectElement* object = HTMLObjectElement::FromNode(parent)) {
 | |
|       uint32_t type = object->DisplayedType();
 | |
|       if (type != eType_Null) {
 | |
|         return true;
 | |
|       }
 | |
|     }
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| // SetupProtoChainRunner implementation
 | |
| nsObjectLoadingContent::SetupProtoChainRunner::SetupProtoChainRunner(
 | |
|     nsObjectLoadingContent* aContent)
 | |
|     : mContent(aContent) {}
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| nsObjectLoadingContent::SetupProtoChainRunner::Run() {
 | |
|   AutoJSAPI jsapi;
 | |
|   jsapi.Init();
 | |
|   JSContext* cx = jsapi.cx();
 | |
| 
 | |
|   nsCOMPtr<nsIContent> content;
 | |
|   CallQueryInterface(mContent.get(), getter_AddRefs(content));
 | |
|   JS::Rooted<JSObject*> obj(cx, content->GetWrapper());
 | |
|   if (!obj) {
 | |
|     // No need to set up our proto chain if we don't even have an object
 | |
|     return NS_OK;
 | |
|   }
 | |
|   nsObjectLoadingContent* objectLoadingContent =
 | |
|       static_cast<nsObjectLoadingContent*>(mContent.get());
 | |
|   objectLoadingContent->SetupProtoChain(cx, obj);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(nsObjectLoadingContent::SetupProtoChainRunner, nsIRunnable)
 | 
