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

436 lines
12 KiB
JavaScript

// Utility functions for offline tests.
var Cc = SpecialPowers.Cc;
var Ci = SpecialPowers.Ci;
var Cu = SpecialPowers.Cu;
var LoadContextInfo = Cc["@mozilla.org/load-context-info-factory;1"].getService(Ci.nsILoadContextInfoFactory);
var CommonUtils = Cu.import("resource://services-common/utils.js", {}).CommonUtils;
const kNetBase = 2152398848; // 0x804B0000
var NS_ERROR_CACHE_KEY_NOT_FOUND = kNetBase + 61;
var NS_ERROR_CACHE_KEY_WAIT_FOR_VALIDATION = kNetBase + 64;
// Reading the contents of multiple cache entries asynchronously
function OfflineCacheContents(urls) {
this.urls = urls;
this.contents = {};
}
OfflineCacheContents.prototype = {
QueryInterface: function(iid) {
if (!iid.equals(Ci.nsISupports) &&
!iid.equals(Ci.nsICacheEntryOpenCallback)) {
throw Cr.NS_ERROR_NO_INTERFACE;
}
return this;
},
onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
onCacheEntryAvailable: function(desc, isnew, applicationCache, status) {
if (!desc) {
this.fetch(this.callback);
return;
}
var stream = desc.openInputStream(0);
var sstream = Cc["@mozilla.org/scriptableinputstream;1"]
.createInstance(SpecialPowers.Ci.nsIScriptableInputStream);
sstream.init(stream);
this.contents[desc.key] = sstream.read(sstream.available());
sstream.close();
desc.close();
this.fetch(this.callback);
},
fetch: function(callback)
{
this.callback = callback;
if (this.urls.length == 0) {
callback(this.contents);
return;
}
var url = this.urls.shift();
var self = this;
var cacheStorage = OfflineTest.getActiveStorage();
cacheStorage.asyncOpenURI(CommonUtils.makeURI(url), "", Ci.nsICacheStorage.OPEN_READONLY, this);
}
};
var OfflineTest = {
_allowedByDefault: false,
_hasSlave: false,
// The window where test results should be sent.
_masterWindow: null,
// Array of all PUT overrides on the server
_pathOverrides: [],
// SJSs whom state was changed to be reverted on teardown
_SJSsStated: [],
setupChild: function()
{
if (this._allowedByDefault) {
this._masterWindow = window;
return true;
}
if (window.parent.OfflineTest._hasSlave) {
return false;
}
this._masterWindow = window.top;
return true;
},
/**
* Setup the tests. This will reload the current page in a new window
* if necessary.
*
* @return boolean Whether this window is the slave window
* to actually run the test in.
*/
setup: function()
{
netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
try {
this._allowedByDefault = SpecialPowers.getBoolPref("offline-apps.allow_by_default");
} catch (e) {}
if (this._allowedByDefault) {
this._masterWindow = window;
return true;
}
if (!window.opener || !window.opener.OfflineTest ||
!window.opener.OfflineTest._hasSlave) {
// Offline applications must be toplevel windows and have the
// offline-app permission. Because we were loaded without the
// offline-app permission and (probably) in an iframe, we need to
// enable the pref and spawn a new window to perform the actual
// tests. It will use this window to report successes and
// failures.
if (SpecialPowers.testPermission("offline-app", Ci.nsIPermissionManager.ALLOW_ACTION, document)) {
ok(false, "Previous test failed to clear offline-app permission! Expect failures.");
}
SpecialPowers.addPermission("offline-app", Ci.nsIPermissionManager.ALLOW_ACTION, document);
// Tests must run as toplevel windows. Open a slave window to run
// the test.
this._hasSlave = true;
window.open(window.location, "offlinetest");
return false;
}
this._masterWindow = window.opener;
return true;
},
teardownAndFinish: function()
{
this.teardown(function(self) { self.finish(); });
},
teardown: function(callback)
{
// First wait for any pending scheduled updates to finish
this.waitForUpdates(function(self) {
// Remove the offline-app permission we gave ourselves.
SpecialPowers.removePermission("offline-app", window.document);
// Clear all overrides on the server
for (override in self._pathOverrides)
self.deleteData(self._pathOverrides[override]);
for (statedSJS in self._SJSsStated)
self.setSJSState(self._SJSsStated[statedSJS], "");
self.clear();
callback(self);
});
},
finish: function()
{
if (this._allowedByDefault) {
SimpleTest.executeSoon(SimpleTest.finish);
} else if (this._masterWindow) {
// Slave window: pass control back to master window, close itself.
this._masterWindow.SimpleTest.executeSoon(this._masterWindow.OfflineTest.finish);
window.close();
} else {
// Master window: finish test.
SimpleTest.finish();
}
},
//
// Mochitest wrappers - These forward tests to the proper mochitest window.
//
ok: function(condition, name, diag)
{
return this._masterWindow.SimpleTest.ok(condition, name, diag);
},
is: function(a, b, name)
{
return this._masterWindow.SimpleTest.is(a, b, name);
},
isnot: function(a, b, name)
{
return this._masterWindow.SimpleTest.isnot(a, b, name);
},
todo: function(a, name)
{
return this._masterWindow.SimpleTest.todo(a, name);
},
clear: function()
{
// XXX: maybe we should just wipe out the entire disk cache.
var applicationCache = this.getActiveCache();
if (applicationCache) {
applicationCache.discard();
}
},
waitForUpdates: function(callback)
{
var self = this;
var observer = {
notified: false,
observe: function(subject, topic, data) {
if (subject) {
subject.QueryInterface(SpecialPowers.Ci.nsIOfflineCacheUpdate);
dump("Update of " + subject.manifestURI.spec + " finished\n");
}
SimpleTest.executeSoon(function() {
if (observer.notified) {
return;
}
var updateservice = Cc["@mozilla.org/offlinecacheupdate-service;1"]
.getService(SpecialPowers.Ci.nsIOfflineCacheUpdateService);
var updatesPending = updateservice.numUpdates;
if (updatesPending == 0) {
try {
SpecialPowers.removeObserver(observer, "offline-cache-update-completed");
} catch(ex) {}
dump("All pending updates done\n");
observer.notified = true;
callback(self);
return;
}
dump("Waiting for " + updateservice.numUpdates + " update(s) to finish\n");
});
}
}
SpecialPowers.addObserver(observer, "offline-cache-update-completed", false);
// Call now to check whether there are some updates scheduled
observer.observe();
},
failEvent: function(e)
{
OfflineTest.ok(false, "Unexpected event: " + e.type);
},
// The offline API as specified has no way to watch the load of a resource
// added with applicationCache.mozAdd().
waitForAdd: function(url, onFinished) {
// Check every half second for ten seconds.
var numChecks = 20;
var waitForAddListener = {
onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
onCacheEntryAvailable: function(entry, isnew, applicationCache, status) {
if (entry) {
entry.close();
onFinished();
return;
}
if (--numChecks == 0) {
onFinished();
return;
}
setTimeout(OfflineTest.priv(waitFunc), 500);
}
};
var waitFunc = function() {
var cacheStorage = OfflineTest.getActiveStorage();
cacheStorage.asyncOpenURI(CommonUtils.makeURI(url), "", Ci.nsICacheStorage.OPEN_READONLY, waitForAddListener);
}
setTimeout(this.priv(waitFunc), 500);
},
manifestURL: function(overload)
{
var manifestURLspec;
if (overload) {
manifestURLspec = overload;
} else {
var win = window;
while (win && !win.document.documentElement.getAttribute("manifest")) {
if (win == win.parent)
break;
win = win.parent;
}
if (win)
manifestURLspec = win.document.documentElement.getAttribute("manifest");
}
var ios = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService)
var baseURI = ios.newURI(window.location.href, null, null);
return ios.newURI(manifestURLspec, null, baseURI);
},
loadContext: function()
{
return SpecialPowers.wrap(window).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
.getInterface(SpecialPowers.Ci.nsIWebNavigation)
.QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
.getInterface(SpecialPowers.Ci.nsILoadContext);
},
loadContextInfo: function()
{
return LoadContextInfo.fromLoadContext(this.loadContext(), false);
},
getActiveCache: function(overload)
{
// Note that this is the current active cache in the cache stack, not the
// one associated with this window.
var serv = Cc["@mozilla.org/network/application-cache-service;1"]
.getService(Ci.nsIApplicationCacheService);
var groupID = serv.buildGroupIDForInfo(this.manifestURL(overload), this.loadContextInfo());
return serv.getActiveCache(groupID);
},
getActiveStorage: function()
{
var cache = this.getActiveCache();
if (!cache) {
return null;
}
var cacheService = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
.getService(Ci.nsICacheStorageService);
return cacheService.appCacheStorage(LoadContextInfo.default, cache);
},
priv: function(func)
{
var self = this;
return function() {
func(arguments);
}
},
checkCacheEntries: function(entries, callback)
{
var checkNextEntry = function() {
if (entries.length == 0) {
setTimeout(OfflineTest.priv(callback), 0);
} else {
OfflineTest.checkCache(entries[0][0], entries[0][1], checkNextEntry);
entries.shift();
}
}
checkNextEntry();
},
checkCache: function(url, expectEntry, callback)
{
var cacheStorage = this.getActiveStorage();
this._checkCache(cacheStorage, url, expectEntry, callback);
},
_checkCache: function(cacheStorage, url, expectEntry, callback)
{
if (!cacheStorage) {
if (expectEntry) {
this.ok(false, url + " should exist in the offline cache (no session)");
} else {
this.ok(true, url + " should not exist in the offline cache (no session)");
}
if (callback) setTimeout(this.priv(callback), 0);
return;
}
var _checkCacheListener = {
onCacheEntryCheck: function() { return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED; },
onCacheEntryAvailable: function(entry, isnew, applicationCache, status) {
if (entry) {
if (expectEntry) {
OfflineTest.ok(true, url + " should exist in the offline cache");
} else {
OfflineTest.ok(false, url + " should not exist in the offline cache");
}
entry.close();
} else {
if (status == NS_ERROR_CACHE_KEY_NOT_FOUND) {
if (expectEntry) {
OfflineTest.ok(false, url + " should exist in the offline cache");
} else {
OfflineTest.ok(true, url + " should not exist in the offline cache");
}
} else if (status == NS_ERROR_CACHE_KEY_WAIT_FOR_VALIDATION) {
// There was a cache key that we couldn't access yet, that's good enough.
if (expectEntry) {
OfflineTest.ok(!mustBeValid, url + " should exist in the offline cache");
} else {
OfflineTest.ok(mustBeValid, url + " should not exist in the offline cache");
}
} else {
OfflineTest.ok(false, "got invalid error for " + url);
}
}
if (callback) setTimeout(OfflineTest.priv(callback), 0);
}
};
cacheStorage.asyncOpenURI(CommonUtils.makeURI(url), "", Ci.nsICacheStorage.OPEN_READONLY, _checkCacheListener);
},
setSJSState: function(sjsPath, stateQuery)
{
var client = new XMLHttpRequest();
client.open("GET", sjsPath + "?state=" + stateQuery, false);
var appcachechannel = SpecialPowers.wrap(client).channel.QueryInterface(Ci.nsIApplicationCacheChannel);
appcachechannel.chooseApplicationCache = false;
appcachechannel.inheritApplicationCache = false;
appcachechannel.applicationCache = null;
client.send();
if (stateQuery == "")
delete this._SJSsStated[sjsPath];
else
this._SJSsStated.push(sjsPath);
}
};