test_synthesized_response.js (8477B)
1 "use strict"; 2 3 const { HttpServer } = ChromeUtils.importESModule( 4 "resource://testing-common/httpd.sys.mjs" 5 ); 6 7 ChromeUtils.defineLazyGetter(this, "URL", function () { 8 return "http://localhost:" + httpServer.identity.primaryPort; 9 }); 10 11 var httpServer = null; 12 13 function isParentProcess() { 14 let appInfo = Cc["@mozilla.org/xre/app-info;1"]; 15 return ( 16 !appInfo || 17 Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT 18 ); 19 } 20 21 if (isParentProcess()) { 22 // ensure the cache service is prepped when running the test 23 // We only do this in the main process, as the cache storage service leaks 24 // when instantiated in the content process. 25 Services.cache2; 26 } 27 28 var gotOnProgress; 29 var gotOnStatus; 30 31 function make_channel(url, body, cb) { 32 gotOnProgress = false; 33 gotOnStatus = false; 34 var chan = NetUtil.newChannel({ 35 uri: url, 36 loadUsingSystemPrincipal: true, 37 }).QueryInterface(Ci.nsIHttpChannel); 38 chan.notificationCallbacks = { 39 numChecks: 0, 40 QueryInterface: ChromeUtils.generateQI([ 41 "nsINetworkInterceptController", 42 "nsIInterfaceRequestor", 43 "nsIProgressEventSink", 44 ]), 45 getInterface(iid) { 46 return this.QueryInterface(iid); 47 }, 48 onProgress() { 49 gotOnProgress = true; 50 }, 51 onStatus() { 52 gotOnStatus = true; 53 }, 54 shouldPrepareForIntercept() { 55 Assert.equal(this.numChecks, 0); 56 this.numChecks++; 57 return true; 58 }, 59 channelIntercepted(channel) { 60 channel.QueryInterface(Ci.nsIInterceptedChannel); 61 if (body) { 62 var synthesized = Cc[ 63 "@mozilla.org/io/string-input-stream;1" 64 ].createInstance(Ci.nsIStringInputStream); 65 synthesized.setByteStringData(body); 66 67 channel.startSynthesizedResponse(synthesized, null, null, "", false); 68 channel.finishSynthesizedResponse(); 69 } 70 if (cb) { 71 cb(channel); 72 } 73 return { 74 dispatch() {}, 75 }; 76 }, 77 }; 78 return chan; 79 } 80 81 const REMOTE_BODY = "http handler body"; 82 const NON_REMOTE_BODY = "synthesized body"; 83 const NON_REMOTE_BODY_2 = "synthesized body #2"; 84 85 function bodyHandler(metadata, response) { 86 response.setHeader("Content-Type", "text/plain"); 87 response.write(REMOTE_BODY); 88 } 89 90 function run_test() { 91 httpServer = new HttpServer(); 92 httpServer.registerPathHandler("/body", bodyHandler); 93 httpServer.start(-1); 94 95 run_next_test(); 96 } 97 98 function handle_synthesized_response(request, buffer) { 99 Assert.equal(buffer, NON_REMOTE_BODY); 100 Assert.ok(gotOnStatus); 101 Assert.ok(gotOnProgress); 102 run_next_test(); 103 } 104 105 function handle_synthesized_response_2(request, buffer) { 106 Assert.equal(buffer, NON_REMOTE_BODY_2); 107 Assert.ok(gotOnStatus); 108 Assert.ok(gotOnProgress); 109 run_next_test(); 110 } 111 112 function handle_remote_response(request, buffer) { 113 Assert.equal(buffer, REMOTE_BODY); 114 Assert.ok(gotOnStatus); 115 Assert.ok(gotOnProgress); 116 run_next_test(); 117 } 118 119 // hit the network instead of synthesizing 120 add_test(function () { 121 var chan = make_channel(URL + "/body", null, function (channel) { 122 channel.resetInterception(false); 123 }); 124 chan.asyncOpen(new ChannelListener(handle_remote_response, null)); 125 }); 126 127 // synthesize a response 128 add_test(function () { 129 var chan = make_channel(URL + "/body", NON_REMOTE_BODY); 130 chan.asyncOpen( 131 new ChannelListener(handle_synthesized_response, null, CL_ALLOW_UNKNOWN_CL) 132 ); 133 }); 134 135 // hit the network instead of synthesizing, to test that no previous synthesized 136 // cache entry is used. 137 add_test(function () { 138 var chan = make_channel(URL + "/body", null, function (channel) { 139 channel.resetInterception(false); 140 }); 141 chan.asyncOpen(new ChannelListener(handle_remote_response, null)); 142 }); 143 144 // synthesize a different response to ensure no previous response is cached 145 add_test(function () { 146 var chan = make_channel(URL + "/body", NON_REMOTE_BODY_2); 147 chan.asyncOpen( 148 new ChannelListener( 149 handle_synthesized_response_2, 150 null, 151 CL_ALLOW_UNKNOWN_CL 152 ) 153 ); 154 }); 155 156 // ensure that the channel waits for a decision and synthesizes headers correctly 157 add_test(function () { 158 var chan = make_channel(URL + "/body", null, function (channel) { 159 do_timeout(100, function () { 160 var synthesized = Cc[ 161 "@mozilla.org/io/string-input-stream;1" 162 ].createInstance(Ci.nsIStringInputStream); 163 synthesized.setByteStringData(NON_REMOTE_BODY); 164 channel.synthesizeHeader("Content-Length", NON_REMOTE_BODY.length); 165 channel.startSynthesizedResponse(synthesized, null, null, "", false); 166 channel.finishSynthesizedResponse(); 167 }); 168 }); 169 chan.asyncOpen(new ChannelListener(handle_synthesized_response, null)); 170 }); 171 172 // ensure that the channel waits for a decision 173 add_test(function () { 174 var chan = make_channel(URL + "/body", null, function (channel) { 175 do_timeout(100, function () { 176 channel.resetInterception(false); 177 }); 178 }); 179 chan.asyncOpen(new ChannelListener(handle_remote_response, null)); 180 }); 181 182 // ensure that the intercepted channel supports suspend/resume 183 add_test(function () { 184 var chan = make_channel(URL + "/body", null, function (intercepted) { 185 var synthesized = Cc[ 186 "@mozilla.org/io/string-input-stream;1" 187 ].createInstance(Ci.nsIStringInputStream); 188 synthesized.setByteStringData(NON_REMOTE_BODY); 189 190 // set the content-type to ensure that the stream converter doesn't hold up notifications 191 // and cause the test to fail 192 intercepted.synthesizeHeader("Content-Type", "text/plain"); 193 intercepted.startSynthesizedResponse(synthesized, null, null, "", false); 194 intercepted.finishSynthesizedResponse(); 195 }); 196 chan.asyncOpen( 197 new ChannelListener( 198 handle_synthesized_response, 199 null, 200 CL_ALLOW_UNKNOWN_CL | CL_SUSPEND | CL_EXPECT_3S_DELAY 201 ) 202 ); 203 }); 204 205 // ensure that the intercepted channel can be cancelled 206 add_test(function () { 207 var chan = make_channel(URL + "/body", null, function (intercepted) { 208 intercepted.cancelInterception(Cr.NS_BINDING_ABORTED); 209 }); 210 chan.asyncOpen(new ChannelListener(run_next_test, null, CL_EXPECT_FAILURE)); 211 }); 212 213 // ensure that the channel can't be cancelled via nsIInterceptedChannel after making a decision 214 add_test(function () { 215 var chan = make_channel(URL + "/body", null, function (channel) { 216 channel.resetInterception(false); 217 do_timeout(0, function () { 218 var gotexception = false; 219 try { 220 channel.cancelInterception(); 221 } catch (x) { 222 gotexception = true; 223 } 224 Assert.ok(gotexception); 225 }); 226 }); 227 chan.asyncOpen(new ChannelListener(handle_remote_response, null)); 228 }); 229 230 // ensure that the intercepted channel can be canceled during the response 231 add_test(function () { 232 var chan = make_channel(URL + "/body", null, function (intercepted) { 233 var synthesized = Cc[ 234 "@mozilla.org/io/string-input-stream;1" 235 ].createInstance(Ci.nsIStringInputStream); 236 synthesized.setByteStringData(NON_REMOTE_BODY); 237 238 let channel = intercepted.channel; 239 intercepted.startSynthesizedResponse(synthesized, null, null, "", false); 240 intercepted.finishSynthesizedResponse(); 241 channel.cancel(Cr.NS_BINDING_ABORTED); 242 }); 243 chan.asyncOpen( 244 new ChannelListener( 245 run_next_test, 246 null, 247 CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL 248 ) 249 ); 250 }); 251 252 // ensure that the intercepted channel can be canceled before the response 253 add_test(function () { 254 var chan = make_channel(URL + "/body", null, function (intercepted) { 255 var synthesized = Cc[ 256 "@mozilla.org/io/string-input-stream;1" 257 ].createInstance(Ci.nsIStringInputStream); 258 synthesized.setByteStringData(NON_REMOTE_BODY); 259 260 intercepted.channel.cancel(Cr.NS_BINDING_ABORTED); 261 262 // This should not throw, but result in the channel firing callbacks 263 // with an error status. 264 intercepted.startSynthesizedResponse(synthesized, null, null, "", false); 265 intercepted.finishSynthesizedResponse(); 266 }); 267 chan.asyncOpen( 268 new ChannelListener( 269 run_next_test, 270 null, 271 CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL 272 ) 273 ); 274 }); 275 276 // Ensure that nsIInterceptedChannel.channelIntercepted() can return an error. 277 // In this case we should automatically ResetInterception() and complete the 278 // network request. 279 add_test(function () { 280 var chan = make_channel(URL + "/body", null, function () { 281 throw new Error("boom"); 282 }); 283 chan.asyncOpen(new ChannelListener(handle_remote_response, null)); 284 }); 285 286 add_test(function () { 287 httpServer.stop(run_next_test); 288 });