summaryrefslogtreecommitdiffstats
path: root/dom/file/uri/BlobURLProtocolHandler.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/file/uri/BlobURLProtocolHandler.cpp')
-rw-r--r--dom/file/uri/BlobURLProtocolHandler.cpp990
1 files changed, 990 insertions, 0 deletions
diff --git a/dom/file/uri/BlobURLProtocolHandler.cpp b/dom/file/uri/BlobURLProtocolHandler.cpp
new file mode 100644
index 0000000000..756ce01544
--- /dev/null
+++ b/dom/file/uri/BlobURLProtocolHandler.cpp
@@ -0,0 +1,990 @@
+/* -*- 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 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 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 Maybe<nsID>& aAgentClusterId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aBlobImpl);
+ MOZ_ASSERT(aPrincipal);
+
+ if (XRE_IsParentProcess()) {
+ dom::ContentParent::BroadcastBlobURLRegistration(
+ aURI, aBlobImpl, aPrincipal, aAgentClusterId);
+ 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, 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();
+ 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(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() 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 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 (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->mAgentClusterId,
+ 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 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);
+
+ 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 += 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 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(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;
+}
+
+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