"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, getHandleOnStopRequest) { 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 ); 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); }, }; const { promise: handleOrCcPromise, resolve: handleOrCcResolve } = Promise.withResolvers(); nonAltChan.asyncOpen( new ChannelListener((request, buffer) => { const cc = request.QueryInterface(Ci.nsICacheInfoChannel); Assert.equal(buffer, responseContent); Assert.equal(cc.alternativeDataType, ""); if (getHandleOnStopRequest) { handleOrCcResolve(cc.getCacheEntryWriteHandle()); } else { handleOrCcResolve(cc); } }) ); let handle; if (getHandleOnStopRequest) { handle = await handleOrCcPromise; } else { const cc = await handleOrCcPromise; // In nsHttpChannel's case, this is after clearing the mCacheEntry field, // and this should fallback to mAltDataCacheEntry field. handle = cc.getCacheEntryWriteHandle(); } 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); }); for (let i = 1; i <= 4; i++) { httpServer.registerPathHandler(`/content${i}`, (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, true); testTask(port, "/content2", responseContent, false); }); 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, "/content3", responseContent, true], testTask); await page.spawn([port, "/content4", responseContent, false], testTask); });