dom/tests/mochitest/ajax/offline/offlineTests.js

branch
TOR_BUG_9701
changeset 8
97036ab72558
equal deleted inserted replaced
-1:000000000000 0:4f91b4683ef8
1 // Utility functions for offline tests.
2 var Cc = SpecialPowers.Cc;
3 var Ci = SpecialPowers.Ci;
4 var Cu = SpecialPowers.Cu;
5 var LoadContextInfo = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {}).LoadContextInfo;
6
7 const kNetBase = 2152398848; // 0x804B0000
8 var NS_ERROR_CACHE_KEY_NOT_FOUND = kNetBase + 61;
9 var NS_ERROR_CACHE_KEY_WAIT_FOR_VALIDATION = kNetBase + 64;
10
11 // Reading the contents of multiple cache entries asynchronously
12 function OfflineCacheContents(urls) {
13 this.urls = urls;
14 this.contents = {};
15 }
16
17 OfflineCacheContents.prototype = {
18 QueryInterface: function(iid) {
19 if (!iid.equals(Ci.nsISupports) &&
20 !iid.equals(Ci.nsICacheListener)) {
21 throw Cr.NS_ERROR_NO_INTERFACE;
22 }
23 return this;
24 },
25 onCacheEntryAvailable: function(desc, accessGranted, status) {
26 if (!desc) {
27 this.fetch(this.callback);
28 return;
29 }
30
31 var stream = desc.QueryInterface(Ci.nsICacheEntryDescriptor).openInputStream(0);
32 var sstream = Cc["@mozilla.org/scriptableinputstream;1"]
33 .createInstance(SpecialPowers.Ci.nsIScriptableInputStream);
34 sstream.init(stream);
35 this.contents[desc.key] = sstream.read(sstream.available());
36 sstream.close();
37 desc.close();
38 this.fetch(this.callback);
39 },
40
41 fetch: function(callback)
42 {
43 this.callback = callback;
44 if (this.urls.length == 0) {
45 callback(this.contents);
46 return;
47 }
48
49 var url = this.urls.shift();
50 var self = this;
51
52 var cacheSession = OfflineTest.getActiveSession();
53 cacheSession.asyncOpenCacheEntry(url, Ci.nsICache.ACCESS_READ, this);
54 }
55 };
56
57 var OfflineTest = {
58
59 _allowedByDefault: false,
60
61 _hasSlave: false,
62
63 // The window where test results should be sent.
64 _masterWindow: null,
65
66 // Array of all PUT overrides on the server
67 _pathOverrides: [],
68
69 // SJSs whom state was changed to be reverted on teardown
70 _SJSsStated: [],
71
72 setupChild: function()
73 {
74 if (this._allowedByDefault) {
75 this._masterWindow = window;
76 return true;
77 }
78
79 if (window.parent.OfflineTest._hasSlave) {
80 return false;
81 }
82
83 this._masterWindow = window.top;
84
85 return true;
86 },
87
88 /**
89 * Setup the tests. This will reload the current page in a new window
90 * if necessary.
91 *
92 * @return boolean Whether this window is the slave window
93 * to actually run the test in.
94 */
95 setup: function()
96 {
97 netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
98
99 try {
100 this._allowedByDefault = SpecialPowers.getBoolPref("offline-apps.allow_by_default");
101 } catch (e) {}
102
103 if (this._allowedByDefault) {
104 this._masterWindow = window;
105
106 return true;
107 }
108
109 if (!window.opener || !window.opener.OfflineTest ||
110 !window.opener.OfflineTest._hasSlave) {
111 // Offline applications must be toplevel windows and have the
112 // offline-app permission. Because we were loaded without the
113 // offline-app permission and (probably) in an iframe, we need to
114 // enable the pref and spawn a new window to perform the actual
115 // tests. It will use this window to report successes and
116 // failures.
117
118 if (SpecialPowers.testPermission("offline-app", Ci.nsIPermissionManager.ALLOW_ACTION, document)) {
119 ok(false, "Previous test failed to clear offline-app permission! Expect failures.");
120 }
121 SpecialPowers.addPermission("offline-app", Ci.nsIPermissionManager.ALLOW_ACTION, document);
122
123 // Tests must run as toplevel windows. Open a slave window to run
124 // the test.
125 this._hasSlave = true;
126 window.open(window.location, "offlinetest");
127
128 return false;
129 }
130
131 this._masterWindow = window.opener;
132
133 return true;
134 },
135
136 teardownAndFinish: function()
137 {
138 this.teardown(function(self) { self.finish(); });
139 },
140
141 teardown: function(callback)
142 {
143 // First wait for any pending scheduled updates to finish
144 this.waitForUpdates(function(self) {
145 // Remove the offline-app permission we gave ourselves.
146
147 SpecialPowers.removePermission("offline-app", window.document);
148
149 // Clear all overrides on the server
150 for (override in self._pathOverrides)
151 self.deleteData(self._pathOverrides[override]);
152 for (statedSJS in self._SJSsStated)
153 self.setSJSState(self._SJSsStated[statedSJS], "");
154
155 self.clear();
156 callback(self);
157 });
158 },
159
160 finish: function()
161 {
162 if (this._allowedByDefault) {
163 SimpleTest.executeSoon(SimpleTest.finish);
164 } else if (this._masterWindow) {
165 // Slave window: pass control back to master window, close itself.
166 this._masterWindow.SimpleTest.executeSoon(this._masterWindow.OfflineTest.finish);
167 window.close();
168 } else {
169 // Master window: finish test.
170 SimpleTest.finish();
171 }
172 },
173
174 //
175 // Mochitest wrappers - These forward tests to the proper mochitest window.
176 //
177 ok: function(condition, name, diag)
178 {
179 return this._masterWindow.SimpleTest.ok(condition, name, diag);
180 },
181
182 is: function(a, b, name)
183 {
184 return this._masterWindow.SimpleTest.is(a, b, name);
185 },
186
187 isnot: function(a, b, name)
188 {
189 return this._masterWindow.SimpleTest.isnot(a, b, name);
190 },
191
192 todo: function(a, name)
193 {
194 return this._masterWindow.SimpleTest.todo(a, name);
195 },
196
197 clear: function()
198 {
199 // XXX: maybe we should just wipe out the entire disk cache.
200 var applicationCache = this.getActiveCache();
201 if (applicationCache) {
202 applicationCache.discard();
203 }
204 },
205
206 waitForUpdates: function(callback)
207 {
208 var self = this;
209 var observer = {
210 notified: false,
211 observe: function(subject, topic, data) {
212 if (subject) {
213 subject.QueryInterface(SpecialPowers.Ci.nsIOfflineCacheUpdate);
214 dump("Update of " + subject.manifestURI.spec + " finished\n");
215 }
216
217 SimpleTest.executeSoon(function() {
218 if (observer.notified) {
219 return;
220 }
221
222 var updateservice = Cc["@mozilla.org/offlinecacheupdate-service;1"]
223 .getService(SpecialPowers.Ci.nsIOfflineCacheUpdateService);
224 var updatesPending = updateservice.numUpdates;
225 if (updatesPending == 0) {
226 try {
227 SpecialPowers.removeObserver(observer, "offline-cache-update-completed");
228 } catch(ex) {}
229 dump("All pending updates done\n");
230 observer.notified = true;
231 callback(self);
232 return;
233 }
234
235 dump("Waiting for " + updateservice.numUpdates + " update(s) to finish\n");
236 });
237 }
238 }
239
240 SpecialPowers.addObserver(observer, "offline-cache-update-completed", false);
241
242 // Call now to check whether there are some updates scheduled
243 observer.observe();
244 },
245
246 failEvent: function(e)
247 {
248 OfflineTest.ok(false, "Unexpected event: " + e.type);
249 },
250
251 // The offline API as specified has no way to watch the load of a resource
252 // added with applicationCache.mozAdd().
253 waitForAdd: function(url, onFinished) {
254 // Check every half second for ten seconds.
255 var numChecks = 20;
256
257 var waitForAddListener = {
258 onCacheEntryAvailable: function(entry, access, status) {
259 if (entry) {
260 entry.close();
261 onFinished();
262 return;
263 }
264
265 if (--numChecks == 0) {
266 onFinished();
267 return;
268 }
269
270 setTimeout(OfflineTest.priv(waitFunc), 500);
271 }
272 };
273
274 var waitFunc = function() {
275 var cacheSession = OfflineTest.getActiveSession();
276 cacheSession.asyncOpenCacheEntry(url,
277 Ci.nsICache.ACCESS_READ,
278 waitForAddListener);
279 }
280
281 setTimeout(this.priv(waitFunc), 500);
282 },
283
284 manifestURL: function(overload)
285 {
286 var manifestURLspec;
287 if (overload) {
288 manifestURLspec = overload;
289 } else {
290 var win = window;
291 while (win && !win.document.documentElement.getAttribute("manifest")) {
292 if (win == win.parent)
293 break;
294 win = win.parent;
295 }
296 if (win)
297 manifestURLspec = win.document.documentElement.getAttribute("manifest");
298 }
299
300 var ios = Cc["@mozilla.org/network/io-service;1"]
301 .getService(Ci.nsIIOService)
302
303 var baseURI = ios.newURI(window.location.href, null, null);
304 return ios.newURI(manifestURLspec, null, baseURI);
305 },
306
307 loadContext: function()
308 {
309 return SpecialPowers.wrap(window).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
310 .getInterface(SpecialPowers.Ci.nsIWebNavigation)
311 .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
312 .getInterface(SpecialPowers.Ci.nsILoadContext);
313 },
314
315 loadContextInfo: function()
316 {
317 return LoadContextInfo.fromLoadContext(this.loadContext(), false);
318 },
319
320 getActiveCache: function(overload)
321 {
322 // Note that this is the current active cache in the cache stack, not the
323 // one associated with this window.
324 var serv = Cc["@mozilla.org/network/application-cache-service;1"]
325 .getService(Ci.nsIApplicationCacheService);
326 var groupID = serv.buildGroupID(this.manifestURL(overload), this.loadContextInfo());
327 return serv.getActiveCache(groupID);
328 },
329
330 getActiveSession: function()
331 {
332 var cache = this.getActiveCache();
333 if (!cache) {
334 return null;
335 }
336
337 var cacheService = Cc["@mozilla.org/network/cache-service;1"]
338 .getService(Ci.nsICacheService);
339 return cacheService.createSession(cache.clientID,
340 Ci.nsICache.STORE_OFFLINE,
341 true);
342 },
343
344 priv: function(func)
345 {
346 var self = this;
347 return function() {
348 func(arguments);
349 }
350 },
351
352 checkCacheEntries: function(entries, callback)
353 {
354 var checkNextEntry = function() {
355 if (entries.length == 0) {
356 setTimeout(OfflineTest.priv(callback), 0);
357 } else {
358 OfflineTest.checkCache(entries[0][0], entries[0][1], checkNextEntry);
359 entries.shift();
360 }
361 }
362
363 checkNextEntry();
364 },
365
366 checkCache: function(url, expectEntry, callback)
367 {
368 var cacheSession = this.getActiveSession();
369 this._checkCache(cacheSession, url, expectEntry, callback);
370 },
371
372 _checkCache: function(cacheSession, url, expectEntry, callback)
373 {
374 if (!cacheSession) {
375 if (expectEntry) {
376 this.ok(false, url + " should exist in the offline cache (no session)");
377 } else {
378 this.ok(true, url + " should not exist in the offline cache (no session)");
379 }
380 if (callback) setTimeout(this.priv(callback), 0);
381 return;
382 }
383
384 var _checkCacheListener = {
385 onCacheEntryAvailable: function(entry, access, status) {
386 if (entry) {
387 if (expectEntry) {
388 OfflineTest.ok(true, url + " should exist in the offline cache");
389 } else {
390 OfflineTest.ok(false, url + " should not exist in the offline cache");
391 }
392 entry.close();
393 } else {
394 if (status == NS_ERROR_CACHE_KEY_NOT_FOUND) {
395 if (expectEntry) {
396 OfflineTest.ok(false, url + " should exist in the offline cache");
397 } else {
398 OfflineTest.ok(true, url + " should not exist in the offline cache");
399 }
400 } else if (status == NS_ERROR_CACHE_KEY_WAIT_FOR_VALIDATION) {
401 // There was a cache key that we couldn't access yet, that's good enough.
402 if (expectEntry) {
403 OfflineTest.ok(!mustBeValid, url + " should exist in the offline cache");
404 } else {
405 OfflineTest.ok(mustBeValid, url + " should not exist in the offline cache");
406 }
407 } else {
408 OfflineTest.ok(false, "got invalid error for " + url);
409 }
410 }
411 if (callback) setTimeout(OfflineTest.priv(callback), 0);
412 }
413 };
414
415 cacheSession.asyncOpenCacheEntry(url,
416 Ci.nsICache.ACCESS_READ,
417 _checkCacheListener,
418 false);
419 },
420
421 setSJSState: function(sjsPath, stateQuery)
422 {
423 var client = new XMLHttpRequest();
424 client.open("GET", sjsPath + "?state=" + stateQuery, false);
425
426 var appcachechannel = SpecialPowers.wrap(client).channel.QueryInterface(Ci.nsIApplicationCacheChannel);
427 appcachechannel.chooseApplicationCache = false;
428 appcachechannel.inheritApplicationCache = false;
429 appcachechannel.applicationCache = null;
430
431 client.send();
432
433 if (stateQuery == "")
434 delete this._SJSsStated[sjsPath];
435 else
436 this._SJSsStated.push(sjsPath);
437 }
438
439 };

mercurial