1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 /** 6 * @fileoverview measure_page_load_time.js implements a Firefox extension 7 * for measuring how long a page takes to load. It waits on TCP port 8 * 42492 for connections, then accepts URLs and returns strings of the 9 * form url,time, where "time" is the load time in milliseconds or the 10 * string "timeout" or "error". Load time is measured from the call to 11 * loadURI until the load event fires, or until the status changes to 12 * STATUS_STOP if the load event doesn't fire (there's an error.) 13 * @author jhaas (a] google.com (Jonathan Haas) */ 14 15 // Shorthand reference to nsIWebProgress[Listener] interfaces 16 var IWP = Components.interfaces.nsIWebProgress; 17 var IWPL = Components.interfaces.nsIWebProgressListener; 18 19 20 var MPLT = { 21 /** 22 * Constants 23 */ 24 PORT_NUMBER : 42492, // port to listen for connections on 25 TIME_OUT : 4 * 60 * 1000, // timeout in 4 minutes 26 27 /** 28 * Incoming URL buffer 29 * @type {string} 30 */ 31 textBuffer : '', 32 33 /** 34 * URL we're currently visiting 35 * @type {string} 36 */ 37 URL : '', 38 39 /** 40 * Listener to accept incoming connections 41 * @type {nsIServerSocketListener} 42 */ 43 acceptListener : 44 { 45 onSocketAccepted : function(serverSocket, transport) 46 { 47 MPLT.streamInput = transport.openInputStream(0,0,0); 48 MPLT.streamOutput = transport.openOutputStream(0,0,0); 49 50 MPLT.scriptStream = Components.classes['@mozilla.org/scriptableinputstream;1'] 51 .createInstance(Components.interfaces.nsIScriptableInputStream); 52 MPLT.scriptStream.init(MPLT.streamInput); 53 MPLT.pump = Components.classes['@mozilla.org/network/input-stream-pump;1'] 54 .createInstance(Components.interfaces.nsIInputStreamPump); 55 MPLT.pump.init(MPLT.streamInput, -1, -1, 0, 0, false); 56 MPLT.pump.asyncRead(MPLT.dataListener,null); 57 }, 58 59 onStopListening : function(){} 60 }, 61 62 /** 63 * Listener for network input 64 * @type {nsIStreamListener} 65 */ 66 dataListener : 67 { 68 onStartRequest: function(){}, 69 onStopRequest: function(){}, 70 onDataAvailable: function(request, context, inputStream, offset, count){ 71 // Add the received data to the buffer, then process it 72 // Change CRLF to newline while we're at it 73 MPLT.textBuffer += MPLT.scriptStream.read(count).replace('\r\n', '\n'); 74 75 MPLT.process(); 76 } 77 }, 78 79 /** 80 * Process the incoming data buffer 81 */ 82 process : function() 83 { 84 // If we're waiting for a page to finish loading, wait 85 if (MPLT.timeLoadStarted) 86 return; 87 88 // Look for a carriage return 89 var firstCR = MPLT.textBuffer.indexOf('\n'); 90 91 // If we haven't received a carriage return yet, wait 92 if (firstCR < 0) 93 return; 94 95 // If the first character was a carriage return, we're done! 96 if (firstCR == 0) { 97 MPLT.textBuffer = ''; 98 MPLT.streamInput.close(); 99 MPLT.streamOutput.close(); 100 101 return; 102 } 103 104 // Remove the URL from the buffer 105 MPLT.URL = MPLT.textBuffer.substr(0, firstCR); 106 MPLT.textBuffer = MPLT.textBuffer.substr(firstCR + 1); 107 108 // Remember the current time and navigate to the new URL 109 MPLT.timeLoadStarted = new Date(); 110 gBrowser.loadURIWithFlags(MPLT.URL, gBrowser.LOAD_FLAGS_BYPASS_CACHE); 111 setTimeout('MPLT.onTimeOut()', MPLT.TIME_OUT); 112 }, 113 114 /** 115 * Page load completion handler 116 */ 117 onPageLoad : function(e) { 118 // Ignore loads of non-HTML documents 119 if (!(e.originalTarget instanceof HTMLDocument)) 120 return; 121 122 // Also ignore subframe loads 123 if (e.originalTarget.defaultView.frameElement) 124 return; 125 126 clearTimeout(); 127 var timeElapsed = new Date() - MPLT.timeLoadStarted; 128 129 MPLT.outputResult(timeElapsed); 130 }, 131 132 /** 133 * Timeout handler 134 */ 135 onTimeOut : function() { 136 gBrowser.stop(); 137 138 MPLT.outputResult('timeout'); 139 }, 140 141 142 /** 143 * Sends a properly-formatted result to the client 144 * @param {string} result The value to send along with the URL 145 */ 146 outputResult : function(result) { 147 148 if (MPLT.URL) { 149 var outputString = MPLT.URL + ',' + result + '\n'; 150 MPLT.streamOutput.write(outputString, outputString.length); 151 MPLT.URL = ''; 152 } 153 154 MPLT.timeLoadStarted = null; 155 MPLT.process(); 156 }, 157 158 /** 159 * Time the page load started. If null, we're waiting for the 160 * initial page load, or otherwise don't care about the page 161 * that's currently loading 162 * @type {number} 163 */ 164 timeLoadStarted : null, 165 166 /* 167 * TODO(jhaas): add support for nsIWebProgressListener 168 * If the URL being visited died as part of a network error 169 * (host not found, connection reset by peer, etc), the onload 170 * event doesn't fire. The only way to catch it would be in 171 * a web progress listener. However, nsIWebProgress is not 172 * behaving according to documentation. More research is needed. 173 * For now, omitting it means that if any of our URLs are "dirty" 174 * (do not point to real web servers with real responses), we'll log 175 * them as timeouts. This doesn't affect pages where the server 176 * exists but returns an error code. 177 */ 178 179 /** 180 * Initialize the plugin, create the socket and listen 181 */ 182 initialize: function() { 183 // Register for page load events 184 gBrowser.addEventListener('load', this.onPageLoad, true); 185 186 // Set a timeout to wait for the initial page to load 187 MPLT.timeLoadStarted = new Date(); 188 setTimeout('MPLT.onTimeOut()', MPLT.TIME_OUT); 189 190 // Create the listening socket 191 MPLT.serverSocket = Components.classes['@mozilla.org/network/server-socket;1'] 192 .createInstance(Components.interfaces.nsIServerSocket); 193 194 MPLT.serverSocket.init(MPLT.PORT_NUMBER, true, 1); 195 MPLT.serverSocket.asyncListen(this.acceptListener); 196 }, 197 198 /** 199 * Close the socket(s) 200 */ 201 deinitialize: function() { 202 if (MPLT.streamInput) MPLT.streamInput.close(); 203 if (MPLT.streamOutput) MPLT.streamOutput.close(); 204 if (MPLT.serverSocket) MPLT.serverSocket.close(); 205 } 206 }; 207 208 window.addEventListener('load', function(e) { MPLT.initialize(); }, false); 209 window.addEventListener('unload', function(e) { MPLT.deinitialize(); }, false); 210