Bug 1796551 [wpt PR 36570] - WebKit export of https://bugs.webkit.org/show_bug.cgi...
[gecko.git] / netwerk / test / unit / test_httpcancel.js
blob21fb64c96dfb5fbb0c290c5d965a307730316769
1 // This file ensures that canceling a channel early does not
2 // send the request to the server (bug 350790)
3 //
4 // I've also shoehorned in a test that ENSURE_CALLED_BEFORE_CONNECT works as
5 // expected: see comments that start with ENSURE_CALLED_BEFORE_CONNECT:
6 //
7 // This test also checks that cancelling a channel before asyncOpen, after
8 // onStopRequest, or during onDataAvailable works as expected.
10 "use strict";
12 const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
13 const reason = "testing";
15 function inChildProcess() {
16   return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
19 var ios = Services.io;
20 var ReferrerInfo = Components.Constructor(
21   "@mozilla.org/referrer-info;1",
22   "nsIReferrerInfo",
23   "init"
25 var observer = {
26   QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
28   observe(subject, topic, data) {
29     subject = subject.QueryInterface(Ci.nsIRequest);
30     subject.cancelWithReason(Cr.NS_BINDING_ABORTED, reason);
32     // ENSURE_CALLED_BEFORE_CONNECT: setting values should still work
33     try {
34       subject.QueryInterface(Ci.nsIHttpChannel);
35       let currentReferrer = subject.getRequestHeader("Referer");
36       Assert.equal(currentReferrer, "http://site1.com/");
37       var uri = ios.newURI("http://site2.com");
38       subject.referrerInfo = new ReferrerInfo(
39         Ci.nsIReferrerInfo.EMPTY,
40         true,
41         uri
42       );
43     } catch (ex) {
44       do_throw("Exception: " + ex);
45     }
46   },
49 let cancelDuringOnStartListener = {
50   onStartRequest: function test_onStartR(request) {
51     Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
52     // We didn't sync the reason to child process.
53     if (!inChildProcess()) {
54       Assert.equal(request.canceledReason, reason);
55     }
57     // ENSURE_CALLED_BEFORE_CONNECT: setting referrer should now fail
58     try {
59       request.QueryInterface(Ci.nsIHttpChannel);
60       let currentReferrer = request.getRequestHeader("Referer");
61       Assert.equal(currentReferrer, "http://site2.com/");
62       var uri = ios.newURI("http://site3.com/");
64       // Need to set NECKO_ERRORS_ARE_FATAL=0 else we'll abort process
65       var env = Cc["@mozilla.org/process/environment;1"].getService(
66         Ci.nsIEnvironment
67       );
68       env.set("NECKO_ERRORS_ARE_FATAL", "0");
69       // we expect setting referrer to fail
70       try {
71         request.referrerInfo = new ReferrerInfo(
72           Ci.nsIReferrerInfo.EMPTY,
73           true,
74           uri
75         );
76         do_throw("Error should have been thrown before getting here");
77       } catch (ex) {}
78     } catch (ex) {
79       do_throw("Exception: " + ex);
80     }
81   },
83   onDataAvailable: function test_ODA() {
84     do_throw("Should not get any data!");
85   },
87   onStopRequest: function test_onStopR(request, status) {
88     this.resolved();
89   },
92 var cancelDuringOnDataListener = {
93   data: "",
94   channel: null,
95   receivedSomeData: null,
96   onStartRequest: function test_onStartR(request, ctx) {
97     Assert.equal(request.status, Cr.NS_OK);
98   },
100   onDataAvailable: function test_ODA(request, stream, offset, count) {
101     let string = NetUtil.readInputStreamToString(stream, count);
102     Assert.ok(!string.includes("b"));
103     this.data += string;
104     this.channel.cancel(Cr.NS_BINDING_ABORTED);
105     if (this.receivedSomeData) {
106       this.receivedSomeData();
107     }
108   },
110   onStopRequest: function test_onStopR(request, ctx, status) {
111     Assert.ok(this.data.includes("a"), `data: ${this.data}`);
112     Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
113     this.resolved();
114   },
117 function makeChan(url) {
118   var chan = NetUtil.newChannel({
119     uri: url,
120     loadUsingSystemPrincipal: true,
121   }).QueryInterface(Ci.nsIHttpChannel);
123   // ENSURE_CALLED_BEFORE_CONNECT: set original value
124   var uri = ios.newURI("http://site1.com");
125   chan.referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
126   return chan;
129 var httpserv = null;
131 add_task(async function setup() {
132   httpserv = new HttpServer();
133   httpserv.registerPathHandler("/failtest", failtest);
134   httpserv.registerPathHandler("/cancel_middle", cancel_middle);
135   httpserv.registerPathHandler("/normal_response", normal_response);
136   httpserv.start(-1);
138   registerCleanupFunction(async () => {
139     await new Promise(resolve => httpserv.stop(resolve));
140   });
143 add_task(async function test_cancel_during_onModifyRequest() {
144   var chan = makeChan(
145     "http://localhost:" + httpserv.identity.primaryPort + "/failtest"
146   );
148   if (!inChildProcess()) {
149     Services.obs.addObserver(observer, "http-on-modify-request");
150   } else {
151     do_send_remote_message("register-observer");
152     await do_await_remote_message("register-observer-done");
153   }
155   await new Promise(resolve => {
156     cancelDuringOnStartListener.resolved = resolve;
157     chan.asyncOpen(cancelDuringOnStartListener);
158   });
160   if (!inChildProcess()) {
161     Services.obs.removeObserver(observer, "http-on-modify-request");
162   } else {
163     do_send_remote_message("unregister-observer");
164     await do_await_remote_message("unregister-observer-done");
165   }
168 add_task(async function test_cancel_before_asyncOpen() {
169   var chan = makeChan(
170     "http://localhost:" + httpserv.identity.primaryPort + "/failtest"
171   );
173   chan.cancel(Cr.NS_BINDING_ABORTED);
175   Assert.throws(
176     () => {
177       chan.asyncOpen(cancelDuringOnStartListener);
178     },
179     /NS_BINDING_ABORTED/,
180     "cannot open if already cancelled"
181   );
184 add_task(async function test_cancel_during_onData() {
185   var chan = makeChan(
186     "http://localhost:" + httpserv.identity.primaryPort + "/cancel_middle"
187   );
189   await new Promise(resolve => {
190     cancelDuringOnDataListener.resolved = resolve;
191     cancelDuringOnDataListener.channel = chan;
192     chan.asyncOpen(cancelDuringOnDataListener);
193   });
196 var cancelAfterOnStopListener = {
197   data: "",
198   channel: null,
199   onStartRequest: function test_onStartR(request, ctx) {
200     Assert.equal(request.status, Cr.NS_OK);
201   },
203   onDataAvailable: function test_ODA(request, stream, offset, count) {
204     let string = NetUtil.readInputStreamToString(stream, count);
205     this.data += string;
206   },
208   onStopRequest: function test_onStopR(request, status) {
209     info("onStopRequest");
210     Assert.equal(request.status, Cr.NS_OK);
211     this.resolved();
212   },
215 add_task(async function test_cancel_after_onStop() {
216   var chan = makeChan(
217     "http://localhost:" + httpserv.identity.primaryPort + "/normal_response"
218   );
220   await new Promise(resolve => {
221     cancelAfterOnStopListener.resolved = resolve;
222     cancelAfterOnStopListener.channel = chan;
223     chan.asyncOpen(cancelAfterOnStopListener);
224   });
225   Assert.equal(chan.status, Cr.NS_OK);
227   // For now it's unclear if cancelling after onStop should throw,
228   // silently fail, or overwrite the channel's status as we currently do.
229   // See discussion in bug 1553083
230   chan.cancel(Cr.NS_BINDING_ABORTED);
231   Assert.equal(chan.status, Cr.NS_BINDING_ABORTED);
234 // PATHS
236 // /failtest
237 function failtest(metadata, response) {
238   do_throw("This should not be reached");
241 function cancel_middle(metadata, response) {
242   response.processAsync();
243   response.setStatusLine(metadata.httpVersion, 200, "OK");
244   let str1 = "a".repeat(128 * 1024);
245   response.write(str1, str1.length);
246   response.bodyOutputStream.flush();
248   let p = new Promise(resolve => {
249     cancelDuringOnDataListener.receivedSomeData = resolve;
250   });
251   p.then(() => {
252     let str1 = "b".repeat(128 * 1024);
253     response.write(str1, str1.length);
254     response.finish();
255   });
258 function normal_response(metadata, response) {
259   response.setStatusLine(metadata.httpVersion, 200, "OK");
260   let str1 = "Is this normal?";
261   response.write(str1, str1.length);