Home | History | Annotate | Download | only in content
      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