summaryrefslogtreecommitdiffstats
path: root/dom/file/uri
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dom/file/uri/BlobURL.cpp171
-rw-r--r--dom/file/uri/BlobURL.h123
-rw-r--r--dom/file/uri/BlobURLChannel.cpp86
-rw-r--r--dom/file/uri/BlobURLChannel.h37
-rw-r--r--dom/file/uri/BlobURLInputStream.cpp604
-rw-r--r--dom/file/uri/BlobURLInputStream.h81
-rw-r--r--dom/file/uri/BlobURLProtocolHandler.cpp1016
-rw-r--r--dom/file/uri/BlobURLProtocolHandler.h143
-rw-r--r--dom/file/uri/components.conf24
-rw-r--r--dom/file/uri/moz.build34
10 files changed, 2319 insertions, 0 deletions
diff --git a/dom/file/uri/BlobURL.cpp b/dom/file/uri/BlobURL.cpp
new file mode 100644
index 0000000000..b3afd60437
--- /dev/null
+++ b/dom/file/uri/BlobURL.cpp
@@ -0,0 +1,171 @@
+/* -*- 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 "nsIClassInfoImpl.h"
+#include "nsIObjectInputStream.h"
+#include "nsIObjectOutputStream.h"
+
+#include "mozilla/dom/BlobURL.h"
+#include "mozilla/dom/BlobURLProtocolHandler.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/URIUtils.h"
+
+using namespace mozilla::dom;
+
+static NS_DEFINE_CID(kThisSimpleURIImplementationCID,
+ NS_THIS_SIMPLEURI_IMPLEMENTATION_CID);
+
+NS_IMPL_ADDREF_INHERITED(BlobURL, mozilla::net::nsSimpleURI)
+NS_IMPL_RELEASE_INHERITED(BlobURL, mozilla::net::nsSimpleURI)
+
+NS_IMPL_CLASSINFO(BlobURL, nullptr, nsIClassInfo::THREADSAFE,
+ NS_HOSTOBJECTURI_CID);
+// Empty CI getter. We only need nsIClassInfo for Serialization
+NS_IMPL_CI_INTERFACE_GETTER0(BlobURL)
+
+NS_INTERFACE_MAP_BEGIN(BlobURL)
+ if (aIID.Equals(kHOSTOBJECTURICID))
+ foundInterface = static_cast<nsIURI*>(this);
+ else if (aIID.Equals(kThisSimpleURIImplementationCID)) {
+ // Need to return explicitly here, because if we just set foundInterface
+ // to null the NS_INTERFACE_MAP_END_INHERITING will end up calling into
+ // nsSimplURI::QueryInterface and finding something for this CID.
+ *aInstancePtr = nullptr;
+ return NS_NOINTERFACE;
+ } else
+ NS_IMPL_QUERY_CLASSINFO(BlobURL)
+NS_INTERFACE_MAP_END_INHERITING(mozilla::net::nsSimpleURI)
+
+BlobURL::BlobURL() : mRevoked(false) {}
+
+// nsISerializable methods:
+
+NS_IMETHODIMP
+BlobURL::Read(nsIObjectInputStream* aStream) {
+ MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+nsresult BlobURL::ReadPrivate(nsIObjectInputStream* aStream) {
+ nsresult rv = mozilla::net::nsSimpleURI::ReadPrivate(aStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->ReadBoolean(&mRevoked);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+BlobURL::Write(nsIObjectOutputStream* aStream) {
+ nsresult rv = mozilla::net::nsSimpleURI::Write(aStream);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = aStream->WriteBoolean(mRevoked);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(void)
+BlobURL::Serialize(mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ HostObjectURIParams hostParams;
+ URIParams simpleParams;
+
+ mozilla::net::nsSimpleURI::Serialize(simpleParams);
+ hostParams.simpleParams() = simpleParams;
+
+ hostParams.revoked() = mRevoked;
+
+ aParams = hostParams;
+}
+
+bool BlobURL::Deserialize(const mozilla::ipc::URIParams& aParams) {
+ using namespace mozilla::ipc;
+
+ if (aParams.type() != URIParams::THostObjectURIParams) {
+ NS_ERROR("Received unknown parameters from the other process!");
+ return false;
+ }
+
+ const HostObjectURIParams& hostParams = aParams.get_HostObjectURIParams();
+
+ if (!mozilla::net::nsSimpleURI::Deserialize(hostParams.simpleParams())) {
+ return false;
+ }
+
+ mRevoked = hostParams.revoked();
+ return true;
+}
+
+nsresult BlobURL::SetScheme(const nsACString& aScheme) {
+ // Disallow setting the scheme, since that could cause us to be associated
+ // with a different protocol handler.
+ return NS_ERROR_FAILURE;
+}
+
+// nsIURI methods:
+nsresult BlobURL::CloneInternal(
+ mozilla::net::nsSimpleURI::RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef, nsIURI** aClone) {
+ nsCOMPtr<nsIURI> simpleClone;
+ nsresult rv = mozilla::net::nsSimpleURI::CloneInternal(
+ aRefHandlingMode, newRef, getter_AddRefs(simpleClone));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+#ifdef DEBUG
+ RefPtr<BlobURL> uriCheck;
+ rv = simpleClone->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(uriCheck));
+ MOZ_ASSERT(NS_SUCCEEDED(rv) && uriCheck);
+#endif
+
+ BlobURL* u = static_cast<BlobURL*>(simpleClone.get());
+ u->mRevoked = mRevoked;
+
+ simpleClone.forget(aClone);
+ return NS_OK;
+}
+
+/* virtual */
+nsresult BlobURL::EqualsInternal(
+ nsIURI* aOther, mozilla::net::nsSimpleURI::RefHandlingEnum aRefHandlingMode,
+ bool* aResult) {
+ if (!aOther) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ RefPtr<BlobURL> otherUri;
+ aOther->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(otherUri));
+ if (!otherUri) {
+ *aResult = false;
+ return NS_OK;
+ }
+
+ // Compare the member data that our base class knows about.
+ *aResult =
+ mozilla::net::nsSimpleURI::EqualsInternal(otherUri, aRefHandlingMode);
+
+ // We don't want to compare the revoked flag.
+ return NS_OK;
+}
+
+// Queries this list of interfaces. If none match, it queries mURI.
+NS_IMPL_NSIURIMUTATOR_ISUPPORTS(BlobURL::Mutator, nsIURISetters, nsIURIMutator,
+ nsISerializable, nsIBlobURLMutator)
+
+NS_IMETHODIMP
+BlobURL::Mutate(nsIURIMutator** aMutator) {
+ RefPtr<BlobURL::Mutator> mutator = new BlobURL::Mutator();
+ nsresult rv = mutator->InitFromURI(this);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ mutator.forget(aMutator);
+ return NS_OK;
+}
diff --git a/dom/file/uri/BlobURL.h b/dom/file/uri/BlobURL.h
new file mode 100644
index 0000000000..1e7a91daa4
--- /dev/null
+++ b/dom/file/uri/BlobURL.h
@@ -0,0 +1,123 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_BlobURL_h
+#define mozilla_dom_BlobURL_h
+
+#include "nsCOMPtr.h"
+#include "nsISerializable.h"
+#include "nsSimpleURI.h"
+#include "prtime.h"
+
+#define NS_HOSTOBJECTURI_CID \
+ { \
+ 0xf5475c51, 0x59a7, 0x4757, { \
+ 0xb3, 0xd9, 0xe2, 0x11, 0xa9, 0x41, 0x08, 0x72 \
+ } \
+ }
+
+#define NS_IBLOBURLMUTATOR_IID \
+ { \
+ 0xf91e646d, 0xe87b, 0x485e, { \
+ 0xbb, 0xc8, 0x0e, 0x8a, 0x2e, 0xe9, 0x87, 0xa9 \
+ } \
+ }
+
+class NS_NO_VTABLE nsIBlobURLMutator : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IBLOBURLMUTATOR_IID)
+ NS_IMETHOD SetRevoked(bool aRevoked) = 0;
+};
+
+inline NS_DEFINE_CID(kHOSTOBJECTURICID, NS_HOSTOBJECTURI_CID);
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIBlobURLMutator, NS_IBLOBURLMUTATOR_IID)
+
+namespace mozilla::dom {
+
+/**
+ * These URIs refer to host objects with "blob" scheme.
+ */
+class BlobURL final : public mozilla::net::nsSimpleURI {
+ private:
+ BlobURL();
+
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSISERIALIZABLE
+
+ // Override CloneInternal() and EqualsInternal()
+ nsresult CloneInternal(RefHandlingEnum aRefHandlingMode,
+ const nsACString& newRef, nsIURI** aClone) override;
+ nsresult EqualsInternal(nsIURI* aOther, RefHandlingEnum aRefHandlingMode,
+ bool* aResult) override;
+ NS_IMETHOD_(void) Serialize(mozilla::ipc::URIParams& aParams) override;
+
+ // Override StartClone to hand back a BlobURL
+ mozilla::net::nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode,
+ const nsACString& newRef) override {
+ BlobURL* url = new BlobURL();
+ SetRefOnClone(url, refHandlingMode, newRef);
+ return url;
+ }
+
+ bool Revoked() const { return mRevoked; }
+
+ NS_IMETHOD Mutate(nsIURIMutator** _retval) override;
+
+ private:
+ ~BlobURL() override = default;
+
+ nsresult SetScheme(const nsACString& aProtocol) override;
+ bool Deserialize(const mozilla::ipc::URIParams&);
+ nsresult ReadPrivate(nsIObjectInputStream* stream);
+
+ bool mRevoked;
+
+ public:
+ class Mutator final : public nsIURIMutator,
+ public BaseURIMutator<BlobURL>,
+ public nsIBlobURLMutator,
+ public nsISerializable {
+ NS_DECL_ISUPPORTS
+ NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI)
+ NS_DEFINE_NSIMUTATOR_COMMON
+
+ NS_IMETHOD
+ Write(nsIObjectOutputStream* aOutputStream) override {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override {
+ return InitFromInputStream(aStream);
+ }
+
+ NS_IMETHOD SetRevoked(bool aRevoked) override {
+ mURI->mRevoked = aRevoked;
+ return NS_OK;
+ }
+
+ Mutator() = default;
+
+ private:
+ ~Mutator() = default;
+
+ friend class BlobURL;
+ };
+
+ friend BaseURIMutator<BlobURL>;
+};
+
+#define NS_HOSTOBJECTURIMUTATOR_CID \
+ { \
+ 0xbbe50ef2, 0x80eb, 0x469d, { \
+ 0xb7, 0x0d, 0x02, 0x85, 0x82, 0x75, 0x38, 0x9f \
+ } \
+ }
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_BlobURL_h */
diff --git a/dom/file/uri/BlobURLChannel.cpp b/dom/file/uri/BlobURLChannel.cpp
new file mode 100644
index 0000000000..6a7d4f1be6
--- /dev/null
+++ b/dom/file/uri/BlobURLChannel.cpp
@@ -0,0 +1,86 @@
+/* -*- 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 "BlobURLChannel.h"
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/dom/BlobURL.h"
+#include "mozilla/dom/BlobURLInputStream.h"
+
+using namespace mozilla::dom;
+
+BlobURLChannel::BlobURLChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo)
+ : mContentStreamOpened(false) {
+ SetURI(aURI);
+ SetOriginalURI(aURI);
+ SetLoadInfo(aLoadInfo);
+
+ // If we're sandboxed, make sure to clear any owner the channel
+ // might already have.
+ if (aLoadInfo && aLoadInfo->GetLoadingSandboxed()) {
+ SetOwner(nullptr);
+ }
+}
+
+BlobURLChannel::~BlobURLChannel() = default;
+
+NS_IMETHODIMP
+BlobURLChannel::SetContentType(const nsACString& aContentType) {
+ // If the blob type is empty, set the content type of the channel to the
+ // empty string.
+ if (aContentType.IsEmpty()) {
+ mContentType.Truncate();
+ return NS_OK;
+ }
+
+ return nsBaseChannel::SetContentType(aContentType);
+}
+
+nsresult BlobURLChannel::OpenContentStream(bool aAsync,
+ nsIInputStream** aResult,
+ nsIChannel** aChannel) {
+ if (mContentStreamOpened) {
+ return NS_ERROR_ALREADY_OPENED;
+ }
+
+ mContentStreamOpened = true;
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetURI(getter_AddRefs(uri));
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI);
+
+ RefPtr<BlobURL> blobURL;
+ rv = uri->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(blobURL));
+
+ if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!blobURL)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ if (blobURL->Revoked()) {
+#ifdef MOZ_WIDGET_ANDROID
+ nsCOMPtr<nsILoadInfo> loadInfo;
+ GetLoadInfo(getter_AddRefs(loadInfo));
+ // if the channel was not triggered by the system principal,
+ // then we return here because the URL had been revoked
+ if (loadInfo && !loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+#else
+ return NS_ERROR_MALFORMED_URI;
+#endif
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream =
+ BlobURLInputStream::Create(this, blobURL);
+ if (NS_WARN_IF(!inputStream)) {
+ return NS_ERROR_MALFORMED_URI;
+ }
+
+ EnableSynthesizedProgressEvents(true);
+
+ inputStream.forget(aResult);
+
+ return NS_OK;
+}
diff --git a/dom/file/uri/BlobURLChannel.h b/dom/file/uri/BlobURLChannel.h
new file mode 100644
index 0000000000..232537171e
--- /dev/null
+++ b/dom/file/uri/BlobURLChannel.h
@@ -0,0 +1,37 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_BlobURLChannel_h
+#define mozilla_dom_BlobURLChannel_h
+
+#include "nsBaseChannel.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+
+class nsIURI;
+
+namespace mozilla::dom {
+
+class BlobImpl;
+
+class BlobURLChannel final : public nsBaseChannel {
+ public:
+ BlobURLChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo);
+
+ NS_IMETHOD SetContentType(const nsACString& aContentType) override;
+
+ private:
+ ~BlobURLChannel() override;
+
+ nsresult OpenContentStream(bool aAsync, nsIInputStream** aResult,
+ nsIChannel** aChannel) override;
+
+ bool mContentStreamOpened;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_BlobURLChannel_h */
diff --git a/dom/file/uri/BlobURLInputStream.cpp b/dom/file/uri/BlobURLInputStream.cpp
new file mode 100644
index 0000000000..34a1bb63e8
--- /dev/null
+++ b/dom/file/uri/BlobURLInputStream.cpp
@@ -0,0 +1,604 @@
+/* -*- 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 "BlobURLInputStream.h"
+#include "BlobURL.h"
+#include "BlobURLChannel.h"
+#include "BlobURLProtocolHandler.h"
+
+#include "mozilla/ScopeExit.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/IPCBlobUtils.h"
+#include "nsStreamUtils.h"
+#include "nsMimeTypes.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_ADDREF(BlobURLInputStream);
+NS_IMPL_RELEASE(BlobURLInputStream);
+
+NS_INTERFACE_MAP_BEGIN(BlobURLInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStreamLength)
+ NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAsyncInputStream)
+NS_INTERFACE_MAP_END
+
+/* static */
+already_AddRefed<nsIInputStream> BlobURLInputStream::Create(
+ BlobURLChannel* const aChannel, BlobURL* const aBlobURL) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (NS_WARN_IF(!aChannel) || NS_WARN_IF(!aBlobURL)) {
+ return nullptr;
+ }
+
+ nsAutoCString spec;
+
+ nsresult rv = aBlobURL->GetSpec(spec);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return nullptr;
+ }
+
+ return MakeAndAddRef<BlobURLInputStream>(aChannel, spec);
+}
+
+// from nsIInputStream interface
+NS_IMETHODIMP BlobURLInputStream::Close() {
+ return CloseWithStatus(NS_BASE_STREAM_CLOSED);
+}
+
+NS_IMETHODIMP BlobURLInputStream::Available(uint64_t* aLength) {
+ MutexAutoLock lock(mStateMachineMutex);
+
+ if (mState == State::ERROR) {
+ MOZ_ASSERT(NS_FAILED(mError));
+ return mError;
+ }
+
+ if (mState == State::CLOSED) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (mState == State::READY) {
+ MOZ_ASSERT(mAsyncInputStream);
+ return mAsyncInputStream->Available(aLength);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP BlobURLInputStream::StreamStatus() {
+ MutexAutoLock lock(mStateMachineMutex);
+
+ if (mState == State::ERROR) {
+ MOZ_ASSERT(NS_FAILED(mError));
+ return mError;
+ }
+
+ if (mState == State::CLOSED) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (mState == State::READY) {
+ MOZ_ASSERT(mAsyncInputStream);
+ return mAsyncInputStream->StreamStatus();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP BlobURLInputStream::Read(char* aBuffer, uint32_t aCount,
+ uint32_t* aReadCount) {
+ MutexAutoLock lock(mStateMachineMutex);
+ if (mState == State::ERROR) {
+ MOZ_ASSERT(NS_FAILED(mError));
+ return mError;
+ }
+
+ // Read() should not return NS_BASE_STREAM_CLOSED if stream is closed.
+ // A read count of 0 should indicate closed or consumed stream.
+ // See:
+ // https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/xpcom/io/nsIInputStream.idl#104
+ if (mState == State::CLOSED) {
+ *aReadCount = 0;
+ return NS_OK;
+ }
+
+ if (mState == State::READY) {
+ MOZ_ASSERT(mAsyncInputStream);
+ nsresult rv = mAsyncInputStream->Read(aBuffer, aCount, aReadCount);
+ if (NS_SUCCEEDED(rv) && aReadCount && !*aReadCount) {
+ mState = State::CLOSED;
+ ReleaseUnderlyingStream(lock);
+ }
+ return rv;
+ }
+
+ return NS_BASE_STREAM_WOULD_BLOCK;
+}
+
+NS_IMETHODIMP BlobURLInputStream::ReadSegments(nsWriteSegmentFun aWriter,
+ void* aClosure, uint32_t aCount,
+ uint32_t* aResult) {
+ // This means the caller will have to wrap the stream in an
+ // nsBufferedInputStream in order to use ReadSegments
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP BlobURLInputStream::IsNonBlocking(bool* aNonBlocking) {
+ *aNonBlocking = true;
+ return NS_OK;
+}
+
+// from nsIAsyncInputStream interface
+NS_IMETHODIMP BlobURLInputStream::CloseWithStatus(nsresult aStatus) {
+ MutexAutoLock lock(mStateMachineMutex);
+ if (mState == State::READY) {
+ MOZ_ASSERT(mAsyncInputStream);
+ mAsyncInputStream->CloseWithStatus(aStatus);
+ }
+
+ mState = State::CLOSED;
+ ReleaseUnderlyingStream(lock);
+ return NS_OK;
+}
+
+NS_IMETHODIMP BlobURLInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+ uint32_t aFlags,
+ uint32_t aRequestedCount,
+ nsIEventTarget* aEventTarget) {
+ MutexAutoLock lock(mStateMachineMutex);
+
+ if (mState == State::ERROR) {
+ MOZ_ASSERT(NS_FAILED(mError));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Pre-empting a valid callback with another is not allowed.
+ if (NS_WARN_IF(mAsyncWaitCallback && aCallback &&
+ mAsyncWaitCallback != aCallback)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mAsyncWaitTarget = aEventTarget;
+ mAsyncWaitRequestedCount = aRequestedCount;
+ mAsyncWaitFlags = aFlags;
+ mAsyncWaitCallback = aCallback;
+
+ if (mState == State::INITIAL) {
+ mState = State::WAITING;
+ // RetrieveBlobData will execute NotifyWWaitTarget() when retrieve succeeds
+ // or fails
+ if (NS_IsMainThread()) {
+ RetrieveBlobData(lock);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod(
+ "BlobURLInputStream::CallRetrieveBlobData", this,
+ &BlobURLInputStream::CallRetrieveBlobData);
+ NS_DispatchToMainThread(runnable.forget(), NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+
+ if (mState == State::WAITING) {
+ // RetrieveBlobData is already in progress and will execute
+ // NotifyWaitTargets when retrieve succeeds or fails
+ return NS_OK;
+ }
+
+ if (mState == State::READY) {
+ // Ask the blob's input stream if reading is possible or not
+ return mAsyncInputStream->AsyncWait(
+ mAsyncWaitCallback ? this : nullptr, mAsyncWaitFlags,
+ mAsyncWaitRequestedCount, mAsyncWaitTarget);
+ }
+
+ MOZ_ASSERT(mState == State::CLOSED);
+ NotifyWaitTargets(lock);
+ return NS_OK;
+}
+
+// from nsIInputStreamLength interface
+NS_IMETHODIMP BlobURLInputStream::Length(int64_t* aLength) {
+ MutexAutoLock lock(mStateMachineMutex);
+
+ if (mState == State::CLOSED) {
+ return NS_BASE_STREAM_CLOSED;
+ }
+
+ if (mState == State::ERROR) {
+ MOZ_ASSERT(NS_FAILED(mError));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mState == State::READY) {
+ *aLength = mBlobSize;
+ return NS_OK;
+ }
+ return NS_BASE_STREAM_WOULD_BLOCK;
+}
+
+// from nsIAsyncInputStreamLength interface
+NS_IMETHODIMP BlobURLInputStream::AsyncLengthWait(
+ nsIInputStreamLengthCallback* aCallback, nsIEventTarget* aEventTarget) {
+ MutexAutoLock lock(mStateMachineMutex);
+
+ if (mState == State::ERROR) {
+ MOZ_ASSERT(NS_FAILED(mError));
+ return mError;
+ }
+
+ // Pre-empting a valid callback with another is not allowed.
+ if (mAsyncLengthWaitCallback && aCallback) {
+ return NS_ERROR_FAILURE;
+ }
+
+ mAsyncLengthWaitTarget = aEventTarget;
+ mAsyncLengthWaitCallback = aCallback;
+
+ if (mState == State::INITIAL) {
+ mState = State::WAITING;
+ // RetrieveBlobData will execute NotifyWWaitTarget() when retrieve succeeds
+ // or fails
+ if (NS_IsMainThread()) {
+ RetrieveBlobData(lock);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod(
+ "BlobURLInputStream::CallRetrieveBlobData", this,
+ &BlobURLInputStream::CallRetrieveBlobData);
+ NS_DispatchToMainThread(runnable.forget(), NS_DISPATCH_NORMAL);
+ return NS_OK;
+ }
+
+ if (mState == State::WAITING) {
+ // RetrieveBlobData is already in progress and will execute
+ // NotifyWaitTargets when retrieve succeeds or fails
+ return NS_OK;
+ }
+
+ // Since here the state must be READY (in which case the size of the blob is
+ // already known) or CLOSED, callback can be called immediately
+ NotifyWaitTargets(lock);
+ return NS_OK;
+}
+
+// from nsIInputStreamCallback interface
+NS_IMETHODIMP BlobURLInputStream::OnInputStreamReady(
+ nsIAsyncInputStream* aStream) {
+ nsCOMPtr<nsIInputStreamCallback> callback;
+
+ {
+ MutexAutoLock lock(mStateMachineMutex);
+ MOZ_ASSERT_IF(mAsyncInputStream, aStream == mAsyncInputStream);
+
+ // aborted in the meantime
+ if (!mAsyncWaitCallback) {
+ return NS_OK;
+ }
+
+ mAsyncWaitCallback.swap(callback);
+ mAsyncWaitTarget = nullptr;
+ }
+
+ MOZ_ASSERT(callback);
+ return callback->OnInputStreamReady(this);
+}
+
+// from nsIInputStreamLengthCallback interface
+NS_IMETHODIMP BlobURLInputStream::OnInputStreamLengthReady(
+ nsIAsyncInputStreamLength* aStream, int64_t aLength) {
+ nsCOMPtr<nsIInputStreamLengthCallback> callback;
+ {
+ MutexAutoLock lock(mStateMachineMutex);
+
+ // aborted in the meantime
+ if (!mAsyncLengthWaitCallback) {
+ return NS_OK;
+ }
+
+ mAsyncLengthWaitCallback.swap(callback);
+ mAsyncLengthWaitCallback = nullptr;
+ }
+
+ return callback->OnInputStreamLengthReady(this, aLength);
+}
+
+// private:
+BlobURLInputStream::~BlobURLInputStream() {
+ if (mChannel) {
+ NS_ReleaseOnMainThread("BlobURLInputStream::mChannel", mChannel.forget());
+ }
+}
+
+BlobURLInputStream::BlobURLInputStream(BlobURLChannel* const aChannel,
+ nsACString& aBlobURLSpec)
+ : mChannel(aChannel),
+ mBlobURLSpec(std::move(aBlobURLSpec)),
+ mStateMachineMutex("BlobURLInputStream::mStateMachineMutex"),
+ mState(State::INITIAL),
+ mError(NS_OK),
+ mBlobSize(-1),
+ mAsyncWaitFlags(),
+ mAsyncWaitRequestedCount() {}
+
+void BlobURLInputStream::WaitOnUnderlyingStream(
+ const MutexAutoLock& aProofOfLock) {
+ if (mAsyncWaitCallback || mAsyncWaitTarget) {
+ // AsyncWait should be called on the underlying stream
+ mAsyncInputStream->AsyncWait(mAsyncWaitCallback ? this : nullptr,
+ mAsyncWaitFlags, mAsyncWaitRequestedCount,
+ mAsyncWaitTarget);
+ }
+
+ if (mAsyncLengthWaitCallback || mAsyncLengthWaitTarget) {
+ // AsyncLengthWait should be called on the underlying stream
+ nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength =
+ do_QueryInterface(mAsyncInputStream);
+ if (asyncStreamLength) {
+ asyncStreamLength->AsyncLengthWait(
+ mAsyncLengthWaitCallback ? this : nullptr, mAsyncLengthWaitTarget);
+ }
+ }
+}
+
+void BlobURLInputStream::CallRetrieveBlobData() {
+ MutexAutoLock lock(mStateMachineMutex);
+ RetrieveBlobData(lock);
+}
+
+void BlobURLInputStream::RetrieveBlobData(const MutexAutoLock& aProofOfLock) {
+ MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+
+ MOZ_ASSERT(mState == State::WAITING);
+
+ auto cleanupOnEarlyExit = MakeScopeExit([&] {
+ mState = State::ERROR;
+ mError = NS_ERROR_FAILURE;
+ NS_ReleaseOnMainThread("BlobURLInputStream::mChannel", mChannel.forget());
+ NotifyWaitTargets(aProofOfLock);
+ });
+
+ nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo();
+ nsCOMPtr<nsIPrincipal> triggeringPrincipal;
+ nsCOMPtr<nsIPrincipal> loadingPrincipal;
+ if (NS_WARN_IF(NS_FAILED(loadInfo->GetTriggeringPrincipal(
+ getter_AddRefs(triggeringPrincipal)))) ||
+ NS_WARN_IF(!triggeringPrincipal)) {
+ NS_WARNING("Failed to get owning channel's triggering principal");
+ return;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(
+ loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal))))) {
+ NS_WARNING("Failed to get owning channel's loading principal");
+ return;
+ }
+
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
+ loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings));
+
+ nsAutoString partKey;
+ cookieJarSettings->GetPartitionKey(partKey);
+
+ if (XRE_IsParentProcess() || !BlobURLSchemeIsHTTPOrHTTPS(mBlobURLSpec)) {
+ RefPtr<BlobImpl> blobImpl;
+
+ // Since revoked blobs are also retrieved, it is possible that the blob no
+ // longer exists (due to the 5 second timeout) when execution reaches here
+ if (!BlobURLProtocolHandler::GetDataEntry(
+ mBlobURLSpec, getter_AddRefs(blobImpl), loadingPrincipal,
+ triggeringPrincipal, loadInfo->GetOriginAttributes(),
+ loadInfo->GetInnerWindowID(), NS_ConvertUTF16toUTF8(partKey),
+ true /* AlsoIfRevoked */)) {
+ NS_WARNING("Failed to get data entry principal. URL revoked?");
+ return;
+ }
+
+ if (NS_WARN_IF(
+ NS_FAILED(StoreBlobImplStream(blobImpl.forget(), aProofOfLock)))) {
+ return;
+ }
+
+ mState = State::READY;
+
+ // By design, execution can only reach here when a caller has called
+ // AsyncWait or AsyncLengthWait on this stream. The underlying stream is
+ // valid, but the caller should not be informed until that stream has data
+ // to read or it is closed.
+ WaitOnUnderlyingStream(aProofOfLock);
+
+ cleanupOnEarlyExit.release();
+ return;
+ }
+
+ ContentChild* contentChild{ContentChild::GetSingleton()};
+ MOZ_ASSERT(contentChild);
+
+ const RefPtr<BlobURLInputStream> self = this;
+
+ cleanupOnEarlyExit.release();
+
+ contentChild
+ ->SendBlobURLDataRequest(
+ mBlobURLSpec, triggeringPrincipal, loadingPrincipal,
+ loadInfo->GetOriginAttributes(), loadInfo->GetInnerWindowID(),
+ NS_ConvertUTF16toUTF8(partKey))
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self](const BlobURLDataRequestResult& aResult) {
+ MutexAutoLock lock(self->mStateMachineMutex);
+ if (aResult.type() == BlobURLDataRequestResult::TIPCBlob) {
+ if (self->mState == State::WAITING) {
+ RefPtr<BlobImpl> blobImpl =
+ IPCBlobUtils::Deserialize(aResult.get_IPCBlob());
+ if (blobImpl && self->StoreBlobImplStream(blobImpl.forget(),
+ lock) == NS_OK) {
+ self->mState = State::READY;
+ // By design, execution can only reach here when a caller has
+ // called AsyncWait or AsyncLengthWait on this stream. The
+ // underlying stream is valid, but the caller should not be
+ // informed until that stream has data to read or it is
+ // closed.
+ self->WaitOnUnderlyingStream(lock);
+ return;
+ }
+ } else {
+ MOZ_ASSERT(self->mState == State::CLOSED);
+ // Callback can be called immediately
+ self->NotifyWaitTargets(lock);
+ return;
+ }
+ }
+ NS_WARNING("Blob data was not retrieved!");
+ self->mState = State::ERROR;
+ self->mError = aResult.type() == BlobURLDataRequestResult::Tnsresult
+ ? aResult.get_nsresult()
+ : NS_ERROR_FAILURE;
+ NS_ReleaseOnMainThread("BlobURLInputStream::mChannel",
+ self->mChannel.forget());
+ self->NotifyWaitTargets(lock);
+ },
+ [self](mozilla::ipc::ResponseRejectReason aReason) {
+ MutexAutoLock lock(self->mStateMachineMutex);
+ NS_WARNING("IPC call to SendBlobURLDataRequest failed!");
+ self->mState = State::ERROR;
+ self->mError = NS_ERROR_FAILURE;
+ NS_ReleaseOnMainThread("BlobURLInputStream::mChannel",
+ self->mChannel.forget());
+ self->NotifyWaitTargets(lock);
+ });
+}
+
+nsresult BlobURLInputStream::StoreBlobImplStream(
+ already_AddRefed<BlobImpl> aBlobImpl, const MutexAutoLock& aProofOfLock) {
+ MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread");
+ RefPtr<BlobImpl> blobImpl = aBlobImpl;
+ nsAutoString blobContentType;
+ nsAutoCString channelContentType;
+
+ // If a Range header was in the request then fetch/XHR will have set a
+ // ContentRange on the channel earlier so we may slice the blob now.
+ blobImpl->GetType(blobContentType);
+ const Maybe<nsBaseChannel::ContentRange>& contentRange =
+ mChannel->GetContentRange();
+ if (contentRange.isSome()) {
+ IgnoredErrorResult result;
+ uint64_t start = contentRange->Start();
+ uint64_t end = contentRange->End();
+ RefPtr<BlobImpl> slice =
+ blobImpl->CreateSlice(start, end - start + 1, blobContentType, result);
+ if (!result.Failed()) {
+ blobImpl = slice;
+ }
+ }
+
+ mChannel->GetContentType(channelContentType);
+ // A empty content type is the correct channel content type in the case of a
+ // fetch of a blob where the type was not set. It is invalid in others cases
+ // such as a XHR (See https://xhr.spec.whatwg.org/#response-mime-type). The
+ // XMLHttpRequestMainThread will set the channel content type to the correct
+ // fallback value before this point, so we need to be careful to only override
+ // it when the blob type is valid.
+ if (!blobContentType.IsEmpty() ||
+ channelContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) {
+ mChannel->SetContentType(NS_ConvertUTF16toUTF8(blobContentType));
+ }
+
+ auto cleanupOnExit = MakeScopeExit([&] { mChannel = nullptr; });
+
+ if (blobImpl->IsFile()) {
+ nsAutoString filename;
+ blobImpl->GetName(filename);
+
+ // Don't overwrite existing name.
+ nsString ignored;
+ bool hasName =
+ NS_SUCCEEDED(mChannel->GetContentDispositionFilename(ignored));
+
+ if (!filename.IsEmpty() && !hasName) {
+ mChannel->SetContentDispositionFilename(filename);
+ }
+ }
+
+ mozilla::ErrorResult errorResult;
+
+ mBlobSize = blobImpl->GetSize(errorResult);
+
+ if (NS_WARN_IF(errorResult.Failed())) {
+ return errorResult.StealNSResult();
+ }
+
+ mChannel->SetContentLength(mBlobSize);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ blobImpl->CreateInputStream(getter_AddRefs(inputStream), errorResult);
+
+ if (NS_WARN_IF(errorResult.Failed())) {
+ return errorResult.StealNSResult();
+ }
+
+ if (NS_WARN_IF(!inputStream)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsresult rv = NS_MakeAsyncNonBlockingInputStream(
+ inputStream.forget(), getter_AddRefs(mAsyncInputStream));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ if (NS_WARN_IF(!mAsyncInputStream)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ return NS_OK;
+}
+
+void BlobURLInputStream::NotifyWaitTargets(const MutexAutoLock& aProofOfLock) {
+ if (mAsyncWaitCallback) {
+ auto callback = mAsyncWaitTarget
+ ? NS_NewInputStreamReadyEvent(
+ "BlobURLInputStream::OnInputStreamReady",
+ mAsyncWaitCallback, mAsyncWaitTarget)
+ : mAsyncWaitCallback;
+
+ mAsyncWaitCallback = nullptr;
+ mAsyncWaitTarget = nullptr;
+ callback->OnInputStreamReady(this);
+ }
+
+ if (mAsyncLengthWaitCallback) {
+ const RefPtr<BlobURLInputStream> self = this;
+ nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
+ "BlobURLInputStream::OnInputStreamLengthReady", [self] {
+ self->mAsyncLengthWaitCallback->OnInputStreamLengthReady(
+ self, self->mBlobSize);
+ });
+
+ mAsyncLengthWaitCallback = nullptr;
+
+ if (mAsyncLengthWaitTarget) {
+ mAsyncLengthWaitTarget->Dispatch(runnable, NS_DISPATCH_NORMAL);
+ mAsyncLengthWaitTarget = nullptr;
+ } else {
+ runnable->Run();
+ }
+ }
+}
+
+void BlobURLInputStream::ReleaseUnderlyingStream(
+ const MutexAutoLock& aProofOfLock) {
+ mAsyncInputStream = nullptr;
+ mBlobSize = -1;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/file/uri/BlobURLInputStream.h b/dom/file/uri/BlobURLInputStream.h
new file mode 100644
index 0000000000..b9215e5ffd
--- /dev/null
+++ b/dom/file/uri/BlobURLInputStream.h
@@ -0,0 +1,81 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_BlobURLInputStream_h
+#define mozilla_dom_BlobURLInputStream_h
+
+#include "mozilla/dom/BlobImpl.h"
+#include "mozilla/Mutex.h"
+#include "nsCOMPtr.h"
+#include "nsIAsyncInputStream.h"
+#include "nsIInputStreamLength.h"
+
+namespace mozilla::dom {
+
+class BlobURL;
+class BlobURLChannel;
+class BlobURLInputStream final : public nsIAsyncInputStream,
+ public nsIInputStreamLength,
+ public nsIAsyncInputStreamLength,
+ public nsIInputStreamCallback,
+ public nsIInputStreamLengthCallback {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIINPUTSTREAM
+ NS_DECL_NSIASYNCINPUTSTREAM
+ NS_DECL_NSIINPUTSTREAMLENGTH
+ NS_DECL_NSIASYNCINPUTSTREAMLENGTH
+ NS_DECL_NSIINPUTSTREAMCALLBACK
+ NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK
+
+ static already_AddRefed<nsIInputStream> Create(BlobURLChannel* const aChannel,
+ BlobURL* const aBlobURL);
+
+ BlobURLInputStream(BlobURLChannel* const aChannel, nsACString& aBlobURLSpec);
+
+ private:
+ enum class State { INITIAL, READY, WAITING, CLOSED, ERROR };
+
+ ~BlobURLInputStream();
+
+ void WaitOnUnderlyingStream(const MutexAutoLock& aProofOfLock);
+
+ // This method should only be used to call RetrieveBlobData in a different
+ // thread
+ void CallRetrieveBlobData();
+
+ void RetrieveBlobData(const MutexAutoLock& aProofOfLock);
+
+ nsresult StoreBlobImplStream(already_AddRefed<BlobImpl> aBlobImpl,
+ const MutexAutoLock& aProofOfLock);
+ void NotifyWaitTargets(const MutexAutoLock& aProofOfLock);
+ void ReleaseUnderlyingStream(const MutexAutoLock& aProofOfLock);
+
+ RefPtr<BlobURLChannel> mChannel;
+ const nsCString mBlobURLSpec;
+
+ // Non-recursive mutex introduced in order to guard access to mState, mError
+ // and mAsyncInputStream
+ Mutex mStateMachineMutex MOZ_UNANNOTATED;
+ State mState;
+ // Stores the error code if stream is in error state
+ nsresult mError;
+
+ int64_t mBlobSize;
+
+ nsCOMPtr<nsIAsyncInputStream> mAsyncInputStream;
+ nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
+ nsCOMPtr<nsIEventTarget> mAsyncWaitTarget;
+ uint32_t mAsyncWaitFlags;
+ uint32_t mAsyncWaitRequestedCount;
+
+ nsCOMPtr<nsIInputStreamLengthCallback> mAsyncLengthWaitCallback;
+ nsCOMPtr<nsIEventTarget> mAsyncLengthWaitTarget;
+};
+
+} // namespace mozilla::dom
+
+#endif /* mozilla_dom_BlobURLInputStream_h */
diff --git a/dom/file/uri/BlobURLProtocolHandler.cpp b/dom/file/uri/BlobURLProtocolHandler.cpp
new file mode 100644
index 0000000000..3e2d8e788e
--- /dev/null
+++ b/dom/file/uri/BlobURLProtocolHandler.cpp
@@ -0,0 +1,1016 @@
+/* -*- 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/glean/GleanMetrics.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)) {
+ mozilla::glean::bloburl::resolve_stopped.Add();
+ 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 nsBaseChannel, 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;
+ }
+ nsBaseChannel* bchan = static_cast<nsBaseChannel*>(aChannel);
+ MOZ_ASSERT(bchan);
+ if (!bchan->SetContentRange(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
diff --git a/dom/file/uri/BlobURLProtocolHandler.h b/dom/file/uri/BlobURLProtocolHandler.h
new file mode 100644
index 0000000000..c34fcfbd4c
--- /dev/null
+++ b/dom/file/uri/BlobURLProtocolHandler.h
@@ -0,0 +1,143 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_BlobURLProtocolHandler_h
+#define mozilla_dom_BlobURLProtocolHandler_h
+
+#include "mozilla/Attributes.h"
+#include "nsIProtocolHandler.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsWeakReference.h"
+#include <functional>
+
+#define BLOBURI_SCHEME "blob"
+
+class nsIPrincipal;
+
+namespace mozilla {
+class BlobURLsReporter;
+class OriginAttributes;
+template <class T>
+class Maybe;
+
+namespace dom {
+
+class BlobImpl;
+class BlobURLRegistrationData;
+class ContentParent;
+class MediaSource;
+
+class BlobURLProtocolHandler final : public nsIProtocolHandler,
+ public nsSupportsWeakReference {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPROTOCOLHANDLER
+
+ BlobURLProtocolHandler();
+
+ static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset,
+ nsIURI* aBaseURI, nsIURI** result);
+
+ // Methods for managing uri->object mapping
+ // AddDataEntry creates the URI with the given scheme and returns it in aUri
+ static nsresult AddDataEntry(BlobImpl*, nsIPrincipal*,
+ const nsCString& aPartitionKey,
+ nsACString& aUri);
+ static nsresult AddDataEntry(MediaSource*, nsIPrincipal*,
+ const nsCString& aPartitionKey,
+ nsACString& aUri);
+ // IPC only
+ static void AddDataEntry(const nsACString& aURI, nsIPrincipal* aPrincipal,
+ const nsCString& aPartitionKey, BlobImpl* aBlobImpl);
+
+ // These methods revoke a blobURL. Because some operations could still be in
+ // progress, the revoking consists in marking the blobURL as revoked and in
+ // removing it after RELEASING_TIMER milliseconds.
+ static void RemoveDataEntry(const nsACString& aUri,
+ bool aBroadcastToOTherProcesses = true);
+ // Returns true if the entry was allowed to be removed.
+ static bool RemoveDataEntry(const nsACString& aUri, nsIPrincipal* aPrincipal,
+ const nsCString& aPartitionKey);
+
+ static void RemoveDataEntries();
+
+ static bool HasDataEntry(const nsACString& aUri);
+
+ static bool GetDataEntry(const nsACString& aUri, BlobImpl** aBlobImpl,
+ nsIPrincipal* aLoadingPrincipal,
+ nsIPrincipal* aTriggeringPrincipal,
+ const OriginAttributes& aOriginAttributes,
+ uint64_t aInnerWindowId,
+ const nsCString& aPartitionKey,
+ bool aAlsoIfRevoked = false);
+
+ static void Traverse(const nsACString& aUri,
+ nsCycleCollectionTraversalCallback& aCallback);
+
+ // Main-thread only method to invoke a helper function that gets called for
+ // every known and recently revoked Blob URL. The helper function should
+ // return true to keep going or false to stop enumerating (presumably because
+ // of an unexpected XPCOM or IPC error). This method returns false if already
+ // shutdown or if the helper method returns false, true otherwise.
+ static bool ForEachBlobURL(
+ std::function<bool(BlobImpl*, nsIPrincipal*, const nsCString&,
+ const nsACString&, bool aRevoked)>&& aCb);
+
+ // This method returns false if aURI is not a known BlobURL. Otherwise it
+ // returns true.
+ //
+ // When true is returned, the aPrincipal out param is meaningful. It gets
+ // set to the principal that a channel loaded from the blob would get if
+ // the blob is not already revoked and to a NullPrincipal if the blob is
+ // revoked.
+ //
+ // This means that for a revoked blob URL this method may either return
+ // false or return true and hand out a NullPrincipal in aPrincipal,
+ // depending on whether the "remove it from the hashtable" timer has
+ // fired. See RemoveDataEntry().
+ static bool GetBlobURLPrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal);
+
+ // Check if metadata about Blob URLs created with this principal should be
+ // broadcast into every content process. This is currently the case for
+ // extension blob URLs and system principal blob URLs, as they can be loaded
+ // by system code and content scripts respectively.
+ static bool IsBlobURLBroadcastPrincipal(nsIPrincipal* aPrincipal);
+
+ private:
+ ~BlobURLProtocolHandler();
+
+ static void Init();
+
+ // If principal is not null, its origin will be used to generate the URI.
+ static nsresult GenerateURIString(nsIPrincipal* aPrincipal, nsACString& aUri);
+};
+
+bool IsBlobURI(nsIURI* aUri);
+bool IsMediaSourceURI(nsIURI* aUri);
+
+// Return true if inner scheme of blobURL is http or https, false otherwise.
+bool BlobURLSchemeIsHTTPOrHTTPS(const nsACString& aUri);
+
+} // namespace dom
+} // namespace mozilla
+
+extern nsresult NS_GetBlobForBlobURI(nsIURI* aURI,
+ mozilla::dom::BlobImpl** aBlob);
+
+extern nsresult NS_GetBlobForBlobURISpec(const nsACString& aSpec,
+ mozilla::dom::BlobImpl** aBlob,
+ bool aAlsoIfRevoked = false);
+
+extern nsresult NS_SetChannelContentRangeForBlobURI(nsIChannel* aChannel,
+ nsIURI* aURI,
+ nsACString& aRangeHeader);
+
+extern nsresult NS_GetSourceForMediaSourceURI(
+ nsIURI* aURI, mozilla::dom::MediaSource** aSource);
+
+#endif /* mozilla_dom_BlobURLProtocolHandler_h */
diff --git a/dom/file/uri/components.conf b/dom/file/uri/components.conf
new file mode 100644
index 0000000000..190a3a6c54
--- /dev/null
+++ b/dom/file/uri/components.conf
@@ -0,0 +1,24 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ 'cid': '{b43964aa-a078-44b2-b06b-fd4d1b172e66}',
+ 'contract_ids': ['@mozilla.org/network/protocol;1?name=blob'],
+ 'type': 'mozilla::dom::BlobURLProtocolHandler',
+ 'headers': ['mozilla/dom/BlobURLProtocolHandler.h'],
+ 'protocol_config': {
+ 'scheme': 'blob',
+ 'flags': [
+ 'URI_NORELATIVE',
+ 'URI_NOAUTH',
+ 'URI_LOADABLE_BY_SUBSUMERS',
+ 'URI_NON_PERSISTABLE',
+ 'URI_IS_LOCAL_RESOURCE',
+ ],
+ },
+ },
+]
diff --git a/dom/file/uri/moz.build b/dom/file/uri/moz.build
new file mode 100644
index 0000000000..a1c4aed54c
--- /dev/null
+++ b/dom/file/uri/moz.build
@@ -0,0 +1,34 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: File")
+
+EXPORTS.mozilla.dom += [
+ "BlobURL.h",
+ "BlobURLInputStream.h",
+ "BlobURLProtocolHandler.h",
+]
+
+UNIFIED_SOURCES += [
+ "BlobURL.cpp",
+ "BlobURLChannel.cpp",
+ "BlobURLInputStream.cpp",
+ "BlobURLProtocolHandler.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+LOCAL_INCLUDES += [
+ "/dom/file",
+ "/netwerk/base",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"