From 49352588c2841f54bb034ca4d52a4d82c624c447 Mon Sep 17 00:00:00 2001 From: John Schoenick Date: Thu, 5 Jul 2012 14:07:46 -0700 Subject: [PATCH] Bug 745030 - Refactor nsObjectLoadingContent loading paths. r=josh,sr=jst --- .../base/public/nsIObjectLoadingContent.idl | 3 + content/base/src/nsObjectLoadingContent.cpp | 2003 ++++++++++------- content/base/src/nsObjectLoadingContent.h | 370 +-- .../html/content/src/nsHTMLObjectElement.cpp | 47 +- .../content/src/nsHTMLSharedObjectElement.cpp | 37 +- content/html/document/src/PluginDocument.cpp | 3 +- .../base/nsPluginStreamListenerPeer.cpp | 2 +- 7 files changed, 1407 insertions(+), 1058 deletions(-) diff --git a/content/base/public/nsIObjectLoadingContent.idl b/content/base/public/nsIObjectLoadingContent.idl index 2e1f346a313..02df0defdc7 100644 --- a/content/base/public/nsIObjectLoadingContent.idl +++ b/content/base/public/nsIObjectLoadingContent.idl @@ -24,6 +24,9 @@ interface nsIURI; [scriptable, uuid(e3e284a3-b4a8-49ef-af6b-c8c4a158db86)] interface nsIObjectLoadingContent : nsISupports { + /** + * See notes in nsObjectLoadingContent.h + */ const unsigned long TYPE_LOADING = 0; const unsigned long TYPE_IMAGE = 1; const unsigned long TYPE_PLUGIN = 2; diff --git a/content/base/src/nsObjectLoadingContent.cpp b/content/base/src/nsObjectLoadingContent.cpp index 9a7635f834e..f2cb92140a7 100644 --- a/content/base/src/nsObjectLoadingContent.cpp +++ b/content/base/src/nsObjectLoadingContent.cpp @@ -81,61 +81,68 @@ static PRLogModuleInfo* gObjectLog = PR_NewLogModule("objlc"); #define LOG(args) PR_LOG(gObjectLog, PR_LOG_DEBUG, args) #define LOG_ENABLED() PR_LOG_TEST(gObjectLog, PR_LOG_DEBUG) +/// +/// Runnables and helper classes +/// + class nsAsyncInstantiateEvent : public nsRunnable { public: - nsObjectLoadingContent *mContent; - nsAsyncInstantiateEvent(nsObjectLoadingContent* aContent) - : mContent(aContent) - { - static_cast(mContent)->AddRef(); - } + nsAsyncInstantiateEvent(nsObjectLoadingContent *aContent) + : mContent(aContent) {} - ~nsAsyncInstantiateEvent() - { - static_cast(mContent)->Release(); - } + ~nsAsyncInstantiateEvent() {} NS_IMETHOD Run(); + +private: + nsCOMPtr mContent; }; NS_IMETHODIMP nsAsyncInstantiateEvent::Run() { + nsObjectLoadingContent *objLC = + static_cast(mContent.get()); + // do nothing if we've been revoked - if (mContent->mPendingInstantiateEvent != this) { + if (objLC->mPendingInstantiateEvent != this) { return NS_OK; } - mContent->mPendingInstantiateEvent = nullptr; + objLC->mPendingInstantiateEvent = nullptr; - return mContent->SyncStartPluginInstance(); + return objLC->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(nsObjectLoadingContent *aContent) + : mContent(aContent) {} - InDocCheckEvent(nsIContent* aContent) - : mContent(aContent) - { - } - - ~InDocCheckEvent() - { - } + ~InDocCheckEvent() {} NS_IMETHOD Run(); + +private: + nsCOMPtr mContent; }; NS_IMETHODIMP InDocCheckEvent::Run() { - if (!mContent->IsInDoc()) { - nsCOMPtr olc = do_QueryInterface(mContent); - if (olc) { - olc->StopPluginInstance(); - } + nsObjectLoadingContent *objLC = + static_cast(mContent.get()); + + nsCOMPtr content = + do_QueryInterface(static_cast(objLC)); + + if (!content->IsInDoc()) { + nsObjectLoadingContent *objLC = + static_cast(mContent.get()); + objLC->mType = nsObjectLoadingContent::eType_Null; + objLC->UnloadContent(); + objLC->StopPluginInstance(); } return NS_OK; } @@ -161,7 +168,7 @@ public: NS_IMETHODIMP nsPluginErrorEvent::Run() { - LOG(("OBJLC []: Firing plugin not found event for content %p\n", + LOG(("OBJLC [%p]: Firing plugin not found event\n", mContent.get())); nsString type; switch (mState) { @@ -229,7 +236,7 @@ public: NS_IMETHODIMP nsPluginCrashedEvent::Run() { - LOG(("OBJLC []: Firing plugin crashed event for content %p\n", + LOG(("OBJLC [%p]: Firing plugin crashed event\n", mContent.get())); nsCOMPtr domDoc = @@ -251,7 +258,7 @@ nsPluginCrashedEvent::Run() event->InitEvent(NS_LITERAL_STRING("PluginCrashed"), true, true); event->SetTrusted(true); event->GetInternalNSEvent()->flags |= NS_EVENT_FLAG_ONLY_CHROME_DISPATCH; - + nsCOMPtr variant; // add a "pluginDumpID" property to this event @@ -353,7 +360,8 @@ nsStopPluginRunnable::Run() 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); + nsresult rv = mTimer->InitWithCallback(this, 100, + nsITimer::TYPE_ONE_SHOT); if (NS_SUCCEEDED(rv)) { return rv; } @@ -375,7 +383,7 @@ class AutoNotifier { public: AutoNotifier(nsObjectLoadingContent* aContent, bool aNotify) : mContent(aContent), mNotify(aNotify) { - mOldType = aContent->Type(); + mOldType = aContent->mType; mOldState = aContent->ObjectState(); } ~AutoNotifier() { @@ -391,7 +399,7 @@ class AutoNotifier { NS_ASSERTION(mNotify, "Should not notify when notify=false"); mContent->NotifyStateChanged(mOldType, mOldState, true, true); - mOldType = mContent->Type(); + mOldType = mContent->mType; mOldState = mContent->ObjectState(); } @@ -403,8 +411,8 @@ class AutoNotifier { }; /** - * A class that will automatically fall back if a |rv| variable has a failure - * code when this class is destroyed. It does not notify. + * Depending on the state of the |rv| variable, will either trigger loading + * fallback or mark the load as complete */ class AutoFallback { public: @@ -417,6 +425,9 @@ class AutoFallback { if (mPluginState != ePluginOtherState) { mContent->mFallbackReason = mPluginState; } + } else { + // Otherwise, mark the load as complete + mContent->mLoaded = true; } } @@ -425,7 +436,8 @@ class AutoFallback { * a specific failure that should be passed on. */ void SetPluginState(PluginSupportState aState) { - NS_ASSERTION(aState != ePluginOtherState, "Should not be setting ePluginOtherState"); + NS_ASSERTION(aState != ePluginOtherState, + "Should not be setting ePluginOtherState"); mPluginState = aState; } private: @@ -434,19 +446,89 @@ class AutoFallback { PluginSupportState mPluginState; }; -/** - * A class that automatically sets mInstantiating to false when it goes - * out of scope. - */ +// 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: - AutoSetInstantiatingToFalse(nsObjectLoadingContent* objlc) : mContent(objlc) {} - ~AutoSetInstantiatingToFalse() { mContent->mInstantiating = false; } - private: - nsObjectLoadingContent* mContent; +public: + AutoSetInstantiatingToFalse(nsObjectLoadingContent *aContent) + : mContent(aContent) {} + ~AutoSetInstantiatingToFalse() { mContent->mInstantiating = false; } +private: + nsObjectLoadingContent* mContent; }; -// helper functions +// Sets a object's mInstantiating bit to false when destroyed +class AutoSetLoadingToFalse { +public: + AutoSetLoadingToFalse(nsObjectLoadingContent *aContent) + : mContent(aContent) {} + ~AutoSetLoadingToFalse() { mContent->mIsLoading = false; } +private: + nsObjectLoadingContent* mContent; +}; + +/// +/// Helper functions +/// + +static bool +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 +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 == 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 bool IsSupportedImage(const nsCString& aMimeType) { @@ -460,77 +542,6 @@ IsSupportedImage(const nsCString& aMimeType) return NS_SUCCEEDED(rv) && supported; } -nsresult nsObjectLoadingContent::IsPluginEnabledForType(const nsCString& aMIMEType) -{ - nsCOMPtr pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); - nsPluginHost *pluginHost = static_cast(pluginHostCOM.get()); - if (!pluginHost) { - return NS_ERROR_FAILURE; - } - - nsresult rv = pluginHost->IsPluginEnabledForType(aMIMEType.get()); - - // Check to see if the plugin is disabled before deciding if it - // should be in the "click to play" state, since we only want to - // display "click to play" UI for enabled plugins. - if (NS_FAILED(rv)) { - return rv; - } - - if (!pluginHost->IsPluginClickToPlayForType(aMIMEType.get())) { - mCTPPlayable = true; - } - - if (!mCTPPlayable) { - nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); - MOZ_ASSERT(thisContent); - nsIDocument* ownerDoc = thisContent->OwnerDoc(); - - nsCOMPtr window = ownerDoc->GetWindow(); - if (!window) { - return NS_ERROR_FAILURE; - } - nsCOMPtr topWindow; - rv = window->GetTop(getter_AddRefs(topWindow)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr topDocument; - rv = topWindow->GetDocument(getter_AddRefs(topDocument)); - NS_ENSURE_SUCCESS(rv, rv); - nsCOMPtr topDoc = do_QueryInterface(topDocument); - - nsCOMPtr permissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); - NS_ENSURE_SUCCESS(rv, rv); - bool allowPerm = 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 (!nsContentUtils::IsSystemPrincipal(topDoc->NodePrincipal())) { - PRUint32 permission; - rv = permissionManager->TestPermissionFromPrincipal(topDoc->NodePrincipal(), - "plugins", - &permission); - NS_ENSURE_SUCCESS(rv, rv); - allowPerm = permission == nsIPermissionManager::ALLOW_ACTION; - } - - PRUint32 state; - rv = pluginHost->GetBlocklistStateForType(aMIMEType.get(), &state); - NS_ENSURE_SUCCESS(rv, rv); - - if (allowPerm && - state != nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE && - state != nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) { - mCTPPlayable = true; - } else { - return NS_ERROR_PLUGIN_CLICKTOPLAY; - } - } - - return NS_OK; -} - static void GetExtensionFromURI(nsIURI* uri, nsCString& ext) { @@ -562,9 +573,11 @@ bool nsObjectLoadingContent::IsPluginEnabledByExtension(nsIURI* uri, nsCString& return false; } - nsCOMPtr pluginHostCOM(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID)); - nsPluginHost *pluginHost = static_cast(pluginHostCOM.get()); + nsRefPtr pluginHost = + already_AddRefed(nsPluginHost::GetInst()); + if (!pluginHost) { + NS_NOTREACHED("No pluginhost"); return false; } @@ -585,8 +598,130 @@ bool nsObjectLoadingContent::IsPluginEnabledByExtension(nsIURI* uri, nsCString& } } +/// +/// Member Functions +/// + nsresult -nsObjectLoadingContent::BindToTree(nsIDocument* aDocument, nsIContent* /*aParent*/, +nsObjectLoadingContent::IsPluginEnabledForType(const nsCString& aMIMEType) +{ + nsRefPtr pluginHost = + already_AddRefed(nsPluginHost::GetInst()); + + if (!pluginHost) { + NS_NOTREACHED("No pluginhost"); + return false; + } + + nsresult rv = pluginHost->IsPluginEnabledForType(aMIMEType.get()); + + // Check to see if the plugin is disabled before deciding if it + // should be in the "click to play" state, since we only want to + // display "click to play" UI for enabled plugins. + if (NS_FAILED(rv)) { + return rv; + } + + if (!pluginHost->IsPluginClickToPlayForType(aMIMEType.get())) { + mCTPPlayable = true; + } + + if (!mCTPPlayable) { + nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); + MOZ_ASSERT(thisContent); + nsIDocument* ownerDoc = thisContent->OwnerDoc(); + + nsCOMPtr window = ownerDoc->GetWindow(); + if (!window) { + return NS_ERROR_FAILURE; + } + nsCOMPtr topWindow; + rv = window->GetTop(getter_AddRefs(topWindow)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr topDocument; + rv = topWindow->GetDocument(getter_AddRefs(topDocument)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr topDoc = do_QueryInterface(topDocument); + + nsCOMPtr permissionManager = + do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + bool allowPerm = 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 (!nsContentUtils::IsSystemPrincipal(topDoc->NodePrincipal())) { + PRUint32 permission; + rv = permissionManager->TestPermissionFromPrincipal(topDoc->NodePrincipal(), + "plugins", + &permission); + NS_ENSURE_SUCCESS(rv, rv); + allowPerm = permission == nsIPermissionManager::ALLOW_ACTION; + } + + PRUint32 state; + rv = pluginHost->GetBlocklistStateForType(aMIMEType.get(), &state); + NS_ENSURE_SUCCESS(rv, rv); + + if (allowPerm && + state != nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE && + state != nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) { + mCTPPlayable = true; + } else { + return NS_ERROR_PLUGIN_CLICKTOPLAY; + } + } + + return NS_OK; +} + +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; +} + +nsresult +nsObjectLoadingContent::BindToTree(nsIDocument* aDocument, + nsIContent* /*aParent*/, nsIContent* /*aBindingParent*/, bool /*aCompileEventHandlers*/) { @@ -599,37 +734,75 @@ nsObjectLoadingContent::BindToTree(nsIDocument* aDocument, nsIContent* /*aParent void nsObjectLoadingContent::UnbindFromTree(bool /*aDeep*/, bool /*aNullParent*/) { - nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); + nsCOMPtr thisContent = + do_QueryInterface(static_cast(this)); MOZ_ASSERT(thisContent); nsIDocument* ownerDoc = thisContent->OwnerDoc(); ownerDoc->RemovePlugin(this); + + if (mType == eType_Plugin) { + // 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 + nsCOMPtr event = new InDocCheckEvent(this); + + nsCOMPtr appShell = do_GetService(kAppShellCID); + if (appShell) { + appShell->RunInStableState(event); + } + } else { + /// XXX(johns): The implementation for GenericFrame notes that ideally we + /// would keep the docshell around, but trash the frameloader + UnloadContent(); + mType = eType_Null; + } + } nsObjectLoadingContent::nsObjectLoadingContent() : mPendingInstantiateEvent(nullptr) , mChannel(nullptr) , mType(eType_Loading) + , mLoaded(false) + , mChannelLoaded(false) , mInstantiating(false) , mUserDisabled(false) , mSuppressed(false) , mNetworkCreated(true) - , mIsStopping(false) - , mSrcStreamLoading(false) - , mFallbackReason(ePluginOtherState) , mCTPPlayable(false) - , mActivated(false) {} + , mActivated(false) + , mIsStopping(false) + , mIsLoading(false) + , mSrcStreamLoading(false) + , mFallbackReason(ePluginOtherState) {} nsObjectLoadingContent::~nsObjectLoadingContent() { - DestroyImageLoadingContent(); + // Should have been unbound from the tree at this point, and InDocCheckEvent + // keeps us alive if (mFrameLoader) { + NS_NOTREACHED("Should not be tearing down frame loaders at this point"); mFrameLoader->Destroy(); } + if (mInstanceOwner) { + // This is especially bad as delayed stop will try to hold on to this + // object... + NS_NOTREACHED("Should not be tearing down a plugin at this point!"); + StopPluginInstance(); + } + DestroyImageLoadingContent(); } nsresult -nsObjectLoadingContent::InstantiatePluginInstance(const char* aMimeType, nsIURI* aURI) +nsObjectLoadingContent::InstantiatePluginInstance() { + if (!mLoaded || mType != eType_Plugin) { + LOG(("OBJLC [%p]: Refusing to instantiate incomplete plugin, " + "loaded %u, type %u", this, mLoaded, mType)); + return NS_OK; + } + // Don't do anything if we already have an active instance. if (mInstanceOwner) { return NS_OK; @@ -646,35 +819,31 @@ nsObjectLoadingContent::InstantiatePluginInstance(const char* aMimeType, nsIURI* // 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(thisContent, getter_AddRefs(baseURI)); - aURI = baseURI; - } + nsCOMPtr thisContent = + do_QueryInterface(static_cast(this)); // Flush layout so that the plugin is initialized with the latest information. nsIDocument* doc = thisContent->GetCurrentDoc(); if (!doc) { return NS_ERROR_FAILURE; } if (!doc->IsActive()) { - NS_ERROR("Shouldn't be calling InstantiatePluginInstance in an inactive document"); + NS_ERROR("Shouldn't be calling " + "InstantiatePluginInstance in an inactive document"); 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; + nsRefPtr pluginHost = + already_AddRefed(nsPluginHost::GetInst()); + + if (!pluginHost) { + NS_NOTREACHED("No pluginhost"); + return false; } - if (!pluginHost->IsPluginClickToPlayForType(aMimeType)) { + if (!pluginHost->IsPluginClickToPlayForType(mContentType.get())) { mCTPPlayable = true; } @@ -698,13 +867,16 @@ nsObjectLoadingContent::InstantiatePluginInstance(const char* aMimeType, nsIURI* if (fullPageMode) { nsCOMPtr stream; - rv = pluginHost->InstantiateFullPagePluginInstance(aMimeType, aURI, this, - getter_AddRefs(mInstanceOwner), getter_AddRefs(stream)); + rv = pluginHost->InstantiateFullPagePluginInstance(mContentType.get(), + mURI.get(), this, + getter_AddRefs(mInstanceOwner), + getter_AddRefs(stream)); if (NS_SUCCEEDED(rv)) { pDoc->SetStreamListener(stream); } } else { - rv = pluginHost->InstantiateEmbeddedPluginInstance(aMimeType, aURI, this, + rv = pluginHost->InstantiateEmbeddedPluginInstance(mContentType.get(), + mURI.get(), this, getter_AddRefs(mInstanceOwner)); } @@ -723,16 +895,17 @@ nsObjectLoadingContent::InstantiatePluginInstance(const char* aMimeType, nsIURI* GetPluginInstance(getter_AddRefs(pluginInstance)); if (pluginInstance) { nsCOMPtr pluginTag; - pluginHost->GetPluginTagForInstance(pluginInstance, getter_AddRefs(pluginTag)); - + pluginHost->GetPluginTagForInstance(pluginInstance, + getter_AddRefs(pluginTag)); + nsCOMPtr blocklist = - do_GetService("@mozilla.org/extensions/blocklist;1"); + 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); + FirePluginError(ePluginOutdated); } } @@ -747,7 +920,8 @@ nsObjectLoadingContent::NotifyOwnerDocumentActivityChanged() return; } - nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); + nsCOMPtr thisContent = + do_QueryInterface(static_cast(this)); nsIDocument* ownerDoc = thisContent->OwnerDoc(); if (!ownerDoc->IsActive()) { StopPluginInstance(); @@ -759,261 +933,43 @@ 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)); + if (aRequest != mChannel || !aRequest) { - // This is a bit of an edge case - happens when a new load starts before the - // previous one got here + // happens when a new load starts before the previous one got here return NS_BINDING_ABORTED; } - AutoNotifier notifier(this, true); + NS_ASSERTION(!mChannelLoaded, "mChannelLoaded set already?"); + NS_ASSERTION(!mFinalListener, "mFinalListener exists already?"); - if (!IsSuccessfulRequest(aRequest)) { - LOG(("OBJLC [%p]: OnStartRequest: Request failed\n", this)); - Fallback(false); - return NS_BINDING_ABORTED; - } + mChannelLoaded = true; 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 ignore the channel type if one of the following is true: - // - // 1) The channel type is application/octet-stream or binary/octet-stream - // and we have a type hint (in mContentType) and the type hint is not a - // document type. - // 2) Our type hint is a type that we support with a plugin - // (where "support" means it is enabled or it is click-to-play) - // and this object loading content has the capability to load a plugin. - // We have to be careful here - there might be a plugin that supports - // image types, so make sure the type of the content is not an image. - bool isOctetStream = (channelType.EqualsASCII(APPLICATION_OCTET_STREAM) || - channelType.EqualsASCII(BINARY_OCTET_STREAM)); - ObjectType typeOfContent = GetTypeOfContent(mContentType); - bool caseOne = (isOctetStream && - !mContentType.IsEmpty() && - typeOfContent != eType_Document); - nsresult pluginState = IsPluginEnabledForType(mContentType); - bool pluginSupported = (NS_SUCCEEDED(pluginState) || - pluginState == NS_ERROR_PLUGIN_CLICKTOPLAY); - PRUint32 caps = GetCapabilities(); - bool caseTwo = (pluginSupported && - (caps & eSupportPlugins) && - typeOfContent != eType_Image && - typeOfContent != eType_Document); - if (caseOne || caseTwo) { - // 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 (IsSuccessfulRequest(aRequest)) { + chan->GetURI(getter_AddRefs(uri)); + } + if (!uri) { + 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; } - if (mContentType.EqualsASCII(APPLICATION_OCTET_STREAM) || - mContentType.EqualsASCII(BINARY_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, - nullptr, //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->NewEmbeddedPluginStreamListener(uri, this, nullptr, - 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; + return LoadObject(true, false, aRequest); } NS_IMETHODIMP @@ -1053,10 +1009,14 @@ nsObjectLoadingContent::OnDataAvailable(nsIRequest *aRequest, } if (mFinalListener) { - return mFinalListener->OnDataAvailable(aRequest, aContext, aInputStream, aOffset, aCount); + return mFinalListener->OnDataAvailable(aRequest, aContext, aInputStream, + aOffset, aCount); } - // Abort this load if we have no listener here + // We shouldn't have a connected channel with no final listener + NS_NOTREACHED("Got data for channel with no connected final listener"); + mChannel = nullptr; + return NS_ERROR_UNEXPECTED; } @@ -1064,8 +1024,7 @@ nsObjectLoadingContent::OnDataAvailable(nsIRequest *aRequest, NS_IMETHODIMP nsObjectLoadingContent::GetFrameLoader(nsIFrameLoader** aFrameLoader) { - *aFrameLoader = mFrameLoader; - NS_IF_ADDREF(*aFrameLoader); + NS_IF_ADDREF(*aFrameLoader = mFrameLoader); return NS_OK; } @@ -1083,7 +1042,6 @@ nsObjectLoadingContent::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoader) return NS_ERROR_NOT_IMPLEMENTED; } -// nsIObjectLoadingContent NS_IMETHODIMP nsObjectLoadingContent::GetActualType(nsACString& aType) { @@ -1101,24 +1059,25 @@ nsObjectLoadingContent::GetDisplayedType(PRUint32* aType) 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; + if (mType == eType_Plugin) { + if (!mInstanceOwner) { + // We have successfully set ourselves up in LoadObject, but not spawned an + // instance due to a lack of a frame. + 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()); } - - // 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; } @@ -1239,43 +1198,7 @@ nsObjectLoadingContent::ObjectState() const 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(thisContent, 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); + return NS_EVENT_STATE_LOADING; } void @@ -1287,266 +1210,739 @@ nsObjectLoadingContent::UpdateFallbackState(nsIContent* aContent, PluginSupportState state = GetPluginSupportState(aContent, aTypeHint); if (state != ePluginOtherState) { fallback.SetPluginState(state); - FirePluginError(aContent, state); + FirePluginError(state); } } -nsresult -nsObjectLoadingContent::LoadObject(nsIURI* aURI, - bool aNotify, - const nsCString& aTypeHint, - bool aForceLoad) +// Helper to call CheckURILoad on URI -> BaseURI and BaseURI -> Origin +bool nsObjectLoadingContent::CheckObjectURIs(PRInt16 *aContentPolicy, + PRInt32 aContentPolicyType) { - // 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(); + nsCOMPtr thisContent = + do_QueryInterface(static_cast(this)); + NS_ASSERTION(thisContent, "Must be an instance of content"); + + nsCOMPtr docBaseURI = thisContent->GetBaseURI(); + + // Must have these to load + if (!aContentPolicy || !mBaseURI) { + return false; + } + + bool ret; + if (!URIEquals(mBaseURI, docBaseURI)) { + // If our object sets a new baseURI, make sure that base URI could be + // loaded by the document + ret = CheckURILoad(mBaseURI, aContentPolicy, aContentPolicyType); + if (!ret) { + return false; } } - // Need to revoke any potentially pending instantiate events - if (mType == eType_Plugin && mPendingInstantiateEvent) { - mPendingInstantiateEvent = nullptr; + if (mURI) { + return CheckURILoad(mURI, aContentPolicy, aContentPolicyType); } - AutoNotifier notifier(this, aNotify); + return true; +} - mUserDisabled = mSuppressed = false; +bool nsObjectLoadingContent::CheckURILoad(nsIURI *aURI, + PRInt16 *aContentPolicy, + PRInt32 aContentPolicyType) +{ + nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager(); + NS_ASSERTION(secMan, "No security manager!?"); - mURI = aURI; - mContentType = aTypeHint; - - nsCOMPtr thisContent = + nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); - NS_ASSERTION(thisContent, "must be a content"); + NS_ASSERTION(thisContent, "Must be an instance of content"); + + nsCOMPtr docBaseURI = thisContent->GetBaseURI(); nsIDocument* doc = thisContent->OwnerDoc(); - if (doc->IsBeingUsedAsImage()) { + nsresult rv = + secMan->CheckLoadURIWithPrincipal(thisContent->NodePrincipal(), aURI, 0); + NS_ENSURE_SUCCESS(rv, false); + + PRInt16 shouldLoad = nsIContentPolicy::ACCEPT; // default permit + rv = NS_CheckContentLoadPolicy(aContentPolicyType, + aURI, + doc->NodePrincipal(), + static_cast(this), + mContentType, + nullptr, //extra + &shouldLoad, + nsContentUtils::GetContentPolicy(), + secMan); + NS_ENSURE_SUCCESS(rv, false); + if (aContentPolicy) { + *aContentPolicy = shouldLoad; + } + if (NS_CP_REJECTED(shouldLoad)) { + return false; + } + return true; +} + +nsObjectLoadingContent::ParameterUpdateFlags +nsObjectLoadingContent::UpdateObjectParameters() +{ + nsCOMPtr thisContent = + do_QueryInterface(static_cast(this)); + NS_ASSERTION(thisContent, "Must be an instance of content"); + + PRUint32 caps = GetCapabilities(); + LOG(("OBJLC [%p]: Updating object parameters", this)); + + nsresult rv; + nsCAutoString newMime; + nsCOMPtr newURI; + nsCOMPtr 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 (thisContent->NodeInfo()->Equals(nsGkAtoms::applet)) { + newMime.AssignLiteral("application/x-java-vm"); + } else { + nsAutoString typeAttr; + thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, typeAttr); + if (!typeAttr.IsEmpty()) { + CopyUTF16toUTF8(typeAttr, newMime); + } + } + + /// + /// classID + /// + + bool usingClassID = false; + if (caps & eSupportClassID) { + nsAutoString classIDAttr; + thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::classid, classIDAttr); + if (!classIDAttr.IsEmpty()) { + usingClassID = true; + if (NS_FAILED(TypeForClassID(classIDAttr, newMime))) { + // XXX(johns): Our de-facto behavior since forever was to refuse to load + // Objects who don't have a classid we support, regardless of other type + // or uri info leads to a valid plugin. + newMime.Assign(""); + stateInvalid = true; + } + } + } + + /// + /// Codebase + /// + + nsAutoString codebaseStr; + nsCOMPtr docBaseURI = thisContent->GetBaseURI(); + thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::codebase, codebaseStr); + if (codebaseStr.IsEmpty() && thisContent->NodeInfo()->Equals(nsGkAtoms::applet)) { + // bug 406541 + // NOTE we send the full absolute URI resolved here to java in + // pluginInstanceOwner to avoid disagreements between parsing of + // relative URIs. We need to mimic java's quirks here to make that + // not break things. + codebaseStr.AssignLiteral("/"); // Java resolves codebase="" as "/" + // XXX(johns) This doesn't catch the case of "file:" which java would + // interpret as "file:///" but we would interpret as this document's URI + // but with a changed scheme. + } + + if (!codebaseStr.IsEmpty()) { + rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(newBaseURI), + codebaseStr, + thisContent->OwnerDoc(), + docBaseURI); + if (NS_SUCCEEDED(rv)) { + NS_TryToSetImmutable(newBaseURI); + } else { + // Malformed URI + LOG(("OBJLC [%p]: Could not parse plugin's codebase as a URI, " + "will use document baseURI instead", this)); + } + } + + // Otherwise, use normal document baseURI + if (!newBaseURI) { + newBaseURI = docBaseURI; + } + + /// + /// URI + /// + + nsAutoString uriStr; + // Different elements keep this in various locations + if (thisContent->NodeInfo()->Equals(nsGkAtoms::object)) { + thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::data, uriStr); + } else if (thisContent->NodeInfo()->Equals(nsGkAtoms::embed)) { + thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::src, uriStr); + } else if (thisContent->NodeInfo()->Equals(nsGkAtoms::applet)) { + // Applet tags have no URI, and their 'code=' parameter is not parsed as one + } else { + NS_NOTREACHED("Unrecognized plugin-loading tag"); + } + + // 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, + thisContent->OwnerDoc(), + newBaseURI); + if (NS_SUCCEEDED(rv)) { + NS_TryToSetImmutable(newURI); + } else { + stateInvalid = true; + } + } + + /// + /// 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 (mChannel && useChannel) { + nsCString channelType; + rv = mChannel->GetContentType(channelType); + if (NS_FAILED(rv)) { + NS_NOTREACHED("GetContentType failed"); + stateInvalid = true; + channelType.Assign(""); + } + + 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)) { + NS_NOTREACHED("NS_GetFinalChannelURI failure"); + stateInvalid = true; + } + + // The channel type overrides the guessed / provided type, except when: + // + // 1) If the channel returns a binary stream type, and we have a type hint + // for a non-document (we never want to display binary-as-document), + // use our type hint instead. + // 2) Our type hint is a type that we support with a plugin, ignore the + // server's type + // + // XXX(johns): HTML5's "typesmustmatch" attribute would need to be + // honored here if implemented + bool oldPlay = mCTPPlayable; + mCTPPlayable = true; + ObjectType typeHint = newMime.IsEmpty() ? eType_Null + : GetTypeOfContent(newMime); + mCTPPlayable = oldPlay; + + bool caseOne = binaryChannelType + && typeHint != eType_Null + && typeHint != eType_Document; + bool caseTwo = typeHint == eType_Plugin; + if (caseOne || caseTwo) { + // 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(newMime, typeHint, dummy); + if (!typeHint.IsEmpty()) { + mChannel->SetContentType(typeHint); + } + } else if (binaryChannelType + && IsPluginEnabledByExtension(newURI, newMime)) { + mChannel->SetContentType(newMime); + } else { + newMime = channelType; + } + } + + bool isJava = nsPluginHost::IsJavaMIMEType(newMime.get()); + if (useChannel && (!mChannel || isJava)) { + // Sanity checks + // - Java does not load with a channel, and being java retroactively changes + // how we may have interpreted the codebase to construct this URI above. + // Because the behavior here is more or less undefined, play it safe and + // reject the load. + // + // - (useChannel && !mChannel) is true if a channel was opened but was + // subsequently invalidated + // in that case. + stateInvalid = true; + } + + /// + /// Determine final type + /// + // 1) If we have attempted channel load, or set stateInvalid above, the type + // is always null (fallback) + // 2) Otherwise, If we have a loaded channel, we grabbed its mimeType above, + // use that type. + // 3) Otherwise, See if we can load this as a plugin without a channel + // (image/document types always need a channel). + // - If we have indication this is a plugin (mime, extension, or classID) + // AND: + // - We have eAllowPluginSkipChannel OR + // - We have no URI in the first place OR + // - We're loading based on classID + // XXX(johns): Legacy behavior is to skip channel loading if we have + // a classID. I don't know why. + // 3) Otherwise, if we have a URI, set type to loading to indicate + // we'd need a channel to proceed. + // 4) Otherwise, type null to indicate unloadable content (fallback) + // + // XXX(johns): tags both support URIs and have + // eAllowPluginSkipChannel, meaning it is possible that we have a URI, but + // are not going to open a channel for it. The old objLC code did this (in a + // less obviously-intended way), so it's probably best not to change our + // behavior at this point. + // We ALSO skip channel loading for objects whose type is found by ClassID + // (We only support a tiny subset of classid: java and ActiveX, above) + // + + if (stateInvalid) { + newType = eType_Null; + } else if (useChannel) { + // If useChannel is set above, we considered it in setting newMime + newType = GetTypeOfContent(newMime); + LOG(("OBJLC [%p]: Using channel type", this)); + } else if (((caps & eAllowPluginSkipChannel) || !newURI || usingClassID) && + (GetTypeOfContent(newMime) == eType_Plugin)) { + newType = eType_Plugin; + LOG(("OBJLC [%p]: Skipping loading channel, type plugin", this)); + } else if (newURI) { + // We could potentially load this if we opened a channel on mURI, indicate + // This by leaving type as loading + newType = eType_Loading; + } else { + // Unloadable - no URI, and no plugin type. Non-plugin types (images, + // documents) always load with a channel. + newType = eType_Null; + } + + /// + /// Update changed values + /// + + if (newType != mType) { + retval = (ParameterUpdateFlags)(retval | eParamStateChanged); + LOG(("OBJLC [%p]: Type changed from %u -> %u", this, mType, newType)); + mType = newType; + } + + if (!URIEquals(mBaseURI, newBaseURI)) { + if (isJava) { + // Java bases its class loading on the base URI, so we consider the state + // to have changed if this changes. If the object is using a relative URI, + // mURI will have changed below regardless + retval = (ParameterUpdateFlags)(retval | eParamStateChanged); + } + 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; + } + + if (mContentType != newMime) { + retval = (ParameterUpdateFlags)(retval | eParamStateChanged); + LOG(("OBJLC [%p]: Object effective mime type changed (%s -> %s)", this, mContentType.get(), newMime.get())); + mContentType = newMime; + } + + return retval; +} + +// 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 thisContent = + do_QueryInterface(static_cast(this)); + NS_ASSERTION(thisContent, "must be a content"); + nsIDocument* doc = thisContent->OwnerDoc(); + + // 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; } - // 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)); + // Sanity check + if (!thisContent->IsInDoc()) { + NS_NOTREACHED("LoadObject called while not bound to a document"); + return NS_ERROR_UNEXPECTED; + } + // If our type changes, e.g. from document -> plugin, we'll need to do frame + // recreation, so track this + ObjectType oldType = mType; + + AutoNotifier notifier(this, aNotify); + + 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; + } + + 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)); + + // 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 subseqent load + // with no subsequently changed state will be a no-op. + mIsLoading = true; + AutoSetLoadingToFalse reentryCheck(this); + + // Unload existing content, keeping in mind stopping plugins might spin the + // event loop + UnloadContent(); + StopPluginInstance(); + if (!mIsLoading) { + // The event loop must've spun and re-entered into LoadObject, which + // finished the load + return NS_OK; + } + + 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 (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 + NS_NOTREACHED("Loading with a channel, but state doesn't make sense"); + return NS_OK; + } + + // We need to notify of new state now that we've trashed old content + // The fallback variable MUST be declared after the notifier variable. Do NOT + // change the order of the declarations! + nsresult rv = NS_ERROR_UNEXPECTED; + AutoFallback fallback(this, &rv); + + if (mType == eType_Null) { + // No type found, update our fallback state (for plugins) and set rv to + // trigger AutoFallback + UpdateFallbackState(thisContent, fallback, mContentType); + rv = NS_ERROR_FAILURE; + return NS_OK; + } + + /// + /// Security checks + /// + + // NOTE For eType_Loading we'll try all three types, as we want to go ahead + // with the channel if it could be any acceptable type. This type is + // passed to OpenChannel() as the LoadType. We pass through LoadObject + // again once the channel is opened and we're actually loading, so if + // the final URI doesn't pass the now-known type, we'll abort. + bool allowLoad = false; + PRInt32 policyType; + PRInt16 contentPolicy = nsIContentPolicy::ACCEPT; + if (mType == eType_Image || mType == eType_Loading) { + policyType = nsIContentPolicy::TYPE_IMAGE; + allowLoad = CheckObjectURIs(&contentPolicy, policyType); + } + if (!allowLoad && (mType == eType_Document || mType == eType_Loading)) { + contentPolicy = nsIContentPolicy::ACCEPT; + policyType = nsIContentPolicy::TYPE_SUBDOCUMENT; + allowLoad = CheckObjectURIs(&contentPolicy, policyType); + } + if (!allowLoad && (mType == eType_Plugin || mType == eType_Loading)) { + contentPolicy = nsIContentPolicy::ACCEPT; + policyType = nsIContentPolicy::TYPE_OBJECT; + allowLoad = CheckObjectURIs(&contentPolicy, policyType); + } + + // Load denied, switch to fallback and set disabled/suppressed if applicable + if (!allowLoad) { + mType = eType_Null; + mUserDisabled = mSuppressed = false; + if (contentPolicy == nsIContentPolicy::REJECT_TYPE) { + mUserDisabled = true; + } else if (contentPolicy == nsIContentPolicy::REJECT_SERVER) { + mSuppressed = true; + } + rv = NS_ERROR_FAILURE; + return NS_OK; + } + + // Sanity check: We shouldn't have any loaded resources, pending events, or + // a final listener at this point + if (mFrameLoader || mPendingInstantiateEvent || mInstanceOwner || + mFinalListener) + { + NS_NOTREACHED("Trying to load new plugin with existing content"); + rv = NS_ERROR_UNEXPECTED; + return NS_OK; + } + + // More sanity-checking: + // If mChannel is set, mChannelLoaded should be set, and vice-versa + if (mType != eType_Null && !!mChannel != mChannelLoaded) { + NS_NOTREACHED("Trying to load with bad channel state"); + rv = NS_ERROR_UNEXPECTED; + return NS_OK; + } + + /// + /// Attempt to load new type + /// + 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. + NS_NOTREACHED("Attempting to load image without a channel?"); + rv = NS_ERROR_UNEXPECTED; + return NS_OK; + } + rv = LoadImageWithChannel(mChannel, getter_AddRefs(mFinalListener)); + if (mFinalListener) { + // Note that LoadObject is called from mChannel's OnStartRequest + // when loading with a channel + mSrcStreamLoading = true; + rv = mFinalListener->OnStartRequest(mChannel, nullptr); + mSrcStreamLoading = false; + } else { + // We can have success with no final listener if the image is cached + return NS_OK; + } + break; + case eType_Plugin: + { + if (aNotify && oldType != eType_Plugin) { + // Old type is not plugin, rebuild frames + notifier.Notify(); + } + if (mChannel) { + nsRefPtr pluginHost = + already_AddRefed(nsPluginHost::GetInst()); + if (!pluginHost) { + NS_NOTREACHED("No pluginHost"); + rv = NS_ERROR_UNEXPECTED; + break; + } + rv = pluginHost->NewEmbeddedPluginStreamListener(mURI, this, nullptr, + getter_AddRefs(mFinalListener)); + if (NS_SUCCEEDED(rv)) { + // Note that LoadObject is called from mChannel's OnStartRequest + // when loading with a channel + + // mFinalListener->OnStartRequest will call InstantiatePluginInstance, + // so this needs to be set early (usually set by AutoFallback) + // NOTE this goes away with fallback cleanup patch (bug 745030) + mLoaded = true; + mSrcStreamLoading = true; + rv = mFinalListener->OnStartRequest(mChannel, nullptr); + mSrcStreamLoading = false; + if (NS_SUCCEEDED(rv)) { + NotifyContentObjectWrapper(); + } + } + } 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. + NS_NOTREACHED("Attempting to load a document without a channel"); + rv = NS_ERROR_UNEXPECTED; + return NS_OK; + } + if (!mFrameLoader) { + if (oldType != eType_Document && aNotify) { + // Old type is not document, rebuild frames + notifier.Notify(); + } + mFrameLoader = nsFrameLoader::Create(thisContent->AsElement(), + mNetworkCreated); + if (!mFrameLoader) { + rv = NS_ERROR_UNEXPECTED; + return NS_OK; + } + } + + rv = mFrameLoader->CheckForRecursiveLoad(mURI); + if (NS_FAILED(rv)) { + return NS_OK; + } + + // 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 docShell; + rv = mFrameLoader->GetDocShell(getter_AddRefs(docShell)); + NS_ENSURE_SUCCESS(rv, NS_OK); + + 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, NS_OK); + rv = uriLoader->OpenChannel(mChannel, nsIURILoader::DONT_RETARGET, req, + getter_AddRefs(mFinalListener)); + if (NS_SUCCEEDED(rv)) { + // Note that LoadObject is called from mChannel's OnStartRequest + // when loading with a channel + mSrcStreamLoading = true; + rv = mFinalListener->OnStartRequest(mChannel, nullptr); + mSrcStreamLoading = false; + } + } + break; + case eType_Loading: + // If our type remains Loading, we need a channel to proceed + rv = OpenChannel(policyType); + if (NS_FAILED(rv)) { + LOG(("OBJLC [%p]: OpenChannel returned failure (%u)", this, rv)); + } + break; + default: + // Unknown type, eType_Null should've been handled above + rv = NS_ERROR_UNEXPECTED; + NS_NOTREACHED("Attempted to load with invalid type"); + break; + }; + + // Note that if rv is false here, we'll trigger AutoFallback + return NS_OK; +} + +nsresult +nsObjectLoadingContent::CloseChannel() +{ + if (mChannel) { + LOG(("OBJLC [%p]: Closing channel\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. + // NOTE mFinalListener is only created when we load with a channel, which + // LoadObject() requires come from a OnStartRequest call mFinalListener->OnStopRequest(mChannel, nullptr, NS_BINDING_ABORTED); mFinalListener = nullptr; } mChannel = nullptr; } + return NS_OK; +} - // Security checks - if (doc->IsLoadedAsData()) { - if (!doc->IsStaticDocument()) { - Fallback(false); - } - return NS_OK; - } +nsresult +nsObjectLoadingContent::OpenChannel(PRInt32 aPolicyType) +{ + nsCOMPtr thisContent = + do_QueryInterface(static_cast(this)); + NS_ASSERTION(thisContent, "must be a content"); + nsIDocument* doc = thisContent->OwnerDoc(); + NS_ASSERTION(doc, "No owner document?"); + NS_ASSERTION(!mLoaded && !mInstanceOwner && !mInstantiating, + "opening a new channel with already loaded content"); - // 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, - nullptr, //extra - &shouldLoad, - nsContentUtils::GetContentPolicy(), - secMan); - if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { - HandleBeingBlockedByContentPolicy(rv, shouldLoad); - 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 = nullptr; - 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(thisContent, 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; - } + nsresult rv; + mChannel = nullptr; // 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; + if (!mURI || !CanHandleURI(mURI)) { + return NS_ERROR_NOT_AVAILABLE; } nsCOMPtr group = doc->GetDocumentLoadGroup(); @@ -1558,9 +1954,9 @@ nsObjectLoadingContent::LoadObject(nsIURI* aURI, if (csp) { channelPolicy = do_CreateInstance("@mozilla.org/nschannelpolicy;1"); channelPolicy->SetContentSecurityPolicy(csp); - channelPolicy->SetLoadType(nsIContentPolicy::TYPE_OBJECT); + channelPolicy->SetLoadType(aPolicyType); } - rv = NS_NewChannel(getter_AddRefs(chan), aURI, nullptr, group, this, + rv = NS_NewChannel(getter_AddRefs(chan), mURI, nullptr, group, this, nsIChannel::LOAD_CALL_CONTENT_SNIFFERS | nsIChannel::LOAD_CLASSIFY_URI, channelPolicy); @@ -1572,35 +1968,21 @@ nsObjectLoadingContent::LoadObject(nsIURI* aURI, 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); + chan, mURI, true); nsCOMPtr scriptChannel = do_QueryInterface(chan); if (scriptChannel) { // Allow execution against our context if the principals match - scriptChannel-> - SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL); + scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL); } // AsyncOpen can fail if a file does not exist. - // Show fallback content in that case. rv = chan->AsyncOpen(this, nullptr); - if (NS_SUCCEEDED(rv)) { - LOG(("OBJLC [%p]: Channel opened.\n", this)); - - mChannel = chan; - mType = eType_Loading; - } + NS_ENSURE_SUCCESS(rv, rv); + LOG(("OBJLC [%p]: Channel opened", this)); + mChannel = chan; return NS_OK; } @@ -1619,33 +2001,20 @@ nsObjectLoadingContent::Fallback(bool aNotify) AutoNotifier notifier(this, aNotify); UnloadContent(); + CloseChannel(); + mType = eType_Null; + StopPluginInstance(); } void -nsObjectLoadingContent::RemovedFromDocument() +nsObjectLoadingContent::DestroyContent() { if (mFrameLoader) { - // XXX This is very temporary and must go away mFrameLoader->Destroy(); mFrameLoader = nullptr; - - // Clear the current URI, so that LoadObject doesn't think that we - // have already loaded the content. - mURI = nullptr; } - // 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); - } + StopPluginInstance(); } /* static */ @@ -1657,97 +2026,6 @@ nsObjectLoadingContent::Traverse(nsObjectLoadingContent *tmp, 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 == nullptr; -} - -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() { @@ -1757,7 +2035,7 @@ nsObjectLoadingContent::UnloadContent() mFrameLoader->Destroy(); mFrameLoader = nullptr; } - mType = eType_Null; + mLoaded = false; mUserDisabled = mSuppressed = false; mFallbackReason = ePluginOtherState; } @@ -1778,6 +2056,8 @@ nsObjectLoadingContent::NotifyStateChanged(ObjectType aOldType, NS_ASSERTION(thisContent->IsElement(), "Not 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. @@ -1818,12 +2098,15 @@ nsObjectLoadingContent::NotifyStateChanged(ObjectType aOldType, } } -/* static */ void -nsObjectLoadingContent::FirePluginError(nsIContent* thisContent, - PluginSupportState state) +void +nsObjectLoadingContent::FirePluginError(PluginSupportState state) { - LOG(("OBJLC []: Dispatching nsPluginErrorEvent for content %p\n", - thisContent)); + nsCOMPtr thisContent = + do_QueryInterface(static_cast(this)); + NS_ASSERTION(thisContent, "must be a content"); + + LOG(("OBJLC [%p]: Dispatching nsPluginErrorEvent for content %p\n", + this)); nsCOMPtr ev = new nsPluginErrorEvent(thisContent, state); nsresult rv = NS_DispatchToCurrentThread(ev); @@ -1835,16 +2118,20 @@ nsObjectLoadingContent::FirePluginError(nsIContent* thisContent, nsObjectLoadingContent::ObjectType nsObjectLoadingContent::GetTypeOfContent(const nsCString& aMIMEType) { + if (aMIMEType.IsEmpty()) { + return eType_Null; + } + PRUint32 caps = GetCapabilities(); if ((caps & eSupportImages) && IsSupportedImage(aMIMEType)) { return eType_Image; } + // SVGs load as documents, but are their own capability bool isSVG = aMIMEType.LowerCaseEqualsLiteral("image/svg+xml"); - bool supportedSVG = isSVG && (caps & eSupportSVG); - if (((caps & eSupportDocuments) || supportedSVG) && - IsSupportedDocument(aMIMEType)) { + bool supportType = isSVG ? eSupportSVG : eSupportDocuments; + if ((caps & supportType) && IsSupportedDocument(aMIMEType)) { return eType_Document; } @@ -1883,27 +2170,6 @@ nsObjectLoadingContent::TypeForClassID(const nsAString& aClassID, return NS_ERROR_NOT_AVAILABLE; } -void -nsObjectLoadingContent::GetObjectBaseURI(nsIContent* thisContent, nsIURI** aURI) -{ - // We want to use swap(); since this is just called from this file, - // we can assert this (callers use comptrs) - NS_PRECONDITION(*aURI == nullptr, "URI must be inited to zero"); - - // 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()) { - nsContentUtils::NewURIWithDocumentCharset(aURI, codebase, - thisContent->OwnerDoc(), - baseURI); - } else { - baseURI.swap(*aURI); - } -} - nsObjectFrame* nsObjectLoadingContent::GetExistingFrame() { @@ -1913,23 +2179,6 @@ nsObjectLoadingContent::GetExistingFrame() 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) @@ -2038,6 +2287,9 @@ nsObjectLoadingContent::PluginCrashed(nsIPluginTag* aPluginTag, const nsAString& browserDumpID, bool submittedCrashReport) { + LOG(("OBJLC [%p]: Plugin Crashed, queuing crash event", this)); + NS_ASSERTION(mType == eType_Plugin, "PluginCrashed at non-plugin type"); + AutoNotifier notifier(this, true); UnloadContent(); mFallbackReason = ePluginCrashed; @@ -2079,7 +2331,7 @@ nsObjectLoadingContent::SyncStartPluginInstance() nsCOMPtr kungFuURIGrip(mURI); nsCString contentType(mContentType); - return InstantiatePluginInstance(contentType.get(), mURI.get()); + return InstantiatePluginInstance(); } NS_IMETHODIMP @@ -2129,7 +2381,7 @@ DoDelayedStop(nsPluginInstanceOwner* aInstanceOwner, 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 @@ -2174,11 +2426,12 @@ nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner, 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); + nsRefPtr pluginHost = + already_AddRefed(nsPluginHost::GetInst()); + NS_ASSERTION(pluginHost, "No plugin host?"); + pluginHost->StopPluginInstance(inst); } - + aInstanceOwner->Destroy(); mIsStopping = false; } @@ -2186,10 +2439,23 @@ nsObjectLoadingContent::DoStopPlugin(nsPluginInstanceOwner* aInstanceOwner, NS_IMETHODIMP nsObjectLoadingContent::StopPluginInstance() { + // Prevents any pending plugin starts from running + mPendingInstantiateEvent = nullptr; + 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(); + } + DisconnectFrame(); bool delayedStop = false; @@ -2202,7 +2468,7 @@ nsObjectLoadingContent::StopPluginInstance() if (NS_SUCCEEDED(inst->GetMIMEType(&mime)) && mime) { if (strcmp(mime, "audio/x-pn-realaudio-plugin") == 0) { delayedStop = true; - } + } } } #endif @@ -2217,39 +2483,40 @@ nsObjectLoadingContent::StopPluginInstance() void nsObjectLoadingContent::NotifyContentObjectWrapper() { - nsCOMPtr thisContent = do_QueryInterface(static_cast(this)); + 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 = nullptr; nsresult rv = wrapper->GetJSObject(&obj); if (NS_FAILED(rv)) return; - + nsHTMLPluginObjElementSH::SetupProtoChain(wrapper, cx, obj); } @@ -2260,7 +2527,7 @@ nsObjectLoadingContent::PlayPlugin() return NS_OK; mCTPPlayable = true; - return LoadObject(mURI, true, mContentType, true); + return LoadObject(true, true); } NS_IMETHODIMP diff --git a/content/base/src/nsObjectLoadingContent.h b/content/base/src/nsObjectLoadingContent.h index cf7e3f62cab..fde4a008112 100644 --- a/content/base/src/nsObjectLoadingContent.h +++ b/content/base/src/nsObjectLoadingContent.h @@ -43,24 +43,6 @@ enum PluginSupportState { ePluginVulnerableNoUpdate // The plugin is vulnerable (no update available) }; -/** - * INVARIANTS OF THIS CLASS - * - mChannel is non-null between asyncOpen and onStopRequest (NOTE: Only needs - * to be valid until onStopRequest is called on mFinalListener, not - * necessarily until the channel calls onStopRequest on us) - * - mChannel corresponds to the channel that gets passed to the - * nsIRequestObserver/nsIStreamListener methods - * - mChannel can be cancelled and ODA calls will stop - * - mFinalListener is non-null (only) after onStartRequest has been called on - * it and before onStopRequest has been called on it - * (i.e. calling onStopRequest doesn't violate the nsIRequestObserver - * contract) - * - mFrameLoader is null while this node is not in a document (XXX this - * invariant only exists due to nsFrameLoader suckage and needs to go away) - * - mInstantiating is true while in LoadObject (it may be true in other - * cases as well). Only the function that set mInstantiating should trigger - * frame construction or notifications like ContentStatesChanged or flushes. - */ class nsObjectLoadingContent : public nsImageLoadingContent , public nsIStreamListener , public nsIFrameLoaderOwner @@ -71,6 +53,8 @@ class nsObjectLoadingContent : public nsImageLoadingContent friend class AutoNotifier; friend class AutoFallback; friend class AutoSetInstantiatingToFalse; + friend class AutoSetLoadingToFalse; + friend class InDocCheckEvent; friend class nsStopPluginRunnable; friend class nsAsyncInstantiateEvent; @@ -78,11 +62,17 @@ class nsObjectLoadingContent : public nsImageLoadingContent // This enum's values must be the same as the constants on // nsIObjectLoadingContent enum ObjectType { - eType_Loading = TYPE_LOADING, ///< Type not yet known - eType_Image = TYPE_IMAGE, ///< This content is an image - eType_Plugin = TYPE_PLUGIN, ///< This content is a plugin - eType_Document = TYPE_DOCUMENT, ///< This is a document type (e.g. HTML) - eType_Null = TYPE_NULL ///< Type can't be handled + // Loading, type not yet known. We may be waiting for a channel to open. + eType_Loading = TYPE_LOADING, + // Content is a *non-svg* image + eType_Image = TYPE_IMAGE, + // Content is a plugin + eType_Plugin = TYPE_PLUGIN, + // Content is a subdocument, possibly SVG + eType_Document = TYPE_DOCUMENT, + // No content loaded (fallback). May be showing alternate content or + // a custom error handler - *including* click-to-play dialogs + eType_Null = TYPE_NULL }; nsObjectLoadingContent(); @@ -102,110 +92,114 @@ class nsObjectLoadingContent : public nsImageLoadingContent using nsImageLoadingContent::OnStopRequest; #endif - ObjectType Type() { return mType; } - /** - * Object state. This is a bitmask consisting of a subset of - * NS_EVENT_STATE_BROKEN, NS_EVENT_STATE_USERDISABLED and - * NS_EVENT_STATE_SUPPRESSED representing the current state of the object. + * Object state. This is a bitmask of NS_EVENT_STATEs epresenting the + * current state of the object. */ nsEventStates ObjectState() const; + ObjectType Type() { return mType; } + void SetIsNetworkCreated(bool aNetworkCreated) { mNetworkCreated = aNetworkCreated; } - // Can flush layout. - nsresult InstantiatePluginInstance(const char* aMimeType, nsIURI* aURI); + /** + * Immediately instantiate a plugin instance. This is a no-op if + * mType != eType_Plugin or a plugin is already running. + */ + nsresult InstantiatePluginInstance(); + /** + * Notify this class the document state has changed + * Called by nsDocument so we may suspend plugins in inactive documents) + */ void NotifyOwnerDocumentActivityChanged(); + /** + * Used by pluginHost to know if we're loading with a channel, so it + * will not open its own. + */ bool SrcStreamLoading() { return mSrcStreamLoading; }; protected: /** - * Load the object from the given URI. - * @param aURI The URI to load. - * @param aNotify If true, nsIDocumentObserver state change notifications - * will be sent as needed. - * @param aTypeHint MIME Type hint. Overridden by the server unless this - * class has the eOverrideServerType capability. - * @param aForceLoad If true, the object will be refetched even if the URI - * is the same as the currently-loaded object. - * @note Prefer the nsIURI-taking version of this function if a URI object - * is already available. - * @see the URI-taking version of this function for a detailed description - * of how a plugin will be found. - */ - nsresult LoadObject(const nsAString& aURI, - bool aNotify, - const nsCString& aTypeHint = EmptyCString(), - bool aForceLoad = false); - /** - * Loads the object from the given URI. + * Begins loading the object when called * - * The URI and type can both be null; if the URI is null a plugin will be - * instantiated in the hope that there is a with a useful URI - * somewhere around. Other attributes of |this| QI'd to nsIContent will be - * inspected. This function attempts hard to find a suitable plugin. + * Attributes of |this| QI'd to nsIContent will be inspected, depending on + * the node type. This function currently assumes it is a , + * , or tag. * - * The instantiated plugin depends on three values: - * - The passed-in URI - * - The passed-in type hint + * The instantiated plugin depends on: + * - The URI (, ) + * - The type 'hint' (type attribute) + * - The mime type returned by opening the URI + * - Enabled plugins claiming the ultimate mime type + * - The capabilities returned by GetCapabilities * - The classid attribute, if eSupportClassID is among the capabilities - * and such an attribute is present.. * - * Supported class ID attributes override any other value. - * - * If no class ID is present and aForceType is true, the handler given by - * aTypeHint will be instantiated for this content. - * If the URI is null or not supported, and a type hint is given, the plugin - * corresponding to that type is instantiated. + * If eAllowPluginSkipChannel is true, we may skip opening the URI if our + * type hint points to a valid plugin, deferring that responsibility to the + * plugin. + * Similarly, if no URI is provided, but a type hint for a valid plugin is + * present, that plugin will be instantiated * * Otherwise a request to that URI is made and the type sent by the server - * is used to find a suitable handler. + * is used to find a suitable handler, EXCEPT when: + * - The type hint refers to a *supported* plugin, in which case that + * plugin will be instantiated regardless of the server provided type + * - The server returns a binary-stream type, and our type hint refers to + * a valid non-document type, we will use the type hint * - * @param aForceLoad If true, the object will be refetched even if the URI - * is the same as the currently-loaded object. + * @param aNotify If we should send notifications. If false, content + * loading may be deferred while appropriate frames are + * created + * @param aForceLoad If we should reload this content (and re-attempt the + * channel open) even if our parameters did not change */ - nsresult LoadObject(nsIURI* aURI, - bool aNotify, - const nsCString& aTypeHint = EmptyCString(), + nsresult LoadObject(bool aNotify, bool aForceLoad = false); enum Capabilities { - eSupportImages = PR_BIT(0), // Images are supported (imgILoader) - eSupportPlugins = PR_BIT(1), // Plugins are supported (nsIPluginHost) - eSupportDocuments = PR_BIT(2), // Documents are supported - // (nsIDocumentLoaderFactory) - // This flag always includes SVG - eSupportSVG = PR_BIT(3), // SVG is supported (image/svg+xml) - eSupportClassID = PR_BIT(4), // The classid attribute is supported - eOverrideServerType = PR_BIT(5) // The server-sent MIME type is ignored - // (ignored if no type is specified) + eSupportImages = PR_BIT(0), // Images are supported (imgILoader) + eSupportPlugins = PR_BIT(1), // Plugins are supported (nsIPluginHost) + eSupportDocuments = PR_BIT(2), // Documents are supported + // (nsIDocumentLoaderFactory) + // This flag always includes SVG + eSupportSVG = PR_BIT(3), // SVG is supported (image/svg+xml) + eSupportClassID = PR_BIT(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 tags, whereas object + // will always open a channel and check its MIME if a URI is present. + eAllowPluginSkipChannel = PR_BIT(5) }; /** * Returns the list of capabilities this content node supports. This is a * bitmask consisting of flags from the Capabilities enum. * - * The default implementation supports all types but no classids. + * The default implementation supports all types but not + * eSupportClassID or eAllowPluginSkipChannel */ virtual PRUint32 GetCapabilities() const; /** * Fall back to rendering the alternative content. + * + * @param aNotify If notifications should be sent. If false, the caller is + * responsible for sending proper notifications. */ void Fallback(bool aNotify); /** - * Subclasses must call this function when they are removed from the - * document. - * - * XXX This is a temporary workaround for docshell suckyness + * Destroys all loaded documents/plugins and releases references */ - void RemovedFromDocument(); + void DestroyContent(); static void Traverse(nsObjectLoadingContent *tmp, nsCycleCollectionTraversalCallback &cb); @@ -222,28 +216,107 @@ class nsObjectLoadingContent : public nsImageLoadingContent bool aNullParent = true); private: + // Object parameter changes returned by UpdateObjectParameters + enum ParameterUpdateFlags { + eParamNoChange = 0, + // Parameters that potentially affect the channel changed + // - mOriginalURI, mOriginalContentType + eParamChannelChanged = PR_BIT(0), + // Parameters that affect displayed content changed + // - mURI, mContentType, mType, mBaseURI + eParamStateChanged = PR_BIT(1) + }; + + /** + * Internal version of LoadObject that should only be used by this class + * aLoadingChannel is passed by the LoadObject call from OnStartRequest, + * primarily for sanity-preservation + */ + nsresult LoadObject(bool aNotify, + bool aForceLoad, + nsIRequest *aLoadingChannel); + + /** + * Introspects the object and sets the following member variables: + * - mOriginalContentType : This is the type attribute on the element + * - mOriginalURI : The src or data attribute on the element + * - mURI : The final URI, considering mChannel if + * mChannelLoaded is set + * - mContentType : The final content type, considering mChannel if + * mChannelLoaded is set + * - mBaseURI : The object's base URI, which may be set by the + * object (codebase attribute) + * - mType : The type the object is determined to be based + * on the above + * + * NOTE The class assumes that mType is the currently loaded type at various + * points, so the caller of this function must take the appropriate + * actions to ensure this + * + * NOTE This function does not perform security checks, only determining the + * requested type and parameters of the object. + * + * @return Returns a bitmask of ParameterUpdateFlags values + */ + ParameterUpdateFlags UpdateObjectParameters(); void NotifyContentObjectWrapper(); /** - * Check whether the given request represents a successful load. + * Opens the channel pointed to by mURI into mChannel. + * + * @param aPolicyType The value to be passed to channelPolicy->SetLoadType */ - static bool IsSuccessfulRequest(nsIRequest* aRequest); + nsresult OpenChannel(PRInt32 aPolicyType); /** - * Check whether the URI can be handled internally. + * Closes and releases references to mChannel and, if opened, mFinalListener */ - static bool CanHandleURI(nsIURI* aURI); + nsresult CloseChannel(); /** - * Checks whether the given type is a supported document type. + * Checks if a URI passes security checks and content policy, relative to + * the current document's principal + * + * @param aURI The URI to consider + * @param aContentPolicy [in/out] A pointer to the initial content + * policy, that will be updated to contain the + * final determined policy + * @param aContentPolicyType The 'contentType' parameter passed to + * NS_CheckContentLoadPolicy + * + * @return true if this URI is acceptable for loading + */ + bool CheckURILoad(nsIURI *aURI, + PRInt16 *aContentPolicy, + PRInt32 aContentPolicyType); + + /** + * Checks if the current mURI and mBaseURI pass content policy and security + * checks for loading + * + * @param aContentPolicy [in/out] A pointer to the initial content + * policy, that will be updated to contain the + * final determined policy if a URL is rejected + * @param aContentPolicyType The 'contentType' parameter passed to + * NS_CheckContentLoadPolicy + * + * @return true if the URIs are acceptable for loading + */ + bool CheckObjectURIs(PRInt16 *aContentPolicy, PRInt32 aContentPolicyType); + + /** + * Checks whether the given type is a supported document type + * + * NOTE Does not take content policy or capabilities into account */ bool IsSupportedDocument(const nsCString& aType); /** * Unload the currently loaded content. This removes all state related to * the displayed content and sets the type to eType_Null. - * Note: This does not send any notifications. + * + * NOTE This does not send any notifications, or handle loading fallback */ void UnloadContent(); @@ -261,29 +334,32 @@ class nsObjectLoadingContent : public nsImageLoadingContent bool aSync, bool aNotify); /** - * Fires the "Plugin not found" event. This function doesn't do any checks - * whether it should be fired, the caller should do that. + * Fires the nsPluginErrorEvent. This function doesn't do any checks + * whether it should be fired, or whether the given state translates to a + * meaningful event */ - static void FirePluginError(nsIContent* thisContent, PluginSupportState state); + void FirePluginError(PluginSupportState state); + /** + * Returns a ObjectType value corresponding to the type of content we would + * support the given MIME type as, taking capabilities and plugin state + * into account + * + * NOTE this does not consider whether the content would be suppressed by + * click-to-play or other content policy checks + */ ObjectType GetTypeOfContent(const nsCString& aMIMEType); /** * For a classid, returns the MIME type that can be used to instantiate * a plugin for this ID. * + * @param aClassID The class ID in question + * @param aType [out] The corresponding type, if the call is successful * @return NS_ERROR_NOT_AVAILABLE Unsupported class ID. */ nsresult TypeForClassID(const nsAString& aClassID, nsACString& aType); - /** - * Gets the base URI to be used for this object. This differs from - * nsIContent::GetBaseURI in that it takes codebase attributes into - * account. - */ - void GetObjectBaseURI(nsIContent* thisContent, nsIURI** aURI); - - /** * Gets the frame that's associated with this content node. * Does not flush. @@ -305,7 +381,8 @@ class nsObjectLoadingContent : public nsImageLoadingContent * * This should only be called if the type of this content is eType_Null. */ - PluginSupportState GetPluginSupportState(nsIContent* aContent, const nsCString& aContentType); + PluginSupportState GetPluginSupportState(nsIContent* aContent, + const nsCString& aContentType); /** * If the plugin for aContentType is disabled, return ePluginDisabled. @@ -321,52 +398,76 @@ class nsObjectLoadingContent : public nsImageLoadingContent * update the AutoFallback object appropriate to the reason for there being * no plugin available. */ - void UpdateFallbackState(nsIContent* aContent, AutoFallback& fallback, const nsCString& aTypeHint); + void UpdateFallbackState(nsIContent* aContent, AutoFallback& fallback, + const nsCString& aTypeHint); + /** + * Checks if a plugin is available and enabled for this MIME type + */ nsresult IsPluginEnabledForType(const nsCString& aMIMEType); + + /** + * Checks if a plugin is available and enabled for this file extension + * @param nsIURI The URI to read the file extension of + * @param mimeType [out] The corresponding MIME type if successful + */ bool IsPluginEnabledByExtension(nsIURI* uri, nsCString& mimeType); - /** - * The final listener to ship the data to (imagelib, uriloader, etc) - */ + // The final listener for mChannel (uriloader, pluginstreamlistener, etc.) nsCOMPtr mFinalListener; - /** - * Frame loader, for content documents we load. - */ + // Frame loader, for content documents we load. nsRefPtr mFrameLoader; - /** - * A pending nsAsyncInstantiateEvent (may be null). This is a weak ref. - */ + // A pending nsAsyncInstantiateEvent (may be null). This is a weak ref. nsIRunnable *mPendingInstantiateEvent; - /** - * The content type of the resource we were last asked to load. - */ + // The content type of our current load target, updated by + // UpdateObjectParameters(). Takes the channel's type into account once + // opened. + // + // May change if a channel is opened, does not imply a loaded state nsCString mContentType; - /** - * The channel that's currently being loaded. This is a weak reference. - * Non-null between asyncOpen and onStopRequest. - */ - nsIChannel* mChannel; + // The content type 'hint' provided by the element's type attribute. May + // or may not be used as a final type + nsCString mOriginalContentType; - // The data we were last asked to load + // The channel that's currently being loaded. If set, but mChannelLoaded is + // false, has not yet reached OnStartRequest + nsCOMPtr mChannel; + + // The URI of the current content. + // May change as we open channels and encounter redirects - does not imply + // a loaded type nsCOMPtr mURI; - /** - * Type of the currently-loaded content. - */ + // The original URI obtained from inspecting the element (codebase, and + // src/data). May differ from mURI due to redirects + nsCOMPtr mOriginalURI; + + // The baseURI used for constructing mURI, and used by some plugins (java) + // as a root for other resource requests. + nsCOMPtr mBaseURI; + + + // Type of the currently-loaded content. ObjectType mType : 16; - /** - * Whether we are about to call instantiate on our frame. If we aren't, - * SetFrame needs to asynchronously call Instantiate. - */ + // If true, we have loaded, non-fallback, content + bool mLoaded : 1; + + // If true, the current load has finished opening a channel. Does not imply + // mChannel -- mChannelLoaded && !mChannel may occur for a load that failed + bool mChannelLoaded : 1; + + // Whether we are about to call instantiate on our frame. If we aren't, + // SetFrame needs to asynchronously call Instantiate. bool mInstantiating : 1; - // Blocking status from content policy + + // True if we were blocked by content policy bool mUserDisabled : 1; + // True if we were blocked by security policy bool mSuppressed : 1; // True when the object is created for an element which the parser has @@ -387,11 +488,14 @@ class nsObjectLoadingContent : public nsImageLoadingContent // Protects DoStopPlugin from reentry (bug 724781). bool mIsStopping : 1; + // Protects LoadObject from re-entry + bool mIsLoading : 1; + // Used to track when we might try to instantiate a plugin instance based on - // a src data stream being delivered to this object. When this is true we don't - // want plugin instance instantiation code to attempt to load src data again or - // we'll deliver duplicate streams. Should be cleared when we are not loading - // src data. + // a src data stream being delivered to this object. When this is true we + // don't want plugin instance instantiation code to attempt to load src data + // again or we'll deliver duplicate streams. Should be cleared when we are + // not loading src data. bool mSrcStreamLoading; // A specific state that caused us to fallback diff --git a/content/html/content/src/nsHTMLObjectElement.cpp b/content/html/content/src/nsHTMLObjectElement.cpp index 25ac0f0c091..930f38b6c62 100644 --- a/content/html/content/src/nsHTMLObjectElement.cpp +++ b/content/html/content/src/nsHTMLObjectElement.cpp @@ -268,7 +268,6 @@ void nsHTMLObjectElement::UnbindFromTree(bool aDeep, bool aNullParent) { - RemovedFromDocument(); nsObjectLoadingContent::UnbindFromTree(aDeep, aNullParent); nsGenericHTMLFormElement::UnbindFromTree(aDeep, aNullParent); } @@ -280,10 +279,12 @@ nsHTMLObjectElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom *aName, nsIAtom *aPrefix, const nsAString &aValue, bool aNotify) { - // If we plan to call LoadObject, we want to do it first so that the - // object load kicks off _before_ the reflow triggered by the SetAttr. But if - // aNotify is false, we are coming from the parser or some such place; we'll - // get bound after all the attributes have been set, so we'll do the + nsresult rv = nsGenericHTMLFormElement::SetAttr(aNameSpaceID, aName, aPrefix, + aValue, aNotify); + NS_ENSURE_SUCCESS(rv, rv); + + // if aNotify is false, we are coming from the parser or some such place; + // we'll get bound after all the attributes have been set, so we'll do the // object load from BindToTree/DoneAddingChildren. // Skip the LoadObject call in that case. // We also don't want to start loading the object when we're not yet in @@ -291,24 +292,27 @@ nsHTMLObjectElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom *aName, // attributes before inserting the node into the document. if (aNotify && IsInDoc() && mIsDoneAddingChildren && aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::data) { - nsAutoString type; - GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); - LoadObject(aValue, aNotify, NS_ConvertUTF16toUTF8(type), true); + return LoadObject(aNotify, true); } - return nsGenericHTMLFormElement::SetAttr(aNameSpaceID, aName, aPrefix, - aValue, aNotify); + return NS_OK; } nsresult nsHTMLObjectElement::UnsetAttr(PRInt32 aNameSpaceID, nsIAtom* aAttribute, bool aNotify) { - if (aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::data) { - Fallback(aNotify); + nsresult rv = nsGenericHTMLFormElement::UnsetAttr(aNameSpaceID, + aAttribute, aNotify); + NS_ENSURE_SUCCESS(rv, rv); + + // See comment in SetAttr + if (aNotify && IsInDoc() && mIsDoneAddingChildren && + aNameSpaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::data) { + return LoadObject(aNotify, true); } - return nsGenericHTMLFormElement::UnsetAttr(aNameSpaceID, aAttribute, aNotify); + return NS_OK; } bool @@ -513,20 +517,7 @@ nsHTMLObjectElement::GetAttributeMappingFunction() const void nsHTMLObjectElement::StartObjectLoad(bool aNotify) { - nsAutoString type; - GetAttr(kNameSpaceID_None, nsGkAtoms::type, type); - NS_ConvertUTF16toUTF8 ctype(type); - - nsAutoString uri; - if (GetAttr(kNameSpaceID_None, nsGkAtoms::data, uri)) { - LoadObject(uri, aNotify, ctype); - } - else { - // Be sure to call the nsIURI version if we have no attribute - // That handles the case where no URI is specified. An empty string would - // get interpreted as the page itself, instead of absence of URI. - LoadObject(nullptr, aNotify, ctype); - } + LoadObject(aNotify); SetIsNetworkCreated(false); } @@ -545,7 +536,7 @@ nsHTMLObjectElement::GetCapabilities() const void nsHTMLObjectElement::DestroyContent() { - RemovedFromDocument(); + nsObjectLoadingContent::DestroyContent(); nsGenericHTMLFormElement::DestroyContent(); } diff --git a/content/html/content/src/nsHTMLSharedObjectElement.cpp b/content/html/content/src/nsHTMLSharedObjectElement.cpp index b68df7e1859..7f1147f8ad1 100644 --- a/content/html/content/src/nsHTMLSharedObjectElement.cpp +++ b/content/html/content/src/nsHTMLSharedObjectElement.cpp @@ -298,22 +298,22 @@ void nsHTMLSharedObjectElement::UnbindFromTree(bool aDeep, bool aNullParent) { - RemovedFromDocument(); nsObjectLoadingContent::UnbindFromTree(aDeep, aNullParent); nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); } - nsresult nsHTMLSharedObjectElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom *aName, nsIAtom *aPrefix, const nsAString &aValue, bool aNotify) { - // If we plan to call LoadObject, we want to do it first so that the - // object load kicks off _before_ the reflow triggered by the SetAttr. But if - // aNotify is false, we are coming from the parser or some such place; we'll - // get bound after all the attributes have been set, so we'll do the + nsresult rv = nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, + aValue, aNotify); + NS_ENSURE_SUCCESS(rv, rv); + + // if aNotify is false, we are coming from the parser or some such place; + // we'll get bound after all the attributes have been set, so we'll do the // object load from BindToTree/DoneAddingChildren. // Skip the LoadObject call in that case. // We also don't want to start loading the object when we're not yet in @@ -321,13 +321,10 @@ nsHTMLSharedObjectElement::SetAttr(PRInt32 aNameSpaceID, nsIAtom *aName, // attributes before inserting the node into the document. if (aNotify && IsInDoc() && mIsDoneAddingChildren && aNameSpaceID == kNameSpaceID_None && aName == URIAttrName()) { - nsCAutoString type; - GetTypeAttrValue(type); - LoadObject(aValue, aNotify, type, true); + return LoadObject(aNotify, true); } - return nsGenericHTMLElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue, - aNotify); + return NS_OK; } bool @@ -468,19 +465,7 @@ nsHTMLSharedObjectElement::GetAttributeMappingFunction() const void nsHTMLSharedObjectElement::StartObjectLoad(bool aNotify) { - nsCAutoString type; - GetTypeAttrValue(type); - - nsAutoString uri; - if (!GetAttr(kNameSpaceID_None, URIAttrName(), uri)) { - // Be sure to call the nsIURI version if we have no attribute - // That handles the case where no URI is specified. An empty string would - // get interpreted as the page itself, instead of absence of URI. - LoadObject(nullptr, aNotify, type); - } - else { - LoadObject(uri, aNotify, type); - } + LoadObject(aNotify); SetIsNetworkCreated(false); } @@ -493,7 +478,7 @@ nsHTMLSharedObjectElement::IntrinsicState() const PRUint32 nsHTMLSharedObjectElement::GetCapabilities() const { - PRUint32 capabilities = eSupportPlugins | eOverrideServerType; + PRUint32 capabilities = eSupportPlugins | eAllowPluginSkipChannel; if (mNodeInfo->Equals(nsGkAtoms::embed)) { capabilities |= eSupportSVG | eSupportImages; } @@ -504,7 +489,7 @@ nsHTMLSharedObjectElement::GetCapabilities() const void nsHTMLSharedObjectElement::DestroyContent() { - RemovedFromDocument(); + nsObjectLoadingContent::DestroyContent(); nsGenericHTMLElement::DestroyContent(); } diff --git a/content/html/document/src/PluginDocument.cpp b/content/html/document/src/PluginDocument.cpp index 668da4b8f87..e61cd10548b 100644 --- a/content/html/document/src/PluginDocument.cpp +++ b/content/html/document/src/PluginDocument.cpp @@ -120,8 +120,7 @@ PluginStreamListener::SetupPlugin() return NS_ERROR_UNEXPECTED; } nsObjectLoadingContent* olcc = static_cast(olc.get()); - nsresult rv = olcc->InstantiatePluginInstance(mPluginDoc->GetType().get(), - mDocument->nsIDocument::GetDocumentURI()); + nsresult rv = olcc->InstantiatePluginInstance(); if (NS_FAILED(rv)) { return rv; } diff --git a/dom/plugins/base/nsPluginStreamListenerPeer.cpp b/dom/plugins/base/nsPluginStreamListenerPeer.cpp index 29f6a66cbcf..d85fe528416 100644 --- a/dom/plugins/base/nsPluginStreamListenerPeer.cpp +++ b/dom/plugins/base/nsPluginStreamListenerPeer.cpp @@ -601,7 +601,7 @@ nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request, // if we have a mime type now. if (!mPluginInstance && mContent && !aContentType.IsEmpty()) { nsObjectLoadingContent *olc = static_cast(mContent.get()); - rv = olc->InstantiatePluginInstance(aContentType.get(), aURL.get()); + rv = olc->InstantiatePluginInstance(); if (NS_SUCCEEDED(rv)) { rv = olc->GetPluginInstance(getter_AddRefs(mPluginInstance)); if (NS_FAILED(rv)) {