Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/*
* A base class implementing nsIObjectLoadingContent for use by
* various content nodes that want to provide plugin/document/image
* loading functionality (eg <embed>, <object>, etc).
*/
// Interface headers
#include "imgLoader.h"
#include "nsIClassOfService.h"
#include "nsIConsoleService.h"
#include "nsIDocShell.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/dom/BindContext.h"
#include "mozilla/dom/Document.h"
#include "nsIExternalProtocolHandler.h"
#include "nsIPermissionManager.h"
#include "nsIHttpChannel.h"
#include "nsINestedURI.h"
#include "nsScriptSecurityManager.h"
#include "nsIURILoader.h"
#include "nsIScriptChannel.h"
#include "nsIAsyncVerifyRedirectCallback.h"
#include "nsIAppShell.h"
#include "nsIScriptError.h"
#include "nsSubDocumentFrame.h"
#include "nsError.h"
// Util headers
#include "mozilla/Logging.h"
#include "nsContentPolicyUtils.h"
#include "nsContentUtils.h"
#include "nsDocShellLoadState.h"
#include "nsGkAtoms.h"
#include "nsThreadUtils.h"
#include "nsNetUtil.h"
#include "nsMimeTypes.h"
#include "nsStyleUtil.h"
#include "mozilla/Preferences.h"
#include "nsQueryObject.h"
// Concrete classes
#include "nsFrameLoader.h"
#include "nsObjectLoadingContent.h"
#include "nsWidgetsCID.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/Components.h"
#include "mozilla/LoadInfo.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/IMEStateManager.h"
#include "mozilla/widget/IMEData.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/HTMLEmbedElement.h"
#include "mozilla/dom/HTMLObjectElementBinding.h"
#include "mozilla/dom/HTMLObjectElement.h"
#include "mozilla/dom/UserActivation.h"
#include "mozilla/dom/nsCSPContext.h"
#include "mozilla/net/DocumentChannel.h"
#include "mozilla/net/UrlClassifierFeatureFactory.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/StaticPrefs_browser.h"
#include "nsChannelClassifier.h"
#include "nsFocusManager.h"
#include "ReferrerInfo.h"
#include "nsIEffectiveTLDService.h"
#ifdef XP_WIN
// Thanks so much, Microsoft! :(
# ifdef CreateEvent
# undef CreateEvent
# endif
#endif // XP_WIN
static const char kPrefYoutubeRewrite[] = "plugins.rewrite_youtube_embeds";
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::net;
static LogModule* GetObjectLog() {
static LazyLogModule sLog("objlc");
return sLog;
}
#define LOG(args) MOZ_LOG(GetObjectLog(), mozilla::LogLevel::Debug, args)
#define LOG_ENABLED() MOZ_LOG_TEST(GetObjectLog(), mozilla::LogLevel::Debug)
static bool IsFlashMIME(const nsACString& aMIMEType) {
return aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash") ||
aMIMEType.LowerCaseEqualsASCII("application/futuresplash") ||
aMIMEType.LowerCaseEqualsASCII("application/x-shockwave-flash-test");
}
static bool IsPluginMIME(const nsACString& aMIMEType) {
return IsFlashMIME(aMIMEType) ||
aMIMEType.LowerCaseEqualsASCII("application/x-test");
}
///
/// Runnables and helper classes
///
// Sets a object's mIsLoading bit to false when destroyed
class AutoSetLoadingToFalse {
public:
explicit AutoSetLoadingToFalse(nsObjectLoadingContent* aContent)
: mContent(aContent) {}
~AutoSetLoadingToFalse() { mContent->mIsLoading = false; }
private:
nsObjectLoadingContent* mContent;
};
///
/// Helper functions
///
bool nsObjectLoadingContent::IsSuccessfulRequest(nsIRequest* aRequest,
nsresult* aStatus) {
nsresult rv = aRequest->GetStatus(aStatus);
if (NS_FAILED(rv) || NS_FAILED(*aStatus)) {
return false;
}
// This may still be an error page or somesuch
nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(aRequest));
if (httpChan) {
bool success;
rv = httpChan->GetRequestSucceeded(&success);
if (NS_FAILED(rv) || !success) {
return false;
}
}
// Otherwise, the request is successful
return true;
}
static bool CanHandleURI(nsIURI* aURI) {
nsAutoCString scheme;
if (NS_FAILED(aURI->GetScheme(scheme))) {
return false;
}
nsCOMPtr<nsIIOService> ios = mozilla::components::IO::Service();
if (!ios) {
return false;
}
nsCOMPtr<nsIProtocolHandler> handler;
ios->GetProtocolHandler(scheme.get(), getter_AddRefs(handler));
if (!handler) {
return false;
}
nsCOMPtr<nsIExternalProtocolHandler> extHandler = do_QueryInterface(handler);
// We can handle this URI if its protocol handler is not the external one
return extHandler == nullptr;
}
// Helper for tedious URI equality syntax when one or both arguments may be
// null and URIEquals(null, null) should be true
static bool inline URIEquals(nsIURI* a, nsIURI* b) {
bool equal;
return (!a && !b) || (a && b && NS_SUCCEEDED(a->Equals(b, &equal)) && equal);
}
///
/// Member Functions
///
// Helper to spawn the frameloader.
void nsObjectLoadingContent::SetupFrameLoader() {
mFrameLoader = nsFrameLoader::Create(AsElement(), mNetworkCreated);
MOZ_ASSERT(mFrameLoader, "nsFrameLoader::Create failed");
}
// Helper to spawn the frameloader and return a pointer to its docshell.
already_AddRefed<nsIDocShell> nsObjectLoadingContent::SetupDocShell(
nsIURI* aRecursionCheckURI) {
SetupFrameLoader();
if (!mFrameLoader) {
return nullptr;
}
nsCOMPtr<nsIDocShell> docShell;
if (aRecursionCheckURI) {
nsresult rv = mFrameLoader->CheckForRecursiveLoad(aRecursionCheckURI);
if (NS_SUCCEEDED(rv)) {
IgnoredErrorResult result;
docShell = mFrameLoader->GetDocShell(result);
if (result.Failed()) {
MOZ_ASSERT_UNREACHABLE("Could not get DocShell from mFrameLoader?");
}
} else {
LOG(("OBJLC [%p]: Aborting recursive load", this));
}
}
if (!docShell) {
mFrameLoader->Destroy();
mFrameLoader = nullptr;
return nullptr;
}
return docShell.forget();
}
void nsObjectLoadingContent::UnbindFromTree() {
// Reset state and clear pending events
/// XXX(johns): The implementation for GenericFrame notes that ideally we
/// would keep the docshell around, but trash the frameloader
UnloadObject();
}
nsObjectLoadingContent::nsObjectLoadingContent()
: mType(ObjectType::Loading),
mChannelLoaded(false),
mNetworkCreated(true),
mContentBlockingEnabled(false),
mIsStopping(false),
mIsLoading(false),
mScriptRequested(false),
mRewrittenYoutubeEmbed(false) {}
nsObjectLoadingContent::~nsObjectLoadingContent() {
// Should have been unbound from the tree at this point, and
// CheckPluginStopEvent keeps us alive
if (mFrameLoader) {
MOZ_ASSERT_UNREACHABLE(
"Should not be tearing down frame loaders at this point");
mFrameLoader->Destroy();
}
}
// nsIRequestObserver
NS_IMETHODIMP
nsObjectLoadingContent::OnStartRequest(nsIRequest* aRequest) {
AUTO_PROFILER_LABEL("nsObjectLoadingContent::OnStartRequest", NETWORK);
LOG(("OBJLC [%p]: Channel OnStartRequest", this));
if (aRequest != mChannel || !aRequest) {
// happens when a new load starts before the previous one got here
return NS_BINDING_ABORTED;
}
nsCOMPtr<nsIChannel> chan(do_QueryInterface(aRequest));
NS_ASSERTION(chan, "Why is our request not a channel?");
nsresult status = NS_OK;
bool success = IsSuccessfulRequest(aRequest, &status);
// If we have already switched to type document, we're doing a
// process-switching DocumentChannel load. We should be able to pass down the
// load to our inner listener, but should also make sure to update our local
// state.
if (mType == ObjectType::Document) {
if (!mFinalListener) {
MOZ_ASSERT_UNREACHABLE(
"Already is Document, but don't have final listener yet?");
return NS_BINDING_ABORTED;
}
// If the load looks successful, fix up some of our local state before
// forwarding the request to the final URI loader.
//
// Forward load errors down to the document loader, so we don't tear down
// the nsDocShell ourselves.
if (success) {
LOG(("OBJLC [%p]: OnStartRequest: DocumentChannel request succeeded\n",
this));
nsCString channelType;
MOZ_ALWAYS_SUCCEEDS(mChannel->GetContentType(channelType));
if (GetTypeOfContent(channelType) != ObjectType::Document) {
MOZ_CRASH("DocumentChannel request with non-document MIME");
}
mContentType = channelType;
MOZ_ALWAYS_SUCCEEDS(
NS_GetFinalChannelURI(mChannel, getter_AddRefs(mURI)));
}
return mFinalListener->OnStartRequest(aRequest);
}
// Otherwise we should be state loading, and call LoadObject with the channel
if (mType != ObjectType::Loading) {
MOZ_ASSERT_UNREACHABLE("Should be type loading at this point");
return NS_BINDING_ABORTED;
}
NS_ASSERTION(!mChannelLoaded, "mChannelLoaded set already?");
NS_ASSERTION(!mFinalListener, "mFinalListener exists already?");
mChannelLoaded = true;
if (status == NS_ERROR_BLOCKED_URI) {
nsCOMPtr<nsIConsoleService> console(
do_GetService("@mozilla.org/consoleservice;1"));
if (console) {
nsCOMPtr<nsIURI> uri;
chan->GetURI(getter_AddRefs(uri));
nsString message =
u"Blocking "_ns +
NS_ConvertASCIItoUTF16(uri->GetSpecOrDefault().get()) +
nsLiteralString(
u" since it was found on an internal Firefox blocklist.");
console->LogStringMessage(message.get());
}
mContentBlockingEnabled = true;
return NS_ERROR_FAILURE;
}
if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(status)) {
mContentBlockingEnabled = true;
return NS_ERROR_FAILURE;
}
if (!success) {
LOG(("OBJLC [%p]: OnStartRequest: Request failed\n", this));
// If the request fails, we still call LoadObject() to handle fallback
// content and notifying of failure. (mChannelLoaded && !mChannel) indicates
// the bad state.
mChannel = nullptr;
LoadObject(true, false);
return NS_ERROR_FAILURE;
}
return LoadObject(true, false, aRequest);
}
NS_IMETHODIMP
nsObjectLoadingContent::OnStopRequest(nsIRequest* aRequest,
nsresult aStatusCode) {
AUTO_PROFILER_LABEL("nsObjectLoadingContent::OnStopRequest", NETWORK);
// Handle object not loading error because source was a tracking URL (or
// fingerprinting, cryptomining, etc.).
// We make a note of this object node by including it in a dedicated
// array of blocked tracking nodes under its parent document.
if (UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(aStatusCode)) {
nsCOMPtr<nsIContent> thisNode =
do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
if (thisNode && thisNode->IsInComposedDoc()) {
thisNode->GetComposedDoc()->AddBlockedNodeByClassifier(thisNode);
}
}
if (aRequest != mChannel) {
return NS_BINDING_ABORTED;
}
mChannel = nullptr;
if (mFinalListener) {
// This may re-enter in the case of plugin listeners
nsCOMPtr<nsIStreamListener> listenerGrip(mFinalListener);
mFinalListener = nullptr;
listenerGrip->OnStopRequest(aRequest, aStatusCode);
}
// Return value doesn't matter
return NS_OK;
}
// nsIStreamListener
NS_IMETHODIMP
nsObjectLoadingContent::OnDataAvailable(nsIRequest* aRequest,
nsIInputStream* aInputStream,
uint64_t aOffset, uint32_t aCount) {
if (aRequest != mChannel) {
return NS_BINDING_ABORTED;
}
if (mFinalListener) {
// This may re-enter in the case of plugin listeners
nsCOMPtr<nsIStreamListener> listenerGrip(mFinalListener);
return listenerGrip->OnDataAvailable(aRequest, aInputStream, aOffset,
aCount);
}
// We shouldn't have a connected channel with no final listener
MOZ_ASSERT_UNREACHABLE(
"Got data for channel with no connected final "
"listener");
mChannel = nullptr;
return NS_ERROR_UNEXPECTED;
}
NS_IMETHODIMP
nsObjectLoadingContent::GetActualType(nsACString& aType) {
aType = mContentType;
return NS_OK;
}
NS_IMETHODIMP
nsObjectLoadingContent::GetDisplayedType(uint32_t* aType) {
*aType = DisplayedType();
return NS_OK;
}
// nsIInterfaceRequestor
// We use a shim class to implement this so that JS consumers still
// see an interface requestor even though WebIDL bindings don't expose
// that stuff.
class ObjectInterfaceRequestorShim final : public nsIInterfaceRequestor,
public nsIChannelEventSink,
public nsIStreamListener {
public:
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ObjectInterfaceRequestorShim,
nsIInterfaceRequestor)
NS_DECL_NSIINTERFACEREQUESTOR
// RefPtr<nsObjectLoadingContent> fails due to ambiguous AddRef/Release,
// hence the ugly static cast :(
NS_FORWARD_NSICHANNELEVENTSINK(
static_cast<nsObjectLoadingContent*>(mContent.get())->)
NS_FORWARD_NSISTREAMLISTENER(
static_cast<nsObjectLoadingContent*>(mContent.get())->)
NS_FORWARD_NSIREQUESTOBSERVER(
static_cast<nsObjectLoadingContent*>(mContent.get())->)
explicit ObjectInterfaceRequestorShim(nsIObjectLoadingContent* aContent)
: mContent(aContent) {}
protected:
~ObjectInterfaceRequestorShim() = default;
nsCOMPtr<nsIObjectLoadingContent> mContent;
};
NS_IMPL_CYCLE_COLLECTION(ObjectInterfaceRequestorShim, mContent)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ObjectInterfaceRequestorShim)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink)
NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInterfaceRequestor)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(ObjectInterfaceRequestorShim)
NS_IMPL_CYCLE_COLLECTING_RELEASE(ObjectInterfaceRequestorShim)
NS_IMETHODIMP
ObjectInterfaceRequestorShim::GetInterface(const nsIID& aIID, void** aResult) {
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
nsIChannelEventSink* sink = this;
*aResult = sink;
NS_ADDREF(sink);
return NS_OK;
}
if (aIID.Equals(NS_GET_IID(nsIObjectLoadingContent))) {
nsIObjectLoadingContent* olc = mContent;
*aResult = olc;
NS_ADDREF(olc);
return NS_OK;
}
return NS_NOINTERFACE;
}
// nsIChannelEventSink
NS_IMETHODIMP
nsObjectLoadingContent::AsyncOnChannelRedirect(
nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
nsIAsyncVerifyRedirectCallback* cb) {
// If we're already busy with a new load, or have no load at all,
// cancel the redirect.
if (!mChannel || aOldChannel != mChannel) {
return NS_BINDING_ABORTED;
}
mChannel = aNewChannel;
if (mFinalListener) {
nsCOMPtr<nsIChannelEventSink> sink(do_QueryInterface(mFinalListener));
MOZ_RELEASE_ASSERT(sink, "mFinalListener isn't nsIChannelEventSink?");
if (mType != ObjectType::Document) {
MOZ_ASSERT_UNREACHABLE(
"Not a DocumentChannel load, but we're getting a "
"AsyncOnChannelRedirect with a mFinalListener?");
return NS_BINDING_ABORTED;
}
return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, cb);
}
cb->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
void nsObjectLoadingContent::MaybeRewriteYoutubeEmbed(nsIURI* aURI,
nsIURI* aBaseURI,
nsIURI** aRewrittenURI) {
nsCOMPtr<nsIEffectiveTLDService> tldService =
do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
// If we can't analyze the URL, just pass on through.
if (!tldService) {
NS_WARNING("Could not get TLD service!");
return;
}
nsAutoCString currentBaseDomain;
bool ok = NS_SUCCEEDED(tldService->GetBaseDomain(aURI, 0, currentBaseDomain));
if (!ok) {
// Data URIs (commonly used for things like svg embeds) won't parse
// correctly, so just fail silently here.
return;
}
// See if URL is referencing youtube
if (!currentBaseDomain.EqualsLiteral("youtube.com") &&
!currentBaseDomain.EqualsLiteral("youtube-nocookie.com")) {
return;
}
// We should only rewrite URLs with paths starting with "/v/", as we shouldn't
// touch object nodes with "/embed/" urls that already do that right thing.
nsAutoCString path;
aURI->GetPathQueryRef(path);
if (!StringBeginsWith(path, "/v/"_ns)) {
return;
}
// See if requester is planning on using the JS API.
nsAutoCString uri;
nsresult rv = aURI->GetSpec(uri);
if (NS_FAILED(rv)) {
return;
}
// Some YouTube urls have parameters in path components, e.g.
// http://youtube.com/embed/7LcUOEP7Brc&start=35. These URLs work with flash,
// but break iframe/object embedding. If this situation occurs with rewritten
// URLs, convert the parameters to query in order to make the video load
// correctly as an iframe. In either case, warn about it in the
// developer console.
int32_t ampIndex = uri.FindChar('&', 0);
bool replaceQuery = false;
if (ampIndex != -1) {
int32_t qmIndex = uri.FindChar('?', 0);
if (qmIndex == -1 || qmIndex > ampIndex) {
replaceQuery = true;
}
}
Document* doc = AsElement()->OwnerDoc();
// If we've made it this far, we've got a rewritable embed. Log it in
// telemetry.
doc->SetUseCounter(eUseCounter_custom_YouTubeFlashEmbed);
// If we're pref'd off, return after telemetry has been logged.
if (!Preferences::GetBool(kPrefYoutubeRewrite)) {
return;
}
nsAutoString utf16OldURI = NS_ConvertUTF8toUTF16(uri);
// If we need to convert the URL, it means an ampersand comes first.
// Use the index we found earlier.
if (replaceQuery) {
// Replace question marks with ampersands.
uri.ReplaceChar('?', '&');
// Replace the first ampersand with a question mark.
uri.SetCharAt('?', ampIndex);
}
// Switch out video access url formats, which should possibly allow HTML5
// video loading.
uri.ReplaceSubstring("/v/"_ns, "/embed/"_ns);
nsAutoString utf16URI = NS_ConvertUTF8toUTF16(uri);
rv = nsContentUtils::NewURIWithDocumentCharset(aRewrittenURI, utf16URI, doc,
aBaseURI);
if (NS_FAILED(rv)) {
return;
}
AutoTArray<nsString, 2> params = {utf16OldURI, utf16URI};
const char* msgName;
// If there's no query to rewrite, just notify in the developer console
// that we're changing the embed.
if (!replaceQuery) {
msgName = "RewriteYouTubeEmbed";
} else {
msgName = "RewriteYouTubeEmbedPathParams";
}
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Plugins"_ns,
doc, nsContentUtils::eDOM_PROPERTIES, msgName,
params);
}
bool nsObjectLoadingContent::CheckLoadPolicy(int16_t* aContentPolicy) {
if (!aContentPolicy || !mURI) {
MOZ_ASSERT_UNREACHABLE("Doing it wrong");
return false;
}
Element* el = AsElement();
Document* doc = el->OwnerDoc();
nsContentPolicyType contentPolicyType = GetContentPolicyType();
nsCOMPtr<nsILoadInfo> secCheckLoadInfo =
new LoadInfo(doc->NodePrincipal(), // loading principal
doc->NodePrincipal(), // triggering principal
el, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
contentPolicyType);
*aContentPolicy = nsIContentPolicy::ACCEPT;
nsresult rv =
NS_CheckContentLoadPolicy(mURI, secCheckLoadInfo, aContentPolicy,
nsContentUtils::GetContentPolicy());
NS_ENSURE_SUCCESS(rv, false);
if (NS_CP_REJECTED(*aContentPolicy)) {
LOG(("OBJLC [%p]: Content policy denied load of %s", this,
mURI->GetSpecOrDefault().get()));
return false;
}
return true;
}
bool nsObjectLoadingContent::CheckProcessPolicy(int16_t* aContentPolicy) {
if (!aContentPolicy) {
MOZ_ASSERT_UNREACHABLE("Null out variable");
return false;
}
Element* el = AsElement();
Document* doc = el->OwnerDoc();
nsContentPolicyType objectType;
switch (mType) {
case ObjectType::Document:
objectType = nsIContentPolicy::TYPE_DOCUMENT;
break;
default:
MOZ_ASSERT_UNREACHABLE(
"Calling checkProcessPolicy with an unexpected type");
return false;
}
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
doc->NodePrincipal(), // loading principal
doc->NodePrincipal(), // triggering principal
el, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, objectType);
*aContentPolicy = nsIContentPolicy::ACCEPT;
nsresult rv = NS_CheckContentProcessPolicy(
mURI ? mURI : mBaseURI, secCheckLoadInfo, aContentPolicy,
nsContentUtils::GetContentPolicy());
NS_ENSURE_SUCCESS(rv, false);
if (NS_CP_REJECTED(*aContentPolicy)) {
LOG(("OBJLC [%p]: CheckContentProcessPolicy rejected load", this));
return false;
}
return true;
}
bool nsObjectLoadingContent::IsSyntheticImageDocument() const {
if (mType != ObjectType::Document || !mFrameLoader) {
return false;
}
BrowsingContext* browsingContext = mFrameLoader->GetExtantBrowsingContext();
return browsingContext && browsingContext->GetIsSyntheticDocumentContainer();
}
nsObjectLoadingContent::ParameterUpdateFlags
nsObjectLoadingContent::UpdateObjectParameters() {
Element* el = AsElement();
uint32_t caps = GetCapabilities();
LOG(("OBJLC [%p]: Updating object parameters", this));
nsresult rv;
nsAutoCString newMime;
nsAutoString typeAttr;
nsCOMPtr<nsIURI> newURI;
nsCOMPtr<nsIURI> newBaseURI;
ObjectType newType;
// Set if this state can't be used to load anything, forces
// ObjectType::Fallback
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.
ParameterUpdateFlags retval = eParamNoChange;
///
/// Initial MIME Type
///
if (caps & eFallbackIfClassIDPresent &&
el->HasNonEmptyAttr(nsGkAtoms::classid)) {
// We don't support class ID plugin references, so we should always treat
// having class Ids as attributes as invalid, and fallback accordingly.
newMime.Truncate();
stateInvalid = true;
}
///
/// Codebase
///
nsAutoString codebaseStr;
nsIURI* docBaseURI = el->GetBaseURI();
el->GetAttr(nsGkAtoms::codebase, codebaseStr);
if (!codebaseStr.IsEmpty()) {
rv = nsContentUtils::NewURIWithDocumentCharset(
getter_AddRefs(newBaseURI), codebaseStr, el->OwnerDoc(), docBaseURI);
if (NS_FAILED(rv)) {
// Malformed URI
LOG(
("OBJLC [%p]: Could not parse plugin's codebase as a URI, "
"will use document baseURI instead",
this));
}
}
// If we failed to build a valid URI, use the document's base URI
if (!newBaseURI) {
newBaseURI = docBaseURI;
}
nsAutoString rawTypeAttr;
el->GetAttr(nsGkAtoms::type, rawTypeAttr);
if (!rawTypeAttr.IsEmpty()) {
typeAttr = rawTypeAttr;
nsAutoString params;
nsAutoString mime;
nsContentUtils::SplitMimeType(rawTypeAttr, mime, params);
CopyUTF16toUTF8(mime, newMime);
}
///
/// URI
///
nsAutoString uriStr;
// Different elements keep this in various locations
if (el->NodeInfo()->Equals(nsGkAtoms::object)) {
el->GetAttr(nsGkAtoms::data, uriStr);
} else if (el->NodeInfo()->Equals(nsGkAtoms::embed)) {
el->GetAttr(nsGkAtoms::src, uriStr);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized plugin-loading tag");
}
mRewrittenYoutubeEmbed = false;
// Note that the baseURI changing could affect the newURI, even if uriStr did
// not change.
if (!uriStr.IsEmpty()) {
rv = nsContentUtils::NewURIWithDocumentCharset(
getter_AddRefs(newURI), uriStr, el->OwnerDoc(), newBaseURI);
nsCOMPtr<nsIURI> rewrittenURI;
MaybeRewriteYoutubeEmbed(newURI, newBaseURI, getter_AddRefs(rewrittenURI));
if (rewrittenURI) {
newURI = rewrittenURI;
mRewrittenYoutubeEmbed = true;
newMime = "text/html"_ns;
}
if (NS_FAILED(rv)) {
stateInvalid = true;
}
}
///
/// Check if the original (pre-channel) content-type or URI changed, and
/// record mOriginal{ContentType,URI}
///
if ((mOriginalContentType != newMime) || !URIEquals(mOriginalURI, newURI)) {
// These parameters changing requires re-opening the channel, so don't
// consider the currently-open channel below
// XXX(johns): Changing the mime type might change our decision on whether
// or not we load a channel, so we count changes to it as a
// channel parameter change for the sake of simplicity.
retval = (ParameterUpdateFlags)(retval | eParamChannelChanged);
LOG(("OBJLC [%p]: Channel parameters changed", this));
}
mOriginalContentType = newMime;
mOriginalURI = newURI;
///
/// If we have a channel, see if its MIME type should take precendence and
/// check the final (redirected) URL
///
// If we have a loaded channel and channel parameters did not change, use it
// to determine what we would load.
bool useChannel = mChannelLoaded && !(retval & eParamChannelChanged);
// If we have a channel and are type loading, as opposed to having an existing
// channel for a previous load.
bool newChannel = useChannel && mType == ObjectType::Loading;
RefPtr<DocumentChannel> documentChannel = do_QueryObject(mChannel);
if (newChannel && documentChannel) {
// If we've got a DocumentChannel which is marked as loaded using
// `mChannelLoaded`, we are currently in the middle of a
// `UpgradeLoadToDocument`.
//
// As we don't have the real mime-type from the channel, handle this by
// using `newMime`.
newMime = TEXT_HTML;
MOZ_DIAGNOSTIC_ASSERT(GetTypeOfContent(newMime) == ObjectType::Document,
"How is text/html not ObjectType::Document?");
} else if (newChannel && mChannel) {
nsCString channelType;
rv = mChannel->GetContentType(channelType);
if (NS_FAILED(rv)) {
MOZ_ASSERT_UNREACHABLE("GetContentType failed");
stateInvalid = true;
channelType.Truncate();
}
LOG(("OBJLC [%p]: Channel has a content type of %s", this,
channelType.get()));
bool binaryChannelType = false;
if (channelType.EqualsASCII(APPLICATION_GUESS_FROM_EXT)) {
channelType = APPLICATION_OCTET_STREAM;
mChannel->SetContentType(channelType);
binaryChannelType = true;
} else if (channelType.EqualsASCII(APPLICATION_OCTET_STREAM) ||
channelType.EqualsASCII(BINARY_OCTET_STREAM)) {
binaryChannelType = true;
}
// Channel can change our URI through redirection
rv = NS_GetFinalChannelURI(mChannel, getter_AddRefs(newURI));
if (NS_FAILED(rv)) {
MOZ_ASSERT_UNREACHABLE("NS_GetFinalChannelURI failure");
stateInvalid = true;
}
ObjectType typeHint =
newMime.IsEmpty() ? ObjectType::Fallback : GetTypeOfContent(newMime);
// In order of preference:
//
// 1) Use our type hint if it matches a plugin
// 2) If we have eAllowPluginSkipChannel, use the uri file extension if
// it matches a plugin
// 3) If the channel returns a binary stream type:
// 3a) If we have a type non-null non-document type hint, use that
// 3b) If the uri file extension matches a plugin type, use that
// 4) Use the channel type
bool overrideChannelType = false;
if (IsPluginMIME(newMime)) {
LOG(("OBJLC [%p]: Using plugin type hint in favor of any channel type",
this));
overrideChannelType = true;
} else if (binaryChannelType && typeHint != ObjectType::Fallback) {
if (typeHint == ObjectType::Document) {
if (imgLoader::SupportImageWithMimeType(newMime)) {