Source code
Revision control
Copy as Markdown
Other Tools
"use strict";
// utility functions for worker/window communication
function isInWorker() {
try {
return !(self instanceof Window);
} catch (e) {
return true;
}
}
function message(aData) {
if (isInWorker()) {
self.postMessage(aData);
} else {
self.postMessage(aData, "*");
}
}
message.ping = 0;
message.pong = 0;
function is(aActual, aExpected, aMessage) {
var obj = {
type: "is",
actual: aActual,
expected: aExpected,
message: aMessage,
};
++message.ping;
message(obj);
}
function ok(aBool, aMessage) {
var obj = {
type: "ok",
bool: aBool,
message: aMessage,
};
++message.ping;
message(obj);
}
function info(aMessage) {
var obj = {
type: "info",
message: aMessage,
};
++message.ping;
message(obj);
}
function request(aURL) {
return new Promise(function (aResolve) {
var xhr = new XMLHttpRequest();
xhr.open("GET", aURL);
xhr.addEventListener("load", function () {
xhr.succeeded = true;
aResolve(xhr);
});
xhr.addEventListener("error", function () {
xhr.succeeded = false;
aResolve(xhr);
});
xhr.send();
});
}
function createSequentialRequest(aParameters, aTest) {
var sequence = aParameters.reduce(function (aPromise, aParam) {
return aPromise
.then(function () {
return request(aParam.requestURL);
})
.then(function (aXHR) {
return aTest(aXHR, aParam);
});
}, Promise.resolve());
return sequence;
}
function testSuccessResponse() {
var blob = new Blob(["data"], { type: "text/plain" });
var blobURL = URL.createObjectURL(blob);
var parameters = [
// tests that start with same-origin request
{
message: "request to same-origin without redirect",
requestURL:
responseURL:
},
{
message: "request to same-origin redirect to same-origin URL",
requestURL:
responseURL:
},
{
message:
"request to same-origin redirects several times and finally go to same-origin URL",
requestURL:
encodeURIComponent(
),
responseURL:
},
{
message: "request to same-origin redirect to cross-origin URL",
requestURL:
responseURL:
},
{
message:
"request to same-origin redirects several times and finally go to cross-origin URL",
requestURL:
encodeURIComponent(
),
responseURL:
},
// tests that start with cross-origin request
{
message: "request to cross-origin without redirect",
requestURL:
responseURL:
},
{
message: "request to cross-origin redirect back to same-origin URL",
requestURL:
responseURL:
},
{
message: "request to cross-origin redirect to the same cross-origin URL",
requestURL:
responseURL:
},
{
message: "request to cross-origin redirect to another cross-origin URL",
requestURL:
responseURL:
},
{
message:
"request to cross-origin redirects several times and finally go to same-origin URL",
requestURL:
encodeURIComponent(
),
responseURL:
},
{
message:
"request to cross-origin redirects several times and finally go to the same cross-origin URL",
requestURL:
encodeURIComponent(
),
responseURL:
},
{
message:
"request to cross-origin redirects several times and finally go to another cross-origin URL",
requestURL:
encodeURIComponent(
),
responseURL:
},
{
message:
"request to cross-origin redirects to another cross-origin and finally go to the other cross-origin URL",
requestURL:
encodeURIComponent(
),
responseURL:
},
{
message: "request URL has fragment",
requestURL:
responseURL:
},
// tests for non-http(s) URL
{
message: "request to data: URL",
requestURL: "data:text/plain,data",
responseURL: "data:text/plain,data",
},
{
message: "request to blob: URL",
requestURL: blobURL,
responseURL: blobURL,
},
];
var sequence = createSequentialRequest(parameters, function (aXHR, aParam) {
ok(aXHR.succeeded, "assert request succeeded");
is(aXHR.responseURL, aParam.responseURL, aParam.message);
});
sequence.then(function () {
URL.revokeObjectURL(blobURL);
});
return sequence;
}
function testFailedResponse() {
info("test not to leak responseURL for denied cross-origin request");
var parameters = [
{
message:
"should be empty for denied cross-origin request without redirect",
requestURL:
},
{
message: "should be empty for denied cross-origin request with redirect",
requestURL:
},
];
var sequence = createSequentialRequest(parameters, function (aXHR, aParam) {
ok(!aXHR.succeeded, "assert request failed");
is(aXHR.responseURL, "", aParam.message);
});
return sequence;
}
function testNotToLeakResponseURLWhileDoingRedirects() {
info("test not to leak responeseURL while doing redirects");
if (isInWorker()) {
return testNotToLeakResponseURLWhileDoingRedirectsInWorker();
}
return testNotToLeakResponseURLWhileDoingRedirectsInWindow();
}
function testNotToLeakResponseURLWhileDoingRedirectsInWindow() {
var xhr = new XMLHttpRequest();
var requestObserver = {
observe() {
is(xhr.readyState, XMLHttpRequest.OPENED, "assert for XHR state");
is(
xhr.responseURL,
"",
"responseURL should return empty string before HEADERS_RECEIVED"
);
},
};
SpecialPowers.addObserver(
requestObserver,
"specialpowers-http-notify-request"
);
return new Promise(function (aResolve) {
xhr.open(
"GET",
);
xhr.addEventListener("load", function () {
SpecialPowers.removeObserver(
requestObserver,
"specialpowers-http-notify-request"
);
aResolve();
});
xhr.addEventListener("error", function () {
ok(false, "unexpected request falilure");
SpecialPowers.removeObserver(
requestObserver,
"specialpowers-http-notify-request"
);
aResolve();
});
xhr.send();
});
}
function testNotToLeakResponseURLWhileDoingRedirectsInWorker() {
var xhr = new XMLHttpRequest();
var testRedirect = function (e) {
if (e.data === "request" && xhr.readyState === XMLHttpRequest.OPENED) {
is(
xhr.responseURL,
"",
"responseURL should return empty string before HEADERS_RECEIVED"
);
}
};
return new Promise(function (aResolve) {
self.addEventListener("message", testRedirect);
message({ type: "redirect_test", status: "start" });
xhr.open(
"GET",
);
xhr.addEventListener("load", function () {
self.removeEventListener("message", testRedirect);
message({ type: "redirect_test", status: "end" });
aResolve();
});
xhr.addEventListener("error", function () {
ok(false, "unexpected request falilure");
self.removeEventListener("message", testRedirect);
message({ type: "redirect_test", status: "end" });
aResolve();
});
xhr.send();
});
}
function waitForAllMessagesProcessed() {
return new Promise(function (aResolve) {
var id = setInterval(function () {
if (message.ping === message.pong) {
clearInterval(id);
aResolve();
}
}, 100);
});
}
self.addEventListener("message", function (aEvent) {
if (aEvent.data === "start") {
ok(
"responseURL" in new XMLHttpRequest(),
"XMLHttpRequest should have responseURL attribute"
);
is(
new XMLHttpRequest().responseURL,
"",
"responseURL should be empty string if response's url is null"
);
var promise = testSuccessResponse();
promise
.then(function () {
return testFailedResponse();
})
.then(function () {
return testNotToLeakResponseURLWhileDoingRedirects();
})
.then(function () {
return waitForAllMessagesProcessed();
})
.then(function () {
message("done");
});
}
if (aEvent.data === "pong") {
++message.pong;
}
});