1 // This file ensures that canceling a channel early does not
2 // send the request to the server (bug 350790)
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:
7 // This test also checks that cancelling a channel before asyncOpen, after
8 // onStopRequest, or during onDataAvailable works as expected.
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",
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
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,
44 do_throw("Exception: " + ex);
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);
57 // ENSURE_CALLED_BEFORE_CONNECT: setting referrer should now fail
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(
68 env.set("NECKO_ERRORS_ARE_FATAL", "0");
69 // we expect setting referrer to fail
71 request.referrerInfo = new ReferrerInfo(
72 Ci.nsIReferrerInfo.EMPTY,
76 do_throw("Error should have been thrown before getting here");
79 do_throw("Exception: " + ex);
83 onDataAvailable: function test_ODA() {
84 do_throw("Should not get any data!");
87 onStopRequest: function test_onStopR(request, status) {
92 var cancelDuringOnDataListener = {
95 receivedSomeData: null,
96 onStartRequest: function test_onStartR(request, ctx) {
97 Assert.equal(request.status, Cr.NS_OK);
100 onDataAvailable: function test_ODA(request, stream, offset, count) {
101 let string = NetUtil.readInputStreamToString(stream, count);
102 Assert.ok(!string.includes("b"));
104 this.channel.cancel(Cr.NS_BINDING_ABORTED);
105 if (this.receivedSomeData) {
106 this.receivedSomeData();
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);
117 function makeChan(url) {
118 var chan = NetUtil.newChannel({
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);
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);
138 registerCleanupFunction(async () => {
139 await new Promise(resolve => httpserv.stop(resolve));
143 add_task(async function test_cancel_during_onModifyRequest() {
145 "http://localhost:" + httpserv.identity.primaryPort + "/failtest"
148 if (!inChildProcess()) {
149 Services.obs.addObserver(observer, "http-on-modify-request");
151 do_send_remote_message("register-observer");
152 await do_await_remote_message("register-observer-done");
155 await new Promise(resolve => {
156 cancelDuringOnStartListener.resolved = resolve;
157 chan.asyncOpen(cancelDuringOnStartListener);
160 if (!inChildProcess()) {
161 Services.obs.removeObserver(observer, "http-on-modify-request");
163 do_send_remote_message("unregister-observer");
164 await do_await_remote_message("unregister-observer-done");
168 add_task(async function test_cancel_before_asyncOpen() {
170 "http://localhost:" + httpserv.identity.primaryPort + "/failtest"
173 chan.cancel(Cr.NS_BINDING_ABORTED);
177 chan.asyncOpen(cancelDuringOnStartListener);
179 /NS_BINDING_ABORTED/,
180 "cannot open if already cancelled"
184 add_task(async function test_cancel_during_onData() {
186 "http://localhost:" + httpserv.identity.primaryPort + "/cancel_middle"
189 await new Promise(resolve => {
190 cancelDuringOnDataListener.resolved = resolve;
191 cancelDuringOnDataListener.channel = chan;
192 chan.asyncOpen(cancelDuringOnDataListener);
196 var cancelAfterOnStopListener = {
199 onStartRequest: function test_onStartR(request, ctx) {
200 Assert.equal(request.status, Cr.NS_OK);
203 onDataAvailable: function test_ODA(request, stream, offset, count) {
204 let string = NetUtil.readInputStreamToString(stream, count);
208 onStopRequest: function test_onStopR(request, status) {
209 info("onStopRequest");
210 Assert.equal(request.status, Cr.NS_OK);
215 add_task(async function test_cancel_after_onStop() {
217 "http://localhost:" + httpserv.identity.primaryPort + "/normal_response"
220 await new Promise(resolve => {
221 cancelAfterOnStopListener.resolved = resolve;
222 cancelAfterOnStopListener.channel = chan;
223 chan.asyncOpen(cancelAfterOnStopListener);
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);
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;
252 let str1 = "b".repeat(128 * 1024);
253 response.write(str1, str1.length);
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);