mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-30 21:55:31 +00:00
6ea6d93279
MozReview-Commit-ID: LmIK9RiKsKp --HG-- extra : rebase_source : f908b168eb76781e3f398efed9b111a0aca63c29
223 lines
7.5 KiB
JavaScript
223 lines
7.5 KiB
JavaScript
/**
|
|
* Read count bytes from stream and return as a String object
|
|
*/
|
|
function read_stream(stream, count) {
|
|
/* assume stream has non-ASCII data */
|
|
var wrapper =
|
|
Components.classes["@mozilla.org/binaryinputstream;1"]
|
|
.createInstance(Components.interfaces.nsIBinaryInputStream);
|
|
wrapper.setInputStream(stream);
|
|
/* JS methods can be called with a maximum of 65535 arguments, and input
|
|
streams don't have to return all the data they make .available() when
|
|
asked to .read() that number of bytes. */
|
|
var data = [];
|
|
while (count > 0) {
|
|
var bytes = wrapper.readByteArray(Math.min(65535, count));
|
|
data.push(String.fromCharCode.apply(null, bytes));
|
|
count -= bytes.length;
|
|
if (bytes.length == 0)
|
|
do_throw("Nothing read from input stream!");
|
|
}
|
|
return data.join('');
|
|
}
|
|
|
|
const CL_EXPECT_FAILURE = 0x1;
|
|
const CL_EXPECT_GZIP = 0x2;
|
|
const CL_EXPECT_3S_DELAY = 0x4;
|
|
const CL_SUSPEND = 0x8;
|
|
const CL_ALLOW_UNKNOWN_CL = 0x10;
|
|
const CL_EXPECT_LATE_FAILURE = 0x20;
|
|
const CL_FROM_CACHE = 0x40; // Response must be from the cache
|
|
const CL_NOT_FROM_CACHE = 0x80; // Response must NOT be from the cache
|
|
const CL_IGNORE_CL = 0x100; // don't bother to verify the content-length
|
|
|
|
const SUSPEND_DELAY = 3000;
|
|
|
|
/**
|
|
* A stream listener that calls a callback function with a specified
|
|
* context and the received data when the channel is loaded.
|
|
*
|
|
* Signature of the closure:
|
|
* void closure(in nsIRequest request, in ACString data, in JSObject context);
|
|
*
|
|
* This listener makes sure that various parts of the channel API are
|
|
* implemented correctly and that the channel's status is a success code
|
|
* (you can pass CL_EXPECT_FAILURE or CL_EXPECT_LATE_FAILURE as flags
|
|
* to allow a failure code)
|
|
*
|
|
* Note that it also requires a valid content length on the channel and
|
|
* is thus not fully generic.
|
|
*/
|
|
function ChannelListener(closure, ctx, flags) {
|
|
this._closure = closure;
|
|
this._closurectx = ctx;
|
|
this._flags = flags;
|
|
this._isFromCache = false;
|
|
}
|
|
ChannelListener.prototype = {
|
|
_closure: null,
|
|
_closurectx: null,
|
|
_buffer: "",
|
|
_got_onstartrequest: false,
|
|
_got_onstoprequest: false,
|
|
_contentLen: -1,
|
|
_lastEvent: 0,
|
|
|
|
QueryInterface: function(iid) {
|
|
if (iid.equals(Components.interfaces.nsIStreamListener) ||
|
|
iid.equals(Components.interfaces.nsIRequestObserver) ||
|
|
iid.equals(Components.interfaces.nsISupports))
|
|
return this;
|
|
throw Components.results.NS_ERROR_NO_INTERFACE;
|
|
},
|
|
|
|
onStartRequest: function(request, context) {
|
|
try {
|
|
if (this._got_onstartrequest)
|
|
do_throw("Got second onStartRequest event!");
|
|
this._got_onstartrequest = true;
|
|
this._lastEvent = Date.now();
|
|
|
|
try {
|
|
this._isFromCache = request.QueryInterface(Ci.nsICachingChannel).isFromCache();
|
|
} catch (e) {}
|
|
|
|
request.QueryInterface(Components.interfaces.nsIChannel);
|
|
try {
|
|
this._contentLen = request.contentLength;
|
|
}
|
|
catch (ex) {
|
|
if (!(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL)))
|
|
do_throw("Could not get contentLength");
|
|
}
|
|
if (!request.isPending())
|
|
do_throw("request reports itself as not pending from onStartRequest!");
|
|
if (this._contentLen == -1 && !(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL)))
|
|
do_throw("Content length is unknown in onStartRequest!");
|
|
|
|
if ((this._flags & CL_FROM_CACHE)) {
|
|
request.QueryInterface(Ci.nsICachingChannel);
|
|
if (!request.isFromCache()) {
|
|
do_throw("Response is not from the cache (CL_FROM_CACHE)");
|
|
}
|
|
}
|
|
if ((this._flags & CL_NOT_FROM_CACHE)) {
|
|
request.QueryInterface(Ci.nsICachingChannel);
|
|
if (request.isFromCache()) {
|
|
do_throw("Response is from the cache (CL_NOT_FROM_CACHE)");
|
|
}
|
|
}
|
|
|
|
if (this._flags & CL_SUSPEND) {
|
|
request.suspend();
|
|
do_timeout(SUSPEND_DELAY, function() { request.resume(); });
|
|
}
|
|
} catch (ex) {
|
|
do_throw("Error in onStartRequest: " + ex);
|
|
}
|
|
},
|
|
|
|
onDataAvailable: function(request, context, stream, offset, count) {
|
|
try {
|
|
let current = Date.now();
|
|
|
|
if (!this._got_onstartrequest)
|
|
do_throw("onDataAvailable without onStartRequest event!");
|
|
if (this._got_onstoprequest)
|
|
do_throw("onDataAvailable after onStopRequest event!");
|
|
if (!request.isPending())
|
|
do_throw("request reports itself as not pending from onDataAvailable!");
|
|
if (this._flags & CL_EXPECT_FAILURE)
|
|
do_throw("Got data despite expecting a failure");
|
|
|
|
if (current - this._lastEvent >= SUSPEND_DELAY &&
|
|
!(this._flags & CL_EXPECT_3S_DELAY))
|
|
do_throw("Data received after significant unexpected delay");
|
|
else if (current - this._lastEvent < SUSPEND_DELAY &&
|
|
this._flags & CL_EXPECT_3S_DELAY)
|
|
do_throw("Data received sooner than expected");
|
|
else if (current - this._lastEvent >= SUSPEND_DELAY &&
|
|
this._flags & CL_EXPECT_3S_DELAY)
|
|
this._flags &= ~CL_EXPECT_3S_DELAY; // No more delays expected
|
|
|
|
this._buffer = this._buffer.concat(read_stream(stream, count));
|
|
this._lastEvent = current;
|
|
} catch (ex) {
|
|
do_throw("Error in onDataAvailable: " + ex);
|
|
}
|
|
},
|
|
|
|
onStopRequest: function(request, context, status) {
|
|
try {
|
|
var success = Components.isSuccessCode(status);
|
|
if (!this._got_onstartrequest)
|
|
do_throw("onStopRequest without onStartRequest event!");
|
|
if (this._got_onstoprequest)
|
|
do_throw("Got second onStopRequest event!");
|
|
this._got_onstoprequest = true;
|
|
if ((this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && success)
|
|
do_throw("Should have failed to load URL (status is " + status.toString(16) + ")");
|
|
else if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && !success)
|
|
do_throw("Failed to load URL: " + status.toString(16));
|
|
if (status != request.status)
|
|
do_throw("request.status does not match status arg to onStopRequest!");
|
|
if (request.isPending())
|
|
do_throw("request reports itself as pending from onStopRequest!");
|
|
if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE | CL_IGNORE_CL)) &&
|
|
!(this._flags & CL_EXPECT_GZIP) &&
|
|
this._contentLen != -1)
|
|
do_check_eq(this._buffer.length, this._contentLen)
|
|
} catch (ex) {
|
|
do_throw("Error in onStopRequest: " + ex);
|
|
}
|
|
try {
|
|
this._closure(request, this._buffer, this._closurectx, this._isFromCache);
|
|
} catch (ex) {
|
|
do_throw("Error in closure function: " + ex);
|
|
}
|
|
}
|
|
};
|
|
|
|
var ES_ABORT_REDIRECT = 0x01;
|
|
|
|
function ChannelEventSink(flags)
|
|
{
|
|
this._flags = flags;
|
|
}
|
|
|
|
ChannelEventSink.prototype = {
|
|
QueryInterface: function(iid) {
|
|
if (iid.equals(Ci.nsIInterfaceRequestor) ||
|
|
iid.equals(Ci.nsISupports))
|
|
return this;
|
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
|
},
|
|
|
|
getInterface: function(iid) {
|
|
if (iid.equals(Ci.nsIChannelEventSink))
|
|
return this;
|
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
|
},
|
|
|
|
asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) {
|
|
if (this._flags & ES_ABORT_REDIRECT)
|
|
throw Cr.NS_BINDING_ABORTED;
|
|
|
|
callback.onRedirectVerifyCallback(Cr.NS_OK);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A helper class to construct origin attributes.
|
|
*/
|
|
function OriginAttributes(appId, inIsolatedMozBrowser, privateId) {
|
|
this.appId = appId;
|
|
this.inIsolatedMozBrowser = inIsolatedMozBrowser;
|
|
this.privateBrowsingId = privateId;
|
|
}
|
|
OriginAttributes.prototype = {
|
|
appId: 0,
|
|
inIsolatedMozBrowser: false,
|
|
privateBrowsingId: 0
|
|
};
|