/* Tests the Cache-control: stale-while-revalidate response directive. Purpose is to check we perform the background revalidation when the window is set and we hit it. * Make request #1. - response is from the server and version=1 - max-age=1, stale-while-revalidate=9999 * Switch version of the data on the server and prolong the max-age to not let req #3 do a bck reval at the end of the test (prevent leaks/shutdown races.) * Make request #2 in 2 seconds (entry should be expired by that time, but fall into the reval window.) - response is from the cache, version=1 - a new background request should be made for the data * Wait for "http-on-background-revalidation" notifying finish of the background reval. * Make request #3. - response is from the cache, version=2 * Done. */ "use strict"; const { HttpServer } = ChromeUtils.importESModule( "resource://testing-common/httpd.sys.mjs" ); let max_age; let version; let expectCookies = false; let generate_response = ver => `response version=${ver}`; function test_handler(metadata, response) { // ensures initial network request and background revalidation request sends custom headers // See Bug 1893842 if ( !metadata.hasHeader("X-Custom-header") || metadata.getHeader("X-Custom-header") != "custom-value" ) { response.setStatusLine(metadata.httpVersion, 400, "OK"); return; } // Check if the request has a cookie if (expectCookies) { let cookies = metadata.getHeader("Cookie"); if (cookies !== "Custom=CustomValue") { response.setStatusLine(metadata.httpVersion, 400, "Cookies dont match"); return; } } // Set the cookie in the response response.setHeader( "Set-Cookie", "Custom=CustomValue; HttpOnly; Path=/", true ); const originalBody = generate_response(version); response.setHeader("Content-Type", "text/html", false); response.setHeader( "Cache-control", `max-age=${max_age}, stale-while-revalidate=9999`, false ); response.setStatusLine(metadata.httpVersion, 200, "OK"); response.bodyOutputStream.write(originalBody, originalBody.length); } function make_channel(url) { var chan = NetUtil.newChannel({ uri: url, loadUsingSystemPrincipal: true, }).QueryInterface(Ci.nsIHttpChannel); chan.setRequestHeader("X-Custom-header", "custom-value", false); return chan; } async function get_response(channel, fromCache) { return new Promise(resolve => { channel.asyncOpen( new ChannelListener((request, buffer, ctx, isFromCache) => { Assert.equal( fromCache, isFromCache, `got response from cache = ${fromCache}` ); resolve(buffer); }) ); }); } async function sleep(time) { return new Promise(resolve => { do_timeout(time * 1000, resolve); }); } async function stop_server(httpserver) { return new Promise(resolve => { httpserver.stop(resolve); }); } async function background_reval_promise() { return new Promise(resolve => { Services.obs.addObserver(resolve, "http-on-background-revalidation"); }); } add_task(async function () { Services.prefs.setIntPref("network.cookie.cookieBehavior", 0); Services.prefs.setBoolPref( "network.cookieJarSettings.unblocked_for_testing", true ); let httpserver = new HttpServer(); httpserver.registerPathHandler("/testdir", test_handler); httpserver.start(-1); const PORT = httpserver.identity.primaryPort; const URI = `http://localhost:${PORT}/testdir`; let response; version = 1; max_age = 1; response = await get_response(make_channel(URI), false); Assert.equal(response, generate_response(1), "got response ver 1"); expectCookies = true; await sleep(max_age + 1); // must specifically wait for the internal channel to finish the reval to make // the test race-free. let reval_done = background_reval_promise(); version = 2; max_age = 100; response = await get_response(make_channel(URI), true); Assert.equal(response, generate_response(1), "got response ver 1"); await reval_done; response = await get_response(make_channel(URI), true); Assert.equal(response, generate_response(2), "got response ver 2"); await stop_server(httpserver); });