head.js (6265B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 ChromeUtils.defineESModuleGetters(this, { 7 NetworkHelper: 8 "resource://devtools/shared/network-observer/NetworkHelper.sys.mjs", 9 NetworkObserver: 10 "resource://devtools/shared/network-observer/NetworkObserver.sys.mjs", 11 NetworkUtils: 12 "resource://devtools/shared/network-observer/NetworkUtils.sys.mjs", 13 }); 14 15 const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/")); 16 const CHROME_URL_ROOT = TEST_DIR + "/"; 17 const URL_ROOT = CHROME_URL_ROOT.replace( 18 "chrome://mochitests/content/", 19 "https://example.com/" 20 ); 21 22 /** 23 * Load the provided url in an existing browser. 24 * Returns a promise which will resolve when the page is loaded. 25 * 26 * @param {Browser} browser 27 * The browser element where the URL should be loaded. 28 * @param {string} url 29 * The URL to load in the new tab 30 */ 31 async function loadURL(browser, url) { 32 const loaded = BrowserTestUtils.browserLoaded(browser); 33 BrowserTestUtils.startLoadingURIString(browser, url); 34 return loaded; 35 } 36 37 /** 38 * Create a new foreground tab loading the provided url. 39 * Returns a promise which will resolve when the page is loaded. 40 * 41 * @param {string} url 42 * The URL to load in the new tab 43 */ 44 async function addTab(url) { 45 const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url); 46 registerCleanupFunction(() => { 47 gBrowser.removeTab(tab); 48 }); 49 return tab; 50 } 51 52 /** 53 * Base network event owner class implementing all mandatory callbacks and 54 * keeping track of which callbacks have been called. 55 */ 56 class NetworkEventOwner { 57 hasCacheDetails = false; 58 hasEventTimings = false; 59 hasRawHeaders = false; 60 hasResponseCache = false; 61 hasResponseContent = false; 62 hasResponseContentComplete = false; 63 hasResponseStart = false; 64 hasSecurityInfo = false; 65 hasServerTimings = false; 66 67 addCacheDetails() { 68 this.hasCacheDetails = true; 69 } 70 addEventTimings() { 71 this.hasEventTimings = true; 72 } 73 addRawHeaders() { 74 this.hasRawHeaders = true; 75 } 76 addResponseCache() { 77 this.hasResponseCache = true; 78 } 79 addResponseContent() { 80 this.hasResponseContent = true; 81 } 82 addResponseContentComplete() { 83 this.hasResponseContentComplete = true; 84 } 85 addResponseStart() { 86 this.hasResponseStart = true; 87 } 88 addSecurityInfo() { 89 this.hasSecurityInfo = true; 90 } 91 addServerTimings() { 92 this.hasServerTimings = true; 93 } 94 addServiceWorkerTimings() { 95 this.hasServiceWorkerTimings = true; 96 } 97 } 98 99 /** 100 * Create a simple network event owner, with mock implementations of all 101 * the expected APIs for a NetworkEventOwner. 102 */ 103 function createNetworkEventOwner() { 104 return new NetworkEventOwner(); 105 } 106 107 /** 108 * Wait for network events matching the provided URL, until the count reaches 109 * the provided expected count. 110 * 111 * @param {string|null} expectedUrl 112 * The URL which should be monitored by the NetworkObserver.If set to null watch for 113 * all requests 114 * @param {number} expectedRequestsCount 115 * How many different events (requests) are expected. 116 * @returns {Promise} 117 * A promise which will resolve with an array of network event owners, when 118 * the expected event count is reached. 119 */ 120 async function waitForNetworkEvents(expectedUrl = null, expectedRequestsCount) { 121 const events = []; 122 const networkObserver = new NetworkObserver({ 123 ignoreChannelFunction: channel => 124 expectedUrl ? channel.URI.spec !== expectedUrl : false, 125 onNetworkEvent: () => { 126 info("waitForNetworkEvents received a new event"); 127 const owner = createNetworkEventOwner(); 128 events.push(owner); 129 return owner; 130 }, 131 }); 132 registerCleanupFunction(() => networkObserver.destroy()); 133 134 info("Wait until the events count reaches " + expectedRequestsCount); 135 await BrowserTestUtils.waitForCondition( 136 () => events.length >= expectedRequestsCount 137 ); 138 return events; 139 } 140 141 /** 142 * NetworkEventOwner class which can be used to assert the response content of 143 * a network event. 144 */ 145 class ResponseContentOwner extends NetworkEventOwner { 146 addResponseContent(response) { 147 super.addResponseContent(); 148 this.compressionEncodings = response.compressionEncodings; 149 this.contentCharset = response.contentCharset; 150 this.decodedBodySize = response.decodedBodySize; 151 this.encodedBodySize = response.encodedBodySize; 152 this.encodedData = response.encodedData; 153 this.encoding = response.encoding; 154 this.isContentEncoded = response.isContentEncoded; 155 this.text = response.text; 156 } 157 158 addResponseContentComplete({ truncated }) { 159 super.addResponseContentComplete(); 160 this.truncated = truncated; 161 } 162 163 /** 164 * Simple helper to decode the content of a response from a network event. 165 */ 166 async getDecodedContent() { 167 if (!this.isContentEncoded) { 168 // If the content is not encoded we can directly return the text property. 169 return this.text; 170 } 171 172 // Otherwise call the dedicated NetworkUtils decodeResponseChunks helper. 173 return NetworkUtils.decodeResponseChunks(this.encodedData, { 174 charset: this.contentCharset, 175 compressionEncodings: this.compressionEncodings, 176 encodedBodySize: this.encodedBodySize, 177 encoding: this.encoding, 178 }); 179 } 180 } 181 182 /** 183 * Helper to compress a string using gzip. 184 */ 185 function gzipCompressString(string) { 186 return new Promise(resolve => { 187 const observer = { 188 onStreamComplete(loader, context, status, length, result) { 189 resolve(String.fromCharCode.apply(this, result)); 190 }, 191 }; 192 193 const scs = Cc["@mozilla.org/streamConverters;1"].getService( 194 Ci.nsIStreamConverterService 195 ); 196 const listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance( 197 Ci.nsIStreamLoader 198 ); 199 listener.init(observer); 200 const converter = scs.asyncConvertData( 201 "uncompressed", 202 "gzip", 203 listener, 204 null 205 ); 206 const stringStream = Cc[ 207 "@mozilla.org/io/string-input-stream;1" 208 ].createInstance(Ci.nsIStringInputStream); 209 stringStream.setByteStringData(string); 210 converter.onStartRequest(null, null); 211 converter.onDataAvailable(null, stringStream, 0, string.length); 212 converter.onStopRequest(null, null, null); 213 }); 214 }