forked from mirrors/gecko-dev
Note that this patch only transforms the use of the nsDataHashtable type alias to a directly equivalent use of nsTHashMap. It does not change the specification of the hash key type to make use of the key class deduction that nsTHashMap allows for in some cases. That can be done in a separate step, but requires more attention. Differential Revision: https://phabricator.services.mozilla.com/D106008
1011 lines
29 KiB
C++
1011 lines
29 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/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 "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 Maybe<nsID>& aAgentClusterId)
|
|
: mObjectType(eBlobImpl),
|
|
mBlobImpl(aBlobImpl),
|
|
mPrincipal(aPrincipal),
|
|
mAgentClusterId(aAgentClusterId),
|
|
mRevoked(false) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
}
|
|
|
|
DataInfo(MediaSource* aMediaSource, nsIPrincipal* aPrincipal,
|
|
const Maybe<nsID>& aAgentClusterId)
|
|
: mObjectType(eMediaSource),
|
|
mMediaSource(aMediaSource),
|
|
mPrincipal(aPrincipal),
|
|
mAgentClusterId(aAgentClusterId),
|
|
mRevoked(false) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
}
|
|
|
|
ObjectType mObjectType;
|
|
|
|
RefPtr<BlobImpl> mBlobImpl;
|
|
RefPtr<MediaSource> mMediaSource;
|
|
|
|
nsCOMPtr<nsIPrincipal> mPrincipal;
|
|
Maybe<nsID> mAgentClusterId;
|
|
|
|
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;
|
|
|
|
// 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 Maybe<nsID>& aAgentClusterId) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(aBlobImpl);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
if (XRE_IsParentProcess()) {
|
|
dom::ContentParent::BroadcastBlobURLRegistration(
|
|
aURI, aBlobImpl, aPrincipal, aAgentClusterId);
|
|
return;
|
|
}
|
|
|
|
dom::ContentChild* cc = dom::ContentChild::GetSingleton();
|
|
|
|
IPCBlob ipcBlob;
|
|
nsresult rv = IPCBlobUtils::Serialize(aBlobImpl, cc, ipcBlob);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return;
|
|
}
|
|
|
|
Unused << NS_WARN_IF(!cc->SendStoreAndBroadcastBlobURLRegistration(
|
|
nsCString(aURI), ipcBlob, IPC::Principal(aPrincipal), aAgentClusterId));
|
|
}
|
|
|
|
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();
|
|
Unused << NS_WARN_IF(!cc->SendUnstoreAndBroadcastBlobURLUnregistration(
|
|
aURI, IPC::Principal(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 (auto iter = gDataTable->Iter(); !iter.Done(); iter.Next()) {
|
|
if (iter.UserData()->mObjectType != mozilla::dom::DataInfo::eBlobImpl) {
|
|
continue;
|
|
}
|
|
|
|
mozilla::dom::BlobImpl* blobImpl = iter.UserData()->mBlobImpl;
|
|
MOZ_ASSERT(blobImpl);
|
|
|
|
refCounts.LookupOrInsert(blobImpl, 0) += 1;
|
|
}
|
|
|
|
for (auto iter = gDataTable->Iter(); !iter.Done(); iter.Next()) {
|
|
nsCStringHashKey::KeyType key = iter.Key();
|
|
mozilla::dom::DataInfo* info = iter.UserData();
|
|
|
|
if (iter.UserData()->mObjectType == mozilla::dom::DataInfo::eBlobImpl) {
|
|
mozilla::dom::BlobImpl* blobImpl = iter.UserData()->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;
|
|
|
|
for (uint32_t i = 0; frame; ++i) {
|
|
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);
|
|
|
|
auto raii = MakeScopeExit([holder] { holder->CancelTimerAndRevokeURI(); });
|
|
|
|
// ReleasingTimerHolder potentially dispatches after we've
|
|
// shutdown the main thread, so guard agains that.
|
|
if (NS_WARN_IF(gXPCOMThreadsShutDown)) {
|
|
return;
|
|
}
|
|
|
|
nsresult rv =
|
|
SchedulerGroup::Dispatch(TaskCategory::Other, 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() = 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 Maybe<nsID>& aAgentClusterId) {
|
|
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,
|
|
aAgentClusterId);
|
|
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 Maybe<nsID>& aAgentClusterId, nsACString& aUri) {
|
|
MOZ_ASSERT(aBlobImpl);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
Init();
|
|
|
|
nsresult rv = GenerateURIString(aPrincipal, aUri);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
AddDataEntryInternal(aUri, aBlobImpl, aPrincipal, aAgentClusterId);
|
|
|
|
BroadcastBlobURLRegistration(aUri, aBlobImpl, aPrincipal, aAgentClusterId);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
nsresult BlobURLProtocolHandler::AddDataEntry(
|
|
MediaSource* aMediaSource, nsIPrincipal* aPrincipal,
|
|
const Maybe<nsID>& aAgentClusterId, nsACString& aUri) {
|
|
MOZ_ASSERT(aMediaSource);
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
Init();
|
|
|
|
nsresult rv = GenerateURIString(aPrincipal, aUri);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
AddDataEntryInternal(aUri, aMediaSource, aPrincipal, aAgentClusterId);
|
|
return NS_OK;
|
|
}
|
|
|
|
/* static */
|
|
void BlobURLProtocolHandler::AddDataEntry(const nsACString& aURI,
|
|
nsIPrincipal* aPrincipal,
|
|
const Maybe<nsID>& aAgentClusterId,
|
|
mozilla::dom::BlobImpl* aBlobImpl) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
MOZ_ASSERT(aBlobImpl);
|
|
AddDataEntryInternal(aURI, aBlobImpl, aPrincipal, aAgentClusterId);
|
|
}
|
|
|
|
/* static */
|
|
bool BlobURLProtocolHandler::ForEachBlobURL(
|
|
std::function<bool(mozilla::dom::BlobImpl*, nsIPrincipal*,
|
|
const Maybe<nsID>&, const nsACString&, bool aRevoked)>&&
|
|
aCb) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (!gDataTable) {
|
|
return false;
|
|
}
|
|
|
|
for (auto iter = gDataTable->ConstIter(); !iter.Done(); iter.Next()) {
|
|
mozilla::dom::DataInfo* info = iter.UserData();
|
|
MOZ_ASSERT(info);
|
|
|
|
if (info->mObjectType != mozilla::dom::DataInfo::eBlobImpl) {
|
|
continue;
|
|
}
|
|
|
|
MOZ_ASSERT(info->mBlobImpl);
|
|
if (!aCb(info->mBlobImpl, info->mPrincipal, info->mAgentClusterId,
|
|
iter.Key(), 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 Maybe<nsID>& aAgentClusterId) {
|
|
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_agent_cluster() &&
|
|
aAgentClusterId.isSome() && info->mAgentClusterId.isSome() &&
|
|
!aAgentClusterId.value().Equals(info->mAgentClusterId.value())) {
|
|
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);
|
|
|
|
char chars[NSID_LENGTH];
|
|
id.ToProvidedString(chars);
|
|
|
|
aUri.AssignLiteral(BLOBURI_SCHEME);
|
|
aUri.Append(':');
|
|
|
|
if (aPrincipal) {
|
|
nsAutoCString origin;
|
|
rv = aPrincipal->GetAsciiOrigin(origin);
|
|
if (NS_FAILED(rv)) {
|
|
origin.AssignLiteral("null");
|
|
}
|
|
|
|
aUri.Append(origin);
|
|
aUri.Append('/');
|
|
}
|
|
|
|
aUri += Substring(chars + 1, chars + NSID_LENGTH - 2);
|
|
|
|
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 Maybe<nsID>& aAgentClusterId, 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;
|
|
}
|
|
|
|
// BlobURLs are openable on the same agent-cluster-id only.
|
|
if (StaticPrefs::privacy_partition_bloburl_per_agent_cluster() &&
|
|
aAgentClusterId.isSome() && info->mAgentClusterId.isSome() &&
|
|
NS_WARN_IF(!aAgentClusterId->Equals(info->mAgentClusterId.value()))) {
|
|
nsAutoString localizedMsg;
|
|
AutoTArray<nsString, 1> param;
|
|
CopyUTF8toUTF16(aUri, *param.AppendElement());
|
|
nsresult rv = nsContentUtils::FormatLocalizedString(
|
|
nsContentUtils::eDOM_PROPERTIES, "BlobDifferentClusterError", 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(res->mMediaSource);
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS(BlobURLProtocolHandler, nsIProtocolHandler,
|
|
nsISupportsWeakReference)
|
|
|
|
NS_IMETHODIMP
|
|
BlobURLProtocolHandler::GetDefaultPort(int32_t* result) {
|
|
*result = -1;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BlobURLProtocolHandler::GetProtocolFlags(uint32_t* result) {
|
|
*result = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_SUBSUMERS |
|
|
URI_NON_PERSISTABLE | URI_IS_LOCAL_RESOURCE;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
BlobURLProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aResult) {
|
|
Unused << BlobURLProtocolHandler::GetProtocolFlags(aResult);
|
|
if (IsBlobURI(aURI)) {
|
|
*aResult |= URI_IS_LOCAL_RESOURCE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/* 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(NS_MutatorMethod(&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;
|
|
}
|
|
|
|
MOZ_ASSERT(NS_IsMainThread(),
|
|
"without locking gDataTable is main-thread only");
|
|
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;
|
|
}
|
|
|
|
} // 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;
|
|
}
|
|
|
|
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
|