Bug 1857386 [wpt PR 42383] - Update wpt metadata, a=testonly
[gecko.git] / netwerk / test / unit / test_race_cache_with_network.js
blobfc2b1d80ecef494ad57817ac9d2b69657bf4d8df
1 /* Any copyright is dedicated to the Public Domain.
2  * http://creativecommons.org/publicdomain/zero/1.0/
3  */
5 "use strict";
7 const { HttpServer } = ChromeUtils.importESModule(
8   "resource://testing-common/httpd.sys.mjs"
9 );
11 var httpserver = new HttpServer();
12 httpserver.start(-1);
13 const PORT = httpserver.identity.primaryPort;
15 function make_channel(url) {
16   return NetUtil.newChannel({
17     uri: url,
18     loadUsingSystemPrincipal: true,
19   }).QueryInterface(Ci.nsIHttpChannel);
22 let gResponseBody = "blahblah";
23 let g200Counter = 0;
24 let g304Counter = 0;
25 function test_handler(metadata, response) {
26   response.setHeader("Content-Type", "text/plain");
27   response.setHeader("Cache-Control", "no-cache");
28   response.setHeader("ETag", "test-etag1");
30   let etag;
31   try {
32     etag = metadata.getHeader("If-None-Match");
33   } catch (ex) {
34     etag = "";
35   }
37   if (etag == "test-etag1") {
38     response.setStatusLine(metadata.httpVersion, 304, "Not Modified");
39     g304Counter++;
40   } else {
41     response.setStatusLine(metadata.httpVersion, 200, "OK");
42     response.bodyOutputStream.write(gResponseBody, gResponseBody.length);
43     g200Counter++;
44   }
47 function cached_handler(metadata, response) {
48   response.setHeader("Content-Type", "text/plain");
49   response.setHeader("Cache-Control", "max-age=3600");
50   response.setHeader("ETag", "test-etag1");
52   response.setStatusLine(metadata.httpVersion, 200, "OK");
53   response.bodyOutputStream.write(gResponseBody, gResponseBody.length);
55   g200Counter++;
58 let gResponseCounter = 0;
59 let gIsFromCache = 0;
60 function checkContent(request, buffer, context, isFromCache) {
61   Assert.equal(buffer, gResponseBody);
62   info(
63     "isRacing: " +
64       request.QueryInterface(Ci.nsICacheInfoChannel).isRacing() +
65       "\n"
66   );
67   gResponseCounter++;
68   if (isFromCache) {
69     gIsFromCache++;
70   }
71   executeSoon(() => {
72     testGenerator.next();
73   });
76 function run_test() {
77   do_get_profile();
78   // In this test, we manually use |TriggerNetwork| to prove we could send
79   // net and cache reqeust simultaneously. Therefore we should disable
80   // racing in the HttpChannel first.
81   Services.prefs.setBoolPref("network.http.rcwn.enabled", false);
82   httpserver.registerPathHandler("/rcwn", test_handler);
83   httpserver.registerPathHandler("/rcwn_cached", cached_handler);
84   testGenerator.next();
85   do_test_pending();
88 let testGenerator = testSteps();
89 function* testSteps() {
90   /*
91    * In this test, we have a relatively low timeout of 200ms and an assertion that
92    * the timer works properly by checking that the time was greater than 200ms.
93    * With a timer precision of 100ms (for example) we will clamp downwards to 200
94    * and cause the assertion to fail. To resolve this, we hardcode a precision of
95    * 20ms.
96    */
97   Services.prefs.setBoolPref("privacy.reduceTimerPrecision", true);
98   Services.prefs.setIntPref(
99     "privacy.resistFingerprinting.reduceTimerPrecision.microseconds",
100     20000
101   );
103   registerCleanupFunction(function () {
104     Services.prefs.clearUserPref("privacy.reduceTimerPrecision");
105     Services.prefs.clearUserPref(
106       "privacy.resistFingerprinting.reduceTimerPrecision.microseconds"
107     );
108   });
110   // Initial request. Stores the response in the cache.
111   let channel = make_channel("http://localhost:" + PORT + "/rcwn");
112   channel.asyncOpen(new ChannelListener(checkContent, null));
113   yield undefined;
114   equal(gResponseCounter, 1);
115   equal(g200Counter, 1, "check number of 200 responses");
116   equal(g304Counter, 0, "check number of 304 responses");
118   // Checks that response is returned from the cache, after a 304 response.
119   channel = make_channel("http://localhost:" + PORT + "/rcwn");
120   channel.asyncOpen(new ChannelListener(checkContent, null));
121   yield undefined;
122   equal(gResponseCounter, 2);
123   equal(g200Counter, 1, "check number of 200 responses");
124   equal(g304Counter, 1, "check number of 304 responses");
126   // Checks that delaying the response from the cache works.
127   channel = make_channel("http://localhost:" + PORT + "/rcwn");
128   channel
129     .QueryInterface(Ci.nsIRaceCacheWithNetwork)
130     .test_delayCacheEntryOpeningBy(200);
131   let startTime = Date.now();
132   channel.asyncOpen(new ChannelListener(checkContent, null));
133   yield undefined;
134   greaterOrEqual(
135     Date.now() - startTime,
136     200,
137     "Check that timer works properly"
138   );
139   equal(gResponseCounter, 3);
140   equal(g200Counter, 1, "check number of 200 responses");
141   equal(g304Counter, 2, "check number of 304 responses");
143   // Checks that we can trigger the cache open immediately, even if the cache delay is set very high.
144   channel = make_channel("http://localhost:" + PORT + "/rcwn");
145   channel
146     .QueryInterface(Ci.nsIRaceCacheWithNetwork)
147     .test_delayCacheEntryOpeningBy(100000);
148   channel.asyncOpen(new ChannelListener(checkContent, null));
149   do_timeout(50, function () {
150     channel
151       .QueryInterface(Ci.nsIRaceCacheWithNetwork)
152       .test_triggerDelayedOpenCacheEntry();
153   });
154   yield undefined;
155   equal(gResponseCounter, 4);
156   equal(g200Counter, 1, "check number of 200 responses");
157   equal(g304Counter, 3, "check number of 304 responses");
159   // Sets a high delay for the cache fetch, and triggers the network activity.
160   channel = make_channel("http://localhost:" + PORT + "/rcwn");
161   channel
162     .QueryInterface(Ci.nsIRaceCacheWithNetwork)
163     .test_delayCacheEntryOpeningBy(100000);
164   channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
165   channel.asyncOpen(new ChannelListener(checkContent, null));
166   // Trigger network after 50 ms.
167   yield undefined;
168   equal(gResponseCounter, 5);
169   equal(g200Counter, 2, "check number of 200 responses");
170   equal(g304Counter, 3, "check number of 304 responses");
172   // Sets a high delay for the cache fetch, and triggers the network activity.
173   // While the network response is produced, we trigger the cache fetch.
174   // Because the network response was the first, a non-conditional request is sent.
175   channel = make_channel("http://localhost:" + PORT + "/rcwn");
176   channel
177     .QueryInterface(Ci.nsIRaceCacheWithNetwork)
178     .test_delayCacheEntryOpeningBy(100000);
179   channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
180   channel.asyncOpen(new ChannelListener(checkContent, null));
181   yield undefined;
182   equal(gResponseCounter, 6);
183   equal(g200Counter, 3, "check number of 200 responses");
184   equal(g304Counter, 3, "check number of 304 responses");
186   // Triggers cache open before triggering network.
187   channel = make_channel("http://localhost:" + PORT + "/rcwn");
188   channel
189     .QueryInterface(Ci.nsIRaceCacheWithNetwork)
190     .test_delayCacheEntryOpeningBy(100000);
191   channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(100);
192   channel.asyncOpen(new ChannelListener(checkContent, null));
193   channel
194     .QueryInterface(Ci.nsIRaceCacheWithNetwork)
195     .test_triggerDelayedOpenCacheEntry();
196   yield undefined;
197   equal(gResponseCounter, 7);
198   equal(g200Counter, 3, "check number of 200 responses");
199   equal(g304Counter, 4, "check number of 304 responses");
201   // Load the cached handler so we don't need to revalidate
202   channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
203   channel.asyncOpen(new ChannelListener(checkContent, null));
204   yield undefined;
205   equal(gResponseCounter, 8);
206   equal(g200Counter, 4, "check number of 200 responses");
207   equal(g304Counter, 4, "check number of 304 responses");
209   // Make sure response is loaded from the cache, not the network
210   channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
211   channel.asyncOpen(new ChannelListener(checkContent, null));
212   yield undefined;
213   equal(gResponseCounter, 9);
214   equal(g200Counter, 4, "check number of 200 responses");
215   equal(g304Counter, 4, "check number of 304 responses");
217   // Cache times out, so we trigger the network
218   gIsFromCache = 0;
219   channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
220   channel
221     .QueryInterface(Ci.nsIRaceCacheWithNetwork)
222     .test_delayCacheEntryOpeningBy(100000);
223   // trigger network after 50 ms
224   channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
225   channel.asyncOpen(new ChannelListener(checkContent, null));
226   yield undefined;
227   equal(gResponseCounter, 10);
228   equal(gIsFromCache, 0, "should be from the network");
229   equal(g200Counter, 5, "check number of 200 responses");
230   equal(g304Counter, 4, "check number of 304 responses");
232   // Cache callback comes back right after network is triggered.
233   channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
234   channel
235     .QueryInterface(Ci.nsIRaceCacheWithNetwork)
236     .test_delayCacheEntryOpeningBy(55);
237   channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50);
238   channel.asyncOpen(new ChannelListener(checkContent, null));
239   yield undefined;
240   equal(gResponseCounter, 11);
241   info("IsFromCache: " + gIsFromCache + "\n");
242   info("Number of 200 responses: " + g200Counter + "\n");
243   equal(g304Counter, 4, "check number of 304 responses");
245   // Set an increasingly high timeout to trigger opening the cache entry
246   // This way we ensure that some of the entries we will get from the network,
247   // and some we will get from the cache.
248   gIsFromCache = 0;
249   for (var i = 0; i < 50; i++) {
250     channel = make_channel("http://localhost:" + PORT + "/rcwn_cached");
251     channel
252       .QueryInterface(Ci.nsIRaceCacheWithNetwork)
253       .test_delayCacheEntryOpeningBy(i * 100);
254     channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(10);
255     channel.asyncOpen(new ChannelListener(checkContent, null));
256     // This may be racy. The delay was chosen because the distribution of net-cache
257     // results was around 25-25 on my machine.
258     yield undefined;
259   }
261   greater(gIsFromCache, 0, "Some of the responses should be from the cache");
262   less(gIsFromCache, 50, "Some of the responses should be from the net");
264   httpserver.stop(do_test_finished);