michael@0: /* michael@0: * This file is a modified version of netwerk/test/unit/head_channels.js michael@0: * The changes consist of making it work in mochitest files and removing michael@0: * unused code. michael@0: */ michael@0: michael@0: /** michael@0: * Read count bytes from stream and return as a String object michael@0: */ michael@0: function read_stream(stream, count) { michael@0: /* assume stream has non-ASCII data */ michael@0: var wrapper = michael@0: Components.classes["@mozilla.org/binaryinputstream;1"] michael@0: .createInstance(Components.interfaces.nsIBinaryInputStream); michael@0: wrapper.setInputStream(stream); michael@0: /* JS methods can be called with a maximum of 65535 arguments, and input michael@0: streams don't have to return all the data they make .available() when michael@0: asked to .read() that number of bytes. */ michael@0: var data = []; michael@0: while (count > 0) { michael@0: var bytes = wrapper.readByteArray(Math.min(65535, count)); michael@0: data.push(String.fromCharCode.apply(null, bytes)); michael@0: count -= bytes.length; michael@0: if (bytes.length == 0) michael@0: throw("Nothing read from input stream!"); michael@0: } michael@0: return data.join(''); michael@0: } michael@0: michael@0: const CL_EXPECT_FAILURE = 0x1; michael@0: const CL_EXPECT_GZIP = 0x2; michael@0: const CL_EXPECT_3S_DELAY = 0x4; michael@0: const CL_SUSPEND = 0x8; michael@0: const CL_ALLOW_UNKNOWN_CL = 0x10; michael@0: const CL_EXPECT_LATE_FAILURE = 0x20; michael@0: michael@0: const SUSPEND_DELAY = 3000; michael@0: michael@0: /** michael@0: * A stream listener that calls a callback function with a specified michael@0: * context and the received data when the channel is loaded. michael@0: * michael@0: * Signature of the closure: michael@0: * void closure(in nsIRequest request, in ACString data, in JSObject context); michael@0: * michael@0: * This listener makes sure that various parts of the channel API are michael@0: * implemented correctly and that the channel's status is a success code michael@0: * (you can pass CL_EXPECT_FAILURE or CL_EXPECT_LATE_FAILURE as flags michael@0: * to allow a failure code) michael@0: * michael@0: * Note that it also requires a valid content length on the channel and michael@0: * is thus not fully generic. michael@0: */ michael@0: function ChannelListener(closure, ctx, flags) { michael@0: this._closure = closure; michael@0: this._closurectx = ctx; michael@0: this._flags = flags; michael@0: } michael@0: ChannelListener.prototype = { michael@0: _closure: null, michael@0: _closurectx: null, michael@0: _buffer: "", michael@0: _got_onstartrequest: false, michael@0: _got_onstoprequest: false, michael@0: _contentLen: -1, michael@0: _lastEvent: 0, michael@0: michael@0: QueryInterface: function(iid) { michael@0: if (iid.equals(Components.interfaces.nsIStreamListener) || michael@0: iid.equals(Components.interfaces.nsIRequestObserver) || michael@0: iid.equals(Components.interfaces.nsISupports)) michael@0: return this; michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: michael@0: onStartRequest: function(request, context) { michael@0: try { michael@0: if (this._got_onstartrequest) michael@0: throw("Got second onStartRequest event!"); michael@0: this._got_onstartrequest = true; michael@0: this._lastEvent = Date.now(); michael@0: michael@0: request.QueryInterface(Components.interfaces.nsIChannel); michael@0: try { michael@0: this._contentLen = request.contentLength; michael@0: } michael@0: catch (ex) { michael@0: if (!(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL))) michael@0: throw("Could not get contentLength"); michael@0: } michael@0: if (this._contentLen == -1 && !(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL))) michael@0: throw("Content length is unknown in onStartRequest!"); michael@0: michael@0: if (this._flags & CL_SUSPEND) { michael@0: request.suspend(); michael@0: do_timeout(SUSPEND_DELAY, function() { request.resume(); }); michael@0: } michael@0: michael@0: } catch (ex) { michael@0: throw("Error in onStartRequest: " + ex); michael@0: } michael@0: }, michael@0: michael@0: onDataAvailable: function(request, context, stream, offset, count) { michael@0: try { michael@0: var current = Date.now(); michael@0: michael@0: if (!this._got_onstartrequest) michael@0: throw("onDataAvailable without onStartRequest event!"); michael@0: if (this._got_onstoprequest) michael@0: throw("onDataAvailable after onStopRequest event!"); michael@0: if (!request.isPending()) michael@0: throw("request reports itself as not pending from onDataAvailable!"); michael@0: if (this._flags & CL_EXPECT_FAILURE) michael@0: throw("Got data despite expecting a failure"); michael@0: michael@0: if (current - this._lastEvent >= SUSPEND_DELAY && michael@0: !(this._flags & CL_EXPECT_3S_DELAY)) michael@0: throw("Data received after significant unexpected delay"); michael@0: else if (current - this._lastEvent < SUSPEND_DELAY && michael@0: this._flags & CL_EXPECT_3S_DELAY) michael@0: throw("Data received sooner than expected"); michael@0: else if (current - this._lastEvent >= SUSPEND_DELAY && michael@0: this._flags & CL_EXPECT_3S_DELAY) michael@0: this._flags &= ~CL_EXPECT_3S_DELAY; // No more delays expected michael@0: michael@0: this._buffer = this._buffer.concat(read_stream(stream, count)); michael@0: this._lastEvent = current; michael@0: } catch (ex) { michael@0: throw("Error in onDataAvailable: " + ex); michael@0: } michael@0: }, michael@0: michael@0: onStopRequest: function(request, context, status) { michael@0: try { michael@0: var success = Components.isSuccessCode(status); michael@0: if (!this._got_onstartrequest) michael@0: throw("onStopRequest without onStartRequest event!"); michael@0: if (this._got_onstoprequest) michael@0: throw("Got second onStopRequest event!"); michael@0: this._got_onstoprequest = true; michael@0: if ((this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && success) michael@0: throw("Should have failed to load URL (status is " + status.toString(16) + ")"); michael@0: else if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && !success) michael@0: throw("Failed to load URL: " + status.toString(16)); michael@0: if (status != request.status) michael@0: throw("request.status does not match status arg to onStopRequest!"); michael@0: if (request.isPending()) michael@0: throw("request reports itself as pending from onStopRequest!"); michael@0: if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && michael@0: !(this._flags & CL_EXPECT_GZIP) && michael@0: this._contentLen != -1) michael@0: is(this._buffer.length, this._contentLen); michael@0: } catch (ex) { michael@0: throw("Error in onStopRequest: " + ex); michael@0: } michael@0: try { michael@0: this._closure(request, this._buffer, this._closurectx); michael@0: } catch (ex) { michael@0: throw("Error in closure function: " + ex); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: /** michael@0: * Class that implements nsILoadContext. Use it as callbacks for channel when michael@0: * test needs it. michael@0: */ michael@0: function LoadContextCallback(appId, inBrowserElement, isPrivate, isContent) { michael@0: this.appId = appId; michael@0: this.isInBrowserElement = inBrowserElement; michael@0: this.usePrivateBrowsing = isPrivate; michael@0: this.isContent = isContent; michael@0: } michael@0: michael@0: LoadContextCallback.prototype = { michael@0: associatedWindow: null, michael@0: topWindow : null, michael@0: isAppOfType: function(appType) { michael@0: throw Components.results.NS_ERROR_NOT_IMPLEMENTED; michael@0: }, michael@0: QueryInterface: function(iid) { michael@0: if (iid == Ci.nsILoadContext || michael@0: Ci.nsIInterfaceRequestor || michael@0: Ci.nsISupports) { michael@0: return this; michael@0: } michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: getInterface: function(iid) { michael@0: if (iid.equals(Ci.nsILoadContext)) michael@0: return this; michael@0: throw Components.results.NS_ERROR_NO_INTERFACE; michael@0: }, michael@0: }