mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 23:02:20 +00:00
fbae1edd30
Differential Revision: https://phabricator.services.mozilla.com/D205484
1014 lines
30 KiB
C++
1014 lines
30 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/. */
|
|
|
|
#include "BlobURLProtocolHandler.h"
|
|
#include "BlobURLChannel.h"
|
|
#include "mozilla/dom/BlobURL.h"
|
|
|
|
#include "mozilla/dom/ChromeUtils.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/Exceptions.h"
|
|
#include "mozilla/dom/BlobImpl.h"
|
|
#include "mozilla/dom/IPCBlobUtils.h"
|
|
#include "mozilla/dom/MediaSource.h"
|
|
#include "mozilla/ipc/IPCStreamUtils.h"
|
|
#include "mozilla/AppShutdown.h"
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/LoadInfo.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/NullPrincipal.h"
|
|
#include "mozilla/OriginAttributes.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/SchedulerGroup.h"
|
|
#include "mozilla/ScopeExit.h"
|
|
#include "nsClassHashtable.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsError.h"
|
|
#include "nsIAsyncShutdown.h"
|
|
#include "nsIDUtils.h"
|
|
#include "nsIException.h" // for nsIStackFrame
|
|
#include "nsIMemoryReporter.h"
|
|
#include "nsIPrincipal.h"
|
|
#include "nsIUUIDGenerator.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsReadableUtils.h"
|
|
|
|
#define RELEASING_TIMER 5000
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace ipc;
|
|
|
|
namespace dom {
|
|
|
|
// -----------------------------------------------------------------------
|
|
// Hash table
|
|
struct DataInfo {
|
|
enum ObjectType { eBlobImpl, eMediaSource };
|
|
|
|
DataInfo(mozilla::dom::BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal,
|
|
const nsCString& aPartitionKey)
|
|
: mObjectType(eBlobImpl),
|
|
mBlobImpl(aBlobImpl),
|
|
mPrincipal(aPrincipal),
|
|
mPartitionKey(aPartitionKey),
|
|
mRevoked(false) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
}
|
|
|
|
DataInfo(MediaSource* aMediaSource, nsIPrincipal* aPrincipal,
|
|
const nsCString& aPartitionKey)
|
|
: mObjectType(eMediaSource),
|
|
mMediaSource(aMediaSource),
|
|
mPrincipal(aPrincipal),
|
|
mPartitionKey(aPartitionKey),
|
|
mRevoked(false) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
}
|
|
|
|
ObjectType mObjectType;
|
|
|
|
RefPtr<BlobImpl> mBlobImpl;
|
|
RefPtr<MediaSource> mMediaSource;
|
|
|
|
nsCOMPtr<nsIPrincipal> mPrincipal;
|
|
|
|
nsCString mPartitionKey;
|
|
|
|
nsCString mStack;
|
|
|
|
// When a blobURL is revoked, we keep it alive for RELEASING_TIMER
|
|
// milliseconds in order to support pending operations such as navigation,
|
|
// download and so on.
|
|
bool mRevoked;
|
|
};
|
|
|
|
// The mutex is locked whenever gDataTable is changed, or if gDataTable
|
|
// is accessed off-main-thread.
|
|
static StaticMutex sMutex MOZ_UNANNOTATED;
|
|
|
|
// All changes to gDataTable must happen on the main thread, while locking
|
|
// sMutex. Reading from gDataTable on the main thread may happen without
|
|
// locking, since no changes are possible. Reading it from another thread
|
|
// must also lock sMutex to prevent data races.
|
|
static nsClassHashtable<nsCStringHashKey, mozilla::dom::DataInfo>* gDataTable;
|
|
|
|
static mozilla::dom::DataInfo* GetDataInfo(const nsACString& aUri,
|
|
bool aAlsoIfRevoked = false) {
|
|
if (!gDataTable) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Let's remove any fragment from this URI.
|
|
int32_t fragmentPos = aUri.FindChar('#');
|
|
|
|
mozilla::dom::DataInfo* res;
|
|
if (fragmentPos < 0) {
|
|
res = gDataTable->Get(aUri);
|
|
} else {
|
|
res = gDataTable->Get(StringHead(aUri, fragmentPos));
|
|
}
|
|
|
|
if (!aAlsoIfRevoked && res && res->mRevoked) {
|
|
return nullptr;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static mozilla::dom::DataInfo* GetDataInfoFromURI(nsIURI* aURI,
|
|
bool aAlsoIfRevoked = false) {
|
|
if (!aURI) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCString spec;
|
|
nsresult rv = aURI->GetSpec(spec);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
|
|
return GetDataInfo(spec, aAlsoIfRevoked);
|
|
}
|
|
|
|
// Memory reporting for the hash table.
|
|
void BroadcastBlobURLRegistration(const nsACString& aURI,
|
|
mozilla::dom::BlobImpl* aBlobImpl,
|
|
nsIPrincipal* aPrincipal,
|
|
const nsCString& aPartitionKey) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aBlobImpl);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
dom::ContentParent::BroadcastBlobURLRegistration(aURI, aBlobImpl,
|
|
aPrincipal, aPartitionKey);
|
|
return;
|
|
}
|
|
|
|
IPCBlob ipcBlob;
|
|
nsresult rv = IPCBlobUtils::Serialize(aBlobImpl, ipcBlob);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
dom::ContentChild* cc = dom::ContentChild::GetSingleton();
|
|
(void)NS_WARN_IF(!cc->SendStoreAndBroadcastBlobURLRegistration(
|
|
nsCString(aURI), ipcBlob, aPrincipal, aPartitionKey));
|
|
}
|
|
|
|
void BroadcastBlobURLUnregistration(const nsCString& aURI,
|
|
nsIPrincipal* aPrincipal) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
dom::ContentParent::BroadcastBlobURLUnregistration(aURI, aPrincipal);
|
|
return;
|
|
}
|
|
|
|
dom::ContentChild* cc = dom::ContentChild::GetSingleton();
|
|
if (cc) {
|
|
(void)NS_WARN_IF(
|
|
!cc->SendUnstoreAndBroadcastBlobURLUnregistration(aURI, aPrincipal));
|
|
}
|
|
}
|
|
|
|
class BlobURLsReporter final : public nsIMemoryReporter {
|
|
public:
|
|
NS_DECL_ISUPPORTS
|
|
|
|
NS_IMETHOD CollectReports(nsIHandleReportCallback* aCallback,
|
|
nsISupports* aData, bool aAnonymize) override {
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"without locking gDataTable is main-thread only");
|
|
if (!gDataTable) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsTHashMap<nsPtrHashKey<mozilla::dom::BlobImpl>, uint32_t> refCounts;
|
|
|
|
// Determine number of URLs per mozilla::dom::BlobImpl, to handle the case
|
|
// where it's > 1.
|
|
for (const auto& entry : *gDataTable) {
|
|
if (entry.GetWeak()->mObjectType != mozilla::dom::DataInfo::eBlobImpl) {
|
|
continue;
|
|
}
|
|
|
|
mozilla::dom::BlobImpl* blobImpl = entry.GetWeak()->mBlobImpl;
|
|
MOZ_ASSERT(blobImpl);
|
|
|
|
refCounts.LookupOrInsert(blobImpl, 0) += 1;
|
|
}
|
|
|
|
for (const auto& entry : *gDataTable) {
|
|
nsCStringHashKey::KeyType key = entry.GetKey();
|
|
mozilla::dom::DataInfo* info = entry.GetWeak();
|
|
|
|
if (entry.GetWeak()->mObjectType == mozilla::dom::DataInfo::eBlobImpl) {
|
|
mozilla::dom::BlobImpl* blobImpl = entry.GetWeak()->mBlobImpl;
|
|
MOZ_ASSERT(blobImpl);
|
|
|
|
constexpr auto desc =
|
|
"A blob URL allocated with URL.createObjectURL; the referenced "
|
|
"blob cannot be freed until all URLs for it have been explicitly "
|
|
"invalidated with URL.revokeObjectURL."_ns;
|
|
nsAutoCString path, url, owner, specialDesc;
|
|
uint64_t size = 0;
|
|
uint32_t refCount = 1;
|
|
DebugOnly<bool> blobImplWasCounted;
|
|
|
|
blobImplWasCounted = refCounts.Get(blobImpl, &refCount);
|
|
MOZ_ASSERT(blobImplWasCounted);
|
|
MOZ_ASSERT(refCount > 0);
|
|
|
|
bool isMemoryFile = blobImpl->IsMemoryFile();
|
|
|
|
if (isMemoryFile) {
|
|
ErrorResult rv;
|
|
size = blobImpl->GetSize(rv);
|
|
if (NS_WARN_IF(rv.Failed())) {
|
|
rv.SuppressException();
|
|
size = 0;
|
|
}
|
|
}
|
|
|
|
path = isMemoryFile ? "memory-blob-urls/" : "file-blob-urls/";
|
|
BuildPath(path, key, info, aAnonymize);
|
|
|
|
if (refCount > 1) {
|
|
nsAutoCString addrStr;
|
|
|
|
addrStr = "0x";
|
|
addrStr.AppendInt((uint64_t)(mozilla::dom::BlobImpl*)blobImpl, 16);
|
|
|
|
path += " ";
|
|
path.AppendInt(refCount);
|
|
path += "@";
|
|
path += addrStr;
|
|
|
|
specialDesc = desc;
|
|
specialDesc += "\n\nNOTE: This blob (address ";
|
|
specialDesc += addrStr;
|
|
specialDesc += ") has ";
|
|
specialDesc.AppendInt(refCount);
|
|
specialDesc += " URLs.";
|
|
if (isMemoryFile) {
|
|
specialDesc += " Its size is divided ";
|
|
specialDesc += refCount > 2 ? "among" : "between";
|
|
specialDesc += " them in this report.";
|
|
}
|
|
}
|
|
|
|
const nsACString& descString =
|
|
specialDesc.IsEmpty() ? static_cast<const nsACString&>(desc)
|
|
: static_cast<const nsACString&>(specialDesc);
|
|
if (isMemoryFile) {
|
|
aCallback->Callback(""_ns, path, KIND_OTHER, UNITS_BYTES,
|
|
size / refCount, descString, aData);
|
|
} else {
|
|
aCallback->Callback(""_ns, path, KIND_OTHER, UNITS_COUNT, 1,
|
|
descString, aData);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
// Just report the path for the MediaSource.
|
|
nsAutoCString path;
|
|
path = "media-source-urls/";
|
|
BuildPath(path, key, info, aAnonymize);
|
|
|
|
constexpr auto desc =
|
|
"An object URL allocated with URL.createObjectURL; the referenced "
|
|
"data cannot be freed until all URLs for it have been explicitly "
|
|
"invalidated with URL.revokeObjectURL."_ns;
|
|
|
|
aCallback->Callback(""_ns, path, KIND_OTHER, UNITS_COUNT, 1, desc, aData);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Initialize info->mStack to record JS stack info, if enabled.
|
|
// The string generated here is used in ReportCallback, below.
|
|
static void GetJSStackForBlob(mozilla::dom::DataInfo* aInfo) {
|
|
nsCString& stack = aInfo->mStack;
|
|
MOZ_ASSERT(stack.IsEmpty());
|
|
const uint32_t maxFrames =
|
|
Preferences::GetUint("memory.blob_report.stack_frames");
|
|
|
|
if (maxFrames == 0) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack(maxFrames);
|
|
|
|
nsAutoCString origin;
|
|
|
|
aInfo->mPrincipal->GetPrePath(origin);
|
|
|
|
// If we got a frame, we better have a current JSContext. This is cheating
|
|
// a bit; ideally we'd have our caller pass in a JSContext, or have
|
|
// GetCurrentJSStack() hand out the JSContext it found.
|
|
JSContext* cx = frame ? nsContentUtils::GetCurrentJSContext() : nullptr;
|
|
|
|
while (frame) {
|
|
nsString fileNameUTF16;
|
|
frame->GetFilename(cx, fileNameUTF16);
|
|
|
|
int32_t lineNumber = frame->GetLineNumber(cx);
|
|
|
|
if (!fileNameUTF16.IsEmpty()) {
|
|
NS_ConvertUTF16toUTF8 fileName(fileNameUTF16);
|
|
stack += "js(";
|
|
if (!origin.IsEmpty()) {
|
|
// Make the file name root-relative for conciseness if possible.
|
|
const char* originData;
|
|
uint32_t originLen;
|
|
|
|
originLen = origin.GetData(&originData);
|
|
// If fileName starts with origin + "/", cut up to that "/".
|
|
if (fileName.Length() >= originLen + 1 &&
|
|
memcmp(fileName.get(), originData, originLen) == 0 &&
|
|
fileName[originLen] == '/') {
|
|
fileName.Cut(0, originLen);
|
|
}
|
|
}
|
|
fileName.ReplaceChar('/', '\\');
|
|
stack += fileName;
|
|
if (lineNumber > 0) {
|
|
stack += ", line=";
|
|
stack.AppendInt(lineNumber);
|
|
}
|
|
stack += ")/";
|
|
}
|
|
|
|
frame = frame->GetCaller(cx);
|
|
}
|
|
}
|
|
|
|
private:
|
|
~BlobURLsReporter() = default;
|
|
|
|
static void BuildPath(nsAutoCString& path, nsCStringHashKey::KeyType aKey,
|
|
mozilla::dom::DataInfo* aInfo, bool anonymize) {
|
|
nsAutoCString url, owner;
|
|
aInfo->mPrincipal->GetAsciiSpec(owner);
|
|
if (!owner.IsEmpty()) {
|
|
owner.ReplaceChar('/', '\\');
|
|
path += "owner(";
|
|
if (anonymize) {
|
|
path += "<anonymized>";
|
|
} else {
|
|
path += owner;
|
|
}
|
|
path += ")";
|
|
} else {
|
|
path += "owner unknown";
|
|
}
|
|
path += "/";
|
|
if (anonymize) {
|
|
path += "<anonymized-stack>";
|
|
} else {
|
|
path += aInfo->mStack;
|
|
}
|
|
url = aKey;
|
|
url.ReplaceChar('/', '\\');
|
|
if (anonymize) {
|
|
path += "<anonymized-url>";
|
|
} else {
|
|
path += url;
|
|
}
|
|
}
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS(BlobURLsReporter, nsIMemoryReporter)
|
|
|
|
class ReleasingTimerHolder final : public Runnable,
|
|
public nsITimerCallback,
|
|
public nsIAsyncShutdownBlocker {
|
|
public:
|
|
NS_DECL_ISUPPORTS_INHERITED
|
|
|
|
static void Create(const nsACString& aURI) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<ReleasingTimerHolder> holder = new ReleasingTimerHolder(aURI);
|
|
|
|
// BlobURLProtocolHandler::RemoveDataEntry potentially happens late. We are
|
|
// prepared to RevokeUri synchronously if we run after XPCOMWillShutdown,
|
|
// but we need at least to be able to dispatch to the main thread here.
|
|
auto raii = MakeScopeExit([holder] { holder->CancelTimerAndRevokeURI(); });
|
|
|
|
nsresult rv = SchedulerGroup::Dispatch(holder.forget());
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
raii.release();
|
|
}
|
|
|
|
// Runnable interface
|
|
|
|
NS_IMETHOD
|
|
Run() override {
|
|
RefPtr<ReleasingTimerHolder> self = this;
|
|
auto raii = MakeScopeExit([self] { self->CancelTimerAndRevokeURI(); });
|
|
|
|
nsresult rv = NS_NewTimerWithCallback(
|
|
getter_AddRefs(mTimer), this, RELEASING_TIMER, nsITimer::TYPE_ONE_SHOT);
|
|
NS_ENSURE_SUCCESS(rv, NS_OK);
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
|
|
NS_ENSURE_TRUE(!!phase, NS_OK);
|
|
|
|
rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
|
|
__LINE__, u"ReleasingTimerHolder shutdown"_ns);
|
|
NS_ENSURE_SUCCESS(rv, NS_OK);
|
|
|
|
raii.release();
|
|
return NS_OK;
|
|
}
|
|
|
|
// nsITimerCallback interface
|
|
|
|
NS_IMETHOD
|
|
Notify(nsITimer* aTimer) override {
|
|
RevokeURI();
|
|
return NS_OK;
|
|
}
|
|
|
|
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
|
|
using nsINamed::GetName;
|
|
#endif
|
|
|
|
// nsIAsyncShutdownBlocker interface
|
|
|
|
NS_IMETHOD
|
|
GetName(nsAString& aName) override {
|
|
aName.AssignLiteral("ReleasingTimerHolder for blobURL: ");
|
|
aName.Append(NS_ConvertUTF8toUTF16(mURI));
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD
|
|
BlockShutdown(nsIAsyncShutdownClient* aClient) override {
|
|
CancelTimerAndRevokeURI();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHOD
|
|
GetState(nsIPropertyBag**) override { return NS_OK; }
|
|
|
|
private:
|
|
explicit ReleasingTimerHolder(const nsACString& aURI)
|
|
: Runnable("ReleasingTimerHolder"), mURI(aURI) {}
|
|
|
|
~ReleasingTimerHolder() override = default;
|
|
|
|
void RevokeURI() {
|
|
// Remove the shutting down blocker
|
|
nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase();
|
|
if (phase) {
|
|
phase->RemoveBlocker(this);
|
|
}
|
|
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"without locking gDataTable is main-thread only");
|
|
mozilla::dom::DataInfo* info =
|
|
GetDataInfo(mURI, true /* We care about revoked dataInfo */);
|
|
if (!info) {
|
|
// Already gone!
|
|
return;
|
|
}
|
|
|
|
MOZ_ASSERT(info->mRevoked);
|
|
|
|
StaticMutexAutoLock lock(sMutex);
|
|
gDataTable->Remove(mURI);
|
|
if (gDataTable->Count() == 0) {
|
|
delete gDataTable;
|
|
gDataTable = nullptr;
|
|
}
|
|
}
|
|
|
|
void CancelTimerAndRevokeURI() {
|
|
if (mTimer) {
|
|
mTimer->Cancel();
|
|
mTimer = nullptr;
|
|
}
|
|
|
|
RevokeURI();
|
|
}
|
|
|
|
static nsCOMPtr<nsIAsyncShutdownClient> GetShutdownPhase() {
|
|
nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService();
|
|
NS_ENSURE_TRUE(!!svc, nullptr);
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> phase;
|
|
nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase));
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
return phase;
|
|
}
|
|
|
|
nsCString mURI;
|
|
nsCOMPtr<nsITimer> mTimer;
|
|
};
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(ReleasingTimerHolder, Runnable, nsITimerCallback,
|
|
nsIAsyncShutdownBlocker)
|
|
|
|
template <typename T>
|
|
static void AddDataEntryInternal(const nsACString& aURI, T aObject,
|
|
nsIPrincipal* aPrincipal,
|
|
const nsCString& aPartitionKey) {
|
|
MOZ_ASSERT(NS_IsMainThread(), "changing gDataTable is main-thread only");
|
|
StaticMutexAutoLock lock(sMutex);
|
|
if (!gDataTable) {
|
|
gDataTable = new nsClassHashtable<nsCStringHashKey, mozilla::dom::DataInfo>;
|
|
}
|
|
|
|
mozilla::UniquePtr<mozilla::dom::DataInfo> info =
|
|
mozilla::MakeUnique<mozilla::dom::DataInfo>(aObject, aPrincipal,
|
|
aPartitionKey);
|
|
BlobURLsReporter::GetJSStackForBlob(info.get());
|
|
|
|
gDataTable->InsertOrUpdate(aURI, std::move(info));
|
|
}
|
|
|
|
void BlobURLProtocolHandler::Init(void) {
|
|
static bool initialized = false;
|
|
|
|
if (!initialized) {
|
|
initialized = true;
|
|
RegisterStrongMemoryReporter(new BlobURLsReporter());
|
|
}
|
|
}
|
|
|
|
BlobURLProtocolHandler::BlobURLProtocolHandler() { Init(); }
|
|
|
|
BlobURLProtocolHandler::~BlobURLProtocolHandler() = default;
|
|
|
|
/* static */
|
|
nsresult BlobURLProtocolHandler::AddDataEntry(mozilla::dom::BlobImpl* aBlobImpl,
|
|
nsIPrincipal* aPrincipal,
|
|
const nsCString& aPartitionKey,
|
|
nsACString& aUri) {
|
|
MOZ_ASSERT(aBlobImpl);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
Init();
|
|
|
|
nsresult rv = GenerateURIString(aPrincipal, aUri);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
AddDataEntryInternal(aUri, aBlobImpl, aPrincipal, aPartitionKey);
|
|
|
|
BroadcastBlobURLRegistration(aUri, aBlobImpl, aPrincipal, aPartitionKey);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
nsresult BlobURLProtocolHandler::AddDataEntry(MediaSource* aMediaSource,
|
|
nsIPrincipal* aPrincipal,
|
|
const nsCString& aPartitionKey,
|
|
nsACString& aUri) {
|
|
MOZ_ASSERT(aMediaSource);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
Init();
|
|
|
|
nsresult rv = GenerateURIString(aPrincipal, aUri);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
AddDataEntryInternal(aUri, aMediaSource, aPrincipal, aPartitionKey);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
void BlobURLProtocolHandler::AddDataEntry(const nsACString& aURI,
|
|
nsIPrincipal* aPrincipal,
|
|
const nsCString& aPartitionKey,
|
|
mozilla::dom::BlobImpl* aBlobImpl) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
MOZ_ASSERT(aBlobImpl);
|
|
AddDataEntryInternal(aURI, aBlobImpl, aPrincipal, aPartitionKey);
|
|
}
|
|
|
|
/* static */
|
|
bool BlobURLProtocolHandler::ForEachBlobURL(
|
|
std::function<bool(mozilla::dom::BlobImpl*, nsIPrincipal*, const nsCString&,
|
|
const nsACString&, bool aRevoked)>&& aCb) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gDataTable) {
|
|
return false;
|
|
}
|
|
|
|
for (const auto& entry : *gDataTable) {
|
|
mozilla::dom::DataInfo* info = entry.GetWeak();
|
|
MOZ_ASSERT(info);
|
|
|
|
if (info->mObjectType != mozilla::dom::DataInfo::eBlobImpl) {
|
|
continue;
|
|
}
|
|
|
|
MOZ_ASSERT(info->mBlobImpl);
|
|
if (!aCb(info->mBlobImpl, info->mPrincipal, info->mPartitionKey,
|
|
entry.GetKey(), info->mRevoked)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*static */
|
|
void BlobURLProtocolHandler::RemoveDataEntry(const nsACString& aUri,
|
|
bool aBroadcastToOtherProcesses) {
|
|
MOZ_ASSERT(NS_IsMainThread(), "changing gDataTable is main-thread only");
|
|
if (!gDataTable) {
|
|
return;
|
|
}
|
|
mozilla::dom::DataInfo* info = GetDataInfo(aUri);
|
|
if (!info) {
|
|
return;
|
|
}
|
|
|
|
{
|
|
StaticMutexAutoLock lock(sMutex);
|
|
info->mRevoked = true;
|
|
}
|
|
|
|
if (aBroadcastToOtherProcesses &&
|
|
info->mObjectType == mozilla::dom::DataInfo::eBlobImpl) {
|
|
BroadcastBlobURLUnregistration(nsCString(aUri), info->mPrincipal);
|
|
}
|
|
|
|
// The timer will take care of removing the entry for real after
|
|
// RELEASING_TIMER milliseconds. In the meantime, the mozilla::dom::DataInfo,
|
|
// marked as revoked, will not be exposed.
|
|
ReleasingTimerHolder::Create(aUri);
|
|
}
|
|
|
|
/*static */
|
|
bool BlobURLProtocolHandler::RemoveDataEntry(const nsACString& aUri,
|
|
nsIPrincipal* aPrincipal,
|
|
const nsCString& aPartitionKey) {
|
|
MOZ_ASSERT(NS_IsMainThread(), "changing gDataTable is main-thread only");
|
|
if (!gDataTable) {
|
|
return false;
|
|
}
|
|
|
|
mozilla::dom::DataInfo* info = GetDataInfo(aUri);
|
|
if (!info) {
|
|
return false;
|
|
}
|
|
|
|
if (!aPrincipal || !aPrincipal->Subsumes(info->mPrincipal)) {
|
|
return false;
|
|
}
|
|
|
|
if (StaticPrefs::privacy_partition_bloburl_per_partition_key() &&
|
|
!aPartitionKey.IsEmpty() && !info->mPartitionKey.IsEmpty() &&
|
|
!aPartitionKey.Equals(info->mPartitionKey)) {
|
|
return false;
|
|
}
|
|
|
|
RemoveDataEntry(aUri, true);
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
void BlobURLProtocolHandler::RemoveDataEntries() {
|
|
MOZ_ASSERT(NS_IsMainThread(), "changing gDataTable is main-thread only");
|
|
StaticMutexAutoLock lock(sMutex);
|
|
if (!gDataTable) {
|
|
return;
|
|
}
|
|
|
|
gDataTable->Clear();
|
|
delete gDataTable;
|
|
gDataTable = nullptr;
|
|
}
|
|
|
|
/* static */
|
|
bool BlobURLProtocolHandler::HasDataEntry(const nsACString& aUri) {
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"without locking gDataTable is main-thread only");
|
|
return !!GetDataInfo(aUri);
|
|
}
|
|
|
|
/* static */
|
|
nsresult BlobURLProtocolHandler::GenerateURIString(nsIPrincipal* aPrincipal,
|
|
nsACString& aUri) {
|
|
nsresult rv;
|
|
nsCOMPtr<nsIUUIDGenerator> uuidgen =
|
|
do_GetService("@mozilla.org/uuid-generator;1", &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsID id;
|
|
rv = uuidgen->GenerateUUIDInPlace(&id);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
aUri.AssignLiteral(BLOBURI_SCHEME);
|
|
aUri.Append(':');
|
|
|
|
if (aPrincipal) {
|
|
nsAutoCString origin;
|
|
rv = aPrincipal->GetWebExposedOriginSerialization(origin);
|
|
if (NS_FAILED(rv)) {
|
|
origin.AssignLiteral("null");
|
|
}
|
|
|
|
aUri.Append(origin);
|
|
aUri.Append('/');
|
|
}
|
|
|
|
aUri += NSID_TrimBracketsASCII(id);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
bool BlobURLProtocolHandler::GetDataEntry(
|
|
const nsACString& aUri, mozilla::dom::BlobImpl** aBlobImpl,
|
|
nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal,
|
|
const OriginAttributes& aOriginAttributes, uint64_t aInnerWindowId,
|
|
const nsCString& aPartitionKey, bool aAlsoIfRevoked) {
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"without locking gDataTable is main-thread only");
|
|
MOZ_ASSERT(aTriggeringPrincipal);
|
|
|
|
if (!gDataTable) {
|
|
return false;
|
|
}
|
|
|
|
mozilla::dom::DataInfo* info = GetDataInfo(aUri, aAlsoIfRevoked);
|
|
if (!info) {
|
|
return false;
|
|
}
|
|
|
|
// We want to be sure that we stop the creation of the channel if the blob
|
|
// URL is copy-and-pasted on a different context (ex. private browsing or
|
|
// containers).
|
|
//
|
|
// We also allow the system principal to create the channel regardless of
|
|
// the OriginAttributes. This is primarily for the benefit of mechanisms
|
|
// like the Download API that explicitly create a channel with the system
|
|
// principal and which is never mutated to have a non-zero
|
|
// mPrivateBrowsingId or container.
|
|
|
|
if ((NS_WARN_IF(!aLoadingPrincipal) ||
|
|
!aLoadingPrincipal->IsSystemPrincipal()) &&
|
|
NS_WARN_IF(!ChromeUtils::IsOriginAttributesEqualIgnoringFPD(
|
|
aOriginAttributes,
|
|
BasePrincipal::Cast(info->mPrincipal)->OriginAttributesRef()))) {
|
|
return false;
|
|
}
|
|
|
|
if (NS_WARN_IF(!aTriggeringPrincipal->Subsumes(info->mPrincipal))) {
|
|
return false;
|
|
}
|
|
|
|
if (StaticPrefs::privacy_partition_bloburl_per_partition_key() &&
|
|
!aPartitionKey.IsEmpty() && !info->mPartitionKey.IsEmpty() &&
|
|
!aPartitionKey.Equals(info->mPartitionKey)) {
|
|
nsAutoString localizedMsg;
|
|
AutoTArray<nsString, 1> param;
|
|
CopyUTF8toUTF16(aUri, *param.AppendElement());
|
|
nsresult rv = nsContentUtils::FormatLocalizedString(
|
|
nsContentUtils::eDOM_PROPERTIES, "PartitionKeyDifferentError", param,
|
|
localizedMsg);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return false;
|
|
}
|
|
|
|
nsContentUtils::ReportToConsoleByWindowID(
|
|
localizedMsg, nsIScriptError::errorFlag, "DOM"_ns, aInnerWindowId);
|
|
return false;
|
|
}
|
|
|
|
RefPtr<mozilla::dom::BlobImpl> blobImpl = info->mBlobImpl;
|
|
blobImpl.forget(aBlobImpl);
|
|
|
|
return true;
|
|
}
|
|
|
|
/* static */
|
|
void BlobURLProtocolHandler::Traverse(
|
|
const nsACString& aUri, nsCycleCollectionTraversalCallback& aCallback) {
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"without locking gDataTable is main-thread only");
|
|
if (!gDataTable) {
|
|
return;
|
|
}
|
|
|
|
mozilla::dom::DataInfo* res;
|
|
gDataTable->Get(aUri, &res);
|
|
if (!res) {
|
|
return;
|
|
}
|
|
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
|
|
aCallback, "BlobURLProtocolHandler mozilla::dom::DataInfo.mBlobImpl");
|
|
aCallback.NoteXPCOMChild(res->mBlobImpl);
|
|
|
|
NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(
|
|
aCallback, "BlobURLProtocolHandler mozilla::dom::DataInfo.mMediaSource");
|
|
aCallback.NoteXPCOMChild(static_cast<EventTarget*>(res->mMediaSource));
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(BlobURLProtocolHandler, nsIProtocolHandler,
|
|
nsISupportsWeakReference)
|
|
|
|
/* static */ nsresult BlobURLProtocolHandler::CreateNewURI(
|
|
const nsACString& aSpec, const char* aCharset, nsIURI* aBaseURI,
|
|
nsIURI** aResult) {
|
|
*aResult = nullptr;
|
|
|
|
// This method can be called on any thread, which is why we lock the mutex
|
|
// for read access to gDataTable.
|
|
bool revoked = true;
|
|
{
|
|
StaticMutexAutoLock lock(sMutex);
|
|
mozilla::dom::DataInfo* info = GetDataInfo(aSpec);
|
|
if (info && info->mObjectType == mozilla::dom::DataInfo::eBlobImpl) {
|
|
revoked = info->mRevoked;
|
|
}
|
|
}
|
|
|
|
return NS_MutateURI(new BlobURL::Mutator())
|
|
.SetSpec(aSpec)
|
|
.Apply(&nsIBlobURLMutator::SetRevoked, revoked)
|
|
.Finalize(aResult);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BlobURLProtocolHandler::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo,
|
|
nsIChannel** aResult) {
|
|
auto channel = MakeRefPtr<BlobURLChannel>(aURI, aLoadInfo);
|
|
channel.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BlobURLProtocolHandler::AllowPort(int32_t port, const char* scheme,
|
|
bool* _retval) {
|
|
// don't override anything.
|
|
*_retval = false;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BlobURLProtocolHandler::GetScheme(nsACString& result) {
|
|
result.AssignLiteral(BLOBURI_SCHEME);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
bool BlobURLProtocolHandler::GetBlobURLPrincipal(nsIURI* aURI,
|
|
nsIPrincipal** aPrincipal) {
|
|
MOZ_ASSERT(aURI);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
RefPtr<BlobURL> blobURL;
|
|
nsresult rv =
|
|
aURI->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(blobURL));
|
|
if (NS_FAILED(rv) || !blobURL) {
|
|
return false;
|
|
}
|
|
|
|
StaticMutexAutoLock lock(sMutex);
|
|
mozilla::dom::DataInfo* info =
|
|
GetDataInfoFromURI(aURI, true /*aAlsoIfRevoked */);
|
|
if (!info || info->mObjectType != mozilla::dom::DataInfo::eBlobImpl ||
|
|
!info->mBlobImpl) {
|
|
return false;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
|
|
if (blobURL->Revoked()) {
|
|
principal = NullPrincipal::Create(
|
|
BasePrincipal::Cast(info->mPrincipal)->OriginAttributesRef());
|
|
} else {
|
|
principal = info->mPrincipal;
|
|
}
|
|
|
|
principal.forget(aPrincipal);
|
|
return true;
|
|
}
|
|
|
|
bool BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal(
|
|
nsIPrincipal* aPrincipal) {
|
|
return aPrincipal->IsSystemPrincipal() ||
|
|
aPrincipal->GetIsAddonOrExpandedAddonPrincipal();
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|
|
|
|
nsresult NS_GetBlobForBlobURI(nsIURI* aURI, mozilla::dom::BlobImpl** aBlob) {
|
|
*aBlob = nullptr;
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"without locking gDataTable is main-thread only");
|
|
mozilla::dom::DataInfo* info =
|
|
mozilla::dom::GetDataInfoFromURI(aURI, false /* aAlsoIfRevoked */);
|
|
if (!info || info->mObjectType != mozilla::dom::DataInfo::eBlobImpl) {
|
|
return NS_ERROR_DOM_BAD_URI;
|
|
}
|
|
|
|
RefPtr<mozilla::dom::BlobImpl> blob = info->mBlobImpl;
|
|
blob.forget(aBlob);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult NS_GetBlobForBlobURISpec(const nsACString& aSpec,
|
|
mozilla::dom::BlobImpl** aBlob,
|
|
bool aAlsoIfRevoked) {
|
|
*aBlob = nullptr;
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"without locking gDataTable is main-thread only");
|
|
|
|
mozilla::dom::DataInfo* info =
|
|
mozilla::dom::GetDataInfo(aSpec, aAlsoIfRevoked);
|
|
if (!info || info->mObjectType != mozilla::dom::DataInfo::eBlobImpl ||
|
|
!info->mBlobImpl) {
|
|
return NS_ERROR_DOM_BAD_URI;
|
|
}
|
|
|
|
RefPtr<mozilla::dom::BlobImpl> blob = info->mBlobImpl;
|
|
blob.forget(aBlob);
|
|
return NS_OK;
|
|
}
|
|
|
|
// Blob requests may specify a range header. We parse, validate, and
|
|
// store that info here, and save it on the nsIBaseChannel, where it
|
|
// can be accessed by BlobURLInputStream::StoreBlobImplStream.
|
|
nsresult NS_SetChannelContentRangeForBlobURI(nsIChannel* aChannel, nsIURI* aURI,
|
|
nsACString& aRangeHeader) {
|
|
MOZ_ASSERT(aChannel);
|
|
MOZ_ASSERT(aURI);
|
|
RefPtr<mozilla::dom::BlobImpl> blobImpl;
|
|
if (NS_FAILED(NS_GetBlobForBlobURI(aURI, getter_AddRefs(blobImpl)))) {
|
|
return NS_BINDING_FAILED;
|
|
}
|
|
mozilla::IgnoredErrorResult result;
|
|
int64_t size = static_cast<int64_t>(blobImpl->GetSize(result));
|
|
if (result.Failed()) {
|
|
return NS_ERROR_NO_CONTENT;
|
|
}
|
|
nsCOMPtr<nsIBaseChannel> baseChan = do_QueryInterface(aChannel);
|
|
if (!baseChan || !baseChan->SetContentRangeFromHeader(aRangeHeader, size)) {
|
|
return NS_ERROR_NET_PARTIAL_TRANSFER;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult NS_GetSourceForMediaSourceURI(nsIURI* aURI,
|
|
mozilla::dom::MediaSource** aSource) {
|
|
*aSource = nullptr;
|
|
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"without locking gDataTable is main-thread only");
|
|
mozilla::dom::DataInfo* info = mozilla::dom::GetDataInfoFromURI(aURI);
|
|
if (!info || info->mObjectType != mozilla::dom::DataInfo::eMediaSource) {
|
|
return NS_ERROR_DOM_BAD_URI;
|
|
}
|
|
|
|
RefPtr<mozilla::dom::MediaSource> mediaSource = info->mMediaSource;
|
|
mediaSource.forget(aSource);
|
|
return NS_OK;
|
|
}
|
|
|
|
namespace mozilla::dom {
|
|
|
|
bool IsType(nsIURI* aUri, mozilla::dom::DataInfo::ObjectType aType) {
|
|
// We lock because this may be called off-main-thread
|
|
StaticMutexAutoLock lock(sMutex);
|
|
mozilla::dom::DataInfo* info = GetDataInfoFromURI(aUri);
|
|
if (!info) {
|
|
return false;
|
|
}
|
|
|
|
return info->mObjectType == aType;
|
|
}
|
|
|
|
bool IsBlobURI(nsIURI* aUri) {
|
|
return IsType(aUri, mozilla::dom::DataInfo::eBlobImpl);
|
|
}
|
|
|
|
bool BlobURLSchemeIsHTTPOrHTTPS(const nsACString& aUri) {
|
|
return (StringBeginsWith(aUri, "blob:http://"_ns) ||
|
|
StringBeginsWith(aUri, "blob:https://"_ns));
|
|
}
|
|
|
|
bool IsMediaSourceURI(nsIURI* aUri) {
|
|
return IsType(aUri, mozilla::dom::DataInfo::eMediaSource);
|
|
}
|
|
|
|
} // namespace mozilla::dom
|