browser_link_hover_speculative_connection.js (12023B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { HttpServer } = ChromeUtils.importESModule( 8 "resource://testing-common/httpd.sys.mjs" 9 ); 10 11 const TEST_PATH = "/browser/netwerk/test/browser/"; 12 13 let gServer; 14 let gServerURL; 15 let gConnectionNumberWhenRequested = 0; 16 17 add_setup(async function () { 18 await SpecialPowers.pushPrefEnv({ 19 set: [ 20 // Enable speculative connections for hover on HTTPS 21 ["network.predictor.enable-hover-on-ssl", true], 22 // Enable network debugging observations 23 ["network.http.debug-observations", true], 24 ], 25 }); 26 27 // Set up local HTTP server for the target page 28 gServer = new HttpServer(); 29 gServer.start(-1); 30 gServerURL = `http://localhost:${gServer.identity.primaryPort}`; 31 32 // Register handler for the target page 33 gServer.registerPathHandler("/target.html", handleTarget); 34 35 registerCleanupFunction(async () => { 36 await gServer.stop(); 37 }); 38 }); 39 40 function handleTarget(request, response) { 41 response.setHeader("Content-Type", "text/html", false); 42 response.setHeader("Cache-Control", "no-cache", false); 43 44 // Record which connection number handled this request 45 gConnectionNumberWhenRequested = response._connection.number; 46 47 let body = ` 48 <!DOCTYPE html> 49 <html> 50 <head> 51 <meta charset="UTF-8"> 52 <title>Target Page</title> 53 </head> 54 <body> 55 <h1>Target Page</h1> 56 <p>Connection: ${gConnectionNumberWhenRequested}</p> 57 </body> 58 </html> 59 `; 60 61 response.write(body); 62 } 63 64 /** 65 * Helper function to simulate hovering over a link element 66 */ 67 function hoverOverLink(browser, linkId) { 68 return SpecialPowers.spawn(browser, [linkId], async id => { 69 let link = content.document.getElementById(id); 70 // Dispatch mouseover event which should trigger the speculative connection 71 let event = new content.MouseEvent("mouseover", { 72 bubbles: true, 73 cancelable: true, 74 view: content, 75 }); 76 link.dispatchEvent(event); 77 }); 78 } 79 80 /** 81 * Helper function to click a link and wait for navigation 82 */ 83 async function clickLink(browser, linkId) { 84 let loadedPromise = BrowserTestUtils.browserLoaded(browser); 85 86 await SpecialPowers.spawn(browser, [linkId], async id => { 87 let link = content.document.getElementById(id); 88 link.click(); 89 }); 90 91 await loadedPromise; 92 } 93 94 add_task(async function test_link_hover_https_page() { 95 let speculativeConnectObserved = false; 96 let observer = { 97 QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), 98 observe(aSubject, aTopic, aData) { 99 if ( 100 aTopic == "speculative-connect-request" && 101 aData.includes("localhost") 102 ) { 103 info("Observed speculative connection request for: " + aData); 104 speculativeConnectObserved = true; 105 } 106 }, 107 }; 108 Services.obs.addObserver(observer, "speculative-connect-request"); 109 110 // Load the test page from HTTPS example.com with the target URL pointing to our local server 111 const targetURL = encodeURIComponent(gServerURL + "/target.html"); 112 const pageURL = 113 "https://example.com" + 114 TEST_PATH + 115 "file_link_hover.sjs?target=" + 116 targetURL; 117 118 await BrowserTestUtils.withNewTab( 119 { 120 gBrowser, 121 url: pageURL, 122 waitForLoad: true, 123 }, 124 async function (browser) { 125 // Record the current connection count before hovering 126 let connectionCountBeforeHover = gServer.connectionNumber; 127 info("Connection count before hover: " + connectionCountBeforeHover); 128 129 // Wait for the link element to be available in the page 130 await SpecialPowers.spawn(browser, [], async () => { 131 await ContentTaskUtils.waitForCondition( 132 () => content.document.getElementById("testLink"), 133 "Waiting for link element to be available" 134 ); 135 }); 136 137 // Hover over the link to trigger speculative connection 138 await hoverOverLink(browser, "testLink"); 139 140 // Wait for the speculative connection to be fully established 141 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 142 await new Promise(resolve => setTimeout(resolve, 1000)); 143 144 // Check connection count after hover 145 let connectionCountAfterHover = gServer.connectionNumber; 146 info("Connection count after hover: " + connectionCountAfterHover); 147 148 // Verify that a speculative connection request was observed 149 Assert.ok( 150 speculativeConnectObserved, 151 "Speculative connection should be triggered on link hover" 152 ); 153 154 // Now click the link - it should use the speculative connection 155 await clickLink(browser, "testLink"); 156 157 // Check connection count after click 158 let connectionCountAfterClick = gServer.connectionNumber; 159 info("Connection count after click: " + connectionCountAfterClick); 160 info( 161 "Connection number that handled the request: " + 162 gConnectionNumberWhenRequested 163 ); 164 165 // Verify that exactly one NEW connection was established 166 Assert.equal( 167 connectionCountAfterClick, 168 connectionCountBeforeHover + 1, 169 "Exactly one connection should be established for the HTTP request" 170 ); 171 172 // Verify that the request was handled by the new connection 173 Assert.equal( 174 gConnectionNumberWhenRequested, 175 connectionCountBeforeHover + 1, 176 "The HTTP request should be handled by the new connection" 177 ); 178 } 179 ); 180 181 Services.obs.removeObserver(observer, "speculative-connect-request"); 182 }); 183 184 add_task(async function test_link_hover_http_page() { 185 let speculativeConnectObserved = false; 186 let observer = { 187 QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), 188 observe(aSubject, aTopic, aData) { 189 if ( 190 aTopic == "speculative-connect-request" && 191 aData.includes("localhost") 192 ) { 193 info("Observed speculative connection request for: " + aData); 194 speculativeConnectObserved = true; 195 } 196 }, 197 }; 198 Services.obs.addObserver(observer, "speculative-connect-request"); 199 200 // Load the test page from HTTP example.com with the target URL pointing to our local server 201 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 202 const targetURL = encodeURIComponent(gServerURL + "/target.html"); 203 const pageURL = 204 // eslint-disable-next-line @microsoft/sdl/no-insecure-url 205 "http://example.com" + 206 TEST_PATH + 207 "file_link_hover.sjs?target=" + 208 targetURL; 209 210 await BrowserTestUtils.withNewTab( 211 { 212 gBrowser, 213 url: pageURL, 214 waitForLoad: true, 215 }, 216 async function (browser) { 217 // Record the current connection count before hovering 218 // This should be 0 since we haven't connected to our server yet 219 let connectionCountBeforeHover = gServer.connectionNumber; 220 info("Connection count before hover: " + connectionCountBeforeHover); 221 222 // Wait for the link element to be available in the page 223 await SpecialPowers.spawn(browser, [], async () => { 224 await ContentTaskUtils.waitForCondition( 225 () => content.document.getElementById("testLink"), 226 "Waiting for link element to be available" 227 ); 228 }); 229 230 // Hover over the link to trigger speculative connection 231 await hoverOverLink(browser, "testLink"); 232 233 // Wait for the speculative connection to be fully established 234 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 235 await new Promise(resolve => setTimeout(resolve, 1000)); 236 237 // Check connection count after hover 238 let connectionCountAfterHover = gServer.connectionNumber; 239 info("Connection count after hover: " + connectionCountAfterHover); 240 241 // Verify that a speculative connection request was observed 242 Assert.ok( 243 speculativeConnectObserved, 244 "Speculative connection should be triggered on link hover from HTTP page" 245 ); 246 247 // Now click the link - it should use the speculative connection 248 await clickLink(browser, "testLink"); 249 250 // Check connection count after click 251 let connectionCountAfterClick = gServer.connectionNumber; 252 info("Connection count after click: " + connectionCountAfterClick); 253 info( 254 "Connection number that handled the request: " + 255 gConnectionNumberWhenRequested 256 ); 257 258 // Verify that exactly one NEW connection was established 259 Assert.equal( 260 connectionCountAfterClick, 261 connectionCountBeforeHover + 1, 262 "Exactly one connection should be established for the HTTP request" 263 ); 264 265 // Verify that the request was handled by the new connection 266 Assert.equal( 267 gConnectionNumberWhenRequested, 268 connectionCountBeforeHover + 1, 269 "The HTTP request should be handled by the new connection" 270 ); 271 } 272 ); 273 274 Services.obs.removeObserver(observer, "speculative-connect-request"); 275 }); 276 277 add_task(async function test_link_hover_https_page_pref_disabled() { 278 // Disable the pref for speculative connections on HTTPS 279 await SpecialPowers.pushPrefEnv({ 280 set: [["network.predictor.enable-hover-on-ssl", false]], 281 }); 282 283 let speculativeConnectObserved = false; 284 let observer = { 285 QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), 286 observe(aSubject, aTopic, aData) { 287 if ( 288 aTopic == "speculative-connect-request" && 289 aData.includes("localhost") 290 ) { 291 info("Observed speculative connection request for: " + aData); 292 speculativeConnectObserved = true; 293 } 294 }, 295 }; 296 Services.obs.addObserver(observer, "speculative-connect-request"); 297 298 // Load the test page from HTTPS example.com with the target URL pointing to our local server 299 const targetURL = encodeURIComponent(gServerURL + "/target.html"); 300 const pageURL = 301 "https://example.com" + 302 TEST_PATH + 303 "file_link_hover.sjs?target=" + 304 targetURL; 305 306 await BrowserTestUtils.withNewTab( 307 { 308 gBrowser, 309 url: pageURL, 310 waitForLoad: true, 311 }, 312 async function (browser) { 313 // Record the current connection count before hovering 314 let connectionCountBeforeHover = gServer.connectionNumber; 315 info("Connection count before hover: " + connectionCountBeforeHover); 316 317 // Wait for the link element to be available in the page 318 await SpecialPowers.spawn(browser, [], async () => { 319 await ContentTaskUtils.waitForCondition( 320 () => content.document.getElementById("testLink"), 321 "Waiting for link element to be available" 322 ); 323 }); 324 325 // Hover over the link 326 await hoverOverLink(browser, "testLink"); 327 328 // Wait a bit to see if any speculative connection would be triggered 329 // eslint-disable-next-line mozilla/no-arbitrary-setTimeout 330 await new Promise(resolve => setTimeout(resolve, 1000)); 331 332 // Check connection count after hover 333 let connectionCountAfterHover = gServer.connectionNumber; 334 info("Connection count after hover: " + connectionCountAfterHover); 335 336 // Verify that NO speculative connection request was observed 337 Assert.ok( 338 !speculativeConnectObserved, 339 "Speculative connection should NOT be triggered on link hover from HTTPS page when pref is disabled" 340 ); 341 342 // Now click the link - it will create a new connection 343 await clickLink(browser, "testLink"); 344 345 // Check connection count after click 346 let connectionCountAfterClick = gServer.connectionNumber; 347 info("Connection count after click: " + connectionCountAfterClick); 348 349 // Verify that exactly one NEW connection was created (by the click, not hover) 350 Assert.equal( 351 connectionCountAfterClick, 352 connectionCountBeforeHover + 1, 353 "Exactly one connection should be established (from the click, not hover)" 354 ); 355 } 356 ); 357 358 Services.obs.removeObserver(observer, "speculative-connect-request"); 359 360 // Restore the pref 361 await SpecialPowers.popPrefEnv(); 362 });