Home | History | Annotate | Download | only in ie_bho
      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 // Implements a Browser Helper Object (BHO) which opens a socket
      6 // and waits to receive URLs over it. Visits those URLs, measuring
      7 // how long it takes between the start of navigation and the
      8 // DocumentComplete event, and returns the time in milliseconds as
      9 // a string to the caller.
     10 
     11 #include "stdafx.h"
     12 #include "MeasurePageLoadTimeBHO.h"
     13 
     14 #define MAX_URL 1024                 // size of URL buffer
     15 #define MAX_PAGELOADTIME (4*60*1000) // assume all pages take < 4 minutes
     16 #define PORT 42492                   // port to listen on. Also jhaas's
     17                                      // old MSFT employee number
     18 
     19 
     20 // Static function to serve as thread entry point, takes a "this"
     21 // pointer as pParam and calls the method in the object
     22 static DWORD WINAPI ProcessPageTimeRequests(LPVOID pThis) {
     23   reinterpret_cast<CMeasurePageLoadTimeBHO*>(pThis)->ProcessPageTimeRequests();
     24 
     25   return 0;
     26 }
     27 
     28 
     29 STDMETHODIMP CMeasurePageLoadTimeBHO::SetSite(IUnknown* pUnkSite)
     30 {
     31     if (pUnkSite != NULL)
     32     {
     33         // Cache the pointer to IWebBrowser2.
     34         HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void **)&m_spWebBrowser);
     35         if (SUCCEEDED(hr))
     36         {
     37             // Register to sink events from DWebBrowserEvents2.
     38             hr = DispEventAdvise(m_spWebBrowser);
     39             if (SUCCEEDED(hr))
     40             {
     41                 m_fAdvised = TRUE;
     42             }
     43 
     44             // Stash the interface in the global interface table
     45             CComGITPtr<IWebBrowser2> git(m_spWebBrowser);
     46             m_dwCookie = git.Detach();
     47 
     48             // Create the event to be signaled when navigation completes.
     49             // Start it in nonsignaled state, and allow it to be triggered
     50             // when the initial page load is done.
     51             m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
     52 
     53             // Create a thread to wait on the socket
     54             HANDLE hThread = CreateThread(NULL, 0, ::ProcessPageTimeRequests, this, 0, NULL);
     55         }
     56     }
     57     else
     58     {
     59         // Unregister event sink.
     60         if (m_fAdvised)
     61         {
     62             DispEventUnadvise(m_spWebBrowser);
     63             m_fAdvised = FALSE;
     64         }
     65 
     66         // Release cached pointers and other resources here.
     67         m_spWebBrowser.Release();
     68     }
     69 
     70     // Call base class implementation.
     71     return IObjectWithSiteImpl<CMeasurePageLoadTimeBHO>::SetSite(pUnkSite);
     72 }
     73 
     74 
     75 void STDMETHODCALLTYPE CMeasurePageLoadTimeBHO::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL)
     76 {
     77     if (pDisp == m_spWebBrowser)
     78     {
     79         // Fire the event when the page is done loading
     80         // to unblock the other thread.
     81         SetEvent(m_hEvent);
     82     }
     83 }
     84 
     85 
     86 void CMeasurePageLoadTimeBHO::ProcessPageTimeRequests()
     87 {
     88     CoInitialize(NULL);
     89 
     90     // The event will start in nonsignaled state, meaning that
     91     // the initial page load isn't done yet. Wait for that to
     92     // finish before doing anything.
     93     //
     94     // It seems to be the case that the BHO will get loaded
     95     // and SetSite called always before the initial page load
     96     // even begins, but just to be on the safe side, we won't
     97     // wait indefinitely.
     98     WaitForSingleObject(m_hEvent, MAX_PAGELOADTIME);
     99 
    100     // Retrieve the web browser interface from the global table
    101     CComGITPtr<IWebBrowser2> git(m_dwCookie);
    102     IWebBrowser2* browser;
    103     git.CopyTo(&browser);
    104 
    105     // Create a listening socket
    106     m_sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    107     if (m_sockListen == SOCKET_ERROR)
    108         ErrorExit();
    109 
    110     BOOL on = TRUE;
    111     if (setsockopt(m_sockListen, SOL_SOCKET, SO_REUSEADDR,
    112                    (const char*)&on, sizeof(on)))
    113         ErrorExit();
    114 
    115     // Bind the listening socket
    116     SOCKADDR_IN addrBind;
    117 
    118     addrBind.sin_family      = AF_INET;
    119     addrBind.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    120     addrBind.sin_port        = htons(PORT);
    121 
    122     if (bind(m_sockListen, (sockaddr*)&addrBind, sizeof(addrBind)))
    123         ErrorExit();
    124 
    125     // Listen for incoming connections
    126     if (listen(m_sockListen, 1))
    127         ErrorExit();
    128 
    129     // Ensure the socket is blocking... it should be by default, but
    130     // it can't hurt to make sure
    131     unsigned long nNonblocking = 0;
    132     if (ioctlsocket(m_sockListen, FIONBIO, &nNonblocking))
    133         ErrorExit();
    134 
    135     m_sockTransport = 0;
    136 
    137     // Loop indefinitely waiting for connections
    138     while(1)
    139     {
    140         SOCKADDR_IN addrConnected;
    141         int         sConnected = sizeof(addrConnected);
    142 
    143         // Wait for a client to connect and send a URL
    144         m_sockTransport = accept(
    145             m_sockListen, (sockaddr*)&addrConnected, &sConnected);
    146 
    147         if (m_sockTransport == SOCKET_ERROR)
    148             ErrorExit();
    149 
    150         char pbBuffer[MAX_URL], strURL[MAX_URL];
    151         DWORD cbRead, cbWritten;
    152 
    153         bool fDone = false;
    154 
    155         // Loop until we're done with this client
    156         while (!fDone)
    157         {
    158             *strURL = '\0';
    159             bool fReceivedCR = false;
    160 
    161             do
    162             {
    163                 // Only receive up to the first carriage return
    164                 cbRead = recv(m_sockTransport, pbBuffer, MAX_URL-1, MSG_PEEK);
    165 
    166                 // An error on read most likely means that the remote peer
    167                 // closed the connection. Go back to waiting
    168                 if (cbRead == 0)
    169                 {
    170                     fDone = true;
    171                     break;
    172                 }
    173 
    174                 // Null terminate the received characters so strchr() is safe
    175                 pbBuffer[cbRead] = '\0';
    176 
    177                 if(char* pchFirstCR = strchr(pbBuffer, '\n'))
    178                 {
    179                     cbRead = (DWORD)(pchFirstCR - pbBuffer + 1);
    180                     fReceivedCR = true;
    181                 }
    182 
    183                 // The below call will not block, since we determined with
    184                 // MSG_PEEK that at least cbRead bytes are in the TCP receive buffer
    185                 recv(m_sockTransport, pbBuffer, cbRead, 0);
    186                 pbBuffer[cbRead] = '\0';
    187 
    188                 strcat_s(strURL, sizeof(strURL), pbBuffer);
    189             } while (!fReceivedCR);
    190 
    191             // If an error occurred while reading, exit this loop
    192             if (fDone)
    193                 break;
    194 
    195             // Strip the trailing CR and/or LF
    196             int i;
    197             for (i = (int)strlen(strURL)-1; i >= 0 && isspace(strURL[i]); i--)
    198             {
    199                 strURL[i] = '\0';
    200             }
    201 
    202             if (i < 0)
    203             {
    204                 // Sending a carriage return on a line by itself means that
    205                 // the client is done making requests
    206                 fDone = true;
    207             }
    208             else
    209             {
    210                 // Send the browser to the requested URL
    211                 CComVariant vNavFlags( navNoReadFromCache );
    212                 CComVariant vTargetFrame("_self");
    213                 CComVariant vPostData("");
    214                 CComVariant vHTTPHeaders("");
    215 
    216                 ResetEvent(m_hEvent);
    217                 DWORD dwStartTime = GetTickCount();
    218 
    219                 HRESULT hr = browser->Navigate(
    220                     CComBSTR(strURL),
    221                     &vNavFlags,
    222                     &vTargetFrame, // TargetFrameName
    223                     &vPostData, // PostData
    224                     &vHTTPHeaders // Headers
    225                     );
    226 
    227                 // The main browser thread will call OnDocumentComplete() when
    228                 // the page is done loading, which will in turn trigger
    229                 // m_hEvent. Wait here until then; the event will reset itself
    230                 // once this thread is released
    231                 if (WaitForSingleObject(m_hEvent, MAX_PAGELOADTIME) == WAIT_TIMEOUT)
    232                 {
    233                     sprintf_s(pbBuffer, sizeof(pbBuffer), "%s,timeout\n", strURL);
    234 
    235                     browser->Stop();
    236                 }
    237                 else
    238                 {
    239                     // Format the elapsed time as a string
    240                     DWORD dwLoadTime = GetTickCount() - dwStartTime;
    241                     sprintf_s(
    242                         pbBuffer, sizeof(pbBuffer), "%s,%d\n", strURL, dwLoadTime);
    243                 }
    244 
    245                 // Send the result. Just in case the TCP buffer can't handle
    246                 // the whole thing, send in parts if necessary
    247                 char *chSend = pbBuffer;
    248 
    249                 while (*chSend)
    250                 {
    251                     cbWritten = send(
    252                         m_sockTransport, chSend, (int)strlen(chSend), 0);
    253 
    254                     // Error on send probably means connection reset by peer
    255                     if (cbWritten == 0)
    256                     {
    257                         fDone = true;
    258                         break;
    259                     }
    260 
    261                     chSend += cbWritten;
    262                 }
    263             }
    264         }
    265 
    266         // Close the transport socket and wait for another connection
    267         closesocket(m_sockTransport);
    268         m_sockTransport = 0;
    269     }
    270 }
    271 
    272 
    273 void CMeasurePageLoadTimeBHO::ErrorExit()
    274 {
    275     // Unlink from IE, close the sockets, then terminate this
    276     // thread
    277     SetSite(NULL);
    278 
    279     if (m_sockTransport && m_sockTransport != SOCKET_ERROR)
    280     {
    281         closesocket(m_sockTransport);
    282         m_sockTransport = 0;
    283     }
    284 
    285     if (m_sockListen && m_sockListen != SOCKET_ERROR)
    286     {
    287         closesocket(m_sockListen);
    288         m_sockListen = 0;
    289     }
    290 
    291     TerminateThread(GetCurrentThread(), -1);
    292 }
    293