/* -*- 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).
*/
// 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) { returnfalse;
}
}
// Otherwise, the request is successful returntrue;
}
nsCOMPtr<nsIIOService> ios = mozilla::components::IO::Service(); if (!ios) { returnfalse;
}
nsCOMPtr<nsIProtocolHandler> handler;
ios->GetProtocolHandler(scheme.get(), getter_AddRefs(handler)); if (!handler) { returnfalse;
}
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 staticboolinline 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() { // 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();
}
}
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;
// 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;
}
// 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);
}
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;
// 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())->)
// 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;
}
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}; constchar* 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"); returnfalse;
}
Element* el = AsElement();
Document* doc = el->OwnerDoc();
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(doc->NodePrincipal(), // loading principal
doc->NodePrincipal(), // triggering principal
el, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK,
contentPolicyType);
bool nsObjectLoadingContent::CheckProcessPolicy(int16_t* aContentPolicy) { if (!aContentPolicy) {
MOZ_ASSERT_UNREACHABLE("Null out variable"); returnfalse;
}
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"); returnfalse;
}
nsCOMPtr<nsILoadInfo> secCheckLoadInfo = new LoadInfo(
doc->NodePrincipal(), // loading principal
doc->NodePrincipal(), // triggering principal
el, nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, objectType);
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;
}
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 uriStr; // Different elements keep this in various locations if (el->NodeInfo()->Equals(nsGkAtoms::object)) {
el->GetAttr(nsGkAtoms::data, uriStr);
} elseif (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?");
} elseif (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()));
// 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;
} elseif (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;
}
} elseif (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) //
if (stateInvalid) {
newType = ObjectType::Fallback;
LOG(("OBJLC [%p]: NewType #0: %s - %u", this, newMime.get(),
uint32_t(newType)));
newMime.Truncate();
} elseif (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));
} elseif (((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));
} elseif (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;
} elseif (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(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);
}
// 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;
}
// 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;
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;
} elseif (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();
} elseif (mType == ObjectType::Loading && mChannel) { // We're still waiting on a channel load, already opened one, and // channel parameters didn't change return NS_OK;
} elseif (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;
}
// 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;
}
// 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;
}
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;
}
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()));
}
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);
}
}
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);
// 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;
}
// 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();
}
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::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);
} elseif (auto* object = HTMLObjectElement::FromNode(child)) {
object->StartObjectLoad(true, true); // Skip the children
child = child->GetNextNonChildNode(el);
} else {
child = child->GetNextNode(el);
}
}
}
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();
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)) { returntrue;
} // 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) { returntrue;
}
}
} returnfalse;
}
// (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;
}
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));
} elseif (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);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.