commit ca6e20dc42a8193f407adb8c57c2ac2b1b3a2d82 parent d1ea8bad59ed5a23aace358832a1f3b6d5458d31 Author: Tooru Fujisawa <arai_a@mac.com> Date: Tue, 9 Dec 2025 05:11:36 +0000 Bug 2002954 - Add nsICacheEntryWriteHandle and PCacheEntryWriteHandle to expose the nsICacheEntry::openAlternativeOutputStream to content process. r=valentin,necko-reviewers This allows keeping the parent-process nsICacheEntry reference for later use without keeping the HTTP channels on both processes. Differential Revision: https://phabricator.services.mozilla.com/D274397 Diffstat:
17 files changed, 526 insertions(+), 9 deletions(-)
diff --git a/netwerk/base/nsICacheInfoChannel.idl b/netwerk/base/nsICacheInfoChannel.idl @@ -24,6 +24,23 @@ interface nsIInputStreamReceiver : nsISupports void onInputStreamReady(in nsIInputStream aStream); }; + +/** + * A light variant of nsICacheInfoChannel, retrievable from + * nsICacheInfoChannel::getCacheEntryWriteHandle. + * + * When an alternate data is written asynchronously after the response, + * this can be used in order to avoid keeping the channels alive longer. + */ +[scriptable, builtinclass, uuid(0da1249c-d5f5-494e-842b-2c3197c92511)] +interface nsICacheEntryWriteHandle : nsISupports +{ + /** + * Same as nsICacheInfoChannel::openAlternativeOutputStream. + */ + nsIAsyncOutputStream openAlternativeOutputStream(in ACString type, in long long predictedSize); +}; + [scriptable, builtinclass, uuid(72c34415-c6eb-48af-851f-772fa9ee5972)] interface nsICacheInfoChannel : nsISupports { @@ -192,6 +209,11 @@ interface nsICacheInfoChannel : nsISupports void getOriginalInputStream(in nsIInputStreamReceiver aReceiver); /** + * Get a handle to open the alternative output stream for later use. + */ + nsICacheEntryWriteHandle getCacheEntryWriteHandle(); + + /** * Opens and returns an output stream that a consumer may use to save an * alternate representation of the data. * Must be called after the OnStopRequest that delivered the real data. diff --git a/netwerk/ipc/NeckoChild.cpp b/netwerk/ipc/NeckoChild.cpp @@ -23,6 +23,7 @@ #include "mozilla/dom/network/TCPServerSocketChild.h" #include "mozilla/dom/network/UDPSocketChild.h" #include "mozilla/net/AltDataOutputStreamChild.h" +#include "mozilla/net/CacheEntryWriteHandleChild.h" #include "mozilla/net/SocketProcessBridgeChild.h" #ifdef MOZ_WEBRTC # include "mozilla/net/StunAddrsRequestChild.h" @@ -111,9 +112,27 @@ bool NeckoChild::DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor) { return true; } +PCacheEntryWriteHandleChild* NeckoChild::AllocPCacheEntryWriteHandleChild( + PHttpChannelChild* channel) { + // We don't allocate here: see HttpChannelChild::GetCacheEntryWriteHandle() + MOZ_ASSERT_UNREACHABLE( + "AllocPCacheEntryWriteHandleChild should not be called"); + return nullptr; +} + +bool NeckoChild::DeallocPCacheEntryWriteHandleChild( + PCacheEntryWriteHandleChild* aActor) { + CacheEntryWriteHandleChild* child = + static_cast<CacheEntryWriteHandleChild*>(aActor); + child->ReleaseIPDLReference(); + return true; +} + PAltDataOutputStreamChild* NeckoChild::AllocPAltDataOutputStreamChild( const nsACString& type, const int64_t& predictedSize, - PHttpChannelChild* channel) { + const mozilla::Maybe<mozilla::NotNull<PHttpChannelChild*>>& channel, + const mozilla::Maybe<mozilla::NotNull<PCacheEntryWriteHandleChild*>>& + handle) { // We don't allocate here: see HttpChannelChild::OpenAlternativeOutputStream() MOZ_ASSERT_UNREACHABLE("AllocPAltDataOutputStreamChild should not be called"); return nullptr; diff --git a/netwerk/ipc/NeckoChild.h b/netwerk/ipc/NeckoChild.h @@ -34,9 +34,15 @@ class NeckoChild : public PNeckoChild { PWebrtcTCPSocketChild* AllocPWebrtcTCPSocketChild(const Maybe<TabId>& tabId); bool DeallocPWebrtcTCPSocketChild(PWebrtcTCPSocketChild* aActor); + PCacheEntryWriteHandleChild* AllocPCacheEntryWriteHandleChild( + PHttpChannelChild* channel); + bool DeallocPCacheEntryWriteHandleChild(PCacheEntryWriteHandleChild* aActor); + PAltDataOutputStreamChild* AllocPAltDataOutputStreamChild( const nsACString& type, const int64_t& predictedSize, - PHttpChannelChild* channel); + const mozilla::Maybe<mozilla::NotNull<PHttpChannelChild*>>& channel, + const mozilla::Maybe<mozilla::NotNull<PCacheEntryWriteHandleChild*>>& + handle); bool DeallocPAltDataOutputStreamChild(PAltDataOutputStreamChild* aActor); PCookieServiceChild* AllocPCookieServiceChild(); diff --git a/netwerk/ipc/NeckoParent.cpp b/netwerk/ipc/NeckoParent.cpp @@ -25,6 +25,7 @@ # include "mozilla/net/GeckoViewContentChannelParent.h" #endif #include "mozilla/net/DocumentChannelParent.h" +#include "mozilla/net/CacheEntryWriteHandleParent.h" #include "mozilla/net/AltDataOutputStreamParent.h" #include "mozilla/net/DNSRequestParent.h" #include "mozilla/net/IPCTransportProvider.h" @@ -223,13 +224,41 @@ bool NeckoParent::DeallocPWebrtcTCPSocketParent( return true; } -PAltDataOutputStreamParent* NeckoParent::AllocPAltDataOutputStreamParent( - const nsACString& type, const int64_t& predictedSize, +PCacheEntryWriteHandleParent* NeckoParent::AllocPCacheEntryWriteHandleParent( PHttpChannelParent* channel) { HttpChannelParent* chan = static_cast<HttpChannelParent*>(channel); + CacheEntryWriteHandleParent* parent = chan->AllocCacheEntryWriteHandle(); + parent->AddRef(); + return parent; +} + +bool NeckoParent::DeallocPCacheEntryWriteHandleParent( + PCacheEntryWriteHandleParent* aActor) { + CacheEntryWriteHandleParent* parent = + static_cast<CacheEntryWriteHandleParent*>(aActor); + parent->Release(); + return true; +} + +PAltDataOutputStreamParent* NeckoParent::AllocPAltDataOutputStreamParent( + const nsACString& type, const int64_t& predictedSize, + mozilla::Maybe<mozilla::NotNull<mozilla::net::PHttpChannelParent*>>& + channel, + mozilla::Maybe<mozilla::NotNull<PCacheEntryWriteHandleParent*>>& handle) { + MOZ_ASSERT(channel || handle); + + nsresult rv; nsCOMPtr<nsIAsyncOutputStream> stream; - nsresult rv = chan->OpenAlternativeOutputStream(type, predictedSize, - getter_AddRefs(stream)); + if (channel) { + HttpChannelParent* chan = static_cast<HttpChannelParent*>(channel->get()); + rv = chan->OpenAlternativeOutputStream(type, predictedSize, + getter_AddRefs(stream)); + } else { + CacheEntryWriteHandleParent* h = + static_cast<CacheEntryWriteHandleParent*>(handle->get()); + rv = h->OpenAlternativeOutputStream(type, predictedSize, + getter_AddRefs(stream)); + } AltDataOutputStreamParent* parent = new AltDataOutputStreamParent(stream); parent->AddRef(); // If the return value was not NS_OK, the error code will be sent diff --git a/netwerk/ipc/NeckoParent.h b/netwerk/ipc/NeckoParent.h @@ -76,9 +76,16 @@ class NeckoParent : public PNeckoParent { const Maybe<TabId>& aTabId); bool DeallocPWebrtcTCPSocketParent(PWebrtcTCPSocketParent* aActor); + PCacheEntryWriteHandleParent* AllocPCacheEntryWriteHandleParent( + PHttpChannelParent* channel); + bool DeallocPCacheEntryWriteHandleParent( + PCacheEntryWriteHandleParent* aActor); + PAltDataOutputStreamParent* AllocPAltDataOutputStreamParent( const nsACString& type, const int64_t& predictedSize, - PHttpChannelParent* channel); + mozilla::Maybe<mozilla::NotNull<mozilla::net::PHttpChannelParent*>>& + channel, + mozilla::Maybe<mozilla::NotNull<PCacheEntryWriteHandleParent*>>& handle); bool DeallocPAltDataOutputStreamParent(PAltDataOutputStreamParent* aActor); bool DeallocPCookieServiceParent(PCookieServiceParent*); diff --git a/netwerk/ipc/PNecko.ipdl b/netwerk/ipc/PNecko.ipdl @@ -7,6 +7,7 @@ include protocol PContent; include protocol PHttpChannel; +include protocol PCacheEntryWriteHandle; include protocol PCookieService; include protocol PBrowser; #ifdef MOZ_WIDGET_GTK @@ -50,6 +51,7 @@ namespace net { { manager PContent; manages PHttpChannel; + manages PCacheEntryWriteHandle; manages PCookieService; manages PWebSocket; manages PWebSocketEventListener; @@ -136,7 +138,10 @@ parent: async RequestContextAfterDOMContentLoaded(uint64_t rcid); async RemoveRequestContext(uint64_t rcid); - async PAltDataOutputStream(nsCString type, int64_t predictedSize, PHttpChannel channel); + async PCacheEntryWriteHandle(PHttpChannel channel); + + async PAltDataOutputStream(nsCString type, int64_t predictedSize, PHttpChannel? channel, PCacheEntryWriteHandle? handle); + async PStunAddrsRequest(); diff --git a/netwerk/protocol/http/CacheEntryWriteHandleChild.h b/netwerk/protocol/http/CacheEntryWriteHandleChild.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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_net_CacheEntryWriteHandleChild_h +#define mozilla_net_CacheEntryWriteHandleChild_h + +#include "mozilla/net/PCacheEntryWriteHandleChild.h" +#include "mozilla/net/DocumentChannel.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "mozilla/dom/nsCSPContext.h" + +namespace mozilla { +namespace net { + +/** + * CacheEntryWriteHandleChild is a wrapper for nsICacheEntry, for the + * asynchronous OpenAlternativeOutputStream call. + */ +class CacheEntryWriteHandleChild final : public nsICacheEntryWriteHandle, + public PCacheEntryWriteHandleChild { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICACHEENTRYWRITEHANDLE + CacheEntryWriteHandleChild() = default; + + void AddIPDLReference(); + void ReleaseIPDLReference(); + + private: + virtual ~CacheEntryWriteHandleChild() = default; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_DocumentChannelChild_h diff --git a/netwerk/protocol/http/CacheEntryWriteHandleParent.h b/netwerk/protocol/http/CacheEntryWriteHandleParent.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et 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_net_CacheEntryWriteHandleParent_h +#define mozilla_net_CacheEntryWriteHandleParent_h + +#include "mozilla/net/PCacheEntryWriteHandleParent.h" +#include "nsICacheEntry.h" + +namespace mozilla { +namespace net { + +/** + * CacheEntryWriteHandleParent is a wrapper for nsICacheEntry, for the + * asynchronous OpenAlternativeOutputStream call. + */ +class CacheEntryWriteHandleParent final : public nsICacheEntryWriteHandle, + public PCacheEntryWriteHandleParent { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSICACHEENTRYWRITEHANDLE + explicit CacheEntryWriteHandleParent(nsICacheEntry* aCacheEntry); + + private: + virtual ~CacheEntryWriteHandleParent() = default; + + nsCOMPtr<nsICacheEntry> mCacheEntry; +}; + +} // namespace net +} // namespace mozilla + +#endif // mozilla_net_DocumentChannelChild_h diff --git a/netwerk/protocol/http/HttpChannelChild.cpp b/netwerk/protocol/http/HttpChannelChild.cpp @@ -23,6 +23,7 @@ #include "mozilla/ipc/IPCStreamUtils.h" #include "mozilla/net/NeckoChild.h" #include "mozilla/net/HttpChannelChild.h" +#include "mozilla/net/CacheEntryWriteHandleChild.h" #include "mozilla/net/PBackgroundDataBridge.h" #include "mozilla/net/UrlClassifierCommon.h" #include "mozilla/net/UrlClassifierFeatureFactory.h" @@ -2868,6 +2869,68 @@ HttpChannelChild::GetAlternativeDataType(nsACString& aType) { return NS_OK; } +NS_IMPL_ADDREF(CacheEntryWriteHandleChild) +NS_IMPL_RELEASE(CacheEntryWriteHandleChild) +NS_INTERFACE_MAP_BEGIN(CacheEntryWriteHandleChild) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsICacheEntryWriteHandle) +NS_INTERFACE_MAP_END + +void CacheEntryWriteHandleChild::AddIPDLReference() { AddRef(); } + +void CacheEntryWriteHandleChild::ReleaseIPDLReference() { Release(); } + +[[nodiscard]] nsresult CacheEntryWriteHandleChild::OpenAlternativeOutputStream( + const nsACString& aType, int64_t aPredictedSize, + nsIAsyncOutputStream** _retval) { + MOZ_ASSERT(NS_IsMainThread(), "Main thread only"); + + if (!CanSend()) { + return NS_ERROR_NOT_AVAILABLE; + } + if (static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + RefPtr<AltDataOutputStreamChild> stream = new AltDataOutputStreamChild(); + + if (!gNeckoChild->SendPAltDataOutputStreamConstructor( + stream, nsCString(aType), aPredictedSize, Nothing(), + Some(WrapNotNull(this)))) { + return NS_ERROR_FAILURE; + } + + stream->AddIPDLReference(); + stream.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +HttpChannelChild::GetCacheEntryWriteHandle(nsICacheEntryWriteHandle** _retval) { + MOZ_ASSERT(NS_IsMainThread(), "Main thread only"); + + if (!CanSend()) { + return NS_ERROR_NOT_AVAILABLE; + } + if (static_cast<ContentChild*>(gNeckoChild->Manager())->IsShuttingDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsISerialEventTarget> neckoTarget = GetNeckoTarget(); + MOZ_ASSERT(neckoTarget); + + RefPtr<CacheEntryWriteHandleChild> handle = new CacheEntryWriteHandleChild(); + + if (!gNeckoChild->SendPCacheEntryWriteHandleConstructor(handle, + WrapNotNull(this))) { + return NS_ERROR_FAILURE; + } + + handle->AddIPDLReference(); + handle.forget(_retval); + return NS_OK; +} + NS_IMETHODIMP HttpChannelChild::OpenAlternativeOutputStream(const nsACString& aType, int64_t aPredictedSize, @@ -2888,7 +2951,8 @@ HttpChannelChild::OpenAlternativeOutputStream(const nsACString& aType, stream->AddIPDLReference(); if (!gNeckoChild->SendPAltDataOutputStreamConstructor( - stream, nsCString(aType), aPredictedSize, WrapNotNull(this))) { + stream, nsCString(aType), aPredictedSize, Some(WrapNotNull(this)), + Nothing())) { return NS_ERROR_FAILURE; } diff --git a/netwerk/protocol/http/HttpChannelParent.cpp b/netwerk/protocol/http/HttpChannelParent.cpp @@ -13,6 +13,7 @@ #include "mozilla/ipc/IPCStreamUtils.h" #include "mozilla/net/EarlyHintRegistrar.h" #include "mozilla/net/HttpChannelParent.h" +#include "mozilla/net/CacheEntryWriteHandleParent.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/ContentProcessManager.h" #include "mozilla/dom/Element.h" @@ -1952,6 +1953,43 @@ HttpChannelParent::CompleteRedirect(nsresult status) { return NS_OK; } +NS_IMPL_ADDREF(CacheEntryWriteHandleParent) +NS_IMPL_RELEASE(CacheEntryWriteHandleParent) +NS_INTERFACE_MAP_BEGIN(CacheEntryWriteHandleParent) + NS_INTERFACE_MAP_ENTRY(nsICacheEntryWriteHandle) +NS_INTERFACE_MAP_END + +CacheEntryWriteHandleParent::CacheEntryWriteHandleParent( + nsICacheEntry* aCacheEntry) + : mCacheEntry(aCacheEntry) {} + +[[nodiscard]] nsresult CacheEntryWriteHandleParent::OpenAlternativeOutputStream( + const nsACString& type, int64_t predictedSize, + nsIAsyncOutputStream** _retval) { + if (!mCacheEntry) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = + mCacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval); + if (NS_SUCCEEDED(rv)) { + mCacheEntry->SetMetaDataElement("alt-data-from-child", "1"); + } + return rv; +} + +nsresult HttpChannelParent::GetCacheEntryWriteHandle( + nsICacheEntryWriteHandle** _retval) { + nsCOMPtr<nsICacheEntryWriteHandle> handle = + new CacheEntryWriteHandleParent(mCacheEntry); + handle.forget(_retval); + return NS_OK; +} + +CacheEntryWriteHandleParent* HttpChannelParent::AllocCacheEntryWriteHandle() { + return new CacheEntryWriteHandleParent(mCacheEntry); +} + nsresult HttpChannelParent::OpenAlternativeOutputStream( const nsACString& type, int64_t predictedSize, nsIAsyncOutputStream** _retval) { diff --git a/netwerk/protocol/http/HttpChannelParent.h b/netwerk/protocol/http/HttpChannelParent.h @@ -39,6 +39,7 @@ namespace net { class HttpBackgroundChannelParent; class ParentChannelListener; class ChannelEventQueue; +class CacheEntryWriteHandleParent; class HttpChannelParent final : public nsIInterfaceRequestor, public PHttpChannelParent, @@ -83,6 +84,11 @@ class HttpChannelParent final : public nsIInterfaceRequestor, const nsACString& type, int64_t predictedSize, nsIAsyncOutputStream** _retval); + [[nodiscard]] nsresult GetCacheEntryWriteHandle( + nsICacheEntryWriteHandle** _retval); + + [[nodiscard]] CacheEntryWriteHandleParent* AllocCacheEntryWriteHandle(); + // Callbacks for each asynchronous tasks required in AsyncOpen // procedure, will call InvokeAsyncOpen when all the expected // tasks is finished successfully or when any failure happened. diff --git a/netwerk/protocol/http/InterceptedHttpChannel.cpp b/netwerk/protocol/http/InterceptedHttpChannel.cpp @@ -1483,6 +1483,15 @@ InterceptedHttpChannel::GetAlternativeDataType(nsACString& aType) { } NS_IMETHODIMP +InterceptedHttpChannel::GetCacheEntryWriteHandle( + nsICacheEntryWriteHandle** _retval) { + if (mSynthesizedCacheInfo) { + return mSynthesizedCacheInfo->GetCacheEntryWriteHandle(_retval); + } + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP InterceptedHttpChannel::OpenAlternativeOutputStream( const nsACString& type, int64_t predictedSize, nsIAsyncOutputStream** _retval) { diff --git a/netwerk/protocol/http/PCacheEntryWriteHandle.ipdl b/netwerk/protocol/http/PCacheEntryWriteHandle.ipdl @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ + +/* 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 protocol PNecko; + +namespace mozilla { +namespace net { + +[ManualDealloc] +protocol PCacheEntryWriteHandle +{ + manager PNecko; + +parent: + async __delete__(); +}; + +} // namespace net +} // namespace mozilla diff --git a/netwerk/protocol/http/moz.build b/netwerk/protocol/http/moz.build @@ -54,6 +54,8 @@ EXPORTS.mozilla.net += [ "BackgroundDataBridgeChild.h", "BackgroundDataBridgeParent.h", "CacheControlParser.h", + "CacheEntryWriteHandleChild.h", + "CacheEntryWriteHandleParent.h", "ClassOfService.h", "EarlyHintPreloader.h", "EarlyHintRegistrar.h", @@ -207,6 +209,7 @@ IPDL_SOURCES += [ "PAltService.ipdl", "PAltSvcTransaction.ipdl", "PBackgroundDataBridge.ipdl", + "PCacheEntryWriteHandle.ipdl", "PHttpBackgroundChannel.ipdl", "PHttpChannel.ipdl", "PHttpConnectionMgr.ipdl", diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp @@ -10829,6 +10829,51 @@ nsHttpChannel::GetAlternativeDataType(nsACString& aType) { return NS_OK; } +class CacheEntryWriteHandle : public nsICacheEntryWriteHandle { + virtual ~CacheEntryWriteHandle() = default; + + public: + NS_DECL_ISUPPORTS + + explicit CacheEntryWriteHandle(nsICacheEntry* aCacheEntry) + : mCacheEntry(aCacheEntry) { + MOZ_ASSERT(mCacheEntry); + } + + [[nodiscard]] nsresult OpenAlternativeOutputStream( + const nsACString& type, int64_t predictedSize, + nsIAsyncOutputStream** _retval) override { + nsresult rv = + mCacheEntry->OpenAlternativeOutputStream(type, predictedSize, _retval); + if (NS_SUCCEEDED(rv)) { + mCacheEntry->SetMetaDataElement("alt-data-from-child", nullptr); + } + return rv; + } + + private: + nsCOMPtr<nsICacheEntry> mCacheEntry; +}; + +NS_IMPL_ADDREF(CacheEntryWriteHandle) +NS_IMPL_RELEASE(CacheEntryWriteHandle) +NS_INTERFACE_MAP_BEGIN(CacheEntryWriteHandle) + NS_INTERFACE_MAP_ENTRY(nsISupports) + NS_INTERFACE_MAP_ENTRY(nsICacheEntryWriteHandle) +NS_INTERFACE_MAP_END + +nsresult nsHttpChannel::GetCacheEntryWriteHandle( + nsICacheEntryWriteHandle** _retval) { + if (!mCacheEntry) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsICacheEntryWriteHandle> handle = + new CacheEntryWriteHandle(mCacheEntry); + handle.forget(_retval); + return NS_OK; +} + NS_IMETHODIMP nsHttpChannel::OpenAlternativeOutputStream(const nsACString& type, int64_t predictedSize, diff --git a/netwerk/test/unit/test_cache-entry-write-handle.js b/netwerk/test/unit/test_cache-entry-write-handle.js @@ -0,0 +1,162 @@ +"use strict"; + +const { HttpServer } = ChromeUtils.importESModule( + "resource://testing-common/httpd.sys.mjs" +); + +const { XPCShellContentUtils } = ChromeUtils.importESModule( + "resource://testing-common/XPCShellContentUtils.sys.mjs" +); + +XPCShellContentUtils.ensureInitialized(this); + +const responseContent = "response body"; + +// NOTE: This is executed both on the parent process and the content process. +// Some variables are re-defined. +async function testTask(port, path, responseContent) { + const { NetUtil } = ChromeUtils.importESModule( + "resource://gre/modules/NetUtil.sys.mjs" + ); + + function makeChannel(url) { + return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true }); + } + + const FILE_URL = "http://localhost:" + port + path; + + const altContent = "altData"; + const altContentType = "text/binary"; + + const nonAltChan = makeChannel(FILE_URL); + nonAltChan + .QueryInterface(Ci.nsICacheInfoChannel) + .preferAlternativeDataType( + altContentType, + "", + Ci.nsICacheInfoChannel.ASYNC + ); + + const { promise: handlePromise, resolve: handleResolve } = + Promise.withResolvers(); + + function ChannelListener(callback) { + this._callback = callback; + this._buffer = ""; + } + ChannelListener.prototype = { + QueryInterface: ChromeUtils.generateQI([ + "nsIStreamListener", + "nsIRequestObserver", + ]), + + onStartRequest(_request) {}, + + onDataAvailable(request, stream, offset, count) { + const bi = Cc["@mozilla.org/binaryinputstream;1"].createInstance( + Ci.nsIBinaryInputStream + ); + bi.setInputStream(stream); + while (count > 0) { + const bytes = bi.readByteArray(Math.min(65535, count)); + this._buffer += String.fromCharCode.apply(null, bytes); + count -= bytes.length; + } + }, + + onStopRequest(request, _status) { + this._callback(request, this._buffer); + }, + }; + + nonAltChan.asyncOpen( + new ChannelListener((request, buffer) => { + const cc = request.QueryInterface(Ci.nsICacheInfoChannel); + + Assert.equal(buffer, responseContent); + Assert.equal(cc.alternativeDataType, ""); + + handleResolve(cc.getCacheEntryWriteHandle()); + }) + ); + + const handle = await handlePromise; + + const os = handle.openAlternativeOutputStream( + altContentType, + altContent.length + ); + os.write(altContent, altContent.length); + os.close(); + + const altChan = makeChannel(FILE_URL); + altChan + .QueryInterface(Ci.nsICacheInfoChannel) + .preferAlternativeDataType( + altContentType, + "", + Ci.nsICacheInfoChannel.ASYNC + ); + + const { promise: altDataPromise, resolve: altDataResolve } = + Promise.withResolvers(); + + altChan.asyncOpen( + new ChannelListener((request, buffer) => { + const cc = request.QueryInterface(Ci.nsICacheInfoChannel); + + Assert.equal(buffer, altContent); + Assert.equal(cc.alternativeDataType, altContentType); + + altDataResolve(); + }) + ); + + await altDataPromise; +} + +let httpServer; + +add_setup(async function setup() { + httpServer = new HttpServer(); + httpServer.registerPathHandler("/page", (metadata, response) => { + response.setHeader("Content-Type", "text/plain"); + response.setHeader("Cache-Control", "max-age=86400"); + + response.bodyOutputStream.write("", 0); + }); + httpServer.registerPathHandler("/content1", (metadata, response) => { + response.setHeader("Content-Type", "text/plain"); + response.setHeader("Cache-Control", "max-age=86400"); + + response.bodyOutputStream.write(responseContent, responseContent.length); + }); + httpServer.registerPathHandler("/content2", (metadata, response) => { + response.setHeader("Content-Type", "text/plain"); + response.setHeader("Cache-Control", "max-age=86400"); + + response.bodyOutputStream.write(responseContent, responseContent.length); + }); + httpServer.start(-1); + + registerCleanupFunction(async () => { + await new Promise(resolve => httpServer.stop(resolve)); + }); +}); + +add_task(async function test_CacheEntryWriteHandle_ParentProcess() { + const port = httpServer.identity.primaryPort; + + testTask(port, "/content1", responseContent); +}); + +add_task(async function test_CacheEntryWriteHandle_ContentProcess() { + const port = httpServer.identity.primaryPort; + const PAGE_URL = "http://localhost:" + port + "/page"; + + const page = await XPCShellContentUtils.loadContentPage(PAGE_URL, { + remote: true, + }); + + await page.spawn([port, "/content2", responseContent], testTask); +}); diff --git a/netwerk/test/unit/xpcshell.toml b/netwerk/test/unit/xpcshell.toml @@ -372,6 +372,8 @@ skip-if = [ ["test_cache-entry-id.js"] +["test_cache-entry-write-handle.js"] + ["test_cache2-00-service-get.js"] ["test_cache2-01-basic.js"]