261 lines
7.3 KiB
JavaScript
261 lines
7.3 KiB
JavaScript
// This file ensures that canceling a channel early does not
|
|
// send the request to the server (bug 350790)
|
|
//
|
|
// I've also shoehorned in a test that ENSURE_CALLED_BEFORE_CONNECT works as
|
|
// expected: see comments that start with ENSURE_CALLED_BEFORE_CONNECT:
|
|
//
|
|
// This test also checks that cancelling a channel before asyncOpen, after
|
|
// onStopRequest, or during onDataAvailable works as expected.
|
|
|
|
"use strict";
|
|
|
|
const { HttpServer } = ChromeUtils.importESModule(
|
|
"resource://testing-common/httpd.sys.mjs"
|
|
);
|
|
const reason = "testing";
|
|
|
|
function inChildProcess() {
|
|
return Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
|
}
|
|
|
|
var ios = Services.io;
|
|
var ReferrerInfo = Components.Constructor(
|
|
"@mozilla.org/referrer-info;1",
|
|
"nsIReferrerInfo",
|
|
"init"
|
|
);
|
|
var observer = {
|
|
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
|
|
|
|
observe(subject) {
|
|
subject = subject.QueryInterface(Ci.nsIRequest);
|
|
subject.cancelWithReason(Cr.NS_BINDING_ABORTED, reason);
|
|
|
|
// ENSURE_CALLED_BEFORE_CONNECT: setting values should still work
|
|
try {
|
|
subject.QueryInterface(Ci.nsIHttpChannel);
|
|
let currentReferrer = subject.getRequestHeader("Referer");
|
|
Assert.equal(currentReferrer, "http://site1.com/");
|
|
var uri = ios.newURI("http://site2.com");
|
|
subject.referrerInfo = new ReferrerInfo(
|
|
Ci.nsIReferrerInfo.EMPTY,
|
|
true,
|
|
uri
|
|
);
|
|
} catch (ex) {
|
|
do_throw("Exception: " + ex);
|
|
}
|
|
},
|
|
};
|
|
|
|
let cancelDuringOnStartListener = {
|
|
onStartRequest: function test_onStartR(request) {
|
|
Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
|
|
// We didn't sync the reason to child process.
|
|
if (!inChildProcess()) {
|
|
Assert.equal(request.canceledReason, reason);
|
|
}
|
|
|
|
// ENSURE_CALLED_BEFORE_CONNECT: setting referrer should now fail
|
|
try {
|
|
request.QueryInterface(Ci.nsIHttpChannel);
|
|
let currentReferrer = request.getRequestHeader("Referer");
|
|
Assert.equal(currentReferrer, "http://site2.com/");
|
|
var uri = ios.newURI("http://site3.com/");
|
|
|
|
// Need to set NECKO_ERRORS_ARE_FATAL=0 else we'll abort process
|
|
Services.env.set("NECKO_ERRORS_ARE_FATAL", "0");
|
|
// we expect setting referrer to fail
|
|
try {
|
|
request.referrerInfo = new ReferrerInfo(
|
|
Ci.nsIReferrerInfo.EMPTY,
|
|
true,
|
|
uri
|
|
);
|
|
do_throw("Error should have been thrown before getting here");
|
|
} catch (ex) {}
|
|
} catch (ex) {
|
|
do_throw("Exception: " + ex);
|
|
}
|
|
},
|
|
|
|
onDataAvailable: function test_ODA() {
|
|
do_throw("Should not get any data!");
|
|
},
|
|
|
|
onStopRequest: function test_onStopR() {
|
|
this.resolved();
|
|
},
|
|
};
|
|
|
|
var cancelDuringOnDataListener = {
|
|
data: "",
|
|
channel: null,
|
|
receivedSomeData: null,
|
|
onStartRequest: function test_onStartR(request) {
|
|
Assert.equal(request.status, Cr.NS_OK);
|
|
},
|
|
|
|
onDataAvailable: function test_ODA(request, stream, offset, count) {
|
|
let string = NetUtil.readInputStreamToString(stream, count);
|
|
Assert.ok(!string.includes("b"));
|
|
this.data += string;
|
|
this.channel.cancel(Cr.NS_BINDING_ABORTED);
|
|
if (this.receivedSomeData) {
|
|
this.receivedSomeData();
|
|
}
|
|
},
|
|
|
|
onStopRequest: function test_onStopR(request) {
|
|
Assert.ok(this.data.includes("a"), `data: ${this.data}`);
|
|
Assert.equal(request.status, Cr.NS_BINDING_ABORTED);
|
|
this.resolved();
|
|
},
|
|
};
|
|
|
|
function makeChan(url) {
|
|
var chan = NetUtil.newChannel({
|
|
uri: url,
|
|
loadUsingSystemPrincipal: true,
|
|
}).QueryInterface(Ci.nsIHttpChannel);
|
|
|
|
// ENSURE_CALLED_BEFORE_CONNECT: set original value
|
|
var uri = ios.newURI("http://site1.com");
|
|
chan.referrerInfo = new ReferrerInfo(Ci.nsIReferrerInfo.EMPTY, true, uri);
|
|
return chan;
|
|
}
|
|
|
|
var httpserv = null;
|
|
|
|
add_task(async function setup() {
|
|
httpserv = new HttpServer();
|
|
httpserv.registerPathHandler("/failtest", failtest);
|
|
httpserv.registerPathHandler("/cancel_middle", cancel_middle);
|
|
httpserv.registerPathHandler("/normal_response", normal_response);
|
|
httpserv.start(-1);
|
|
|
|
registerCleanupFunction(async () => {
|
|
await new Promise(resolve => httpserv.stop(resolve));
|
|
});
|
|
});
|
|
|
|
add_task(async function test_cancel_during_onModifyRequest() {
|
|
var chan = makeChan(
|
|
"http://localhost:" + httpserv.identity.primaryPort + "/failtest"
|
|
);
|
|
|
|
if (!inChildProcess()) {
|
|
Services.obs.addObserver(observer, "http-on-modify-request");
|
|
} else {
|
|
do_send_remote_message("register-observer");
|
|
await do_await_remote_message("register-observer-done");
|
|
}
|
|
|
|
await new Promise(resolve => {
|
|
cancelDuringOnStartListener.resolved = resolve;
|
|
chan.asyncOpen(cancelDuringOnStartListener);
|
|
});
|
|
|
|
if (!inChildProcess()) {
|
|
Services.obs.removeObserver(observer, "http-on-modify-request");
|
|
} else {
|
|
do_send_remote_message("unregister-observer");
|
|
await do_await_remote_message("unregister-observer-done");
|
|
}
|
|
});
|
|
|
|
add_task(async function test_cancel_before_asyncOpen() {
|
|
var chan = makeChan(
|
|
"http://localhost:" + httpserv.identity.primaryPort + "/failtest"
|
|
);
|
|
|
|
chan.cancel(Cr.NS_BINDING_ABORTED);
|
|
|
|
Assert.throws(
|
|
() => {
|
|
chan.asyncOpen(cancelDuringOnStartListener);
|
|
},
|
|
/NS_BINDING_ABORTED/,
|
|
"cannot open if already cancelled"
|
|
);
|
|
});
|
|
|
|
add_task(async function test_cancel_during_onData() {
|
|
var chan = makeChan(
|
|
"http://localhost:" + httpserv.identity.primaryPort + "/cancel_middle"
|
|
);
|
|
|
|
await new Promise(resolve => {
|
|
cancelDuringOnDataListener.resolved = resolve;
|
|
cancelDuringOnDataListener.channel = chan;
|
|
chan.asyncOpen(cancelDuringOnDataListener);
|
|
});
|
|
});
|
|
|
|
var cancelAfterOnStopListener = {
|
|
data: "",
|
|
channel: null,
|
|
onStartRequest: function test_onStartR(request) {
|
|
Assert.equal(request.status, Cr.NS_OK);
|
|
},
|
|
|
|
onDataAvailable: function test_ODA(request, stream, offset, count) {
|
|
let string = NetUtil.readInputStreamToString(stream, count);
|
|
this.data += string;
|
|
},
|
|
|
|
onStopRequest: function test_onStopR(request) {
|
|
info("onStopRequest");
|
|
Assert.equal(request.status, Cr.NS_OK);
|
|
this.resolved();
|
|
},
|
|
};
|
|
|
|
add_task(async function test_cancel_after_onStop() {
|
|
var chan = makeChan(
|
|
"http://localhost:" + httpserv.identity.primaryPort + "/normal_response"
|
|
);
|
|
|
|
await new Promise(resolve => {
|
|
cancelAfterOnStopListener.resolved = resolve;
|
|
cancelAfterOnStopListener.channel = chan;
|
|
chan.asyncOpen(cancelAfterOnStopListener);
|
|
});
|
|
Assert.equal(chan.status, Cr.NS_OK);
|
|
|
|
// For now it's unclear if cancelling after onStop should throw,
|
|
// silently fail, or overwrite the channel's status as we currently do.
|
|
// See discussion in bug 1553083
|
|
chan.cancel(Cr.NS_BINDING_ABORTED);
|
|
Assert.equal(chan.status, Cr.NS_BINDING_ABORTED);
|
|
});
|
|
|
|
// PATHS
|
|
|
|
// /failtest
|
|
function failtest() {
|
|
do_throw("This should not be reached");
|
|
}
|
|
|
|
function cancel_middle(metadata, response) {
|
|
response.processAsync();
|
|
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
|
let str1 = "a".repeat(128 * 1024);
|
|
response.write(str1, str1.length);
|
|
response.bodyOutputStream.flush();
|
|
|
|
let p = new Promise(resolve => {
|
|
cancelDuringOnDataListener.receivedSomeData = resolve;
|
|
});
|
|
p.then(() => {
|
|
let str2 = "b".repeat(128 * 1024);
|
|
response.write(str2, str2.length);
|
|
response.finish();
|
|
});
|
|
}
|
|
|
|
function normal_response(metadata, response) {
|
|
response.setStatusLine(metadata.httpVersion, 200, "OK");
|
|
let str1 = "Is this normal?";
|
|
response.write(str1, str1.length);
|
|
}
|