Bug 767633 - Part 2 - nsObjectLoadingContent should handle all initial streams for plugins. r=josh

This commit is contained in:
John Schoenick 2012-12-06 15:10:54 -08:00
parent 0c05d17225
commit 84f92e6c9e
6 changed files with 172 additions and 126 deletions

View File

@ -532,6 +532,35 @@ IsPluginEnabledForType(const nsCString& aMIMEType)
/// Member Functions
///
// Tedious syntax to create a plugin stream listener with checks and put it in
// mFinalListener
bool
nsObjectLoadingContent::MakePluginListener()
{
if (!mInstanceOwner) {
NS_NOTREACHED("expecting a spawned plugin");
return false;
}
nsRefPtr<nsPluginHost> pluginHost =
already_AddRefed<nsPluginHost>(nsPluginHost::GetInst());
if (!pluginHost) {
NS_NOTREACHED("No pluginHost");
return false;
}
NS_ASSERTION(!mFinalListener, "overwriting a final listener");
nsresult rv;
nsRefPtr<nsNPAPIPluginInstance> inst;
nsCOMPtr<nsIStreamListener> finalListener;
rv = mInstanceOwner->GetInstance(getter_AddRefs(inst));
NS_ENSURE_SUCCESS(rv, false);
rv = pluginHost->NewEmbeddedPluginStreamListener(mURI, inst,
getter_AddRefs(finalListener));
NS_ENSURE_SUCCESS(rv, false);
mFinalListener = finalListener;
return true;
}
bool
nsObjectLoadingContent::IsSupportedDocument(const nsCString& aMimeType)
{
@ -656,12 +685,18 @@ nsObjectLoadingContent::~nsObjectLoadingContent()
}
nsresult
nsObjectLoadingContent::InstantiatePluginInstance()
nsObjectLoadingContent::InstantiatePluginInstance(bool aIsLoading)
{
if (mInstanceOwner || mType != eType_Plugin || mIsLoading || mInstantiating) {
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);
@ -763,6 +798,19 @@ nsObjectLoadingContent::InstantiatePluginInstance()
}
}
}
// 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");
if (MakePluginListener()) {
// We intentionally ignore errors here, leaving it up to the plugin to
// deal with not having an initial stream.
OpenChannel();
}
}
}
return NS_OK;
@ -788,9 +836,6 @@ NS_IMETHODIMP
nsObjectLoadingContent::OnStartRequest(nsIRequest *aRequest,
nsISupports *aContext)
{
/// This must call LoadObject, even upon failure, to allow it to either
/// proceed with the load, or trigger fallback content.
SAMPLE_LABEL("nsObjectLoadingContent", "OnStartRequest");
LOG(("OBJLC [%p]: Channel OnStartRequest", this));
@ -801,6 +846,22 @@ nsObjectLoadingContent::OnStartRequest(nsIRequest *aRequest,
}
NS_ASSERTION(!mChannelLoaded, "mChannelLoaded set already?");
// If we already switched to type plugin, this channel can just be passed to
// the final listener.
if (mType == eType_Plugin) {
if (!mInstanceOwner || !mFinalListener) {
// We drop mChannel when stopping plugins, so something is wrong
NS_NOTREACHED("Opened a channel in plugin mode, but don't have a plugin");
return NS_BINDING_ABORTED;
}
return mFinalListener->OnStartRequest(aRequest, nullptr);
}
// Otherwise we should be state loading, and call LoadObject with the channel
if (mType != eType_Loading) {
NS_NOTREACHED("Should be type loading at this point");
return NS_BINDING_ABORTED;
}
NS_ASSERTION(!mFinalListener, "mFinalListener exists already?");
mChannelLoaded = true;
@ -1626,20 +1687,15 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
if (mType != eType_Null) {
int16_t contentPolicy = nsIContentPolicy::ACCEPT;
bool allowLoad = false;
// We check load policy before opening a channel, and process policy before
// going ahead with any final-type load
if (mType == eType_Loading) {
nsCOMPtr<nsIScriptSecurityManager> secMan =
nsContentUtils::GetSecurityManager();
if (!secMan) {
NS_NOTREACHED("No security manager?");
} else {
rv = secMan->CheckLoadURIWithPrincipal(thisContent->NodePrincipal(),
mURI, 0);
allowLoad = NS_SUCCEEDED(rv) && CheckLoadPolicy(&contentPolicy);
}
} else {
bool allowLoad = true;
// If mChannelLoaded is set we presumably already passed load policy
if (mURI && !mChannelLoaded) {
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);
}
@ -1715,6 +1771,9 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
// 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) {
@ -1730,14 +1789,6 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
case eType_Plugin:
{
if (mChannel) {
nsRefPtr<nsPluginHost> pluginHost =
already_AddRefed<nsPluginHost>(nsPluginHost::GetInst());
if (!pluginHost) {
NS_NOTREACHED("No pluginHost");
rv = NS_ERROR_UNEXPECTED;
break;
}
// Force a sync state change now, we need the frame created
NotifyStateChanged(oldType, oldState, true, aNotify);
oldType = mType;
@ -1751,9 +1802,8 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
break;
}
rv = pluginHost->NewEmbeddedPluginStreamListener(mURI, this, nullptr,
getter_AddRefs(finalListener));
// finalListener will receive OnStartRequest below
// We'll handle this below
doSpawnPlugin = true;
} else {
rv = AsyncStartPluginInstance();
}
@ -1848,7 +1898,8 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
CloseChannel();
}
// Don't try to initialize final listener below
// 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
@ -1858,41 +1909,48 @@ nsObjectLoadingContent::LoadObject(bool aNotify,
// Notify of our final state
NotifyStateChanged(oldType, oldState, false, aNotify);
NS_ENSURE_TRUE(mIsLoading, NS_OK);
//
// Pass load on to finalListener if loading with a channel.
// 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.
//
// We do this after finishing our notification such that re-entrance doesn't
// skip notifications or fire them out of order.
// Note that we ensured that we entered into LoadObject() from
// ::OnStartRequest above when loading with a channel.
//
if (!mIsLoading) {
LOG(("OBJLC [%p]: Re-entered before dispatching to final listener", this));
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)) {
// Plugins can continue to run even if their initial stream dies. Some
// plugins will even return failure codes to reject the stream, but expect
// to continue running, so ignore the error code. rv is thus the result of
// spawning the plugin above
if (NS_SUCCEEDED(rv) && MakePluginListener()) {
mFinalListener->OnStartRequest(mChannel, nullptr);
}
}
} else if (finalListener) {
NS_ASSERTION(mType != eType_Null && mType != eType_Loading,
"We should not have a final listener with a non-loaded type");
// Note that we always enter into LoadObject() from ::OnStartRequest when
// loading with a channel.
mSrcStreamLoading = true;
// Remove blocker on entering into instantiate
// (this is otherwise unset by the stack class)
mIsLoading = false;
mFinalListener = finalListener;
rv = finalListener->OnStartRequest(mChannel, nullptr);
mSrcStreamLoading = false;
if (NS_FAILED(rv)) {
// Failed to load new content, but since we've already notified of our
// transition, we can just Unload and call LoadFallback (which will notify
// again)
mType = eType_Null;
// This could *also* technically re-enter if OnStartRequest fails after
// spawning a plugin.
mIsLoading = true;
UnloadObject(false);
NS_ENSURE_TRUE(mIsLoading, NS_OK);
CloseChannel();
LoadFallback(fallbackType, true);
}
}
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;
@ -1922,13 +1980,13 @@ nsObjectLoadingContent::CloseChannel()
nsresult
nsObjectLoadingContent::OpenChannel()
{
nsCOMPtr<nsIContent> thisContent =
nsCOMPtr<nsIContent> thisContent =
do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
nsCOMPtr<nsIScriptSecurityManager> secMan =
nsContentUtils::GetSecurityManager();
NS_ASSERTION(thisContent, "must be a content");
nsIDocument* doc = thisContent->OwnerDoc();
NS_ASSERTION(doc, "No owner document?");
NS_ASSERTION(!mInstanceOwner && !mInstantiating,
"opening a new channel with already loaded content");
nsresult rv;
mChannel = nullptr;
@ -1938,6 +1996,9 @@ nsObjectLoadingContent::OpenChannel()
return NS_ERROR_NOT_AVAILABLE;
}
rv = secMan->CheckLoadURIWithPrincipal(thisContent->NodePrincipal(), mURI, 0);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsILoadGroup> group = doc->GetDocumentLoadGroup();
nsCOMPtr<nsIChannel> chan;
nsCOMPtr<nsIChannelPolicy> channelPolicy;

View File

@ -116,10 +116,13 @@ class nsObjectLoadingContent : public nsImageLoadingContent
}
/**
* Immediately instantiate a plugin instance. This is a no-op if
* mType != eType_Plugin or a plugin is already running.
* Immediately instantiate a plugin instance. This is a no-op if mType !=
* eType_Plugin or a plugin is already running.
*
* aIsLoading indicates that we are in the loading code, and we can bypass
* the mIsLoading check.
*/
nsresult InstantiatePluginInstance();
nsresult InstantiatePluginInstance(bool aIsLoading = false);
/**
* Notify this class the document state has changed
@ -180,12 +183,11 @@ class nsObjectLoadingContent : public nsImageLoadingContent
eSupportSVG = 1u << 3, // SVG is supported (image/svg+xml)
eSupportClassID = 1u << 4, // The classid attribute is supported
// Allows us to load a plugin if it matches a MIME type or file extension
// registered to a plugin without opening its specified URI first. Can
// result in launching plugins for URIs that return differing content
// types. Plugins without URIs may instantiate regardless.
// XXX(johns) this is our legacy behavior on <embed> tags, whereas object
// will always open a channel and check its MIME if a URI is present.
// If possible to get a *plugin* type from the type attribute *or* file
// extension, we can use that type and begin loading the plugin before
// opening a channel.
// A side effect of this is if the channel fails, the plugin is still
// running.
eAllowPluginSkipChannel = 1u << 5
};
@ -329,6 +331,12 @@ class nsObjectLoadingContent : public nsImageLoadingContent
*/
bool IsSupportedDocument(const nsCString& aType);
/**
* Gets the plugin instance and creates a plugin stream listener, assigning
* it to mFinalListener
*/
bool MakePluginListener();
/**
* Unloads all content and resets the object to a completely unloaded state
*
@ -412,8 +420,9 @@ class nsObjectLoadingContent : public nsImageLoadingContent
// The type of fallback content we're showing (see ObjectState())
FallbackType mFallbackType : 8;
// If true, the current load has finished opening a channel. Does not imply
// mChannel -- mChannelLoaded && !mChannel may occur for a load that failed
// If true, we have opened a channel as the listener and it has reached
// OnStartRequest. Does not get set for channels that are passed directly to
// the plugin listener.
bool mChannelLoaded : 1;
// Whether we are about to call instantiate on our frame. If we aren't,

View File

@ -3347,7 +3347,6 @@ nsPluginHost::StopPluginInstance(nsNPAPIPluginInstance* aInstance)
}
nsresult nsPluginHost::NewEmbeddedPluginStreamListener(nsIURI* aURI,
nsObjectLoadingContent *aContent,
nsNPAPIPluginInstance* aInstance,
nsIStreamListener **aStreamListener)
{
@ -3355,7 +3354,7 @@ nsresult nsPluginHost::NewEmbeddedPluginStreamListener(nsIURI* aURI,
NS_ENSURE_ARG_POINTER(aStreamListener);
nsRefPtr<nsPluginStreamListenerPeer> listener = new nsPluginStreamListenerPeer();
nsresult rv = listener->InitializeEmbedded(aURI, aInstance, aContent);
nsresult rv = listener->InitializeEmbedded(aURI, aInstance);
if (NS_FAILED(rv)) {
return rv;
}

View File

@ -199,7 +199,7 @@ public:
nsresult GetPlugin(const char *aMimeType, nsNPAPIPlugin** aPlugin);
nsresult NewEmbeddedPluginStreamListener(nsIURI* aURL, nsObjectLoadingContent *aContent,
nsresult NewEmbeddedPluginStreamListener(nsIURI* aURL,
nsNPAPIPluginInstance* aInstance,
nsIStreamListener **aStreamListener);

View File

@ -27,7 +27,7 @@
#include "nsNetUtil.h"
#include "nsPluginNativeWindow.h"
#include "sampler.h"
#include "nsObjectLoadingContent.h"
#include "nsPluginInstanceOwner.h"
#define MAGIC_REQUEST_CONTEXT 0x01020304
@ -346,39 +346,34 @@ nsresult nsPluginStreamListenerPeer::Initialize(nsIURI *aURL,
}
nsresult nsPluginStreamListenerPeer::InitializeEmbedded(nsIURI *aURL,
nsNPAPIPluginInstance* aInstance,
nsObjectLoadingContent *aContent)
nsNPAPIPluginInstance* aInstance)
{
#ifdef PLUGIN_LOGGING
nsAutoCString urlSpec;
aURL->GetSpec(urlSpec);
PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL,
("nsPluginStreamListenerPeer::InitializeEmbedded url=%s\n", urlSpec.get()));
PR_LogFlush();
#endif
// We have to have one or the other.
if (!aInstance && !aContent) {
// Not gonna work out
if (!aInstance) {
return NS_ERROR_FAILURE;
}
mURL = aURL;
if (aInstance) {
NS_ASSERTION(mPluginInstance == nullptr, "nsPluginStreamListenerPeer::InitializeEmbedded mPluginInstance != nullptr");
mPluginInstance = aInstance;
} else {
mContent = aContent;
}
NS_ASSERTION(mPluginInstance == nullptr, "nsPluginStreamListenerPeer::InitializeEmbedded mPluginInstance != nullptr");
mPluginInstance = aInstance;
mPendingRequests = 1;
mDataForwardToRequest = new nsHashtable(16, false);
if (!mDataForwardToRequest)
return NS_ERROR_FAILURE;
return NS_OK;
}
@ -497,16 +492,16 @@ nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request,
"Only our initial stream should be unknown!");
TrackRequest(request);
}
if (mHaveFiredOnStartRequest) {
return NS_OK;
}
mHaveFiredOnStartRequest = true;
nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
// deal with 404 (Not Found) HTTP response,
// just return, this causes the request to be ignored.
nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
@ -521,7 +516,7 @@ nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request,
mRequestFailed = true;
return NS_ERROR_FAILURE;
}
if (responseCode > 206) { // not normal
bool bWantsAllNetworkStreams = false;
@ -542,7 +537,7 @@ nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request,
}
}
}
// Get the notification callbacks from the channel and save it as
// week ref we'll use it in nsPluginStreamInfo::RequestRead() when
// we'll create channel for byte range request.
@ -550,15 +545,15 @@ nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request,
channel->GetNotificationCallbacks(getter_AddRefs(callbacks));
if (callbacks)
mWeakPtrChannelCallbacks = do_GetWeakReference(callbacks);
nsCOMPtr<nsILoadGroup> loadGroup;
channel->GetLoadGroup(getter_AddRefs(loadGroup));
if (loadGroup)
mWeakPtrChannelLoadGroup = do_GetWeakReference(loadGroup);
int64_t length;
rv = channel->GetContentLength(&length);
// it's possible for the server to not send a Content-Length.
// we should still work in this case.
if (NS_FAILED(rv) || length == -1) {
@ -574,48 +569,34 @@ nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request,
else {
mLength = length;
}
nsAutoCString aContentType; // XXX but we already got the type above!
rv = channel->GetContentType(aContentType);
if (NS_FAILED(rv))
return rv;
nsCOMPtr<nsIURI> aURL;
rv = channel->GetURI(getter_AddRefs(aURL));
if (NS_FAILED(rv))
return rv;
aURL->GetSpec(mURLSpec);
if (!aContentType.IsEmpty())
mContentType = aContentType;
#ifdef PLUGIN_LOGGING
PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NOISY,
("nsPluginStreamListenerPeer::OnStartRequest this=%p request=%p mime=%s, url=%s\n",
this, request, aContentType.get(), mURLSpec.get()));
PR_LogFlush();
#endif
// If we don't have an instance yet it means we weren't able to load
// a plugin previously because we didn't have the mimetype. Try again
// if we have a mime type now.
if (!mPluginInstance && mContent && !aContentType.IsEmpty()) {
nsObjectLoadingContent *olc = static_cast<nsObjectLoadingContent*>(mContent.get());
rv = olc->InstantiatePluginInstance();
if (NS_SUCCEEDED(rv)) {
rv = olc->GetPluginInstance(getter_AddRefs(mPluginInstance));
if (NS_FAILED(rv)) {
return rv;
}
}
}
// Set up the stream listener...
rv = SetUpStreamListener(request, aURL);
if (NS_FAILED(rv)) return rv;
return rv;
}

View File

@ -17,10 +17,8 @@
#include "nsNPAPIPluginInstance.h"
#include "nsIInterfaceRequestor.h"
#include "nsIChannelEventSink.h"
#include "nsIObjectLoadingContent.h"
class nsIChannel;
class nsObjectLoadingContent;
/**
* When a plugin requests opens multiple requests to the same URL and
@ -75,8 +73,7 @@ public:
nsNPAPIPluginStreamListener *aListener);
nsresult InitializeEmbedded(nsIURI *aURL,
nsNPAPIPluginInstance* aInstance,
nsObjectLoadingContent *aContent);
nsNPAPIPluginInstance* aInstance);
nsresult InitializeFullPage(nsIURI* aURL, nsNPAPIPluginInstance *aInstance);
@ -140,7 +137,6 @@ private:
nsCOMPtr<nsIURI> mURL;
nsCString mURLSpec; // Have to keep this member because GetURL hands out char*
nsCOMPtr<nsIObjectLoadingContent> mContent;
nsRefPtr<nsNPAPIPluginStreamListener> mPStreamListener;
// Set to true if we request failed (like with a HTTP response of 404)