summaryrefslogtreecommitdiffstats
path: root/dom/tests/mochitest/ajax/offline/offlineTests.js
diff options
context:
space:
mode:
Diffstat (limited to 'dom/tests/mochitest/ajax/offline/offlineTests.js')
-rw-r--r--dom/tests/mochitest/ajax/offline/offlineTests.js436
1 files changed, 436 insertions, 0 deletions
diff --git a/dom/tests/mochitest/ajax/offline/offlineTests.js b/dom/tests/mochitest/ajax/offline/offlineTests.js
new file mode 100644
index 000000000..f239cd574
--- /dev/null
+++ b/dom/tests/mochitest/ajax/offline/offlineTests.js
@@ -0,0 +1,436 @@
+// 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);
+}
+
+};