test_cache-entry-write-handle.js (4831B)
1 "use strict"; 2 3 const { HttpServer } = ChromeUtils.importESModule( 4 "resource://testing-common/httpd.sys.mjs" 5 ); 6 7 const { XPCShellContentUtils } = ChromeUtils.importESModule( 8 "resource://testing-common/XPCShellContentUtils.sys.mjs" 9 ); 10 11 XPCShellContentUtils.ensureInitialized(this); 12 13 const responseContent = "response body"; 14 15 // NOTE: This is executed both on the parent process and the content process. 16 // Some variables are re-defined. 17 async function testTask(port, path, responseContent, getHandleOnStopRequest) { 18 const { NetUtil } = ChromeUtils.importESModule( 19 "resource://gre/modules/NetUtil.sys.mjs" 20 ); 21 22 function makeChannel(url) { 23 return NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true }); 24 } 25 26 const FILE_URL = "http://localhost:" + port + path; 27 28 const altContent = "altData"; 29 const altContentType = "text/binary"; 30 31 const nonAltChan = makeChannel(FILE_URL); 32 nonAltChan 33 .QueryInterface(Ci.nsICacheInfoChannel) 34 .preferAlternativeDataType( 35 altContentType, 36 "", 37 Ci.nsICacheInfoChannel.ASYNC 38 ); 39 40 function ChannelListener(callback) { 41 this._callback = callback; 42 this._buffer = ""; 43 } 44 ChannelListener.prototype = { 45 QueryInterface: ChromeUtils.generateQI([ 46 "nsIStreamListener", 47 "nsIRequestObserver", 48 ]), 49 50 onStartRequest(_request) {}, 51 52 onDataAvailable(request, stream, offset, count) { 53 const bi = Cc["@mozilla.org/binaryinputstream;1"].createInstance( 54 Ci.nsIBinaryInputStream 55 ); 56 bi.setInputStream(stream); 57 while (count > 0) { 58 const bytes = bi.readByteArray(Math.min(65535, count)); 59 this._buffer += String.fromCharCode.apply(null, bytes); 60 count -= bytes.length; 61 } 62 }, 63 64 onStopRequest(request, _status) { 65 this._callback(request, this._buffer); 66 }, 67 }; 68 69 const { promise: handleOrCcPromise, resolve: handleOrCcResolve } = 70 Promise.withResolvers(); 71 72 nonAltChan.asyncOpen( 73 new ChannelListener((request, buffer) => { 74 const cc = request.QueryInterface(Ci.nsICacheInfoChannel); 75 76 Assert.equal(buffer, responseContent); 77 Assert.equal(cc.alternativeDataType, ""); 78 79 if (getHandleOnStopRequest) { 80 handleOrCcResolve(cc.getCacheEntryWriteHandle()); 81 } else { 82 handleOrCcResolve(cc); 83 } 84 }) 85 ); 86 87 let handle; 88 if (getHandleOnStopRequest) { 89 handle = await handleOrCcPromise; 90 } else { 91 const cc = await handleOrCcPromise; 92 // In nsHttpChannel's case, this is after clearing the mCacheEntry field, 93 // and this should fallback to mAltDataCacheEntry field. 94 handle = cc.getCacheEntryWriteHandle(); 95 } 96 97 const os = handle.openAlternativeOutputStream( 98 altContentType, 99 altContent.length 100 ); 101 os.write(altContent, altContent.length); 102 os.close(); 103 104 const altChan = makeChannel(FILE_URL); 105 altChan 106 .QueryInterface(Ci.nsICacheInfoChannel) 107 .preferAlternativeDataType( 108 altContentType, 109 "", 110 Ci.nsICacheInfoChannel.ASYNC 111 ); 112 113 const { promise: altDataPromise, resolve: altDataResolve } = 114 Promise.withResolvers(); 115 116 altChan.asyncOpen( 117 new ChannelListener((request, buffer) => { 118 const cc = request.QueryInterface(Ci.nsICacheInfoChannel); 119 120 Assert.equal(buffer, altContent); 121 Assert.equal(cc.alternativeDataType, altContentType); 122 123 altDataResolve(); 124 }) 125 ); 126 127 await altDataPromise; 128 } 129 130 let httpServer; 131 132 add_setup(async function setup() { 133 httpServer = new HttpServer(); 134 httpServer.registerPathHandler("/page", (metadata, response) => { 135 response.setHeader("Content-Type", "text/plain"); 136 response.setHeader("Cache-Control", "max-age=86400"); 137 138 response.bodyOutputStream.write("", 0); 139 }); 140 for (let i = 1; i <= 4; i++) { 141 httpServer.registerPathHandler(`/content${i}`, (metadata, response) => { 142 response.setHeader("Content-Type", "text/plain"); 143 response.setHeader("Cache-Control", "max-age=86400"); 144 145 response.bodyOutputStream.write(responseContent, responseContent.length); 146 }); 147 } 148 httpServer.start(-1); 149 150 registerCleanupFunction(async () => { 151 await new Promise(resolve => httpServer.stop(resolve)); 152 }); 153 }); 154 155 add_task(async function test_CacheEntryWriteHandle_ParentProcess() { 156 const port = httpServer.identity.primaryPort; 157 158 testTask(port, "/content1", responseContent, true); 159 testTask(port, "/content2", responseContent, false); 160 }); 161 162 add_task(async function test_CacheEntryWriteHandle_ContentProcess() { 163 const port = httpServer.identity.primaryPort; 164 const PAGE_URL = "http://localhost:" + port + "/page"; 165 166 const page = await XPCShellContentUtils.loadContentPage(PAGE_URL, { 167 remote: true, 168 }); 169 170 await page.spawn([port, "/content3", responseContent, true], testTask); 171 await page.spawn([port, "/content4", responseContent, false], testTask); 172 });