browser_networkobserver_override.js (10786B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 requestLongerTimeout(3); 6 const TEST_URL = URL_ROOT + "doc_network-observer.html"; 7 const TEST_URL_CSP = URL_ROOT + "override_script_src_self.html"; 8 const REQUEST_URL = 9 URL_ROOT + `sjs_network-observer-test-server.sjs?sts=200&fmt=js`; 10 const CORS_REQUEST_URL = REQUEST_URL.replace("example.com", "plop.example.com"); 11 const CSP_SCRIPT_TO_OVERRIDE = URL_ROOT + "csp_script_to_override.js"; 12 const GZIPPED_REQUEST_URL = URL_ROOT + `gzipped.sjs`; 13 const OVERRIDE_FILENAME = "override.js"; 14 const OVERRIDE_HTML_FILENAME = "override.html"; 15 16 add_task(async function testLocalOverride() { 17 await addTab(TEST_URL); 18 19 let eventsCount = 0; 20 const networkObserver = new NetworkObserver({ 21 ignoreChannelFunction: channel => 22 ![REQUEST_URL, CORS_REQUEST_URL].includes(channel.URI.spec), 23 onNetworkEvent: event => { 24 info("received a network event"); 25 eventsCount++; 26 return createNetworkEventOwner(event); 27 }, 28 }); 29 30 const overrideFile = getChromeDir(getResolvedURI(gTestPath)); 31 overrideFile.append(OVERRIDE_FILENAME); 32 info(" override " + REQUEST_URL + " to " + overrideFile.path + "\n"); 33 networkObserver.override(REQUEST_URL, overrideFile.path); 34 35 info("Assert that request and cached request are overriden"); 36 await SpecialPowers.spawn( 37 gBrowser.selectedBrowser, 38 [REQUEST_URL], 39 async _url => { 40 const response = await content.wrappedJSObject.fetch(_url); 41 const responsecontent = await response.text(); 42 is( 43 responsecontent, 44 `"use strict";\ndocument.title = "Override script loaded";\n`, 45 "the response content has been overriden" 46 ); 47 const secondResponse = await content.wrappedJSObject.fetch(_url); 48 const secondResponsecontent = await secondResponse.text(); 49 is( 50 secondResponsecontent, 51 `"use strict";\ndocument.title = "Override script loaded";\n`, 52 "the cached response content has been overriden" 53 ); 54 } 55 ); 56 57 info("Assert that JS scripts can be overriden"); 58 await SpecialPowers.spawn( 59 gBrowser.selectedBrowser, 60 [REQUEST_URL], 61 async _url => { 62 const script = content.document.createElement("script"); 63 const onLoad = new Promise(resolve => 64 script.addEventListener("load", resolve, { once: true }) 65 ); 66 script.src = _url; 67 content.document.body.appendChild(script); 68 await onLoad; 69 is( 70 content.document.title, 71 "Override script loaded", 72 "The <script> tag content has been overriden and correctly evaluated" 73 ); 74 } 75 ); 76 77 info(`Assert that JS scripts with crossorigin="anonymous" can be overriden`); 78 networkObserver.override(CORS_REQUEST_URL, overrideFile.path); 79 80 await SpecialPowers.spawn( 81 gBrowser.selectedBrowser, 82 [CORS_REQUEST_URL], 83 async _url => { 84 content.document.title = "title before crossorigin=anonymous evaluation"; 85 const script = content.document.createElement("script"); 86 script.setAttribute("crossorigin", "anonymous"); 87 script.crossOrigin = "anonymous"; 88 const onLoad = new Promise(resolve => 89 script.addEventListener("load", resolve, { once: true }) 90 ); 91 script.src = _url; 92 content.document.body.appendChild(script); 93 await onLoad; 94 is( 95 content.document.title, 96 "Override script loaded", 97 `The <script crossorigin="anonymous"> tag content has been overriden and correctly evaluated` 98 ); 99 } 100 ); 101 102 await BrowserTestUtils.waitForCondition(() => eventsCount >= 1); 103 104 info( 105 `Assert that requests which would require a CORS preflight can be overridden` 106 ); 107 await SpecialPowers.spawn( 108 gBrowser.selectedBrowser, 109 [CORS_REQUEST_URL], 110 async _url => { 111 // NOTE: This is intentionally using `content.fetch` and not `content.wrappedJSObject.fetch`, 112 // otherwise the `LoadRequireCORSPreflight` flag is not set for the request. 113 const response = await content.fetch(_url, { 114 // Use a extra header to force a CORS preflight. 115 headers: { "X-PINGOTHER": "pingpong" }, 116 }); 117 const responsecontent = await response.text(); 118 is(response.status, 200); 119 is( 120 responsecontent, 121 `"use strict";\ndocument.title = "Override script loaded";\n`, 122 "the content for the CORS (with preflight) request has been overriden" 123 ); 124 } 125 ); 126 127 await BrowserTestUtils.waitForCondition(() => eventsCount >= 2); 128 networkObserver.destroy(); 129 }); 130 131 add_task(async function testHtmlFileOverride() { 132 let eventsCount = 0; 133 const networkObserver = new NetworkObserver({ 134 ignoreChannelFunction: channel => channel.URI.spec !== TEST_URL, 135 onNetworkEvent: event => { 136 info("received a network event"); 137 eventsCount++; 138 return createNetworkEventOwner(event); 139 }, 140 }); 141 142 const overrideFile = getChromeDir(getResolvedURI(gTestPath)); 143 overrideFile.append(OVERRIDE_HTML_FILENAME); 144 info(" override " + TEST_URL + " to " + overrideFile.path + "\n"); 145 networkObserver.override(TEST_URL, overrideFile.path); 146 147 await addTab(TEST_URL); 148 await SpecialPowers.spawn( 149 gBrowser.selectedBrowser, 150 [TEST_URL], 151 async pageUrl => { 152 is( 153 content.document.documentElement.outerHTML, 154 "<html><head></head><body>Overriden!\n</body></html>", 155 "The content of the HTML has been overriden" 156 ); 157 is( 158 content.location.href, 159 pageUrl, 160 "The location of the page is still the original one" 161 ); 162 } 163 ); 164 await BrowserTestUtils.waitForCondition(() => eventsCount >= 1); 165 networkObserver.destroy(); 166 }); 167 168 // Exact same test, but with a gzipped request, which requires very special treatment 169 add_task(async function testLocalOverrideGzipped() { 170 await addTab(TEST_URL); 171 172 let eventsCount = 0; 173 const networkObserver = new NetworkObserver({ 174 ignoreChannelFunction: channel => channel.URI.spec !== GZIPPED_REQUEST_URL, 175 onNetworkEvent: event => { 176 info("received a network event"); 177 eventsCount++; 178 return createNetworkEventOwner(event); 179 }, 180 }); 181 182 const overrideFile = getChromeDir(getResolvedURI(gTestPath)); 183 overrideFile.append(OVERRIDE_FILENAME); 184 info(" override " + GZIPPED_REQUEST_URL + " to " + overrideFile.path + "\n"); 185 networkObserver.override(GZIPPED_REQUEST_URL, overrideFile.path); 186 187 await SpecialPowers.spawn( 188 gBrowser.selectedBrowser, 189 [GZIPPED_REQUEST_URL], 190 async _url => { 191 const response = await content.wrappedJSObject.fetch(_url); 192 const responsecontent = await response.text(); 193 is( 194 responsecontent, 195 `"use strict";\ndocument.title = "Override script loaded";\n`, 196 "the response content for the gzipped script has been overriden" 197 ); 198 const secondResponse = await content.wrappedJSObject.fetch(_url); 199 const secondResponsecontent = await secondResponse.text(); 200 is( 201 secondResponsecontent, 202 `"use strict";\ndocument.title = "Override script loaded";\n`, 203 "the cached response content for the gzipped script has been overriden" 204 ); 205 } 206 ); 207 208 await SpecialPowers.spawn( 209 gBrowser.selectedBrowser, 210 [GZIPPED_REQUEST_URL], 211 async _url => { 212 const script = content.document.createElement("script"); 213 const onLoad = new Promise(resolve => 214 script.addEventListener("load", resolve, { once: true }) 215 ); 216 script.src = _url; 217 content.document.body.appendChild(script); 218 await onLoad; 219 is( 220 content.document.title, 221 "Override script loaded", 222 "The <script> tag content for the gzipped script has been overriden and correctly evaluated" 223 ); 224 } 225 ); 226 227 await BrowserTestUtils.waitForCondition(() => eventsCount >= 1); 228 229 networkObserver.destroy(); 230 }); 231 232 // Check that the override works even if the page uses script 'self' as CSP. 233 add_task(async function testLocalOverrideCSP() { 234 await addTab(TEST_URL_CSP); 235 236 const url = CSP_SCRIPT_TO_OVERRIDE; 237 const browser = gBrowser.selectedBrowser; 238 const originalText = await getResponseText(url, browser); 239 is( 240 originalText, 241 `"use strict";\ndocument.title = "CSP script to override loaded";\n`, 242 "the response content for the CSP script is the original one" 243 ); 244 245 let eventsCount = 0; 246 const networkObserver = new NetworkObserver({ 247 ignoreChannelFunction: channel => channel.URI.spec !== url, 248 onNetworkEvent: event => { 249 info("received a network event"); 250 eventsCount++; 251 return createNetworkEventOwner(event); 252 }, 253 }); 254 255 const overrideFile = getChromeDir(getResolvedURI(gTestPath)); 256 overrideFile.append(OVERRIDE_FILENAME); 257 info(" override " + url + " to " + overrideFile.path + "\n"); 258 networkObserver.override(url, overrideFile.path); 259 260 const overriddenText = await getResponseText(url, browser); 261 is( 262 overriddenText, 263 `"use strict";\ndocument.title = "Override script loaded";\n`, 264 "the response content for the CSP script has been overriden" 265 ); 266 const cachedOverriddenText = await getResponseText(url, browser); 267 is( 268 cachedOverriddenText, 269 `"use strict";\ndocument.title = "Override script loaded";\n`, 270 "the cached response content for the CSP script has been overriden" 271 ); 272 273 await SpecialPowers.spawn(browser, [url], async _url => { 274 const script = content.document.createElement("script"); 275 const onLoad = new Promise(resolve => 276 script.addEventListener("load", resolve, { once: true }) 277 ); 278 script.src = _url; 279 content.document.body.appendChild(script); 280 await onLoad; 281 is( 282 content.document.title, 283 "Override script loaded", 284 "The <script> tag content for the CSP script has been overriden and correctly evaluated" 285 ); 286 }); 287 288 await BrowserTestUtils.waitForCondition(() => eventsCount >= 1); 289 290 info("Remove the override for " + url); 291 networkObserver.removeOverride(url); 292 const restoredText = await getResponseText(url, browser); 293 is( 294 restoredText, 295 `"use strict";\ndocument.title = "CSP script to override loaded";\n`, 296 "the response content for the CSP script is back to the original one" 297 ); 298 299 networkObserver.destroy(); 300 }); 301 302 /** 303 * Retrieve the text content for a request to the provided url, as fetched by 304 * the provided browser. 305 * 306 * @param {string} url 307 * The URL of the request to fetch. 308 * @param {Browser} browser 309 * The content browser where the request should be fetched. 310 * @returns {string} 311 * The text content of the fetch request. 312 */ 313 async function getResponseText(url, browser) { 314 return SpecialPowers.spawn(browser, [url], async _url => { 315 const response = await content.wrappedJSObject.fetch(_url); 316 return await response.text(); 317 }); 318 }