test_race_cache_with_network.js (10000B)
1 /* Any copyright is dedicated to the Public Domain. 2 * http://creativecommons.org/publicdomain/zero/1.0/ 3 */ 4 5 "use strict"; 6 7 const { HttpServer } = ChromeUtils.importESModule( 8 "resource://testing-common/httpd.sys.mjs" 9 ); 10 11 var httpserver = new HttpServer(); 12 httpserver.start(-1); 13 const PORT = httpserver.identity.primaryPort; 14 15 function make_channel(url) { 16 return NetUtil.newChannel({ 17 uri: url, 18 loadUsingSystemPrincipal: true, 19 }).QueryInterface(Ci.nsIHttpChannel); 20 } 21 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"); 29 30 let etag; 31 try { 32 etag = metadata.getHeader("If-None-Match"); 33 } catch (ex) { 34 etag = ""; 35 } 36 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 } 45 } 46 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"); 51 52 response.setStatusLine(metadata.httpVersion, 200, "OK"); 53 response.bodyOutputStream.write(gResponseBody, gResponseBody.length); 54 55 g200Counter++; 56 } 57 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 }); 74 } 75 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(); 86 } 87 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 ); 102 103 registerCleanupFunction(function () { 104 Services.prefs.clearUserPref("privacy.reduceTimerPrecision"); 105 Services.prefs.clearUserPref( 106 "privacy.resistFingerprinting.reduceTimerPrecision.microseconds" 107 ); 108 }); 109 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"); 117 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"); 125 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"); 142 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"); 158 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"); 171 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"); 185 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(5000); 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( 199 g200Counter, 200 3, 201 `check number of 200 responses | 200: ${g200Counter}, 304: ${g304Counter}` 202 ); 203 equal( 204 g304Counter, 205 4, 206 `check number of 304 responses | 200: ${g200Counter}, 304: ${g304Counter}` 207 ); 208 209 // Load the cached handler so we don't need to revalidate 210 channel = make_channel("http://localhost:" + PORT + "/rcwn_cached"); 211 channel.asyncOpen(new ChannelListener(checkContent, null)); 212 yield undefined; 213 equal(gResponseCounter, 8); 214 equal(g200Counter, 4, "check number of 200 responses"); 215 equal(g304Counter, 4, "check number of 304 responses"); 216 217 // Make sure response is loaded from the cache, not the network 218 channel = make_channel("http://localhost:" + PORT + "/rcwn_cached"); 219 channel.asyncOpen(new ChannelListener(checkContent, null)); 220 yield undefined; 221 equal(gResponseCounter, 9); 222 equal(g200Counter, 4, "check number of 200 responses"); 223 equal(g304Counter, 4, "check number of 304 responses"); 224 225 // Cache times out, so we trigger the network 226 gIsFromCache = 0; 227 channel = make_channel("http://localhost:" + PORT + "/rcwn_cached"); 228 channel 229 .QueryInterface(Ci.nsIRaceCacheWithNetwork) 230 .test_delayCacheEntryOpeningBy(100000); 231 // trigger network after 50 ms 232 channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50); 233 channel.asyncOpen(new ChannelListener(checkContent, null)); 234 yield undefined; 235 equal(gResponseCounter, 10); 236 equal(gIsFromCache, 0, "should be from the network"); 237 equal(g200Counter, 5, "check number of 200 responses"); 238 equal(g304Counter, 4, "check number of 304 responses"); 239 240 // Cache callback comes back right after network is triggered. 241 channel = make_channel("http://localhost:" + PORT + "/rcwn_cached"); 242 channel 243 .QueryInterface(Ci.nsIRaceCacheWithNetwork) 244 .test_delayCacheEntryOpeningBy(55); 245 channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(50); 246 channel.asyncOpen(new ChannelListener(checkContent, null)); 247 yield undefined; 248 equal(gResponseCounter, 11); 249 info("IsFromCache: " + gIsFromCache + "\n"); 250 info("Number of 200 responses: " + g200Counter + "\n"); 251 equal(g304Counter, 4, "check number of 304 responses"); 252 253 // Set an increasingly high timeout to trigger opening the cache entry 254 // This way we ensure that some of the entries we will get from the network, 255 // and some we will get from the cache. 256 gIsFromCache = 0; 257 for (var i = 0; i < 50; i++) { 258 channel = make_channel("http://localhost:" + PORT + "/rcwn_cached"); 259 channel 260 .QueryInterface(Ci.nsIRaceCacheWithNetwork) 261 .test_delayCacheEntryOpeningBy(i * 100); 262 channel.QueryInterface(Ci.nsIRaceCacheWithNetwork).test_triggerNetwork(10); 263 channel.asyncOpen(new ChannelListener(checkContent, null)); 264 // This may be racy. The delay was chosen because the distribution of net-cache 265 // results was around 25-25 on my machine. 266 yield undefined; 267 } 268 269 greater(gIsFromCache, 0, "Some of the responses should be from the cache"); 270 less(gIsFromCache, 50, "Some of the responses should be from the net"); 271 272 httpserver.stop(do_test_finished); 273 }