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