"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);
});