summaryrefslogtreecommitdiffstats
path: root/netwerk/test/unit/head_channels.js
diff options
context:
space:
mode:
Diffstat (limited to 'netwerk/test/unit/head_channels.js')
-rw-r--r--netwerk/test/unit/head_channels.js218
1 files changed, 218 insertions, 0 deletions
diff --git a/netwerk/test/unit/head_channels.js b/netwerk/test/unit/head_channels.js
new file mode 100644
index 000000000..5d7171668
--- /dev/null
+++ b/netwerk/test/unit/head_channels.js
@@ -0,0 +1,218 @@
+/**
+ * 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;
+}
+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();
+
+ 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);
+ } 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
+};