1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/tests/mochitest/ajax/offline/offlineTests.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,439 @@ 1.4 +// Utility functions for offline tests. 1.5 +var Cc = SpecialPowers.Cc; 1.6 +var Ci = SpecialPowers.Ci; 1.7 +var Cu = SpecialPowers.Cu; 1.8 +var LoadContextInfo = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {}).LoadContextInfo; 1.9 + 1.10 +const kNetBase = 2152398848; // 0x804B0000 1.11 +var NS_ERROR_CACHE_KEY_NOT_FOUND = kNetBase + 61; 1.12 +var NS_ERROR_CACHE_KEY_WAIT_FOR_VALIDATION = kNetBase + 64; 1.13 + 1.14 +// Reading the contents of multiple cache entries asynchronously 1.15 +function OfflineCacheContents(urls) { 1.16 + this.urls = urls; 1.17 + this.contents = {}; 1.18 +} 1.19 + 1.20 +OfflineCacheContents.prototype = { 1.21 +QueryInterface: function(iid) { 1.22 + if (!iid.equals(Ci.nsISupports) && 1.23 + !iid.equals(Ci.nsICacheListener)) { 1.24 + throw Cr.NS_ERROR_NO_INTERFACE; 1.25 + } 1.26 + return this; 1.27 + }, 1.28 +onCacheEntryAvailable: function(desc, accessGranted, status) { 1.29 + if (!desc) { 1.30 + this.fetch(this.callback); 1.31 + return; 1.32 + } 1.33 + 1.34 + var stream = desc.QueryInterface(Ci.nsICacheEntryDescriptor).openInputStream(0); 1.35 + var sstream = Cc["@mozilla.org/scriptableinputstream;1"] 1.36 + .createInstance(SpecialPowers.Ci.nsIScriptableInputStream); 1.37 + sstream.init(stream); 1.38 + this.contents[desc.key] = sstream.read(sstream.available()); 1.39 + sstream.close(); 1.40 + desc.close(); 1.41 + this.fetch(this.callback); 1.42 + }, 1.43 + 1.44 +fetch: function(callback) 1.45 +{ 1.46 + this.callback = callback; 1.47 + if (this.urls.length == 0) { 1.48 + callback(this.contents); 1.49 + return; 1.50 + } 1.51 + 1.52 + var url = this.urls.shift(); 1.53 + var self = this; 1.54 + 1.55 + var cacheSession = OfflineTest.getActiveSession(); 1.56 + cacheSession.asyncOpenCacheEntry(url, Ci.nsICache.ACCESS_READ, this); 1.57 +} 1.58 +}; 1.59 + 1.60 +var OfflineTest = { 1.61 + 1.62 +_allowedByDefault: false, 1.63 + 1.64 +_hasSlave: false, 1.65 + 1.66 +// The window where test results should be sent. 1.67 +_masterWindow: null, 1.68 + 1.69 +// Array of all PUT overrides on the server 1.70 +_pathOverrides: [], 1.71 + 1.72 +// SJSs whom state was changed to be reverted on teardown 1.73 +_SJSsStated: [], 1.74 + 1.75 +setupChild: function() 1.76 +{ 1.77 + if (this._allowedByDefault) { 1.78 + this._masterWindow = window; 1.79 + return true; 1.80 + } 1.81 + 1.82 + if (window.parent.OfflineTest._hasSlave) { 1.83 + return false; 1.84 + } 1.85 + 1.86 + this._masterWindow = window.top; 1.87 + 1.88 + return true; 1.89 +}, 1.90 + 1.91 +/** 1.92 + * Setup the tests. This will reload the current page in a new window 1.93 + * if necessary. 1.94 + * 1.95 + * @return boolean Whether this window is the slave window 1.96 + * to actually run the test in. 1.97 + */ 1.98 +setup: function() 1.99 +{ 1.100 + netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect"); 1.101 + 1.102 + try { 1.103 + this._allowedByDefault = SpecialPowers.getBoolPref("offline-apps.allow_by_default"); 1.104 + } catch (e) {} 1.105 + 1.106 + if (this._allowedByDefault) { 1.107 + this._masterWindow = window; 1.108 + 1.109 + return true; 1.110 + } 1.111 + 1.112 + if (!window.opener || !window.opener.OfflineTest || 1.113 + !window.opener.OfflineTest._hasSlave) { 1.114 + // Offline applications must be toplevel windows and have the 1.115 + // offline-app permission. Because we were loaded without the 1.116 + // offline-app permission and (probably) in an iframe, we need to 1.117 + // enable the pref and spawn a new window to perform the actual 1.118 + // tests. It will use this window to report successes and 1.119 + // failures. 1.120 + 1.121 + if (SpecialPowers.testPermission("offline-app", Ci.nsIPermissionManager.ALLOW_ACTION, document)) { 1.122 + ok(false, "Previous test failed to clear offline-app permission! Expect failures."); 1.123 + } 1.124 + SpecialPowers.addPermission("offline-app", Ci.nsIPermissionManager.ALLOW_ACTION, document); 1.125 + 1.126 + // Tests must run as toplevel windows. Open a slave window to run 1.127 + // the test. 1.128 + this._hasSlave = true; 1.129 + window.open(window.location, "offlinetest"); 1.130 + 1.131 + return false; 1.132 + } 1.133 + 1.134 + this._masterWindow = window.opener; 1.135 + 1.136 + return true; 1.137 +}, 1.138 + 1.139 +teardownAndFinish: function() 1.140 +{ 1.141 + this.teardown(function(self) { self.finish(); }); 1.142 +}, 1.143 + 1.144 +teardown: function(callback) 1.145 +{ 1.146 + // First wait for any pending scheduled updates to finish 1.147 + this.waitForUpdates(function(self) { 1.148 + // Remove the offline-app permission we gave ourselves. 1.149 + 1.150 + SpecialPowers.removePermission("offline-app", window.document); 1.151 + 1.152 + // Clear all overrides on the server 1.153 + for (override in self._pathOverrides) 1.154 + self.deleteData(self._pathOverrides[override]); 1.155 + for (statedSJS in self._SJSsStated) 1.156 + self.setSJSState(self._SJSsStated[statedSJS], ""); 1.157 + 1.158 + self.clear(); 1.159 + callback(self); 1.160 + }); 1.161 +}, 1.162 + 1.163 +finish: function() 1.164 +{ 1.165 + if (this._allowedByDefault) { 1.166 + SimpleTest.executeSoon(SimpleTest.finish); 1.167 + } else if (this._masterWindow) { 1.168 + // Slave window: pass control back to master window, close itself. 1.169 + this._masterWindow.SimpleTest.executeSoon(this._masterWindow.OfflineTest.finish); 1.170 + window.close(); 1.171 + } else { 1.172 + // Master window: finish test. 1.173 + SimpleTest.finish(); 1.174 + } 1.175 +}, 1.176 + 1.177 +// 1.178 +// Mochitest wrappers - These forward tests to the proper mochitest window. 1.179 +// 1.180 +ok: function(condition, name, diag) 1.181 +{ 1.182 + return this._masterWindow.SimpleTest.ok(condition, name, diag); 1.183 +}, 1.184 + 1.185 +is: function(a, b, name) 1.186 +{ 1.187 + return this._masterWindow.SimpleTest.is(a, b, name); 1.188 +}, 1.189 + 1.190 +isnot: function(a, b, name) 1.191 +{ 1.192 + return this._masterWindow.SimpleTest.isnot(a, b, name); 1.193 +}, 1.194 + 1.195 +todo: function(a, name) 1.196 +{ 1.197 + return this._masterWindow.SimpleTest.todo(a, name); 1.198 +}, 1.199 + 1.200 +clear: function() 1.201 +{ 1.202 + // XXX: maybe we should just wipe out the entire disk cache. 1.203 + var applicationCache = this.getActiveCache(); 1.204 + if (applicationCache) { 1.205 + applicationCache.discard(); 1.206 + } 1.207 +}, 1.208 + 1.209 +waitForUpdates: function(callback) 1.210 +{ 1.211 + var self = this; 1.212 + var observer = { 1.213 + notified: false, 1.214 + observe: function(subject, topic, data) { 1.215 + if (subject) { 1.216 + subject.QueryInterface(SpecialPowers.Ci.nsIOfflineCacheUpdate); 1.217 + dump("Update of " + subject.manifestURI.spec + " finished\n"); 1.218 + } 1.219 + 1.220 + SimpleTest.executeSoon(function() { 1.221 + if (observer.notified) { 1.222 + return; 1.223 + } 1.224 + 1.225 + var updateservice = Cc["@mozilla.org/offlinecacheupdate-service;1"] 1.226 + .getService(SpecialPowers.Ci.nsIOfflineCacheUpdateService); 1.227 + var updatesPending = updateservice.numUpdates; 1.228 + if (updatesPending == 0) { 1.229 + try { 1.230 + SpecialPowers.removeObserver(observer, "offline-cache-update-completed"); 1.231 + } catch(ex) {} 1.232 + dump("All pending updates done\n"); 1.233 + observer.notified = true; 1.234 + callback(self); 1.235 + return; 1.236 + } 1.237 + 1.238 + dump("Waiting for " + updateservice.numUpdates + " update(s) to finish\n"); 1.239 + }); 1.240 + } 1.241 + } 1.242 + 1.243 + SpecialPowers.addObserver(observer, "offline-cache-update-completed", false); 1.244 + 1.245 + // Call now to check whether there are some updates scheduled 1.246 + observer.observe(); 1.247 +}, 1.248 + 1.249 +failEvent: function(e) 1.250 +{ 1.251 + OfflineTest.ok(false, "Unexpected event: " + e.type); 1.252 +}, 1.253 + 1.254 +// The offline API as specified has no way to watch the load of a resource 1.255 +// added with applicationCache.mozAdd(). 1.256 +waitForAdd: function(url, onFinished) { 1.257 + // Check every half second for ten seconds. 1.258 + var numChecks = 20; 1.259 + 1.260 + var waitForAddListener = { 1.261 + onCacheEntryAvailable: function(entry, access, status) { 1.262 + if (entry) { 1.263 + entry.close(); 1.264 + onFinished(); 1.265 + return; 1.266 + } 1.267 + 1.268 + if (--numChecks == 0) { 1.269 + onFinished(); 1.270 + return; 1.271 + } 1.272 + 1.273 + setTimeout(OfflineTest.priv(waitFunc), 500); 1.274 + } 1.275 + }; 1.276 + 1.277 + var waitFunc = function() { 1.278 + var cacheSession = OfflineTest.getActiveSession(); 1.279 + cacheSession.asyncOpenCacheEntry(url, 1.280 + Ci.nsICache.ACCESS_READ, 1.281 + waitForAddListener); 1.282 + } 1.283 + 1.284 + setTimeout(this.priv(waitFunc), 500); 1.285 +}, 1.286 + 1.287 +manifestURL: function(overload) 1.288 +{ 1.289 + var manifestURLspec; 1.290 + if (overload) { 1.291 + manifestURLspec = overload; 1.292 + } else { 1.293 + var win = window; 1.294 + while (win && !win.document.documentElement.getAttribute("manifest")) { 1.295 + if (win == win.parent) 1.296 + break; 1.297 + win = win.parent; 1.298 + } 1.299 + if (win) 1.300 + manifestURLspec = win.document.documentElement.getAttribute("manifest"); 1.301 + } 1.302 + 1.303 + var ios = Cc["@mozilla.org/network/io-service;1"] 1.304 + .getService(Ci.nsIIOService) 1.305 + 1.306 + var baseURI = ios.newURI(window.location.href, null, null); 1.307 + return ios.newURI(manifestURLspec, null, baseURI); 1.308 +}, 1.309 + 1.310 +loadContext: function() 1.311 +{ 1.312 + return SpecialPowers.wrap(window).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) 1.313 + .getInterface(SpecialPowers.Ci.nsIWebNavigation) 1.314 + .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor) 1.315 + .getInterface(SpecialPowers.Ci.nsILoadContext); 1.316 +}, 1.317 + 1.318 +loadContextInfo: function() 1.319 +{ 1.320 + return LoadContextInfo.fromLoadContext(this.loadContext(), false); 1.321 +}, 1.322 + 1.323 +getActiveCache: function(overload) 1.324 +{ 1.325 + // Note that this is the current active cache in the cache stack, not the 1.326 + // one associated with this window. 1.327 + var serv = Cc["@mozilla.org/network/application-cache-service;1"] 1.328 + .getService(Ci.nsIApplicationCacheService); 1.329 + var groupID = serv.buildGroupID(this.manifestURL(overload), this.loadContextInfo()); 1.330 + return serv.getActiveCache(groupID); 1.331 +}, 1.332 + 1.333 +getActiveSession: function() 1.334 +{ 1.335 + var cache = this.getActiveCache(); 1.336 + if (!cache) { 1.337 + return null; 1.338 + } 1.339 + 1.340 + var cacheService = Cc["@mozilla.org/network/cache-service;1"] 1.341 + .getService(Ci.nsICacheService); 1.342 + return cacheService.createSession(cache.clientID, 1.343 + Ci.nsICache.STORE_OFFLINE, 1.344 + true); 1.345 +}, 1.346 + 1.347 +priv: function(func) 1.348 +{ 1.349 + var self = this; 1.350 + return function() { 1.351 + func(arguments); 1.352 + } 1.353 +}, 1.354 + 1.355 +checkCacheEntries: function(entries, callback) 1.356 +{ 1.357 + var checkNextEntry = function() { 1.358 + if (entries.length == 0) { 1.359 + setTimeout(OfflineTest.priv(callback), 0); 1.360 + } else { 1.361 + OfflineTest.checkCache(entries[0][0], entries[0][1], checkNextEntry); 1.362 + entries.shift(); 1.363 + } 1.364 + } 1.365 + 1.366 + checkNextEntry(); 1.367 +}, 1.368 + 1.369 +checkCache: function(url, expectEntry, callback) 1.370 +{ 1.371 + var cacheSession = this.getActiveSession(); 1.372 + this._checkCache(cacheSession, url, expectEntry, callback); 1.373 +}, 1.374 + 1.375 +_checkCache: function(cacheSession, url, expectEntry, callback) 1.376 +{ 1.377 + if (!cacheSession) { 1.378 + if (expectEntry) { 1.379 + this.ok(false, url + " should exist in the offline cache (no session)"); 1.380 + } else { 1.381 + this.ok(true, url + " should not exist in the offline cache (no session)"); 1.382 + } 1.383 + if (callback) setTimeout(this.priv(callback), 0); 1.384 + return; 1.385 + } 1.386 + 1.387 + var _checkCacheListener = { 1.388 + onCacheEntryAvailable: function(entry, access, status) { 1.389 + if (entry) { 1.390 + if (expectEntry) { 1.391 + OfflineTest.ok(true, url + " should exist in the offline cache"); 1.392 + } else { 1.393 + OfflineTest.ok(false, url + " should not exist in the offline cache"); 1.394 + } 1.395 + entry.close(); 1.396 + } else { 1.397 + if (status == NS_ERROR_CACHE_KEY_NOT_FOUND) { 1.398 + if (expectEntry) { 1.399 + OfflineTest.ok(false, url + " should exist in the offline cache"); 1.400 + } else { 1.401 + OfflineTest.ok(true, url + " should not exist in the offline cache"); 1.402 + } 1.403 + } else if (status == NS_ERROR_CACHE_KEY_WAIT_FOR_VALIDATION) { 1.404 + // There was a cache key that we couldn't access yet, that's good enough. 1.405 + if (expectEntry) { 1.406 + OfflineTest.ok(!mustBeValid, url + " should exist in the offline cache"); 1.407 + } else { 1.408 + OfflineTest.ok(mustBeValid, url + " should not exist in the offline cache"); 1.409 + } 1.410 + } else { 1.411 + OfflineTest.ok(false, "got invalid error for " + url); 1.412 + } 1.413 + } 1.414 + if (callback) setTimeout(OfflineTest.priv(callback), 0); 1.415 + } 1.416 + }; 1.417 + 1.418 + cacheSession.asyncOpenCacheEntry(url, 1.419 + Ci.nsICache.ACCESS_READ, 1.420 + _checkCacheListener, 1.421 + false); 1.422 +}, 1.423 + 1.424 +setSJSState: function(sjsPath, stateQuery) 1.425 +{ 1.426 + var client = new XMLHttpRequest(); 1.427 + client.open("GET", sjsPath + "?state=" + stateQuery, false); 1.428 + 1.429 + var appcachechannel = SpecialPowers.wrap(client).channel.QueryInterface(Ci.nsIApplicationCacheChannel); 1.430 + appcachechannel.chooseApplicationCache = false; 1.431 + appcachechannel.inheritApplicationCache = false; 1.432 + appcachechannel.applicationCache = null; 1.433 + 1.434 + client.send(); 1.435 + 1.436 + if (stateQuery == "") 1.437 + delete this._SJSsStated[sjsPath]; 1.438 + else 1.439 + this._SJSsStated.push(sjsPath); 1.440 +} 1.441 + 1.442 +};