blob: 44473ccb10551dd9649c4d9f67593a3e34ea7a05 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview measure_page_load_time.js implements a Firefox extension
* for measuring how long a page takes to load. It waits on TCP port
* 42492 for connections, then accepts URLs and returns strings of the
* form url,time, where "time" is the load time in milliseconds or the
* string "timeout" or "error". Load time is measured from the call to
* loadURI until the load event fires, or until the status changes to
* STATUS_STOP if the load event doesn't fire (there's an error.)
* @author jhaas@google.com (Jonathan Haas) */
// Shorthand reference to nsIWebProgress[Listener] interfaces
var IWP = Components.interfaces.nsIWebProgress;
var IWPL = Components.interfaces.nsIWebProgressListener;
var MPLT = {
/**
* Constants
*/
PORT_NUMBER : 42492, // port to listen for connections on
TIME_OUT : 4 * 60 * 1000, // timeout in 4 minutes
/**
* Incoming URL buffer
* @type {string}
*/
textBuffer : '',
/**
* URL we're currently visiting
* @type {string}
*/
URL : '',
/**
* Listener to accept incoming connections
* @type {nsIServerSocketListener}
*/
acceptListener :
{
onSocketAccepted : function(serverSocket, transport)
{
MPLT.streamInput = transport.openInputStream(0,0,0);
MPLT.streamOutput = transport.openOutputStream(0,0,0);
MPLT.scriptStream = Components.classes['@mozilla.org/scriptableinputstream;1']
.createInstance(Components.interfaces.nsIScriptableInputStream);
MPLT.scriptStream.init(MPLT.streamInput);
MPLT.pump = Components.classes['@mozilla.org/network/input-stream-pump;1']
.createInstance(Components.interfaces.nsIInputStreamPump);
MPLT.pump.init(MPLT.streamInput, -1, -1, 0, 0, false);
MPLT.pump.asyncRead(MPLT.dataListener,null);
},
onStopListening : function(){}
},
/**
* Listener for network input
* @type {nsIStreamListener}
*/
dataListener :
{
onStartRequest: function(){},
onStopRequest: function(){},
onDataAvailable: function(request, context, inputStream, offset, count){
// Add the received data to the buffer, then process it
// Change CRLF to newline while we're at it
MPLT.textBuffer += MPLT.scriptStream.read(count).replace('\r\n', '\n');
MPLT.process();
}
},
/**
* Process the incoming data buffer
*/
process : function()
{
// If we're waiting for a page to finish loading, wait
if (MPLT.timeLoadStarted)
return;
// Look for a carriage return
var firstCR = MPLT.textBuffer.indexOf('\n');
// If we haven't received a carriage return yet, wait
if (firstCR < 0)
return;
// If the first character was a carriage return, we're done!
if (firstCR == 0) {
MPLT.textBuffer = '';
MPLT.streamInput.close();
MPLT.streamOutput.close();
return;
}
// Remove the URL from the buffer
MPLT.URL = MPLT.textBuffer.substr(0, firstCR);
MPLT.textBuffer = MPLT.textBuffer.substr(firstCR + 1);
// Remember the current time and navigate to the new URL
MPLT.timeLoadStarted = new Date();
gBrowser.loadURIWithFlags(MPLT.URL, gBrowser.LOAD_FLAGS_BYPASS_CACHE);
setTimeout('MPLT.onTimeOut()', MPLT.TIME_OUT);
},
/**
* Page load completion handler
*/
onPageLoad : function(e) {
// Ignore loads of non-HTML documents
if (!(e.originalTarget instanceof HTMLDocument))
return;
// Also ignore subframe loads
if (e.originalTarget.defaultView.frameElement)
return;
clearTimeout();
var timeElapsed = new Date() - MPLT.timeLoadStarted;
MPLT.outputResult(timeElapsed);
},
/**
* Timeout handler
*/
onTimeOut : function() {
gBrowser.stop();
MPLT.outputResult('timeout');
},
/**
* Sends a properly-formatted result to the client
* @param {string} result The value to send along with the URL
*/
outputResult : function(result) {
if (MPLT.URL) {
var outputString = MPLT.URL + ',' + result + '\n';
MPLT.streamOutput.write(outputString, outputString.length);
MPLT.URL = '';
}
MPLT.timeLoadStarted = null;
MPLT.process();
},
/**
* Time the page load started. If null, we're waiting for the
* initial page load, or otherwise don't care about the page
* that's currently loading
* @type {number}
*/
timeLoadStarted : null,
/*
* TODO(jhaas): add support for nsIWebProgressListener
* If the URL being visited died as part of a network error
* (host not found, connection reset by peer, etc), the onload
* event doesn't fire. The only way to catch it would be in
* a web progress listener. However, nsIWebProgress is not
* behaving according to documentation. More research is needed.
* For now, omitting it means that if any of our URLs are "dirty"
* (do not point to real web servers with real responses), we'll log
* them as timeouts. This doesn't affect pages where the server
* exists but returns an error code.
*/
/**
* Initialize the plugin, create the socket and listen
*/
initialize: function() {
// Register for page load events
gBrowser.addEventListener('load', this.onPageLoad, true);
// Set a timeout to wait for the initial page to load
MPLT.timeLoadStarted = new Date();
setTimeout('MPLT.onTimeOut()', MPLT.TIME_OUT);
// Create the listening socket
MPLT.serverSocket = Components.classes['@mozilla.org/network/server-socket;1']
.createInstance(Components.interfaces.nsIServerSocket);
MPLT.serverSocket.init(MPLT.PORT_NUMBER, true, 1);
MPLT.serverSocket.asyncListen(this.acceptListener);
},
/**
* Close the socket(s)
*/
deinitialize: function() {
if (MPLT.streamInput) MPLT.streamInput.close();
if (MPLT.streamOutput) MPLT.streamOutput.close();
if (MPLT.serverSocket) MPLT.serverSocket.close();
}
};
window.addEventListener('load', function(e) { MPLT.initialize(); }, false);
window.addEventListener('unload', function(e) { MPLT.deinitialize(); }, false);