Thu, 15 Jan 2015 21:03:48 +0100
Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)
michael@0 | 1 | /** |
michael@0 | 2 | * Read count bytes from stream and return as a String object |
michael@0 | 3 | */ |
michael@0 | 4 | function read_stream(stream, count) { |
michael@0 | 5 | /* assume stream has non-ASCII data */ |
michael@0 | 6 | var wrapper = |
michael@0 | 7 | Components.classes["@mozilla.org/binaryinputstream;1"] |
michael@0 | 8 | .createInstance(Components.interfaces.nsIBinaryInputStream); |
michael@0 | 9 | wrapper.setInputStream(stream); |
michael@0 | 10 | /* JS methods can be called with a maximum of 65535 arguments, and input |
michael@0 | 11 | streams don't have to return all the data they make .available() when |
michael@0 | 12 | asked to .read() that number of bytes. */ |
michael@0 | 13 | var data = []; |
michael@0 | 14 | while (count > 0) { |
michael@0 | 15 | var bytes = wrapper.readByteArray(Math.min(65535, count)); |
michael@0 | 16 | data.push(String.fromCharCode.apply(null, bytes)); |
michael@0 | 17 | count -= bytes.length; |
michael@0 | 18 | if (bytes.length == 0) |
michael@0 | 19 | do_throw("Nothing read from input stream!"); |
michael@0 | 20 | } |
michael@0 | 21 | return data.join(''); |
michael@0 | 22 | } |
michael@0 | 23 | |
michael@0 | 24 | const CL_EXPECT_FAILURE = 0x1; |
michael@0 | 25 | const CL_EXPECT_GZIP = 0x2; |
michael@0 | 26 | const CL_EXPECT_3S_DELAY = 0x4; |
michael@0 | 27 | const CL_SUSPEND = 0x8; |
michael@0 | 28 | const CL_ALLOW_UNKNOWN_CL = 0x10; |
michael@0 | 29 | const CL_EXPECT_LATE_FAILURE = 0x20; |
michael@0 | 30 | const CL_FROM_CACHE = 0x40; // Response must be from the cache |
michael@0 | 31 | const CL_NOT_FROM_CACHE = 0x80; // Response must NOT be from the cache |
michael@0 | 32 | |
michael@0 | 33 | const SUSPEND_DELAY = 3000; |
michael@0 | 34 | |
michael@0 | 35 | /** |
michael@0 | 36 | * A stream listener that calls a callback function with a specified |
michael@0 | 37 | * context and the received data when the channel is loaded. |
michael@0 | 38 | * |
michael@0 | 39 | * Signature of the closure: |
michael@0 | 40 | * void closure(in nsIRequest request, in ACString data, in JSObject context); |
michael@0 | 41 | * |
michael@0 | 42 | * This listener makes sure that various parts of the channel API are |
michael@0 | 43 | * implemented correctly and that the channel's status is a success code |
michael@0 | 44 | * (you can pass CL_EXPECT_FAILURE or CL_EXPECT_LATE_FAILURE as flags |
michael@0 | 45 | * to allow a failure code) |
michael@0 | 46 | * |
michael@0 | 47 | * Note that it also requires a valid content length on the channel and |
michael@0 | 48 | * is thus not fully generic. |
michael@0 | 49 | */ |
michael@0 | 50 | function ChannelListener(closure, ctx, flags) { |
michael@0 | 51 | this._closure = closure; |
michael@0 | 52 | this._closurectx = ctx; |
michael@0 | 53 | this._flags = flags; |
michael@0 | 54 | } |
michael@0 | 55 | ChannelListener.prototype = { |
michael@0 | 56 | _closure: null, |
michael@0 | 57 | _closurectx: null, |
michael@0 | 58 | _buffer: "", |
michael@0 | 59 | _got_onstartrequest: false, |
michael@0 | 60 | _got_onstoprequest: false, |
michael@0 | 61 | _contentLen: -1, |
michael@0 | 62 | _lastEvent: 0, |
michael@0 | 63 | |
michael@0 | 64 | QueryInterface: function(iid) { |
michael@0 | 65 | if (iid.equals(Components.interfaces.nsIStreamListener) || |
michael@0 | 66 | iid.equals(Components.interfaces.nsIRequestObserver) || |
michael@0 | 67 | iid.equals(Components.interfaces.nsISupports)) |
michael@0 | 68 | return this; |
michael@0 | 69 | throw Components.results.NS_ERROR_NO_INTERFACE; |
michael@0 | 70 | }, |
michael@0 | 71 | |
michael@0 | 72 | onStartRequest: function(request, context) { |
michael@0 | 73 | try { |
michael@0 | 74 | if (this._got_onstartrequest) |
michael@0 | 75 | do_throw("Got second onStartRequest event!"); |
michael@0 | 76 | this._got_onstartrequest = true; |
michael@0 | 77 | this._lastEvent = Date.now(); |
michael@0 | 78 | |
michael@0 | 79 | request.QueryInterface(Components.interfaces.nsIChannel); |
michael@0 | 80 | try { |
michael@0 | 81 | this._contentLen = request.contentLength; |
michael@0 | 82 | } |
michael@0 | 83 | catch (ex) { |
michael@0 | 84 | if (!(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL))) |
michael@0 | 85 | do_throw("Could not get contentLength"); |
michael@0 | 86 | } |
michael@0 | 87 | if (this._contentLen == -1 && !(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL))) |
michael@0 | 88 | do_throw("Content length is unknown in onStartRequest!"); |
michael@0 | 89 | |
michael@0 | 90 | if ((this._flags & CL_FROM_CACHE)) { |
michael@0 | 91 | request.QueryInterface(Ci.nsICachingChannel); |
michael@0 | 92 | if (!request.isFromCache()) { |
michael@0 | 93 | do_throw("Response is not from the cache (CL_FROM_CACHE)"); |
michael@0 | 94 | } |
michael@0 | 95 | } |
michael@0 | 96 | if ((this._flags & CL_NOT_FROM_CACHE)) { |
michael@0 | 97 | request.QueryInterface(Ci.nsICachingChannel); |
michael@0 | 98 | if (request.isFromCache()) { |
michael@0 | 99 | do_throw("Response is from the cache (CL_NOT_FROM_CACHE)"); |
michael@0 | 100 | } |
michael@0 | 101 | } |
michael@0 | 102 | |
michael@0 | 103 | if (this._flags & CL_SUSPEND) { |
michael@0 | 104 | request.suspend(); |
michael@0 | 105 | do_timeout(SUSPEND_DELAY, function() { request.resume(); }); |
michael@0 | 106 | } |
michael@0 | 107 | |
michael@0 | 108 | } catch (ex) { |
michael@0 | 109 | do_throw("Error in onStartRequest: " + ex); |
michael@0 | 110 | } |
michael@0 | 111 | }, |
michael@0 | 112 | |
michael@0 | 113 | onDataAvailable: function(request, context, stream, offset, count) { |
michael@0 | 114 | try { |
michael@0 | 115 | let current = Date.now(); |
michael@0 | 116 | |
michael@0 | 117 | if (!this._got_onstartrequest) |
michael@0 | 118 | do_throw("onDataAvailable without onStartRequest event!"); |
michael@0 | 119 | if (this._got_onstoprequest) |
michael@0 | 120 | do_throw("onDataAvailable after onStopRequest event!"); |
michael@0 | 121 | if (!request.isPending()) |
michael@0 | 122 | do_throw("request reports itself as not pending from onDataAvailable!"); |
michael@0 | 123 | if (this._flags & CL_EXPECT_FAILURE) |
michael@0 | 124 | do_throw("Got data despite expecting a failure"); |
michael@0 | 125 | |
michael@0 | 126 | if (current - this._lastEvent >= SUSPEND_DELAY && |
michael@0 | 127 | !(this._flags & CL_EXPECT_3S_DELAY)) |
michael@0 | 128 | do_throw("Data received after significant unexpected delay"); |
michael@0 | 129 | else if (current - this._lastEvent < SUSPEND_DELAY && |
michael@0 | 130 | this._flags & CL_EXPECT_3S_DELAY) |
michael@0 | 131 | do_throw("Data received sooner than expected"); |
michael@0 | 132 | else if (current - this._lastEvent >= SUSPEND_DELAY && |
michael@0 | 133 | this._flags & CL_EXPECT_3S_DELAY) |
michael@0 | 134 | this._flags &= ~CL_EXPECT_3S_DELAY; // No more delays expected |
michael@0 | 135 | |
michael@0 | 136 | this._buffer = this._buffer.concat(read_stream(stream, count)); |
michael@0 | 137 | this._lastEvent = current; |
michael@0 | 138 | } catch (ex) { |
michael@0 | 139 | do_throw("Error in onDataAvailable: " + ex); |
michael@0 | 140 | } |
michael@0 | 141 | }, |
michael@0 | 142 | |
michael@0 | 143 | onStopRequest: function(request, context, status) { |
michael@0 | 144 | try { |
michael@0 | 145 | var success = Components.isSuccessCode(status); |
michael@0 | 146 | if (!this._got_onstartrequest) |
michael@0 | 147 | do_throw("onStopRequest without onStartRequest event!"); |
michael@0 | 148 | if (this._got_onstoprequest) |
michael@0 | 149 | do_throw("Got second onStopRequest event!"); |
michael@0 | 150 | this._got_onstoprequest = true; |
michael@0 | 151 | if ((this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && success) |
michael@0 | 152 | do_throw("Should have failed to load URL (status is " + status.toString(16) + ")"); |
michael@0 | 153 | else if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && !success) |
michael@0 | 154 | do_throw("Failed to load URL: " + status.toString(16)); |
michael@0 | 155 | if (status != request.status) |
michael@0 | 156 | do_throw("request.status does not match status arg to onStopRequest!"); |
michael@0 | 157 | if (request.isPending()) |
michael@0 | 158 | do_throw("request reports itself as pending from onStopRequest!"); |
michael@0 | 159 | if (!(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) && |
michael@0 | 160 | !(this._flags & CL_EXPECT_GZIP) && |
michael@0 | 161 | this._contentLen != -1) |
michael@0 | 162 | do_check_eq(this._buffer.length, this._contentLen) |
michael@0 | 163 | } catch (ex) { |
michael@0 | 164 | do_throw("Error in onStopRequest: " + ex); |
michael@0 | 165 | } |
michael@0 | 166 | try { |
michael@0 | 167 | this._closure(request, this._buffer, this._closurectx); |
michael@0 | 168 | } catch (ex) { |
michael@0 | 169 | do_throw("Error in closure function: " + ex); |
michael@0 | 170 | } |
michael@0 | 171 | } |
michael@0 | 172 | }; |
michael@0 | 173 | |
michael@0 | 174 | var ES_ABORT_REDIRECT = 0x01; |
michael@0 | 175 | |
michael@0 | 176 | function ChannelEventSink(flags) |
michael@0 | 177 | { |
michael@0 | 178 | this._flags = flags; |
michael@0 | 179 | } |
michael@0 | 180 | |
michael@0 | 181 | ChannelEventSink.prototype = { |
michael@0 | 182 | QueryInterface: function(iid) { |
michael@0 | 183 | if (iid.equals(Ci.nsIInterfaceRequestor) || |
michael@0 | 184 | iid.equals(Ci.nsISupports)) |
michael@0 | 185 | return this; |
michael@0 | 186 | throw Cr.NS_ERROR_NO_INTERFACE; |
michael@0 | 187 | }, |
michael@0 | 188 | |
michael@0 | 189 | getInterface: function(iid) { |
michael@0 | 190 | if (iid.equals(Ci.nsIChannelEventSink)) |
michael@0 | 191 | return this; |
michael@0 | 192 | throw Cr.NS_ERROR_NO_INTERFACE; |
michael@0 | 193 | }, |
michael@0 | 194 | |
michael@0 | 195 | asyncOnChannelRedirect: function(oldChannel, newChannel, flags, callback) { |
michael@0 | 196 | if (this._flags & ES_ABORT_REDIRECT) |
michael@0 | 197 | throw Cr.NS_BINDING_ABORTED; |
michael@0 | 198 | |
michael@0 | 199 | callback.onRedirectVerifyCallback(Cr.NS_OK); |
michael@0 | 200 | } |
michael@0 | 201 | }; |
michael@0 | 202 | |
michael@0 | 203 | |
michael@0 | 204 | /** |
michael@0 | 205 | * Class that implements nsILoadContext. Use it as callbacks for channel when |
michael@0 | 206 | * test needs it. |
michael@0 | 207 | */ |
michael@0 | 208 | function LoadContextCallback(appId, inBrowserElement, isPrivate, isContent) { |
michael@0 | 209 | this.appId = appId; |
michael@0 | 210 | this.isInBrowserElement = inBrowserElement; |
michael@0 | 211 | this.usePrivateBrowsing = isPrivate; |
michael@0 | 212 | this.isContent = isContent; |
michael@0 | 213 | } |
michael@0 | 214 | |
michael@0 | 215 | LoadContextCallback.prototype = { |
michael@0 | 216 | associatedWindow: null, |
michael@0 | 217 | topWindow : null, |
michael@0 | 218 | isAppOfType: function(appType) { |
michael@0 | 219 | throw Cr.NS_ERROR_NOT_IMPLEMENTED; |
michael@0 | 220 | }, |
michael@0 | 221 | QueryInterface: function(iid) { |
michael@0 | 222 | if (iid.equals(Ci.nsILoadContext) || |
michael@0 | 223 | iid.equals(Ci.nsIInterfaceRequestor) || |
michael@0 | 224 | iid.equals(Ci.nsISupports)) { |
michael@0 | 225 | return this; |
michael@0 | 226 | } |
michael@0 | 227 | throw Cr.NS_ERROR_NO_INTERFACE; |
michael@0 | 228 | }, |
michael@0 | 229 | getInterface: function(iid) { |
michael@0 | 230 | if (iid.equals(Ci.nsILoadContext)) |
michael@0 | 231 | return this; |
michael@0 | 232 | throw Cr.NS_ERROR_NO_INTERFACE; |
michael@0 | 233 | }, |
michael@0 | 234 | } |
michael@0 | 235 |