/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ // vim:set et cin sw=2 sts=2: /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla loading code. * * The Initial Developer of the Original Code is * Christian Biesinger . * Portions created by the Initial Developer are Copyright (C) 2005 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Justin Dolske * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ /* * A base class implementing nsIObjectLoadingContent for use by * various content nodes that want to provide plugin/document/image * loading functionality (eg , , , etc). */ // Interface headers #include "imgILoader.h" #include "nsEventDispatcher.h" #include "nsIContent.h" #include "nsIDocShell.h" #include "nsIDocument.h" #include "nsIDOMDataContainerEvent.h" #include "nsIDOMDocument.h" #include "nsIDOMEventTarget.h" #include "nsIExternalProtocolHandler.h" #include "nsEventStates.h" #include "nsIObjectFrame.h" #include "nsIPluginDocument.h" #include "nsPluginHost.h" #include "nsIPresShell.h" #include "nsIPrivateDOMEvent.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptSecurityManager.h" #include "nsIStreamConverterService.h" #include "nsIURILoader.h" #include "nsIURL.h" #include "nsIWebNavigation.h" #include "nsIWebNavigationInfo.h" #include "nsIScriptChannel.h" #include "nsIBlocklistService.h" #include "nsIAsyncVerifyRedirectCallback.h" #include "nsIAppShell.h" #include "nsPluginError.h" // Util headers #include "prlog.h" #include "nsAutoPtr.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 "nsGUIEvent.h" #include "nsUnicharUtils.h" // Concrete classes #include "nsFrameLoader.h" #include "nsObjectLoadingContent.h" #include "mozAutoDocUpdate.h" #include "nsIContentSecurityPolicy.h" #include "nsIChannelPolicy.h" #include "nsChannelPolicy.h" #include "mozilla/dom/Element.h" #include "sampler.h" #include "nsObjectFrame.h" #include "nsDOMClassInfo.h" #include "nsWidgetsCID.h" #include "nsContentCID.h" static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); #ifdef PR_LOGGING static PRLogModuleInfo* gObjectLog = PR_NewLogModule("objlc"); #endif #define LOG(args) PR_LOG(gObjectLog, PR_LOG_DEBUG, args) #define LOG_ENABLED() PR_LOG_TEST(gObjectLog, PR_LOG_DEBUG) #include "mozilla/Preferences.h" class nsAsyncInstantiateEvent : public nsRunnable { public: nsObjectLoadingContent *mContent; nsAsyncInstantiateEvent(nsObjectLoadingContent* aContent) : mContent(aContent) { static_cast(mContent)->AddRef(); } ~nsAsyncInstantiateEvent() { static_cast(mContent)->Release(); } NS_IMETHOD Run(); }; NS_IMETHODIMP nsAsyncInstantiateEvent::Run() { // do nothing if we've been revoked if (mContent->mPendingInstantiateEvent != this) { return NS_OK; } mContent->mPendingInstantiateEvent = nsnull; return mContent->SyncStartPluginInstance(); } // Checks to see if the content for a plugin instance has a parent. // The plugin instance is stopped if there is no parent. class InDocCheckEvent : public nsRunnable { public: nsCOMPtr mContent; InDocCheckEvent(nsIContent* aContent) : mContent(aContent) { } ~InDocCheckEvent() { } NS_IMETHOD Run(); }; NS_IMETHODIMP InDocCheckEvent::Run() { if (!mContent->IsInDoc()) { nsCOMPtr olc = do_QueryInterface(mContent); if (olc) { olc->StopPluginInstance(); } } return NS_OK; } /** * A task for firing PluginNotFound and PluginBlocklisted DOM Events. */ class nsPluginErrorEvent : public nsRunnable { public: nsCOMPtr mContent; PluginSupportState mState; nsPluginErrorEvent(nsIContent* aContent, PluginSupportState aState) : mContent(aContent), mState(aState) {} ~nsPluginErrorEvent() {} NS_IMETHOD Run(); }; NS_IMETHODIMP nsPluginErrorEvent::Run() { LOG(("OBJLC []: Firing plugin not found event for content %p\n", mContent.get())); nsString type; switch (mState) { case ePluginClickToPlay: type = NS_LITERAL_STRING("PluginClickToPlay"); break; case ePluginUnsupported: type = NS_LITERAL_STRING("PluginNotFound"); break; case ePluginDisabled: type = NS_LITERAL_STRING("PluginDisabled"); break; case ePluginBlocklisted: type = NS_LITERAL_STRING("PluginBlocklisted"); break; case ePluginOutdated: type = NS_LITERAL_STRING("PluginOutdated"); break; default: return NS_OK; } nsContentUtils::DispatchTrustedEvent(mContent->GetDocument(), mContent, type, true, true); return NS_OK; } /** * A task for firing PluginCrashed DOM Events. */ class nsPluginCrashedEvent : public nsRunnable { public: nsCOMPtr mContent; nsString mPluginDumpID; nsString mBrowserDumpID; nsString mPluginName; nsString mPluginFilename; bool mSubmittedCrashReport; nsPluginCrashedEvent(nsIContent* aContent, const nsAString& aPluginDumpID, const nsAString& aBrowserDumpID, const nsAString& aPluginName, const nsAString& aPluginFilename, bool submittedCrashReport) : mContent(aContent), mPluginDumpID(aPluginDumpID), mBrowserDumpID(aBrowserDumpID), mPluginName(aPluginName), mPluginFilename(aPluginFilename), mSubmittedCrashReport(submittedCrashReport) {} ~nsPluginCrashedEvent() {} NS_IMETHOD Run(); }; NS_IMETHODIMP nsPluginCrashedEvent::Run() { LOG(("OBJLC []: Firing plugin crashed event for content %p\n", mContent.get())); nsCOMPtr domDoc = do_QueryInterface(mContent->GetDocument()); if (!domDoc) { NS_WARNING("Couldn't get document for PluginCrashed event!"); return NS_OK; } nsCOMPtr event; domDoc->CreateEvent(NS_LITERAL_STRING("datacontainerevents"), getter_AddRefs(event)); nsCOMPtr privateEvent(do_QueryInterface(event)); nsCOMPtr containerEvent(do_QueryInterface(event)); if (!privateEvent || !containerEvent) { NS_WARNING("Couldn't QI event for PluginCrashed event!"); return NS_OK; } event->InitEvent(NS_LITERAL_STRING("PluginCrashed"), true, true); privateEvent->SetTrusted(true); privateEvent->GetInternalNSEvent()->flags |= NS_EVENT_FLAG_ONLY_CHROME_DISPATCH; nsCOMPtr variant; // add a "pluginDumpID" property to this event variant = do_CreateInstance("@mozilla.org/variant;1"); if (!variant) { NS_WARNING("Couldn't create pluginDumpID variant for PluginCrashed event!"); return NS_OK; } variant->SetAsAString(mPluginDumpID); containerEvent->SetData(NS_LITERAL_STRING("pluginDumpID"), variant); // add a "browserDumpID" property to this event variant = do_CreateInstance("@mozilla.org/variant;1"); if (!variant) { NS_WARNING("Couldn't create browserDumpID variant for PluginCrashed event!"); return NS_OK; } variant->SetAsAString(mBrowserDumpID); containerEvent->SetData(NS_LITERAL_STRING("browserDumpID"), variant); // add a "pluginName" property to this event variant = do_CreateInstance("@mozilla.org/variant;1"); if (!variant) { NS_WARNING("Couldn't create pluginName variant for PluginCrashed event!"); return NS_OK; } variant->SetAsAString(mPluginName); containerEvent->SetData(NS_LITERAL_STRING("pluginName"), variant); // add a "pluginFilename" property to this event variant = do_CreateInstance("@mozilla.org/variant;1"); if (!variant) { NS_WARNING("Couldn't create pluginFilename variant for PluginCrashed event!"); return NS_OK; } variant->SetAsAString(mPluginFilename); containerEvent->SetData(NS_LITERAL_STRING("pluginFilename"), variant); // add a "submittedCrashReport" property to this event variant = do_CreateInstance("@mozilla.org/variant;1"); if (!variant) { NS_WARNING("Couldn't create crashSubmit variant for PluginCrashed event!"); return NS_OK; } variant->SetAsBool(mSubmittedCrashReport); containerEvent->SetData(NS_LITERAL_STRING("submittedCrashReport"), variant); nsEventDispatcher::DispatchDOMEvent(mContent, nsnull, event, nsnull, nsnull); return NS_OK; } class nsStopPluginRunnable : public nsRunnable, public nsITimerCallback { public: NS_DECL_ISUPPORTS_INHERITED nsStopPluginRunnable(nsPluginInstanceOwner *aInstanceOwner) : mInstanceOwner(aInstanceOwner) { NS_ASSERTION(aInstanceOwner, "need an owner"); } // nsRunnable NS_IMETHOD Run(); // nsITimerCallback NS_IMETHOD Notify(nsITimer *timer); private: nsCOMPtr mTimer; nsRefPtr mInstanceOwner; }; NS_IMPL_ISUPPORTS_INHERITED1(nsStopPluginRunnable, nsRunnable, nsITimerCallback) NS_IMETHODIMP nsStopPluginRunnable::Notify(nsITimer *aTimer) { return Run(); } NS_IMETHODIMP nsStopPluginRunnable::Run() { // InitWithCallback calls Release before AddRef so we need to hold a // strong ref on 'this' since we fall through to this scope if it fails. nsCOMPtr kungFuDeathGrip = this; nsCOMPtr appShell = do_GetService(kAppShellCID); if (appShell) { PRUint32 currentLevel = 0; appShell->GetEventloopNestingLevel(¤tLevel); if (currentLevel > mInstanceOwner->GetLastEventloopNestingLevel()) { if (!mTimer) mTimer = do_CreateInstance("@mozilla.org/timer;1"); if (mTimer) { // Fire 100ms timer to try to tear down this plugin as quickly as // possible once the nesting level comes back down. nsresult rv = mTimer->InitWithCallback(this, 100, nsITimer::TYPE_ONE_SHOT); if (NS_SUCCEEDED(rv)) { return rv; } } NS_ERROR("Failed to setup a timer to stop the plugin later (at a safe " "time). Stopping the plugin now, this might crash."); } } mTimer = nsnull; nsObjectLoadingContent::DoStopPlugin(mInstanceOwner, false); return NS_OK; } class AutoNotifier { public: AutoNotifier(nsObjectLoadingContent* aContent, bool aNotify) : mContent(aContent), mNotify(aNotify) { mOldType = aContent->Type(); mOldState = aContent->ObjectState(); } ~AutoNotifier() { mContent->NotifyStateChanged(mOldType, mOldState, false, mNotify); } /** * Send notifications now, ignoring the value of mNotify. The new type and * state is saved, and the destructor will notify again if mNotify is true * and the values changed. */ void Notify() { NS_ASSERTION(mNotify, "Should not notify when notify=false"); mContent->NotifyStateChanged(mOldType, mOldState, true, true); mOldType = mContent->Type(); mOldState = mContent->ObjectState(); } private: nsObjectLoadingContent* mContent; bool mNotify; nsObjectLoadingContent::ObjectType mOldType; nsEventStates mOldState; }; /** * A class that will automatically fall back if a |rv| variable has a failure * code when this class is destroyed. It does not notify. */ class AutoFallback { public: AutoFallback(nsObjectLoadingContent* aContent, const nsresult* rv) : mContent(aContent), mResult(rv), mPluginState(ePluginOtherState) {} ~AutoFallback() { if (NS_FAILED(*mResult)) { LOG(("OBJLC [%p]: rv=%08x, falling back\n", mContent, *mResult)); mContent->Fallback(false); if (mPluginState != ePluginOtherState) { mContent->mFallbackReason = mPluginState; } } } /** * This should be set to something other than ePluginOtherState to indicate * a specific failure that should be passed on. */ void SetPluginState(PluginSupportState aState) { NS_ASSERTION(aState != ePluginOtherState, "Should not be setting ePluginOtherState"); mPluginState = aState; } private: nsObjectLoadingContent* mContent; const nsresult* mResult; PluginSupportState mPluginState; }; /** * A class that automatically sets mInstantiating to false when it goes * out of scope. */ class AutoSetInstantiatingToFalse { public: AutoSetInstantiatingToFalse(nsObjectLoadingContent* objlc) : mContent(objlc) {} ~AutoSetInstantiatingToFalse() { mContent->mInstantiating = false; } private: nsObjectLoadingContent* mContent; }; // helper functions static bool IsSupportedImage(const nsCString& aMimeType) { imgILoader* loader = nsContentUtils::GetImgLoader(); if (!loader) { return false; } bool supported; nsresult rv = loader->SupportImageWithMimeType(aMimeType.get(), &supported); return NS_SUCCEEDED(rv) && supported; } nsresult nsObjectLoadingContent::IsPluginEnabledForType(const nsCString& aMIMEType) { if (!mShouldPlay) { return NS_ERROR_PLUGIN_CLICKTOPLAY; } nsCOMPtr pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); nsPluginHost *pluginHost = static_cast(pluginHostCOM.get()); if (!pluginHost) { return false; } return pluginHost->IsPluginEnabledForType(aMIMEType.get()); } static void GetExtensionFromURI(nsIURI* uri, nsCString& ext) { nsCOMPtr url(do_QueryInterface(uri)); if (url) { url->GetFileExtension(ext); } else { nsCString spec; uri->GetSpec(spec); PRInt32 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 nsObjectLoadingContent::IsPluginEnabledByExtension(nsIURI* uri, nsCString& mimeType) { if (!mShouldPlay) { return false; } nsCAutoString ext; GetExtensionFromURI(uri, ext); if (ext.IsEmpty()) { return false; } nsCOMPtr pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); nsPluginHost *pluginHost = static_cast(pluginHostCOM.get()); if (!pluginHost) { return false; } const char* typeFromExt; if (NS_SUCCEEDED(pluginHost->IsPluginEnabledForExtension(ext.get(), typeFromExt))) { mimeType = typeFromExt; return true; } return false; } nsObjectLoadingContent::nsObjectLoadingContent() : mPendingInstantiateEvent(nsnull) , mChannel(nsnull) , mType(eType_Loading) , mInstantiating(false) , mUserDisabled(false) , mSuppressed(false) , mNetworkCreated(true) // If plugins.click_to_play is false, plugins should always play , mShouldPlay(!mozilla::Preferences::GetBool("plugins.click_to_play", false)) , mSrcStreamLoading(false) , mFallbackReason(ePluginOtherState) { } nsObjectLoadingContent::~nsObjectLoadingContent() { DestroyImageLoadingContent(); if (mFrameLoader) { mFrameLoader->Destroy(); } } nsresult nsObjectLoadingContent::InstantiatePluginInstance(const char* aMimeType, nsIURI* aURI) { if (!mShouldPlay) { return NS_ERROR_PLUGIN_CLICKTOPLAY; } // Don't do anything if we already have an active instance. if (mInstanceOwner) { return NS_OK; } // Don't allow re-entry into initialization code. if (mInstantiating) { return NS_OK; } mInstantiating = true; AutoSetInstantiatingToFalse autoInstantiating(this); // 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 kungFuDeathGrip = this; nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); nsCOMPtr baseURI; if (!aURI) { // We need some URI. If we have nothing else, use the base URI. // XXX(biesi): The code used to do this. Not sure why this is correct... GetObjectBaseURI(nsCString(aMimeType), getter_AddRefs(baseURI)); aURI = baseURI; } // Flush layout so that the plugin is initialized with the latest information. nsIDocument* doc = thisContent->GetCurrentDoc(); if (!doc) { return NS_ERROR_FAILURE; } doc->FlushPendingNotifications(Flush_Layout); nsresult rv = NS_ERROR_FAILURE; nsCOMPtr pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID, &rv)); nsPluginHost* pluginHost = static_cast(pluginHostCOM.get()); if (NS_FAILED(rv)) { return rv; } // If you add early return(s), be sure to balance this call to // appShell->SuspendNative() with additional call(s) to // appShell->ReturnNative(). nsCOMPtr appShell = do_GetService(kAppShellCID); if (appShell) { appShell->SuspendNative(); } nsCOMPtr pDoc(do_QueryInterface(doc)); bool fullPageMode = false; if (pDoc) { pDoc->GetWillHandleInstantiation(&fullPageMode); } if (fullPageMode) { nsCOMPtr stream; rv = pluginHost->InstantiateFullPagePlugin(aMimeType, aURI, this, getter_AddRefs(mInstanceOwner), getter_AddRefs(stream)); if (NS_SUCCEEDED(rv)) { pDoc->SetStreamListener(stream); } } else { rv = pluginHost->InstantiateEmbeddedPlugin(aMimeType, aURI, this, getter_AddRefs(mInstanceOwner)); } if (appShell) { appShell->ResumeNative(); } if (NS_FAILED(rv)) { return rv; } // Set up scripting interfaces. NotifyContentObjectWrapper(); nsRefPtr pluginInstance; GetPluginInstance(getter_AddRefs(pluginInstance)); if (pluginInstance) { nsCOMPtr pluginTag; pluginHost->GetPluginTagForInstance(pluginInstance, getter_AddRefs(pluginTag)); nsCOMPtr blocklist = do_GetService("@mozilla.org/extensions/blocklist;1"); if (blocklist) { PRUint32 blockState = nsIBlocklistService::STATE_NOT_BLOCKED; blocklist->GetPluginBlocklistState(pluginTag, EmptyString(), EmptyString(), &blockState); if (blockState == nsIBlocklistService::STATE_OUTDATED) FirePluginError(thisContent, ePluginOutdated); } } return NS_OK; } void nsObjectLoadingContent::NotifyOwnerDocumentActivityChanged() { if (!mInstanceOwner) { return; } nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); nsIDocument* ownerDoc = thisContent->OwnerDoc(); if (!ownerDoc->IsActive()) { StopPluginInstance(); } } // nsIRequestObserver NS_IMETHODIMP nsObjectLoadingContent::OnStartRequest(nsIRequest *aRequest, nsISupports *aContext) { SAMPLE_LABEL("nsObjectLoadingContent", "OnStartRequest"); if (aRequest != mChannel || !aRequest) { // This is a bit of an edge case - happens when a new load starts before the // previous one got here return NS_BINDING_ABORTED; } AutoNotifier notifier(this, true); if (!IsSuccessfulRequest(aRequest)) { LOG(("OBJLC [%p]: OnStartRequest: Request failed\n", this)); Fallback(false); return NS_BINDING_ABORTED; } nsCOMPtr chan(do_QueryInterface(aRequest)); NS_ASSERTION(chan, "Why is our request not a channel?"); nsresult rv = NS_ERROR_UNEXPECTED; // This fallback variable MUST be declared after the notifier variable. Do NOT // change the order of the declarations! AutoFallback fallback(this, &rv); nsCString channelType; rv = chan->GetContentType(channelType); NS_ENSURE_SUCCESS(rv, rv); if (channelType.EqualsASCII(APPLICATION_GUESS_FROM_EXT)) { channelType = APPLICATION_OCTET_STREAM; chan->SetContentType(channelType); } // We want to use the channel type unless one of the following is true: // // 1) The channel type is application/octet-stream and we have a // type hint and the type hint is not a document type. // 2) Our type hint is a type that we support with a plugin. if ((channelType.EqualsASCII(APPLICATION_OCTET_STREAM) && !mContentType.IsEmpty() && GetTypeOfContent(mContentType) != eType_Document) || // Need to check IsPluginEnabledForType() in addition to GetTypeOfContent() // because otherwise the default plug-in's catch-all behavior would // confuse things. (NS_SUCCEEDED(IsPluginEnabledForType(mContentType)) && GetTypeOfContent(mContentType) == eType_Plugin)) { // 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 nsCAutoString typeHint, dummy; NS_ParseContentType(mContentType, typeHint, dummy); if (!typeHint.IsEmpty()) { chan->SetContentType(typeHint); } } else { mContentType = channelType; } nsCOMPtr uri; chan->GetURI(getter_AddRefs(uri)); if (mContentType.EqualsASCII(APPLICATION_OCTET_STREAM)) { nsCAutoString extType; if (IsPluginEnabledByExtension(uri, extType)) { mContentType = extType; chan->SetContentType(extType); } } // Now find out what type the content is // UnloadContent will set our type to null; need to be sure to only set it to // the real value on success ObjectType newType = GetTypeOfContent(mContentType); LOG(("OBJLC [%p]: OnStartRequest: Content Type=<%s> Old type=%u New Type=%u\n", this, mContentType.get(), mType, newType)); // Now do a content policy check // XXXbz this duplicates some code in nsContentBlocker::ShouldLoad PRInt32 contentPolicyType; switch (newType) { case eType_Image: contentPolicyType = nsIContentPolicy::TYPE_IMAGE; break; case eType_Document: contentPolicyType = nsIContentPolicy::TYPE_SUBDOCUMENT; break; default: contentPolicyType = nsIContentPolicy::TYPE_OBJECT; break; } nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); NS_ASSERTION(thisContent, "must be a content"); nsIDocument* doc = thisContent->OwnerDoc(); PRInt16 shouldProcess = nsIContentPolicy::ACCEPT; rv = NS_CheckContentProcessPolicy(contentPolicyType, uri, doc->NodePrincipal(), static_cast(this), mContentType, nsnull, //extra &shouldProcess, nsContentUtils::GetContentPolicy(), nsContentUtils::GetSecurityManager()); if (NS_FAILED(rv) || NS_CP_REJECTED(shouldProcess)) { HandleBeingBlockedByContentPolicy(rv, shouldProcess); rv = NS_OK; // otherwise, the AutoFallback will make us fall back return NS_BINDING_ABORTED; } if (mType != newType) { UnloadContent(); } switch (newType) { case eType_Image: rv = LoadImageWithChannel(chan, getter_AddRefs(mFinalListener)); NS_ENSURE_SUCCESS(rv, rv); // If we have a success result but no final listener, then the image is // cached. In that case, we can just return: No need to try to call the // final listener. if (!mFinalListener) { mType = newType; return NS_BINDING_ABORTED; } break; case eType_Document: { if (!mFrameLoader) { mFrameLoader = nsFrameLoader::Create(thisContent->AsElement(), mNetworkCreated); if (!mFrameLoader) { Fallback(false); return NS_ERROR_UNEXPECTED; } } rv = mFrameLoader->CheckForRecursiveLoad(uri); if (NS_FAILED(rv)) { Fallback(false); return rv; } if (mType != newType) { // XXX We must call this before getting the docshell to work around // bug 300540; when that's fixed, this if statement can be removed. mType = newType; notifier.Notify(); if (!mFrameLoader) { // mFrameLoader got nulled out when we notified, which most // likely means the node was removed from the // document. Abort the load that just started. return NS_BINDING_ABORTED; } } // We're loading a document, so we have to set LOAD_DOCUMENT_URI // (especially important for firing onload) nsLoadFlags flags = 0; chan->GetLoadFlags(&flags); flags |= nsIChannel::LOAD_DOCUMENT_URI; chan->SetLoadFlags(flags); nsCOMPtr docShell; rv = mFrameLoader->GetDocShell(getter_AddRefs(docShell)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr req(do_QueryInterface(docShell)); NS_ASSERTION(req, "Docshell must be an ifreq"); nsCOMPtr uriLoader(do_GetService(NS_URI_LOADER_CONTRACTID, &rv)); NS_ENSURE_SUCCESS(rv, rv); rv = uriLoader->OpenChannel(chan, nsIURILoader::DONT_RETARGET, req, getter_AddRefs(mFinalListener)); break; } case eType_Plugin: { if (mType != newType) { mType = newType; notifier.Notify(); } nsCOMPtr pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); nsPluginHost *pluginHost = static_cast(pluginHostCOM.get()); if (!pluginHost) { return NS_ERROR_NOT_AVAILABLE; } pluginHost->CreateListenerForChannel(chan, this, getter_AddRefs(mFinalListener)); break; } case eType_Loading: NS_NOTREACHED("Should not have a loading type here!"); case eType_Null: // Need to fallback here (instead of using the case below), so that we can // set mFallbackReason without it being overwritten. This is also why we // return early. Fallback(false); PluginSupportState pluginState = GetPluginSupportState(thisContent, mContentType); // Do nothing, but fire the plugin not found event if needed if (pluginState != ePluginOtherState) { mFallbackReason = pluginState; FirePluginError(thisContent, pluginState); } return NS_BINDING_ABORTED; } if (mFinalListener) { mType = newType; mSrcStreamLoading = true; rv = mFinalListener->OnStartRequest(aRequest, aContext); mSrcStreamLoading = false; if (NS_SUCCEEDED(rv)) { // Plugins need to set up for NPRuntime. if (mType == eType_Plugin) { NotifyContentObjectWrapper(); } } else { // Plugins don't fall back if there is an error here. if (mType == eType_Plugin) { rv = NS_OK; // this is necessary to avoid auto-fallback return NS_BINDING_ABORTED; } Fallback(false); } return rv; } Fallback(false); return NS_BINDING_ABORTED; } NS_IMETHODIMP nsObjectLoadingContent::OnStopRequest(nsIRequest *aRequest, nsISupports *aContext, nsresult aStatusCode) { NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE); if (aRequest != mChannel) { return NS_BINDING_ABORTED; } mChannel = nsnull; if (mFinalListener) { mFinalListener->OnStopRequest(aRequest, aContext, aStatusCode); mFinalListener = nsnull; } // Return value doesn't matter return NS_OK; } // nsIStreamListener NS_IMETHODIMP nsObjectLoadingContent::OnDataAvailable(nsIRequest *aRequest, nsISupports *aContext, nsIInputStream *aInputStream, PRUint32 aOffset, PRUint32 aCount) { NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE); if (aRequest != mChannel) { return NS_BINDING_ABORTED; } if (mFinalListener) { return mFinalListener->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount); } // Abort this load if we have no listener here return NS_ERROR_UNEXPECTED; } // nsIFrameLoaderOwner NS_IMETHODIMP nsObjectLoadingContent::GetFrameLoader(nsIFrameLoader** aFrameLoader) { *aFrameLoader = mFrameLoader; NS_IF_ADDREF(*aFrameLoader); return NS_OK; } NS_IMETHODIMP_(already_AddRefed) nsObjectLoadingContent::GetFrameLoader() { nsFrameLoader* loader = mFrameLoader; NS_IF_ADDREF(loader); return loader; } NS_IMETHODIMP nsObjectLoadingContent::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoader) { return NS_ERROR_NOT_IMPLEMENTED; } // nsIObjectLoadingContent NS_IMETHODIMP nsObjectLoadingContent::GetActualType(nsACString& aType) { aType = mContentType; return NS_OK; } NS_IMETHODIMP nsObjectLoadingContent::GetDisplayedType(PRUint32* aType) { *aType = mType; return NS_OK; } NS_IMETHODIMP nsObjectLoadingContent::HasNewFrame(nsIObjectFrame* aFrame) { // Not having an instance yet is OK, but try to start one now that // we have a frame. if (!mInstanceOwner) { AsyncStartPluginInstance(); return NS_OK; } // Disconnect any existing frame DisconnectFrame(); // Set up relationship between instance owner and frame. nsObjectFrame *objFrame = static_cast(aFrame); mInstanceOwner->SetFrame(objFrame); // Set up new frame to draw. objFrame->FixupWindow(objFrame->GetContentRectRelativeToSelf().Size()); objFrame->Invalidate(objFrame->GetContentRectRelativeToSelf()); return NS_OK; } NS_IMETHODIMP nsObjectLoadingContent::DisconnectFrame() { if (mInstanceOwner) { mInstanceOwner->SetFrame(nsnull); } return NS_OK; } NS_IMETHODIMP nsObjectLoadingContent::GetPluginInstance(nsNPAPIPluginInstance** aInstance) { *aInstance = nsnull; if (!mInstanceOwner) { return NS_OK; } return mInstanceOwner->GetInstance(aInstance); } NS_IMETHODIMP nsObjectLoadingContent::GetContentTypeForMIMEType(const nsACString& aMIMEType, PRUint32* aType) { *aType = GetTypeOfContent(PromiseFlatCString(aMIMEType)); return NS_OK; } // nsIInterfaceRequestor NS_IMETHODIMP nsObjectLoadingContent::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, PRUint32 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; } // nsEventStates nsObjectLoadingContent::ObjectState() const { switch (mType) { case eType_Loading: return NS_EVENT_STATE_LOADING; case eType_Image: return ImageState(); case eType_Plugin: 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 nsEventStates(); case eType_Null: if (mSuppressed) return NS_EVENT_STATE_SUPPRESSED; if (mUserDisabled) return NS_EVENT_STATE_USERDISABLED; // Otherwise, broken nsEventStates state = NS_EVENT_STATE_BROKEN; switch (mFallbackReason) { case ePluginClickToPlay: return NS_EVENT_STATE_TYPE_CLICK_TO_PLAY; case ePluginDisabled: state |= NS_EVENT_STATE_HANDLER_DISABLED; break; case ePluginBlocklisted: state |= NS_EVENT_STATE_HANDLER_BLOCKED; break; case ePluginCrashed: state |= NS_EVENT_STATE_HANDLER_CRASHED; break; case ePluginUnsupported: state |= NS_EVENT_STATE_TYPE_UNSUPPORTED; break; case ePluginOutdated: case ePluginOtherState: // Do nothing, but avoid a compile warning break; } return state; }; NS_NOTREACHED("unknown type?"); // this return statement only exists to avoid a compile warning return nsEventStates(); } // nsresult nsObjectLoadingContent::LoadObject(const nsAString& aURI, bool aNotify, const nsCString& aTypeHint, bool aForceLoad) { LOG(("OBJLC [%p]: Loading object: URI string=<%s> notify=%i type=<%s> forceload=%i\n", this, NS_ConvertUTF16toUTF8(aURI).get(), aNotify, aTypeHint.get(), aForceLoad)); // Avoid StringToURI in order to use the codebase attribute as base URI nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); NS_ASSERTION(thisContent, "must be a content"); nsIDocument* doc = thisContent->OwnerDoc(); nsCOMPtr baseURI; GetObjectBaseURI(aTypeHint, getter_AddRefs(baseURI)); nsCOMPtr uri; nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(uri), aURI, doc, baseURI); // If URI creation failed, fallback immediately - this only happens for // malformed URIs if (!uri) { Fallback(aNotify); return NS_OK; } NS_TryToSetImmutable(uri); return LoadObject(uri, aNotify, aTypeHint, aForceLoad); } void nsObjectLoadingContent::UpdateFallbackState(nsIContent* aContent, AutoFallback& fallback, const nsCString& aTypeHint) { // Notify the UI and update the fallback state PluginSupportState state = GetPluginSupportState(aContent, aTypeHint); if (state != ePluginOtherState) { fallback.SetPluginState(state); FirePluginError(aContent, state); } } bool nsObjectLoadingContent::IsFileCodebaseAllowable(nsIURI* aBaseURI, nsIURI* aOriginURI) { nsCOMPtr baseFileURL(do_QueryInterface(aBaseURI)); nsCOMPtr originFileURL(do_QueryInterface(aOriginURI)); // get IFile handles and normalize nsCOMPtr originFile; nsCOMPtr baseFile; if (!originFileURL || !baseFileURL || NS_FAILED(originFileURL->GetFile(getter_AddRefs(originFile))) || NS_FAILED(baseFileURL->GetFile(getter_AddRefs(baseFile))) || NS_FAILED(baseFile->Normalize()) || NS_FAILED(originFile->Normalize())) { return false; } // If the origin is a directory, it should contain/equal baseURI // Otherwise, its parent directory should contain/equal baseURI bool origin_is_dir; bool contained = false; nsresult rv = originFile->IsDirectory(&origin_is_dir); NS_ENSURE_SUCCESS(rv, false); if (origin_is_dir) { // originURI is a directory, ensure it contains the baseURI rv = originFile->Contains(baseFile, true, &contained); if (NS_SUCCEEDED(rv) && !contained) { rv = originFile->Equals(baseFile, &contained); } } else { // originURI is a file, ensure its parent contains the baseURI nsCOMPtr originParent; rv = originFile->GetParent(getter_AddRefs(originParent)); if (NS_SUCCEEDED(rv) && originParent) { rv = originParent->Contains(baseFile, true, &contained); if (NS_SUCCEEDED(rv) && !contained) { rv = originParent->Equals(baseFile, &contained); } } } return NS_SUCCEEDED(rv) && contained; } nsresult nsObjectLoadingContent::LoadObject(nsIURI* aURI, bool aNotify, const nsCString& aTypeHint, bool aForceLoad) { // Only do a URI equality check for things that aren't stopped plugins. // This is because we still need to load again if the plugin has been stopped. if (mType == eType_Document || mType == eType_Image || mInstanceOwner) { if (mURI && aURI) { bool equal; nsresult rv = mURI->Equals(aURI, &equal); if (NS_SUCCEEDED(rv) && equal && !aForceLoad) { // URI didn't change, do nothing return NS_OK; } StopPluginInstance(); } } // Need to revoke any potentially pending instantiate events if (mType == eType_Plugin && mPendingInstantiateEvent) { mPendingInstantiateEvent = nsnull; } AutoNotifier notifier(this, aNotify); mUserDisabled = mSuppressed = false; mURI = aURI; mContentType = aTypeHint; nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); NS_ASSERTION(thisContent, "must be a content"); nsIDocument* doc = thisContent->OwnerDoc(); if (doc->IsBeingUsedAsImage()) { return NS_OK; } // From here on, we will always change the content. This means that a // possibly-loading channel should be aborted. if (mChannel) { LOG(("OBJLC [%p]: Cancelling existing load\n", this)); // These three statements are carefully ordered: // - onStopRequest should get a channel whose status is the same as the // status argument // - onStopRequest must get a non-null channel mChannel->Cancel(NS_BINDING_ABORTED); if (mFinalListener) { // NOTE: Since mFinalListener is only set in onStartRequest, which takes // care of calling mFinalListener->OnStartRequest, mFinalListener is only // non-null here if onStartRequest was already called. mFinalListener->OnStopRequest(mChannel, nsnull, NS_BINDING_ABORTED); mFinalListener = nsnull; } mChannel = nsnull; } // Security checks if (doc->IsLoadedAsData()) { if (!doc->IsStaticDocument()) { Fallback(false); } return NS_OK; } // Can't do security checks without a URI - hopefully the plugin will take // care of that // Null URIs happen when the URL to load is specified via other means than the // data/src attribute, for example via custom elements. if (aURI) { nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); NS_ASSERTION(secMan, "No security manager!?"); nsresult rv = secMan->CheckLoadURIWithPrincipal(thisContent->NodePrincipal(), aURI, 0); if (NS_FAILED(rv)) { Fallback(false); return NS_OK; } PRInt16 shouldLoad = nsIContentPolicy::ACCEPT; // default permit rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_OBJECT, aURI, doc->NodePrincipal(), static_cast(this), aTypeHint, nsnull, //extra &shouldLoad, nsContentUtils::GetContentPolicy(), secMan); if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { HandleBeingBlockedByContentPolicy(rv, shouldLoad); return NS_OK; } // If this is a file:// URI, require that the codebase (baseURI) // is contained within the same folder as the document origin (originURI) // or within the document origin, if it is a folder. // No originURI implies chrome, which bypasses the check // -- bug 406541 nsCOMPtr originURI; nsCOMPtr baseURI; GetObjectBaseURI(aTypeHint, getter_AddRefs(baseURI)); rv = thisContent->NodePrincipal()->GetURI(getter_AddRefs(originURI)); if (NS_FAILED(rv)) { Fallback(aNotify); return NS_OK; } if (originURI) { bool isfile; if (NS_FAILED(originURI->SchemeIs("file", &isfile)) || (isfile && !IsFileCodebaseAllowable(baseURI, originURI))) { Fallback(aNotify); return NS_OK; } } } nsresult rv = NS_ERROR_UNEXPECTED; // This fallback variable MUST be declared after the notifier variable. Do NOT // change the order of the declarations! AutoFallback fallback(this, &rv); PRUint32 caps = GetCapabilities(); LOG(("OBJLC [%p]: Capabilities: %04x\n", this, caps)); nsCAutoString overrideType; if ((caps & eOverrideServerType) && ((!aTypeHint.IsEmpty() && NS_SUCCEEDED(IsPluginEnabledForType(aTypeHint))) || (aURI && IsPluginEnabledByExtension(aURI, overrideType)))) { ObjectType newType; if (overrideType.IsEmpty()) { newType = GetTypeOfContent(aTypeHint); } else { mContentType = overrideType; newType = eType_Plugin; } if (newType != mType) { LOG(("OBJLC [%p]: (eOverrideServerType) Changing type from %u to %u\n", this, mType, newType)); UnloadContent(); // Must have a frameloader before creating a frame, or the frame will // create its own. if (!mFrameLoader && newType == eType_Document) { mFrameLoader = nsFrameLoader::Create(thisContent->AsElement(), mNetworkCreated); if (!mFrameLoader) { mURI = nsnull; return NS_OK; } } // Must notify here for plugins // If aNotify is false, we'll just wait until we get a frame and use the // async instantiate path. // XXX is this still needed? (for documents?) mType = newType; if (aNotify) notifier.Notify(); } switch (newType) { case eType_Image: // Don't notify, because we will take care of that ourselves. if (aURI) { rv = LoadImage(aURI, aForceLoad, false); } else { rv = NS_ERROR_NOT_AVAILABLE; } break; case eType_Plugin: rv = AsyncStartPluginInstance(); break; case eType_Document: if (aURI) { rv = mFrameLoader->LoadURI(aURI); } else { rv = NS_ERROR_NOT_AVAILABLE; } break; case eType_Loading: NS_NOTREACHED("Should not have a loading type here!"); case eType_Null: // No need to load anything, notify of the failure. UpdateFallbackState(thisContent, fallback, aTypeHint); break; }; return NS_OK; } // If the class ID specifies a supported plugin, or if we have no explicit URI // but a type, immediately instantiate the plugin. bool isSupportedClassID = false; nsCAutoString typeForID; // Will be set iff isSupportedClassID == true bool hasID = false; if (caps & eSupportClassID) { nsAutoString classid; thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::classid, classid); if (!classid.IsEmpty()) { hasID = true; isSupportedClassID = NS_SUCCEEDED(TypeForClassID(classid, typeForID)); } } if (hasID && !isSupportedClassID) { // We have a class ID and it's unsupported. Fallback in that case. rv = NS_ERROR_NOT_AVAILABLE; return NS_OK; } if (isSupportedClassID || (!aURI && !aTypeHint.IsEmpty() && GetTypeOfContent(aTypeHint) == eType_Plugin)) { // No URI, but we have a type. The plugin will handle the load. // Or: supported class id, plugin will handle the load. mType = eType_Plugin; // At this point, the stored content type // must be equal to our type hint. Similar, // our URI must be the requested URI. // (->Equals would suffice, but == is cheaper // and handles NULL) NS_ASSERTION(mContentType.Equals(aTypeHint), "mContentType wrong!"); NS_ASSERTION(mURI == aURI, "mURI wrong!"); if (isSupportedClassID) { // Use the classid's type NS_ASSERTION(!typeForID.IsEmpty(), "Must have a real type!"); mContentType = typeForID; // XXX(biesi). The plugin instantiation code used to pass the base URI // here instead of the plugin URI for instantiation via class ID, so I // continue to do so. Why that is, no idea... GetObjectBaseURI(mContentType, getter_AddRefs(mURI)); if (!mURI) { mURI = aURI; } } // rv is references by a stack-based object, need to assign here rv = AsyncStartPluginInstance(); return rv; } if (!aURI) { // No URI and if we have got this far no enabled plugin supports the type rv = NS_ERROR_NOT_AVAILABLE; // We should only notify the UI if there is at least a type to go on for // finding a plugin to use, unless it's a supported image or document type. if (!aTypeHint.IsEmpty() && GetTypeOfContent(aTypeHint) == eType_Null) { UpdateFallbackState(thisContent, fallback, aTypeHint); } return NS_OK; } // E.g. mms:// if (!CanHandleURI(aURI)) { if (aTypeHint.IsEmpty()) { rv = NS_ERROR_NOT_AVAILABLE; return NS_OK; } if (NS_SUCCEEDED(IsPluginEnabledForType(aTypeHint))) { mType = eType_Plugin; } else { rv = NS_ERROR_NOT_AVAILABLE; // No plugin to load, notify of the failure. UpdateFallbackState(thisContent, fallback, aTypeHint); } return NS_OK; } nsCOMPtr group = doc->GetDocumentLoadGroup(); nsCOMPtr chan; nsCOMPtr channelPolicy; nsCOMPtr csp; rv = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp)); NS_ENSURE_SUCCESS(rv, rv); if (csp) { channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); channelPolicy->SetContentSecurityPolicy(csp); channelPolicy->SetLoadType(nsIContentPolicy::TYPE_OBJECT); } rv = NS_NewChannel(getter_AddRefs(chan), aURI, nsnull, group, this, nsIChannel::LOAD_CALL_CONTENT_SNIFFERS | nsIChannel::LOAD_CLASSIFY_URI, channelPolicy); NS_ENSURE_SUCCESS(rv, rv); // Referrer nsCOMPtr httpChan(do_QueryInterface(chan)); if (httpChan) { httpChan->SetReferrer(doc->GetDocumentURI()); } // MIME Type hint if (!aTypeHint.IsEmpty()) { nsCAutoString typeHint, dummy; NS_ParseContentType(aTypeHint, typeHint, dummy); if (!typeHint.IsEmpty()) { chan->SetContentType(typeHint); } } // Set up the channel's principal and such, like nsDocShell::DoURILoad does nsContentUtils::SetUpChannelOwner(thisContent->NodePrincipal(), chan, aURI, true); nsCOMPtr 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. // Show fallback content in that case. rv = chan->AsyncOpen(this, nsnull); if (NS_SUCCEEDED(rv)) { LOG(("OBJLC [%p]: Channel opened.\n", this)); mChannel = chan; mType = eType_Loading; } return NS_OK; } PRUint32 nsObjectLoadingContent::GetCapabilities() const { return eSupportImages | eSupportPlugins | eSupportDocuments | eSupportSVG; } void nsObjectLoadingContent::Fallback(bool aNotify) { AutoNotifier notifier(this, aNotify); UnloadContent(); } void nsObjectLoadingContent::RemovedFromDocument() { if (mFrameLoader) { // XXX This is very temporary and must go away mFrameLoader->Destroy(); mFrameLoader = nsnull; // Clear the current URI, so that LoadObject doesn't think that we // have already loaded the content. mURI = nsnull; } // When a plugin instance node is removed from the document 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 stop // the plugin. nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); nsCOMPtr event = new InDocCheckEvent(thisContent); nsCOMPtr appShell = do_GetService(kAppShellCID); if (appShell) { appShell->RunInStableState(event); } } /* static */ void nsObjectLoadingContent::Traverse(nsObjectLoadingContent *tmp, nsCycleCollectionTraversalCallback &cb) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mFrameLoader"); cb.NoteXPCOMChild(static_cast(tmp->mFrameLoader)); } // /* static */ bool nsObjectLoadingContent::IsSuccessfulRequest(nsIRequest* aRequest) { nsresult status; nsresult rv = aRequest->GetStatus(&status); if (NS_FAILED(rv) || NS_FAILED(status)) { return false; } // This may still be an error page or somesuch nsCOMPtr 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 nsObjectLoadingContent::CanHandleURI(nsIURI* aURI) { nsCAutoString scheme; if (NS_FAILED(aURI->GetScheme(scheme))) { return false; } nsIIOService* ios = nsContentUtils::GetIOService(); if (!ios) return false; nsCOMPtr handler; ios->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); if (!handler) { return false; } nsCOMPtr extHandler = do_QueryInterface(handler); // We can handle this URI if its protocol handler is not the external one return extHandler == nsnull; } bool nsObjectLoadingContent::IsSupportedDocument(const nsCString& aMimeType) { nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); NS_ASSERTION(thisContent, "must be a content"); nsresult rv; nsCOMPtr info( do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID, &rv)); PRUint32 supported; if (info) { nsCOMPtr webNav; nsIDocument* currentDoc = thisContent->GetCurrentDoc(); if (currentDoc) { webNav = do_GetInterface(currentDoc->GetScriptGlobalObject()); } rv = info->IsTypeSupported(aMimeType, webNav, &supported); } if (NS_SUCCEEDED(rv)) { if (supported == nsIWebNavigationInfo::UNSUPPORTED) { // Try a stream converter // NOTE: We treat any type we can convert from as a supported type. If a // type is not actually supported, the URI loader will detect that and // return an error, and we'll fallback. nsCOMPtr convServ = do_GetService("@mozilla.org/streamConverters;1"); bool canConvert = false; if (convServ) { rv = convServ->CanConvert(aMimeType.get(), "*/*", &canConvert); } return NS_SUCCEEDED(rv) && canConvert; } // Don't want to support plugins as documents return supported != nsIWebNavigationInfo::PLUGIN; } return false; } void nsObjectLoadingContent::UnloadContent() { // Don't notify in CancelImageRequests. We do it ourselves. CancelImageRequests(false); if (mFrameLoader) { mFrameLoader->Destroy(); mFrameLoader = nsnull; } mType = eType_Null; mUserDisabled = mSuppressed = false; mFallbackReason = ePluginOtherState; } void nsObjectLoadingContent::NotifyStateChanged(ObjectType aOldType, nsEventStates aOldState, bool aSync, bool aNotify) { LOG(("OBJLC [%p]: Notifying about state change: (%u, %llx) -> (%u, %llx) (sync=%i)\n", this, aOldType, aOldState.GetInternalValue(), mType, ObjectState().GetInternalValue(), aSync)); nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); NS_ASSERTION(thisContent, "must be a content"); NS_ASSERTION(thisContent->IsElement(), "Not an element?"); // 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. thisContent->AsElement()->UpdateState(false); if (!aNotify) { // We're done here return; } nsIDocument* doc = thisContent->GetCurrentDoc(); if (!doc) { return; // Nothing to do } nsEventStates newState = ObjectState(); if (newState != aOldState) { // This will trigger frame construction NS_ASSERTION(thisContent->IsInDoc(), "Something is confused"); nsEventStates changedBits = aOldState ^ newState; { nsAutoScriptBlocker scriptBlocker; doc->ContentStateChanged(thisContent, changedBits); } if (aSync) { // Make sure that frames are actually constructed immediately. doc->FlushPendingNotifications(Flush_Frames); } } else if (aOldType != mType) { // If our state changed, then we already recreated frames // Otherwise, need to do that here nsCOMPtr shell = doc->GetShell(); if (shell) { shell->RecreateFramesFor(thisContent); } } } /* static */ void nsObjectLoadingContent::FirePluginError(nsIContent* thisContent, PluginSupportState state) { LOG(("OBJLC []: Dispatching nsPluginErrorEvent for content %p\n", thisContent)); nsCOMPtr ev = new nsPluginErrorEvent(thisContent, state); nsresult rv = NS_DispatchToCurrentThread(ev); if (NS_FAILED(rv)) { NS_WARNING("failed to dispatch nsPluginErrorEvent"); } } nsObjectLoadingContent::ObjectType nsObjectLoadingContent::GetTypeOfContent(const nsCString& aMIMEType) { PRUint32 caps = GetCapabilities(); if ((caps & eSupportImages) && IsSupportedImage(aMIMEType)) { return eType_Image; } bool isSVG = aMIMEType.LowerCaseEqualsLiteral("image/svg+xml"); bool supportedSVG = isSVG && (caps & eSupportSVG); if (((caps & eSupportDocuments) || supportedSVG) && IsSupportedDocument(aMIMEType)) { return eType_Document; } if ((caps & eSupportPlugins) && NS_SUCCEEDED(IsPluginEnabledForType(aMIMEType))) { return eType_Plugin; } return eType_Null; } nsresult nsObjectLoadingContent::TypeForClassID(const nsAString& aClassID, nsACString& aType) { if (StringBeginsWith(aClassID, NS_LITERAL_STRING("java:"))) { // Supported if we have a java plugin aType.AssignLiteral("application/x-java-vm"); nsresult rv = IsPluginEnabledForType(NS_LITERAL_CSTRING("application/x-java-vm")); return NS_SUCCEEDED(rv) ? NS_OK : NS_ERROR_NOT_AVAILABLE; } // If it starts with "clsid:", this is ActiveX content if (StringBeginsWith(aClassID, NS_LITERAL_STRING("clsid:"), nsCaseInsensitiveStringComparator())) { // Check if we have a plugin for that if (NS_SUCCEEDED(IsPluginEnabledForType(NS_LITERAL_CSTRING("application/x-oleobject")))) { aType.AssignLiteral("application/x-oleobject"); return NS_OK; } if (NS_SUCCEEDED(IsPluginEnabledForType(NS_LITERAL_CSTRING("application/oleobject")))) { aType.AssignLiteral("application/oleobject"); return NS_OK; } } return NS_ERROR_NOT_AVAILABLE; } NS_IMETHODIMP nsObjectLoadingContent::GetObjectBaseURI(const nsACString & aMimeType, nsIURI** aURI) { nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); // For plugins, the codebase attribute is the base URI nsCOMPtr baseURI = thisContent->GetBaseURI(); nsAutoString codebase; thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::codebase, codebase); if (codebase.IsEmpty() && aMimeType.Equals("application/x-java-vm")) { // bug 406541 // Java resolves codebase="" as "/" -- so we replicate that quirk, to ensure // we run security checks against the same path. codebase.AssignLiteral("/"); } if (!codebase.IsEmpty()) { nsresult rv = nsContentUtils::NewURIWithDocumentCharset(aURI, codebase, thisContent->OwnerDoc(), baseURI); if (NS_SUCCEEDED(rv)) return rv; NS_WARNING("GetObjectBaseURI: Could not resolve plugin's codebase to a URI, using baseURI instead"); } // Codebase empty or build URI failed, just use baseURI *aURI = NULL; baseURI.swap(*aURI); return NS_OK; } nsObjectFrame* nsObjectLoadingContent::GetExistingFrame() { nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); nsIFrame* frame = thisContent->GetPrimaryFrame(); nsIObjectFrame* objFrame = do_QueryFrame(frame); return static_cast(objFrame); } void nsObjectLoadingContent::HandleBeingBlockedByContentPolicy(nsresult aStatus, PRInt16 aRetval) { // Must call UnloadContent first, as it overwrites // mSuppressed/mUserDisabled. It also takes care of setting the type to // eType_Null. UnloadContent(); if (NS_SUCCEEDED(aStatus)) { if (aRetval == nsIContentPolicy::REJECT_TYPE) { mUserDisabled = true; } else if (aRetval == nsIContentPolicy::REJECT_SERVER) { mSuppressed = true; } } } PluginSupportState nsObjectLoadingContent::GetPluginSupportState(nsIContent* aContent, const nsCString& aContentType) { if (!aContent->IsHTML()) { return ePluginOtherState; } if (aContent->Tag() == nsGkAtoms::embed || aContent->Tag() == nsGkAtoms::applet) { return GetPluginDisabledState(aContentType); } bool hasAlternateContent = false; // Search for a child with a pluginurl name for (nsIContent* child = aContent->GetFirstChild(); child; child = child->GetNextSibling()) { if (child->IsHTML(nsGkAtoms::param)) { if (child->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, NS_LITERAL_STRING("pluginurl"), eIgnoreCase)) { return GetPluginDisabledState(aContentType); } } else if (!hasAlternateContent) { hasAlternateContent = nsStyleUtil::IsSignificantChild(child, true, false); } } return hasAlternateContent ? ePluginOtherState : GetPluginDisabledState(aContentType); } PluginSupportState nsObjectLoadingContent::GetPluginDisabledState(const nsCString& aContentType) { nsresult rv = IsPluginEnabledForType(aContentType); if (rv == NS_ERROR_PLUGIN_DISABLED) return ePluginDisabled; if (rv == NS_ERROR_PLUGIN_CLICKTOPLAY) return ePluginClickToPlay; if (rv == NS_ERROR_PLUGIN_BLOCKLISTED) return ePluginBlocklisted; return ePluginUnsupported; } void nsObjectLoadingContent::CreateStaticClone(nsObjectLoadingContent* aDest) const { nsImageLoadingContent::CreateStaticImageClone(aDest); aDest->mType = mType; nsObjectLoadingContent* thisObj = const_cast(this); if (thisObj->mPrintFrame.IsAlive()) { aDest->mPrintFrame = thisObj->mPrintFrame; } else { aDest->mPrintFrame = const_cast(this)->GetExistingFrame(); } if (mFrameLoader) { nsCOMPtr content = do_QueryInterface(static_cast(aDest)); nsFrameLoader* fl = nsFrameLoader::Create(content->AsElement(), false); if (fl) { aDest->mFrameLoader = fl; mFrameLoader->CreateStaticClone(fl); } } } NS_IMETHODIMP nsObjectLoadingContent::GetPrintFrame(nsIFrame** aFrame) { *aFrame = mPrintFrame.GetFrame(); return NS_OK; } NS_IMETHODIMP nsObjectLoadingContent::PluginCrashed(nsIPluginTag* aPluginTag, const nsAString& pluginDumpID, const nsAString& browserDumpID, bool submittedCrashReport) { AutoNotifier notifier(this, true); UnloadContent(); mFallbackReason = ePluginCrashed; nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); // Note that aPluginTag in invalidated after we're called, so copy // out any data we need now. nsCAutoString pluginName; aPluginTag->GetName(pluginName); nsCAutoString pluginFilename; aPluginTag->GetFilename(pluginFilename); nsCOMPtr ev = new nsPluginCrashedEvent(thisContent, pluginDumpID, browserDumpID, 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; } 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. nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); if (!thisContent->IsInDoc()) { return NS_ERROR_FAILURE; } nsCOMPtr kungFuURIGrip(mURI); nsCString contentType(mContentType); return InstantiatePluginInstance(contentType.get(), mURI.get()); } NS_IMETHODIMP nsObjectLoadingContent::AsyncStartPluginInstance() { // OK to have an instance already. if (mInstanceOwner) { return NS_OK; } nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); nsIDocument* doc = thisContent->OwnerDoc(); if (doc->IsStaticDocument() || doc->IsBeingUsedAsImage()) { return NS_OK; } // We always start plugins on a runnable. // We don't want a script blocker on the stack during instantiation. nsCOMPtr event = new nsAsyncInstantiateEvent(this); if (!event) { return NS_ERROR_OUT_OF_MEMORY; } nsresult rv = NS_DispatchToCurrentThread(event); if (NS_SUCCEEDED(rv)) { // Remember this event. This is a weak reference that will be cleared // when the event runs. mPendingInstantiateEvent = event; } return rv; } NS_IMETHODIMP nsObjectLoadingContent::GetSrcURI(nsIURI** aURI) { NS_IF_ADDREF(*aURI = mURI); return NS_OK; } static bool DoDelayedStop(nsPluginInstanceOwner *aInstanceOwner, bool aDelayedStop) { #if (MOZ_PLATFORM_MAEMO==5) // Don't delay stop on Maemo/Hildon (bug 530739). if (aDelayedStop && aInstanceOwner->MatchPluginName("Shockwave Flash")) return false; #endif // Don't delay stopping QuickTime (bug 425157), Flip4Mac (bug 426524), // XStandard (bug 430219), CMISS Zinc (bug 429604). if (aDelayedStop #if !(defined XP_WIN || defined MOZ_X11) && !aInstanceOwner->MatchPluginName("QuickTime") && !aInstanceOwner->MatchPluginName("Flip4Mac") && !aInstanceOwner->MatchPluginName("XStandard plugin") && !aInstanceOwner->MatchPluginName("CMISS Zinc Plugin") #endif ) { nsCOMPtr evt = new nsStopPluginRunnable(aInstanceOwner); NS_DispatchToCurrentThread(evt); return true; } return false; } void nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner *aInstanceOwner, bool aDelayedStop) { nsRefPtr inst; aInstanceOwner->GetInstance(getter_AddRefs(inst)); if (inst) { if (DoDelayedStop(aInstanceOwner, aDelayedStop)) { return; } #if defined(XP_MACOSX) aInstanceOwner->HidePluginWindow(); #endif nsCOMPtr pluginHost = do_GetService(MOZ_PLUGIN_HOST_CONTRACTID); NS_ASSERTION(pluginHost, "Without a pluginHost, how can we have an instance to destroy?"); static_cast(pluginHost.get())->StopPluginInstance(inst); } aInstanceOwner->Destroy(); } NS_IMETHODIMP nsObjectLoadingContent::StopPluginInstance() { if (!mInstanceOwner) { return NS_OK; } DisconnectFrame(); bool delayedStop = false; #ifdef XP_WIN // Force delayed stop for Real plugin only; see bug 420886, 426852. nsRefPtr inst; mInstanceOwner->GetInstance(getter_AddRefs(inst)); if (inst) { const char* mime = nsnull; if (NS_SUCCEEDED(inst->GetMIMEType(&mime)) && mime) { if (strcmp(mime, "audio/x-pn-realaudio-plugin") == 0) { delayedStop = true; } } } #endif // DoStopPlugin can process events and there may be pending InDocCheckEvent // events which can drop in underneath us and destroy the instance we are // about to destroy. Make sure this doesn't happen via this temp ref ptr and // the !mInstanceOwner check above. nsRefPtr instOwner = mInstanceOwner; mInstanceOwner = nsnull; DoStopPlugin(instOwner, delayedStop); return NS_OK; } void nsObjectLoadingContent::NotifyContentObjectWrapper() { nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); nsCOMPtr doc = thisContent->GetDocument(); if (!doc) return; nsIScriptGlobalObject *sgo = doc->GetScopeObject(); if (!sgo) return; nsIScriptContext *scx = sgo->GetContext(); if (!scx) return; JSContext *cx = scx->GetNativeContext(); nsCOMPtr wrapper; nsContentUtils::XPConnect()-> GetWrappedNativeOfNativeObject(cx, sgo->GetGlobalJSObject(), thisContent, NS_GET_IID(nsISupports), getter_AddRefs(wrapper)); if (!wrapper) { // Nothing to do here if there's no wrapper for mContent. The proto // chain will be fixed appropriately when the wrapper is created. return; } JSObject *obj = nsnull; nsresult rv = wrapper->GetJSObject(&obj); if (NS_FAILED(rv)) return; nsHTMLPluginObjElementSH::SetupProtoChain(wrapper, cx, obj); } NS_IMETHODIMP nsObjectLoadingContent::PlayPlugin() { if (!nsContentUtils::IsCallerChrome()) return NS_OK; mShouldPlay = true; return LoadObject(mURI, true, mContentType, true); }