1924 lines
64 KiB
C++
1924 lines
64 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
/*
|
|
* A base class implementing nsIObjectLoadingContent for use by
|
|
* various content nodes that want to provide plugin/document/image
|
|
* loading functionality (eg <embed>, <object>, etc).
|
|
*/
|
|
|
|
// Interface headers
|
|
#include "imgLoader.h"
|
|
#include "nsIClassOfService.h"
|
|
#include "nsIConsoleService.h"
|
|
#include "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 prePath;
|
|
nsresult rv = aURI->GetPrePath(prePath);
|
|
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 = path.FindChar('&', 0);
|
|
bool replaceQuery = false;
|
|
if (ampIndex != -1) {
|
|
int32_t qmIndex = path.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;
|
|
}
|
|
|
|
NS_ConvertUTF8toUTF16 utf16OldURI(prePath);
|
|
AppendUTF8toUTF16(path, utf16OldURI);
|
|
// 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.
|
|
path.ReplaceChar('?', '&');
|
|
// Replace the first ampersand with a question mark.
|
|
path.SetCharAt('?', ampIndex);
|
|
}
|
|
// Switch out video access url formats, which should possibly allow HTML5
|
|
// video loading.
|
|
path.ReplaceSubstring("/v/"_ns, "/embed/"_ns);
|
|
NS_ConvertUTF8toUTF16 utf16URI(prePath);
|
|
AppendUTF8toUTF16(path, utf16URI);
|
|
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();
|
|
|
|
Result<RefPtr<LoadInfo>, nsresult> maybeLoadInfo =
|
|
LoadInfo::Create(doc->NodePrincipal(), // loading principal
|
|
doc->NodePrincipal(), // triggering principal
|
|
el, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
|
|
contentPolicyType);
|
|
if (NS_WARN_IF(maybeLoadInfo.isErr())) {
|
|
return false;
|
|
}
|
|
RefPtr<LoadInfo> secCheckLoadInfo = maybeLoadInfo.unwrap();
|
|
|
|
*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;
|
|
}
|
|
|
|
Result<RefPtr<LoadInfo>, nsresult> maybeLoadInfo = LoadInfo::Create(
|
|
doc->NodePrincipal(), // loading principal
|
|
doc->NodePrincipal(), // triggering principal
|
|
el, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, objectType);
|
|
if (NS_WARN_IF(maybeLoadInfo.isErr())) {
|
|
return false;
|
|
}
|
|
RefPtr<LoadInfo> secCheckLoadInfo = maybeLoadInfo.unwrap();
|
|
|
|
*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)) {
|
|
LOG(
|
|
("OBJLC [%p]: Using type hint in favor of binary channel type "
|
|
"(Image Document)",
|
|
this));
|
|
overrideChannelType = true;
|
|
}
|
|
} else {
|
|
LOG(
|
|
("OBJLC [%p]: Using type hint in favor of binary channel type "
|
|
"(Non-Image Document)",
|
|
this));
|
|
overrideChannelType = true;
|
|
}
|
|
}
|
|
|
|
if (overrideChannelType) {
|
|
// Set the type we'll use for dispatch on the channel. Otherwise we could
|
|
// end up trying to dispatch to a nsFrameLoader, which will complain that
|
|
// it couldn't find a way to handle application/octet-stream
|
|
nsAutoCString parsedMime, dummy;
|
|
NS_ParseResponseContentType(newMime, parsedMime, dummy);
|
|
if (!parsedMime.IsEmpty()) {
|
|
mChannel->SetContentType(parsedMime);
|
|
}
|
|
} else {
|
|
newMime = channelType;
|
|
}
|
|
} else if (newChannel) {
|
|
LOG(("OBJLC [%p]: We failed to open a channel, marking invalid", this));
|
|
stateInvalid = true;
|
|
}
|
|
|
|
///
|
|
/// Determine final type
|
|
///
|
|
// In order of preference:
|
|
// 1) If we have attempted channel load, or set stateInvalid above, the type
|
|
// is always null (fallback)
|
|
// 2) If we have a loaded channel, we grabbed its mimeType above, use that
|
|
// type.
|
|
// 3) If we have a plugin type and no URI, use that type.
|
|
// 4) If we have a plugin type and eAllowPluginSkipChannel, use that type.
|
|
// 5) if we have a URI, set type to loading to indicate we'd need a channel
|
|
// to proceed.
|
|
// 6) Otherwise, type null to indicate unloadable content (fallback)
|
|
//
|
|
|
|
ObjectType newMime_Type = GetTypeOfContent(newMime);
|
|
|
|
if (stateInvalid) {
|
|
newType = ObjectType::Fallback;
|
|
LOG(("OBJLC [%p]: NewType #0: %s - %u", this, newMime.get(),
|
|
uint32_t(newType)));
|
|
newMime.Truncate();
|
|
} else if (newChannel) {
|
|
// If newChannel is set above, we considered it in setting newMime
|
|
newType = newMime_Type;
|
|
LOG(("OBJLC [%p]: NewType #1: %s - %u", this, newMime.get(),
|
|
uint32_t(newType)));
|
|
LOG(("OBJLC [%p]: Using channel type", this));
|
|
} else if (((caps & eAllowPluginSkipChannel) || !newURI) &&
|
|
IsPluginMIME(newMime)) {
|
|
newType = newMime_Type;
|
|
LOG(("OBJLC [%p]: NewType #2: %s - %u", this, newMime.get(),
|
|
uint32_t(newType)));
|
|
LOG(("OBJLC [%p]: Plugin type with no URI, skipping channel load", this));
|
|
} else if (newURI && (mOriginalContentType.IsEmpty() ||
|
|
newMime_Type != ObjectType::Fallback)) {
|
|
// We could potentially load this if we opened a channel on mURI, indicate
|
|
// this by leaving type as loading.
|
|
//
|
|
// If a MIME type was requested in the tag, but we have decided to set load
|
|
// type to null, ignore (otherwise we'll default to document type loading).
|
|
newType = ObjectType::Loading;
|
|
LOG(("OBJLC [%p]: NewType #3: %u", this, uint32_t(newType)));
|
|
} else {
|
|
// Unloadable - no URI, and no plugin/MIME type. Non-plugin types (images,
|
|
// documents) always load with a channel.
|
|
newType = ObjectType::Fallback;
|
|
LOG(("OBJLC [%p]: NewType #4: %u", this, uint32_t(newType)));
|
|
}
|
|
|
|
///
|
|
/// Handle existing channels
|
|
///
|
|
|
|
if (useChannel && newType == ObjectType::Loading) {
|
|
// We decided to use a channel, and also that the previous channel is still
|
|
// usable, so re-use the existing values.
|
|
newType = mType;
|
|
LOG(("OBJLC [%p]: NewType #5: %u", this, uint32_t(newType)));
|
|
newMime = mContentType;
|
|
newURI = mURI;
|
|
} else if (useChannel && !newChannel) {
|
|
// We have an existing channel, but did not decide to use one.
|
|
retval = (ParameterUpdateFlags)(retval | eParamChannelChanged);
|
|
useChannel = false;
|
|
}
|
|
|
|
///
|
|
/// Update changed values
|
|
///
|
|
|
|
if (newType != mType) {
|
|
retval = (ParameterUpdateFlags)(retval | eParamStateChanged);
|
|
LOG(("OBJLC [%p]: Type changed from %u -> %u", this, uint32_t(mType),
|
|
uint32_t(newType)));
|
|
mType = newType;
|
|
}
|
|
|
|
if (!URIEquals(mBaseURI, newBaseURI)) {
|
|
LOG(("OBJLC [%p]: Object effective baseURI changed", this));
|
|
mBaseURI = newBaseURI;
|
|
}
|
|
|
|
if (!URIEquals(newURI, mURI)) {
|
|
retval = (ParameterUpdateFlags)(retval | eParamStateChanged);
|
|
LOG(("OBJLC [%p]: Object effective URI changed", this));
|
|
mURI = newURI;
|
|
}
|
|
|
|
// We don't update content type when loading, as the type is not final and we
|
|
// don't want to superfluously change between mOriginalContentType ->
|
|
// mContentType when doing |obj.data = obj.data| with a channel and differing
|
|
// type.
|
|
if (mType != ObjectType::Loading && mContentType != newMime) {
|
|
retval = (ParameterUpdateFlags)(retval | eParamStateChanged);
|
|
retval = (ParameterUpdateFlags)(retval | eParamContentTypeChanged);
|
|
LOG(("OBJLC [%p]: Object effective mime type changed (%s -> %s)", this,
|
|
mContentType.get(), newMime.get()));
|
|
mContentType = newMime;
|
|
}
|
|
|
|
// If we decided to keep using info from an old channel, but also that state
|
|
// changed, we need to invalidate it.
|
|
if (useChannel && !newChannel && (retval & eParamStateChanged)) {
|
|
mType = ObjectType::Loading;
|
|
retval = (ParameterUpdateFlags)(retval | eParamChannelChanged);
|
|
}
|
|
|
|
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) {
|
|
Element* el = AsElement();
|
|
Document* doc = el->OwnerDoc();
|
|
nsresult rv = NS_OK;
|
|
|
|
// Per bug 1318303, if the parent document is not active, load the alternative
|
|
// and return.
|
|
if (!doc->IsCurrentActiveDocument()) {
|
|
// Since this can be triggered on change of attributes, make sure we've
|
|
// unloaded whatever is loaded first.
|
|
UnloadObject();
|
|
ObjectType oldType = mType;
|
|
mType = ObjectType::Fallback;
|
|
TriggerInnerFallbackLoads();
|
|
NotifyStateChanged(oldType, true);
|
|
return NS_OK;
|
|
}
|
|
|
|
// XXX(johns): In these cases, we refuse to touch our content and just
|
|
// remain unloaded, as per legacy behavior. It would make more sense to
|
|
// load fallback content initially and refuse to ever change state again.
|
|
if (doc->IsBeingUsedAsImage()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
if (doc->IsLoadedAsData() || doc->IsStaticDocument()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
LOG(("OBJLC [%p]: LoadObject called, notify %u, forceload %u, channel %p",
|
|
this, aNotify, aForceLoad, aLoadingChannel));
|
|
|
|
// We can't re-use an already open channel, but aForceLoad may make us try
|
|
// to load a plugin without any changes in channel state.
|
|
if (aForceLoad && mChannelLoaded) {
|
|
CloseChannel();
|
|
mChannelLoaded = false;
|
|
}
|
|
|
|
// Save these for NotifyStateChanged();
|
|
ObjectType oldType = mType;
|
|
|
|
ParameterUpdateFlags stateChange = UpdateObjectParameters();
|
|
|
|
if (!stateChange && !aForceLoad) {
|
|
return NS_OK;
|
|
}
|
|
|
|
///
|
|
/// State has changed, unload existing content and attempt to load new type
|
|
///
|
|
LOG(("OBJLC [%p]: LoadObject - plugin state changed (%u)", this,
|
|
stateChange));
|
|
|
|
// We synchronously start/stop plugin instances below, which may spin the
|
|
// event loop. Re-entering into the load is fine, but at that point the
|
|
// original load call needs to abort when unwinding
|
|
// NOTE this is located *after* the state change check, a subsequent load
|
|
// with no subsequently changed state will be a no-op.
|
|
if (mIsLoading) {
|
|
LOG(("OBJLC [%p]: Re-entering into LoadObject", this));
|
|
}
|
|
mIsLoading = true;
|
|
AutoSetLoadingToFalse reentryCheck(this);
|
|
|
|
// Unload existing content, keeping in mind stopping plugins might spin the
|
|
// event loop. Note that we check for still-open channels below
|
|
UnloadObject(false); // Don't reset state
|
|
if (!mIsLoading) {
|
|
// The event loop must've spun and re-entered into LoadObject, which
|
|
// finished the load
|
|
LOG(("OBJLC [%p]: Re-entered into LoadObject, aborting outer load", this));
|
|
return NS_OK;
|
|
}
|
|
|
|
// Determine what's going on with our channel.
|
|
if (stateChange & eParamChannelChanged) {
|
|
// If the channel params changed, throw away the channel, but unset
|
|
// mChannelLoaded so we'll still try to open a new one for this load if
|
|
// necessary
|
|
CloseChannel();
|
|
mChannelLoaded = false;
|
|
} else if (mType == ObjectType::Fallback && mChannel) {
|
|
// If we opened a channel but then failed to find a loadable state, throw it
|
|
// away. mChannelLoaded will indicate that we tried to load a channel at one
|
|
// point so we wont recurse
|
|
CloseChannel();
|
|
} else if (mType == ObjectType::Loading && mChannel) {
|
|
// We're still waiting on a channel load, already opened one, and
|
|
// channel parameters didn't change
|
|
return NS_OK;
|
|
} else if (mChannelLoaded && mChannel != aLoadingChannel) {
|
|
// The only time we should have a loaded channel with a changed state is
|
|
// when the channel has just opened -- in which case this call should
|
|
// have originated from OnStartRequest
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"Loading with a channel, but state doesn't make sense");
|
|
return NS_OK;
|
|
}
|
|
|
|
//
|
|
// Security checks
|
|
//
|
|
|
|
if (mType != ObjectType::Fallback) {
|
|
bool allowLoad = true;
|
|
int16_t contentPolicy = nsIContentPolicy::ACCEPT;
|
|
// If mChannelLoaded is set we presumably already passed load policy
|
|
// If mType == ObjectType::Loading then we call OpenChannel() which
|
|
// internally creates a new channel and calls asyncOpen() on that channel
|
|
// which then enforces content policy checks.
|
|
if (allowLoad && mURI && !mChannelLoaded && mType != ObjectType::Loading) {
|
|
allowLoad = CheckLoadPolicy(&contentPolicy);
|
|
}
|
|
// If we're loading a type now, check ProcessPolicy. Note that we may check
|
|
// both now in the case of plugins whose type is determined before opening a
|
|
// channel.
|
|
if (allowLoad && mType != ObjectType::Loading) {
|
|
allowLoad = CheckProcessPolicy(&contentPolicy);
|
|
}
|
|
|
|
// Content policy implementations can mutate the DOM, check for re-entry
|
|
if (!mIsLoading) {
|
|
LOG(("OBJLC [%p]: We re-entered in content policy, leaving original load",
|
|
this));
|
|
return NS_OK;
|
|
}
|
|
|
|
// Load denied, switch to null
|
|
if (!allowLoad) {
|
|
LOG(("OBJLC [%p]: Load denied by policy", this));
|
|
mType = ObjectType::Fallback;
|
|
}
|
|
}
|
|
|
|
// Don't allow view-source scheme.
|
|
// view-source is the only scheme to which this applies at the moment due to
|
|
// potential timing attacks to read data from cross-origin documents. If this
|
|
// widens we should add a protocol flag for whether the scheme is only allowed
|
|
// in top and use something like nsNetUtil::NS_URIChainHasFlags.
|
|
if (mType != ObjectType::Fallback) {
|
|
nsCOMPtr<nsIURI> tempURI = mURI;
|
|
nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(tempURI);
|
|
while (nestedURI) {
|
|
// view-source should always be an nsINestedURI, loop and check the
|
|
// scheme on this and all inner URIs that are also nested URIs.
|
|
if (tempURI->SchemeIs("view-source")) {
|
|
LOG(("OBJLC [%p]: Blocking as effective URI has view-source scheme",
|
|
this));
|
|
mType = ObjectType::Fallback;
|
|
break;
|
|
}
|
|
|
|
nestedURI->GetInnerURI(getter_AddRefs(tempURI));
|
|
nestedURI = do_QueryInterface(tempURI);
|
|
}
|
|
}
|
|
|
|
// Items resolved as Image/Document are not candidates for content blocking,
|
|
// as well as invalid plugins (they will not have the mContentType set).
|
|
if (mType == ObjectType::Fallback && ShouldBlockContent()) {
|
|
LOG(("OBJLC [%p]: Enable content blocking", this));
|
|
mType = ObjectType::Loading;
|
|
}
|
|
|
|
// Sanity check: We shouldn't have any loaded resources, pending events, or
|
|
// a final listener at this point
|
|
if (mFrameLoader || mFinalListener) {
|
|
MOZ_ASSERT_UNREACHABLE("Trying to load new plugin with existing content");
|
|
return NS_OK;
|
|
}
|
|
|
|
// More sanity-checking:
|
|
// If mChannel is set, mChannelLoaded should be set, and vice-versa
|
|
if (mType != ObjectType::Fallback && !!mChannel != mChannelLoaded) {
|
|
MOZ_ASSERT_UNREACHABLE("Trying to load with bad channel state");
|
|
return NS_OK;
|
|
}
|
|
|
|
///
|
|
/// Attempt to load new type
|
|
///
|
|
|
|
// We don't set mFinalListener until OnStartRequest has been called, to
|
|
// prevent re-entry ugliness with CloseChannel()
|
|
nsCOMPtr<nsIStreamListener> finalListener;
|
|
switch (mType) {
|
|
case ObjectType::Document: {
|
|
if (!mChannel) {
|
|
// We could mFrameLoader->LoadURI(mURI), but UpdateObjectParameters
|
|
// requires documents have a channel, so this is not a valid state.
|
|
MOZ_ASSERT_UNREACHABLE(
|
|
"Attempting to load a document without a "
|
|
"channel");
|
|
rv = NS_ERROR_FAILURE;
|
|
break;
|
|
}
|
|
|
|
nsCOMPtr<nsIDocShell> docShell = SetupDocShell(mURI);
|
|
if (!docShell) {
|
|
rv = NS_ERROR_FAILURE;
|
|
break;
|
|
}
|
|
|
|
// We're loading a document, so we have to set LOAD_DOCUMENT_URI
|
|
// (especially important for firing onload)
|
|
nsLoadFlags flags = 0;
|
|
mChannel->GetLoadFlags(&flags);
|
|
flags |= nsIChannel::LOAD_DOCUMENT_URI;
|
|
mChannel->SetLoadFlags(flags);
|
|
|
|
nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(docShell));
|
|
NS_ASSERTION(req, "Docshell must be an ifreq");
|
|
|
|
nsCOMPtr<nsIURILoader> uriLoader(components::URILoader::Service());
|
|
if (NS_WARN_IF(!uriLoader)) {
|
|
MOZ_ASSERT_UNREACHABLE("Failed to get uriLoader service");
|
|
mFrameLoader->Destroy();
|
|
mFrameLoader = nullptr;
|
|
break;
|
|
}
|
|
|
|
uint32_t uriLoaderFlags = nsDocShell::ComputeURILoaderFlags(
|
|
docShell->GetBrowsingContext(), LOAD_NORMAL,
|
|
/* aIsDocumentLoad */ false);
|
|
|
|
rv = uriLoader->OpenChannel(mChannel, uriLoaderFlags, req,
|
|
getter_AddRefs(finalListener));
|
|
// finalListener will receive OnStartRequest either below, or if
|
|
// `mChannel` is a `DocumentChannel`, it will be received after
|
|
// RedirectToRealChannel.
|
|
} break;
|
|
case ObjectType::Loading:
|
|
// If our type remains Loading, we need a channel to proceed
|
|
rv = OpenChannel();
|
|
if (NS_FAILED(rv)) {
|
|
LOG(("OBJLC [%p]: OpenChannel returned failure (%" PRIu32 ")", this,
|
|
static_cast<uint32_t>(rv)));
|
|
}
|
|
break;
|
|
case ObjectType::Fallback:
|
|
// Handled below, silence compiler warnings
|
|
break;
|
|
}
|
|
|
|
//
|
|
// Loaded, handle notifications and fallback
|
|
//
|
|
if (NS_FAILED(rv)) {
|
|
// If we failed in the loading hunk above, switch to null (empty) region
|
|
LOG(("OBJLC [%p]: Loading failed, switching to fallback", this));
|
|
mType = ObjectType::Fallback;
|
|
}
|
|
|
|
if (mType == ObjectType::Fallback) {
|
|
LOG(("OBJLC [%p]: Switching to fallback state", this));
|
|
MOZ_ASSERT(!mFrameLoader, "switched to fallback but also loaded something");
|
|
|
|
MaybeFireErrorEvent();
|
|
|
|
if (mChannel) {
|
|
// If we were loading with a channel but then failed over, throw it away
|
|
CloseChannel();
|
|
}
|
|
|
|
// Don't try to initialize plugins or final listener below
|
|
finalListener = nullptr;
|
|
|
|
TriggerInnerFallbackLoads();
|
|
}
|
|
|
|
// Notify of our final state
|
|
NotifyStateChanged(oldType, aNotify);
|
|
NS_ENSURE_TRUE(mIsLoading, NS_OK);
|
|
|
|
//
|
|
// Spawning plugins and dispatching to the final listener may re-enter, so are
|
|
// delayed until after we fire a notification, to prevent missing
|
|
// notifications or firing them out of order.
|
|
//
|
|
// Note that we ensured that we entered into LoadObject() from
|
|
// ::OnStartRequest above when loading with a channel.
|
|
//
|
|
|
|
rv = NS_OK;
|
|
if (finalListener) {
|
|
NS_ASSERTION(mType != ObjectType::Fallback && mType != ObjectType::Loading,
|
|
"We should not have a final listener with a non-loaded type");
|
|
mFinalListener = finalListener;
|
|
|
|
// If we're a DocumentChannel load, hold off on firing the `OnStartRequest`
|
|
// callback, as we haven't received it yet from our caller.
|
|
RefPtr<DocumentChannel> documentChannel = do_QueryObject(mChannel);
|
|
if (documentChannel) {
|
|
MOZ_ASSERT(
|
|
mType == ObjectType::Document,
|
|
"We have a DocumentChannel here but aren't loading a document?");
|
|
} else {
|
|
rv = finalListener->OnStartRequest(mChannel);
|
|
}
|
|
}
|
|
|
|
if ((NS_FAILED(rv) && rv != NS_ERROR_PARSED_DATA_CACHED) && mIsLoading) {
|
|
// Since we've already notified of our transition, we can just Unload and
|
|
// call ConfigureFallback (which will notify again)
|
|
oldType = mType;
|
|
mType = ObjectType::Fallback;
|
|
UnloadObject(false);
|
|
NS_ENSURE_TRUE(mIsLoading, NS_OK);
|
|
CloseChannel();
|
|
TriggerInnerFallbackLoads();
|
|
NotifyStateChanged(oldType, true);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// This call can re-enter when dealing with plugin listeners
|
|
nsresult nsObjectLoadingContent::CloseChannel() {
|
|
if (mChannel) {
|
|
LOG(("OBJLC [%p]: Closing channel\n", this));
|
|
// Null the values before potentially-reentering, and ensure they survive
|
|
// the call
|
|
nsCOMPtr<nsIChannel> channelGrip(mChannel);
|
|
nsCOMPtr<nsIStreamListener> listenerGrip(mFinalListener);
|
|
mChannel = nullptr;
|
|
mFinalListener = nullptr;
|
|
channelGrip->CancelWithReason(NS_BINDING_ABORTED,
|
|
"nsObjectLoadingContent::CloseChannel"_ns);
|
|
if (listenerGrip) {
|
|
// mFinalListener is only set by LoadObject after OnStartRequest, or
|
|
// by OnStartRequest in the case of late-opened plugin streams
|
|
listenerGrip->OnStopRequest(channelGrip, NS_BINDING_ABORTED);
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsObjectLoadingContent::IsAboutBlankLoadOntoInitialAboutBlank(
|
|
nsIURI* aURI, bool aInheritPrincipal, nsIPrincipal* aPrincipalToInherit) {
|
|
return NS_IsAboutBlank(aURI) && aInheritPrincipal &&
|
|
(!mFrameLoader || !mFrameLoader->GetExistingDocShell() ||
|
|
mFrameLoader->GetExistingDocShell()
|
|
->IsAboutBlankLoadOntoInitialAboutBlank(aURI, aInheritPrincipal,
|
|
aPrincipalToInherit));
|
|
}
|
|
|
|
nsresult nsObjectLoadingContent::OpenChannel() {
|
|
Element* el = AsElement();
|
|
Document* doc = el->OwnerDoc();
|
|
NS_ASSERTION(doc, "No owner document?");
|
|
|
|
nsresult rv;
|
|
mChannel = nullptr;
|
|
|
|
// E.g. mms://
|
|
if (!mURI || !CanHandleURI(mURI)) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
nsCOMPtr<nsILoadGroup> group = doc->GetDocumentLoadGroup();
|
|
nsCOMPtr<nsIChannel> chan;
|
|
RefPtr<ObjectInterfaceRequestorShim> shim =
|
|
new ObjectInterfaceRequestorShim(this);
|
|
|
|
bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal(
|
|
el->NodePrincipal(), // aLoadState->PrincipalToInherit()
|
|
mURI, // aLoadState->URI()
|
|
true, // aInheritForAboutBlank
|
|
false); // aForceInherit
|
|
|
|
bool inheritPrincipal = inheritAttrs && !mURI->SchemeIs("data");
|
|
|
|
nsSecurityFlags securityFlags =
|
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL;
|
|
if (inheritPrincipal) {
|
|
securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
|
|
}
|
|
|
|
nsContentPolicyType contentPolicyType = GetContentPolicyType();
|
|
// The setting of LOAD_BYPASS_SERVICE_WORKER here is now an optimization.
|
|
// ServiceWorkerInterceptController::ShouldPrepareForIntercept does a more
|
|
// expensive check of BrowsingContext ancestors to look for object/embed.
|
|
nsLoadFlags loadFlags = nsIChannel::LOAD_CALL_CONTENT_SNIFFERS |
|
|
nsIChannel::LOAD_BYPASS_SERVICE_WORKER |
|
|
nsIRequest::LOAD_HTML_OBJECT_DATA;
|
|
uint32_t sandboxFlags = doc->GetSandboxFlags();
|
|
|
|
// For object loads we store the CSP that potentially needs to
|
|
// be inherited, e.g. in case we are loading an opaque origin
|
|
// like a data: URI. The actual inheritance check happens within
|
|
// Document::InitCSP(). Please create an actual copy of the CSP
|
|
// (do not share the same reference) otherwise a Meta CSP of an
|
|
// opaque origin will incorrectly be propagated to the embedding
|
|
// document.
|
|
RefPtr<nsCSPContext> cspToInherit;
|
|
if (nsCOMPtr<nsIContentSecurityPolicy> csp = doc->GetCsp()) {
|
|
cspToInherit = new nsCSPContext();
|
|
cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get()));
|
|
}
|
|
|
|
// --- Create LoadInfo
|
|
RefPtr<LoadInfo> loadInfo = MOZ_TRY(LoadInfo::Create(
|
|
/*aLoadingPrincipal = aLoadingContext->NodePrincipal() */ nullptr,
|
|
/*aTriggeringPrincipal = aLoadingPrincipal */ nullptr,
|
|
/*aLoadingContext = */ el,
|
|
/*aSecurityFlags = */ securityFlags,
|
|
/*aContentPolicyType = */ contentPolicyType,
|
|
/*aLoadingClientInfo = */ Nothing(),
|
|
/*aController = */ Nothing(),
|
|
/*aSandboxFlags = */ sandboxFlags));
|
|
|
|
if (inheritAttrs) {
|
|
loadInfo->SetPrincipalToInherit(el->NodePrincipal());
|
|
}
|
|
|
|
if (cspToInherit) {
|
|
loadInfo->SetCSPToInherit(cspToInherit);
|
|
}
|
|
|
|
if (DocumentChannel::CanUseDocumentChannel(mURI) &&
|
|
!IsAboutBlankLoadOntoInitialAboutBlank(mURI, inheritPrincipal,
|
|
el->NodePrincipal())) {
|
|
// --- Create LoadState
|
|
RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(mURI);
|
|
loadState->SetPrincipalToInherit(el->NodePrincipal());
|
|
loadState->SetTriggeringPrincipal(loadInfo->TriggeringPrincipal());
|
|
if (cspToInherit) {
|
|
loadState->SetCsp(cspToInherit);
|
|
}
|
|
loadState->SetTriggeringSandboxFlags(sandboxFlags);
|
|
|
|
// TODO(djg): This was httpChan->SetReferrerInfoWithoutClone(referrerInfo);
|
|
// Is the ...WithoutClone(...) important?
|
|
auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
|
|
loadState->SetReferrerInfo(referrerInfo);
|
|
|
|
loadState->SetShouldCheckForRecursion(true);
|
|
|
|
// When loading using DocumentChannel, ensure that the MIME type hint is
|
|
// propagated to DocumentLoadListener. Object elements can override MIME
|
|
// handling in some scenarios.
|
|
if (!mOriginalContentType.IsEmpty()) {
|
|
nsAutoCString parsedMime, dummy;
|
|
NS_ParseResponseContentType(mOriginalContentType, parsedMime, dummy);
|
|
if (!parsedMime.IsEmpty()) {
|
|
loadState->SetTypeHint(parsedMime);
|
|
}
|
|
}
|
|
|
|
chan =
|
|
DocumentChannel::CreateForObject(loadState, loadInfo, loadFlags, shim);
|
|
MOZ_ASSERT(chan);
|
|
// NS_NewChannel sets the group on the channel. CreateDocumentChannel does
|
|
// not.
|
|
chan->SetLoadGroup(group);
|
|
} else {
|
|
rv = NS_NewChannelInternal(getter_AddRefs(chan), // outChannel
|
|
mURI, // aUri
|
|
loadInfo, // aLoadInfo
|
|
nullptr, // aPerformanceStorage
|
|
group, // aLoadGroup
|
|
shim, // aCallbacks
|
|
loadFlags, // aLoadFlags
|
|
nullptr); // aIoService
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (inheritAttrs) {
|
|
nsCOMPtr<nsILoadInfo> loadinfo = chan->LoadInfo();
|
|
loadinfo->SetPrincipalToInherit(el->NodePrincipal());
|
|
}
|
|
|
|
// For object loads we store the CSP that potentially needs to
|
|
// be inherited, e.g. in case we are loading an opaque origin
|
|
// like a data: URI. The actual inheritance check happens within
|
|
// Document::InitCSP(). Please create an actual copy of the CSP
|
|
// (do not share the same reference) otherwise a Meta CSP of an
|
|
// opaque origin will incorrectly be propagated to the embedding
|
|
// document.
|
|
if (cspToInherit) {
|
|
nsCOMPtr<nsILoadInfo> loadinfo = chan->LoadInfo();
|
|
static_cast<LoadInfo*>(loadinfo.get())->SetCSPToInherit(cspToInherit);
|
|
}
|
|
};
|
|
|
|
// Referrer
|
|
nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan));
|
|
if (httpChan) {
|
|
auto referrerInfo = MakeRefPtr<ReferrerInfo>(*doc);
|
|
|
|
rv = httpChan->SetReferrerInfoWithoutClone(referrerInfo);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
// Set the initiator type
|
|
nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(httpChan));
|
|
if (timedChannel) {
|
|
timedChannel->SetInitiatorType(el->LocalName());
|
|
}
|
|
|
|
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(httpChan));
|
|
if (cos && UserActivation::IsHandlingUserInput()) {
|
|
cos->AddClassFlags(nsIClassOfService::UrgentStart);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(chan);
|
|
if (scriptChannel) {
|
|
// Allow execution against our context if the principals match
|
|
scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
|
|
}
|
|
|
|
// AsyncOpen can fail if a file does not exist.
|
|
rv = chan->AsyncOpen(shim);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
LOG(("OBJLC [%p]: Channel opened", this));
|
|
mChannel = chan;
|
|
return NS_OK;
|
|
}
|
|
|
|
uint32_t nsObjectLoadingContent::GetCapabilities() const {
|
|
return eSupportImages | eSupportDocuments;
|
|
}
|
|
|
|
void nsObjectLoadingContent::Destroy() {
|
|
if (mFrameLoader) {
|
|
mFrameLoader->Destroy();
|
|
mFrameLoader = nullptr;
|
|
}
|
|
|
|
// Reset state so that if the element is re-appended to tree again (e.g.
|
|
// adopting to another document), it will reload resource again.
|
|
UnloadObject();
|
|
}
|
|
|
|
/* static */
|
|
void nsObjectLoadingContent::Traverse(nsObjectLoadingContent* tmp,
|
|
nsCycleCollectionTraversalCallback& cb) {
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader);
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFeaturePolicy);
|
|
}
|
|
|
|
/* static */
|
|
void nsObjectLoadingContent::Unlink(nsObjectLoadingContent* tmp) {
|
|
if (tmp->mFrameLoader) {
|
|
tmp->mFrameLoader->Destroy();
|
|
}
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameLoader);
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFeaturePolicy);
|
|
}
|
|
|
|
void nsObjectLoadingContent::UnloadObject(bool aResetState) {
|
|
if (mFrameLoader) {
|
|
mFrameLoader->Destroy();
|
|
mFrameLoader = nullptr;
|
|
}
|
|
|
|
if (aResetState) {
|
|
CloseChannel();
|
|
mChannelLoaded = false;
|
|
mType = ObjectType::Loading;
|
|
mURI = mOriginalURI = mBaseURI = nullptr;
|
|
mContentType.Truncate();
|
|
mOriginalContentType.Truncate();
|
|
}
|
|
|
|
mScriptRequested = false;
|
|
|
|
mIsStopping = false;
|
|
|
|
mSubdocumentIntrinsicSize.reset();
|
|
mSubdocumentIntrinsicRatio.reset();
|
|
}
|
|
|
|
void nsObjectLoadingContent::NotifyStateChanged(ObjectType aOldType,
|
|
bool aNotify) {
|
|
LOG(("OBJLC [%p]: NotifyStateChanged: (%u) -> (%u) (notify %i)", this,
|
|
uint32_t(aOldType), uint32_t(mType), aNotify));
|
|
|
|
dom::Element* thisEl = AsElement();
|
|
// Non-images are always not broken.
|
|
// XXX: I assume we could just remove this completely?
|
|
thisEl->RemoveStates(ElementState::BROKEN, aNotify);
|
|
|
|
if (mType == aOldType) {
|
|
return;
|
|
}
|
|
|
|
Document* doc = thisEl->GetComposedDoc();
|
|
if (!doc) {
|
|
return; // Nothing to do
|
|
}
|
|
|
|
PresShell* presShell = doc->GetPresShell();
|
|
// If there is no PresShell or it hasn't been initialized there isn't much to
|
|
// do.
|
|
if (!presShell || !presShell->DidInitialize()) {
|
|
return;
|
|
}
|
|
presShell->PostRecreateFramesFor(thisEl);
|
|
}
|
|
|
|
nsObjectLoadingContent::ObjectType nsObjectLoadingContent::GetTypeOfContent(
|
|
const nsCString& aMIMEType) {
|
|
Element* el = AsElement();
|
|
NS_ASSERTION(el, "must be a content");
|
|
|
|
Document* doc = el->OwnerDoc();
|
|
|
|
// Images and documents are always supported.
|
|
MOZ_ASSERT((GetCapabilities() & (eSupportImages | eSupportDocuments)) ==
|
|
(eSupportImages | eSupportDocuments));
|
|
|
|
LOG(
|
|
("OBJLC [%p]: calling HtmlObjectContentTypeForMIMEType: aMIMEType: %s - "
|
|
"el: %p\n",
|
|
this, aMIMEType.get(), el));
|
|
auto ret =
|
|
static_cast<ObjectType>(nsContentUtils::HtmlObjectContentTypeForMIMEType(
|
|
aMIMEType, doc->GetSandboxFlags()));
|
|
LOG(("OBJLC [%p]: called HtmlObjectContentTypeForMIMEType\n", this));
|
|
return ret;
|
|
}
|
|
|
|
void nsObjectLoadingContent::CreateStaticClone(
|
|
nsObjectLoadingContent* aDest) const {
|
|
MOZ_ASSERT(aDest->AsElement()->OwnerDoc()->IsStaticDocument());
|
|
aDest->mType = mType;
|
|
|
|
if (mFrameLoader) {
|
|
aDest->AsElement()->OwnerDoc()->AddPendingFrameStaticClone(aDest,
|
|
mFrameLoader);
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsObjectLoadingContent::GetSrcURI(nsIURI** aURI) {
|
|
NS_IF_ADDREF(*aURI = GetSrcURI());
|
|
return NS_OK;
|
|
}
|
|
|
|
void nsObjectLoadingContent::TriggerInnerFallbackLoads() {
|
|
MOZ_ASSERT(!mFrameLoader && !mChannel,
|
|
"ConfigureFallback called with loaded content");
|
|
MOZ_ASSERT(mType == ObjectType::Fallback);
|
|
|
|
Element* el = AsElement();
|
|
if (!el->IsHTMLElement(nsGkAtoms::object)) {
|
|
return;
|
|
}
|
|
// Do a depth-first traverse of node tree with the current element as root,
|
|
// looking for non-<param> elements. If we find some then we have an HTML
|
|
// fallback for this element.
|
|
for (nsIContent* child = el->GetFirstChild(); child;) {
|
|
// <object> and <embed> elements in the fallback need to StartObjectLoad.
|
|
// Their children should be ignored since they are part of those element's
|
|
// fallback.
|
|
if (auto* embed = HTMLEmbedElement::FromNode(child)) {
|
|
embed->StartObjectLoad(true, true);
|
|
// Skip the children
|
|
child = child->GetNextNonChildNode(el);
|
|
} else if (auto* object = HTMLObjectElement::FromNode(child)) {
|
|
object->StartObjectLoad(true, true);
|
|
// Skip the children
|
|
child = child->GetNextNonChildNode(el);
|
|
} else {
|
|
child = child->GetNextNode(el);
|
|
}
|
|
}
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsObjectLoadingContent::UpgradeLoadToDocument(
|
|
nsIChannel* aRequest, BrowsingContext** aBrowsingContext) {
|
|
AUTO_PROFILER_LABEL("nsObjectLoadingContent::UpgradeLoadToDocument", NETWORK);
|
|
|
|
LOG(("OBJLC [%p]: UpgradeLoadToDocument", this));
|
|
|
|
if (aRequest != mChannel || !aRequest) {
|
|
// happens when a new load starts before the previous one got here.
|
|
return NS_BINDING_ABORTED;
|
|
}
|
|
|
|
// We should be state loading.
|
|
if (mType != ObjectType::Loading) {
|
|
MOZ_ASSERT_UNREACHABLE("Should be type loading at this point");
|
|
return NS_BINDING_ABORTED;
|
|
}
|
|
MOZ_ASSERT(!mChannelLoaded, "mChannelLoaded set already?");
|
|
MOZ_ASSERT(!mFinalListener, "mFinalListener exists already?");
|
|
|
|
mChannelLoaded = true;
|
|
|
|
// We don't need to check for errors here, unlike in `OnStartRequest`, as
|
|
// `UpgradeLoadToDocument` is only called when the load is going to become a
|
|
// process-switching load. As we never process switch for failed object loads,
|
|
// we know our channel status is successful.
|
|
|
|
// Call `LoadObject` to trigger our nsObjectLoadingContext to switch into the
|
|
// specified new state.
|
|
nsresult rv = LoadObject(true, false, aRequest);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
RefPtr<BrowsingContext> bc = GetBrowsingContext();
|
|
if (!bc) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// At this point we know that we have a browsing context, so it's time to make
|
|
// sure that that browsing context gets the correct container feature policy.
|
|
// This is needed for `DocumentLoadListener::MaybeTriggerProcessSwitch` to be
|
|
// able to start loading the document with the correct container feature
|
|
// policy in the load info.
|
|
RefreshFeaturePolicy();
|
|
|
|
bc.forget(aBrowsingContext);
|
|
return NS_OK;
|
|
}
|
|
|
|
bool nsObjectLoadingContent::ShouldBlockContent() {
|
|
return mContentBlockingEnabled && mURI && IsFlashMIME(mContentType) &&
|
|
StaticPrefs::browser_safebrowsing_blockedURIs_enabled();
|
|
}
|
|
|
|
Document* nsObjectLoadingContent::GetContentDocument(
|
|
nsIPrincipal& aSubjectPrincipal) {
|
|
Element* el = AsElement();
|
|
if (!el->IsInComposedDoc()) {
|
|
return nullptr;
|
|
}
|
|
|
|
Document* sub_doc = el->OwnerDoc()->GetSubDocumentFor(el);
|
|
if (!sub_doc) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Return null for cross-origin contentDocument.
|
|
if (!aSubjectPrincipal.SubsumesConsideringDomain(sub_doc->NodePrincipal())) {
|
|
return nullptr;
|
|
}
|
|
|
|
return sub_doc;
|
|
}
|
|
|
|
void nsObjectLoadingContent::MaybeFireErrorEvent() {
|
|
Element* el = AsElement();
|
|
// Queue a task to fire an error event if we're an <object> element. The
|
|
// queueing is important, since then we don't have to worry about reentry.
|
|
if (el->IsHTMLElement(nsGkAtoms::object)) {
|
|
RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
|
|
new LoadBlockingAsyncEventDispatcher(el, u"error"_ns, CanBubble::eNo,
|
|
ChromeOnlyDispatch::eNo);
|
|
loadBlockingAsyncDispatcher->PostDOMEvent();
|
|
}
|
|
}
|
|
|
|
bool nsObjectLoadingContent::BlockEmbedOrObjectContentLoading() {
|
|
Element* el = AsElement();
|
|
|
|
// Traverse up the node tree to see if we have any ancestors that may block us
|
|
// from loading
|
|
for (nsIContent* parent = el->GetParent(); parent;
|
|
parent = parent->GetParent()) {
|
|
if (parent->IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio)) {
|
|
return true;
|
|
}
|
|
// If we have an ancestor that is an object with a source, it'll have an
|
|
// associated displayed type. If that type is not null, don't load content
|
|
// for the embed.
|
|
if (auto* object = HTMLObjectElement::FromNode(parent)) {
|
|
if (object->Type() != ObjectType::Fallback) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void nsObjectLoadingContent::SubdocumentIntrinsicSizeOrRatioChanged(
|
|
const Maybe<IntrinsicSize>& aIntrinsicSize,
|
|
const Maybe<AspectRatio>& aIntrinsicRatio) {
|
|
if (aIntrinsicSize == mSubdocumentIntrinsicSize &&
|
|
aIntrinsicRatio == mSubdocumentIntrinsicRatio) {
|
|
return;
|
|
}
|
|
|
|
mSubdocumentIntrinsicSize = aIntrinsicSize;
|
|
mSubdocumentIntrinsicRatio = aIntrinsicRatio;
|
|
|
|
if (nsSubDocumentFrame* sdf = do_QueryFrame(AsElement()->GetPrimaryFrame())) {
|
|
sdf->SubdocumentIntrinsicSizeOrRatioChanged();
|
|
}
|
|
}
|
|
|
|
void nsObjectLoadingContent::SubdocumentImageLoadComplete(nsresult aResult) {
|
|
ObjectType oldType = mType;
|
|
if (NS_FAILED(aResult)) {
|
|
UnloadObject();
|
|
mType = ObjectType::Fallback;
|
|
TriggerInnerFallbackLoads();
|
|
NotifyStateChanged(oldType, true);
|
|
return;
|
|
}
|
|
|
|
// (mChannelLoaded && mChannel) indicates this is a good state, not any sort
|
|
// of failures.
|
|
MOZ_DIAGNOSTIC_ASSERT_IF(mChannelLoaded && mChannel,
|
|
mType == ObjectType::Document);
|
|
NotifyStateChanged(oldType, true);
|
|
}
|
|
|
|
void nsObjectLoadingContent::MaybeStoreCrossOriginFeaturePolicy() {
|
|
MOZ_DIAGNOSTIC_ASSERT(mFrameLoader);
|
|
if (!mFrameLoader) {
|
|
return;
|
|
}
|
|
|
|
// If the browsingContext is not ready (because docshell is dead), don't try
|
|
// to create one.
|
|
if (!mFrameLoader->IsRemoteFrame() && !mFrameLoader->GetExistingDocShell()) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<BrowsingContext> browsingContext = mFrameLoader->GetBrowsingContext();
|
|
|
|
if (!browsingContext || !browsingContext->IsContentSubframe()) {
|
|
return;
|
|
}
|
|
|
|
auto* el = nsGenericHTMLElement::FromNode(AsElement());
|
|
if (!el->IsInComposedDoc()) {
|
|
return;
|
|
}
|
|
|
|
if (ContentChild* cc = ContentChild::GetSingleton()) {
|
|
Unused << cc->SendSetContainerFeaturePolicy(
|
|
browsingContext, Some(mFeaturePolicy->ToFeaturePolicyInfo()));
|
|
}
|
|
}
|
|
|
|
/* static */ already_AddRefed<nsIPrincipal>
|
|
nsObjectLoadingContent::GetFeaturePolicyDefaultOrigin(nsINode* aNode) {
|
|
auto* el = nsGenericHTMLElement::FromNode(aNode);
|
|
nsCOMPtr<nsIURI> nodeURI;
|
|
// Different elements keep this in various locations
|
|
if (el->NodeInfo()->Equals(nsGkAtoms::object)) {
|
|
el->GetURIAttr(nsGkAtoms::data, nullptr, getter_AddRefs(nodeURI));
|
|
} else if (el->NodeInfo()->Equals(nsGkAtoms::embed)) {
|
|
el->GetURIAttr(nsGkAtoms::src, nullptr, getter_AddRefs(nodeURI));
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
if (nodeURI) {
|
|
principal = BasePrincipal::CreateContentPrincipal(
|
|
nodeURI,
|
|
BasePrincipal::Cast(el->NodePrincipal())->OriginAttributesRef());
|
|
} else {
|
|
principal = el->NodePrincipal();
|
|
}
|
|
|
|
return principal.forget();
|
|
}
|
|
|
|
void nsObjectLoadingContent::RefreshFeaturePolicy() {
|
|
if (mType != ObjectType::Document) {
|
|
return;
|
|
}
|
|
|
|
if (!mFeaturePolicy) {
|
|
mFeaturePolicy = MakeAndAddRef<FeaturePolicy>(AsElement());
|
|
}
|
|
|
|
// The origin can change if 'src' or 'data' attributes change.
|
|
nsCOMPtr<nsIPrincipal> origin = GetFeaturePolicyDefaultOrigin(AsElement());
|
|
MOZ_ASSERT(origin);
|
|
mFeaturePolicy->SetDefaultOrigin(origin);
|
|
|
|
mFeaturePolicy->InheritPolicy(AsElement()->OwnerDoc()->FeaturePolicy());
|
|
MaybeStoreCrossOriginFeaturePolicy();
|
|
}
|