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

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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;
     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;
    11 // Reading the contents of multiple cache entries asynchronously
    12 function OfflineCacheContents(urls) {
    13   this.urls = urls;
    14   this.contents = {};
    15 }
    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     }
    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   },
    41 fetch: function(callback)
    42 {
    43   this.callback = callback;
    44   if (this.urls.length == 0) {
    45     callback(this.contents);
    46     return;
    47   }
    49   var url = this.urls.shift();
    50   var self = this;
    52   var cacheSession = OfflineTest.getActiveSession();
    53   cacheSession.asyncOpenCacheEntry(url, Ci.nsICache.ACCESS_READ, this);
    54 }
    55 };
    57 var OfflineTest = {
    59 _allowedByDefault: false,
    61 _hasSlave: false,
    63 // The window where test results should be sent.
    64 _masterWindow: null,
    66 // Array of all PUT overrides on the server
    67 _pathOverrides: [],
    69 // SJSs whom state was changed to be reverted on teardown
    70 _SJSsStated: [],
    72 setupChild: function()
    73 {
    74   if (this._allowedByDefault) {
    75     this._masterWindow = window;
    76     return true;
    77   }
    79   if (window.parent.OfflineTest._hasSlave) {
    80     return false;
    81   }
    83   this._masterWindow = window.top;
    85   return true;
    86 },
    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");
    99   try {
   100     this._allowedByDefault = SpecialPowers.getBoolPref("offline-apps.allow_by_default");
   101   } catch (e) {}
   103   if (this._allowedByDefault) {
   104     this._masterWindow = window;
   106     return true;
   107   }
   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.
   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);
   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");
   128     return false;
   129   }
   131   this._masterWindow = window.opener;
   133   return true;
   134 },
   136 teardownAndFinish: function()
   137 {
   138   this.teardown(function(self) { self.finish(); });
   139 },
   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.
   147     SpecialPowers.removePermission("offline-app", window.document);
   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], "");
   155     self.clear();
   156     callback(self);
   157   });
   158 },
   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 },
   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 },
   182 is: function(a, b, name)
   183 {
   184   return this._masterWindow.SimpleTest.is(a, b, name);
   185 },
   187 isnot: function(a, b, name)
   188 {
   189   return this._masterWindow.SimpleTest.isnot(a, b, name);
   190 },
   192 todo: function(a, name)
   193 {
   194   return this._masterWindow.SimpleTest.todo(a, name);
   195 },
   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 },
   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       }
   217       SimpleTest.executeSoon(function() {
   218         if (observer.notified) {
   219           return;
   220         }
   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         }
   235         dump("Waiting for " + updateservice.numUpdates + " update(s) to finish\n");
   236       });
   237     }
   238   }
   240   SpecialPowers.addObserver(observer, "offline-cache-update-completed", false);
   242   // Call now to check whether there are some updates scheduled
   243   observer.observe();
   244 },
   246 failEvent: function(e)
   247 {
   248   OfflineTest.ok(false, "Unexpected event: " + e.type);
   249 },
   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;
   257   var waitForAddListener = {
   258     onCacheEntryAvailable: function(entry, access, status) {
   259       if (entry) {
   260         entry.close();
   261         onFinished();
   262         return;
   263       }
   265       if (--numChecks == 0) {
   266         onFinished();
   267         return;
   268       }
   270       setTimeout(OfflineTest.priv(waitFunc), 500);
   271     }
   272   };
   274   var waitFunc = function() {
   275     var cacheSession = OfflineTest.getActiveSession();
   276     cacheSession.asyncOpenCacheEntry(url,
   277                                      Ci.nsICache.ACCESS_READ,
   278                                      waitForAddListener);
   279   }
   281   setTimeout(this.priv(waitFunc), 500);
   282 },
   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   }
   300   var ios = Cc["@mozilla.org/network/io-service;1"]
   301             .getService(Ci.nsIIOService)
   303   var baseURI = ios.newURI(window.location.href, null, null);
   304   return ios.newURI(manifestURLspec, null, baseURI);
   305 },
   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 },
   315 loadContextInfo: function()
   316 {
   317   return LoadContextInfo.fromLoadContext(this.loadContext(), false);
   318 },
   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 },
   330 getActiveSession: function()
   331 {
   332   var cache = this.getActiveCache();
   333   if (!cache) {
   334     return null;
   335   }
   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 },
   344 priv: function(func)
   345 {
   346   var self = this;
   347   return function() {
   348     func(arguments);
   349   }
   350 },
   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   }
   363   checkNextEntry();
   364 },
   366 checkCache: function(url, expectEntry, callback)
   367 {
   368   var cacheSession = this.getActiveSession();
   369   this._checkCache(cacheSession, url, expectEntry, callback);
   370 },
   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   }
   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   };
   415   cacheSession.asyncOpenCacheEntry(url,
   416                                    Ci.nsICache.ACCESS_READ,
   417                                    _checkCacheListener,
   418                                    false);
   419 },
   421 setSJSState: function(sjsPath, stateQuery)
   422 {
   423   var client = new XMLHttpRequest();
   424   client.open("GET", sjsPath + "?state=" + stateQuery, false);
   426   var appcachechannel = SpecialPowers.wrap(client).channel.QueryInterface(Ci.nsIApplicationCacheChannel);
   427   appcachechannel.chooseApplicationCache = false;
   428   appcachechannel.inheritApplicationCache = false;
   429   appcachechannel.applicationCache = null;
   431   client.send();
   433   if (stateQuery == "")
   434     delete this._SJSsStated[sjsPath];
   435   else
   436     this._SJSsStated.push(sjsPath);
   437 }
   439 };

mercurial