summaryrefslogtreecommitdiffstats
path: root/tools/measure_page_load_time/ie_bho/MeasurePageLoadTimeBHO.cpp
blob: 7e9d5c0435c3b6a02c5b4dac407988c464ef6394 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
// Copyright 2008, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//    * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
//    * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// Implements a Browser Helper Object (BHO) which opens a socket
// and waits to receive URLs over it. Visits those URLs, measuring
// how long it takes between the start of navigation and the
// DocumentComplete event, and returns the time in milliseconds as
// a string to the caller.

#include "stdafx.h"
#include "MeasurePageLoadTimeBHO.h"

#define MAX_URL 1024                 // size of URL buffer
#define MAX_PAGELOADTIME (4*60*1000) // assume all pages take < 4 minutes
#define PORT 42492                   // port to listen on. Also jhaas's 
                                     // old MSFT employee number


// Static function to serve as thread entry point, takes a "this"
// pointer as pParam and calls the method in the object
static DWORD WINAPI ProcessPageTimeRequests(LPVOID pThis) {
  reinterpret_cast<CMeasurePageLoadTimeBHO*>(pThis)->ProcessPageTimeRequests();

  return 0;
}


STDMETHODIMP CMeasurePageLoadTimeBHO::SetSite(IUnknown* pUnkSite)
{
    if (pUnkSite != NULL)
    {
        // Cache the pointer to IWebBrowser2.
        HRESULT hr = pUnkSite->QueryInterface(IID_IWebBrowser2, (void **)&m_spWebBrowser);
        if (SUCCEEDED(hr))
        {
            // Register to sink events from DWebBrowserEvents2.
            hr = DispEventAdvise(m_spWebBrowser);
            if (SUCCEEDED(hr))
            {
                m_fAdvised = TRUE;
            }

            // Stash the interface in the global interface table
            CComGITPtr<IWebBrowser2> git(m_spWebBrowser);
            m_dwCookie = git.Detach();

            // Create the event to be signaled when navigation completes.
            // Start it in nonsignaled state, and allow it to be triggered
            // when the initial page load is done.
            m_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);

            // Create a thread to wait on the socket
            HANDLE hThread = CreateThread(NULL, 0, ::ProcessPageTimeRequests, this, 0, NULL);
        }
    }
    else
    {
        // Unregister event sink.
        if (m_fAdvised)
        {
            DispEventUnadvise(m_spWebBrowser);
            m_fAdvised = FALSE;
        }

        // Release cached pointers and other resources here.
        m_spWebBrowser.Release();
    }

    // Call base class implementation.
    return IObjectWithSiteImpl<CMeasurePageLoadTimeBHO>::SetSite(pUnkSite);
}


void STDMETHODCALLTYPE CMeasurePageLoadTimeBHO::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL)
{
    if (pDisp == m_spWebBrowser) 
    {
        // Fire the event when the page is done loading
        // to unblock the other thread.
        SetEvent(m_hEvent);
    }
}


void CMeasurePageLoadTimeBHO::ProcessPageTimeRequests() 
{
    CoInitialize(NULL);

    // The event will start in nonsignaled state, meaning that
    // the initial page load isn't done yet. Wait for that to
    // finish before doing anything.
    //
    // It seems to be the case that the BHO will get loaded
    // and SetSite called always before the initial page load
    // even begins, but just to be on the safe side, we won't
    // wait indefinitely.
    WaitForSingleObject(m_hEvent, MAX_PAGELOADTIME);

    // Retrieve the web browser interface from the global table
    CComGITPtr<IWebBrowser2> git(m_dwCookie);
    IWebBrowser2* browser;
    git.CopyTo(&browser);

    // Create a listening socket
    m_sockListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (m_sockListen == SOCKET_ERROR)
        ErrorExit();

    BOOL on = TRUE;
    if (setsockopt(m_sockListen, SOL_SOCKET, SO_REUSEADDR,
                   (const char*)&on, sizeof(on)))
        ErrorExit();

    // Bind the listening socket
    SOCKADDR_IN addrBind;

    addrBind.sin_family      = AF_INET;
    addrBind.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    addrBind.sin_port        = htons(PORT);

    if (bind(m_sockListen, (sockaddr*)&addrBind, sizeof(addrBind)))
        ErrorExit();

    // Listen for incoming connections
    if (listen(m_sockListen, 1))
        ErrorExit();

    // Ensure the socket is blocking... it should be by default, but
    // it can't hurt to make sure
    unsigned long nNonblocking = 0;
    if (ioctlsocket(m_sockListen, FIONBIO, &nNonblocking))
        ErrorExit();

    m_sockTransport = 0;

    // Loop indefinitely waiting for connections
    while(1)
    {
        SOCKADDR_IN addrConnected;
        int         sConnected = sizeof(addrConnected);

        // Wait for a client to connect and send a URL
        m_sockTransport = accept(
            m_sockListen, (sockaddr*)&addrConnected, &sConnected);

        if (m_sockTransport == SOCKET_ERROR)
            ErrorExit();

        char pbBuffer[MAX_URL], strURL[MAX_URL];
        DWORD cbRead, cbWritten;

        bool fDone = false;

        // Loop until we're done with this client
        while (!fDone)
        {
            *strURL = '\0';
            bool fReceivedCR = false;

            do
            {
                // Only receive up to the first carriage return
                cbRead = recv(m_sockTransport, pbBuffer, MAX_URL-1, MSG_PEEK);

                // An error on read most likely means that the remote peer
                // closed the connection. Go back to waiting
                if (cbRead == 0)
                {
                    fDone = true;
                    break;
                }

                // Null terminate the received characters so strchr() is safe
                pbBuffer[cbRead] = '\0';

                if(char* pchFirstCR = strchr(pbBuffer, '\n'))
                {
                    cbRead = (DWORD)(pchFirstCR - pbBuffer + 1);
                    fReceivedCR = true;
                }

                // The below call will not block, since we determined with 
                // MSG_PEEK that at least cbRead bytes are in the TCP receive buffer
                recv(m_sockTransport, pbBuffer, cbRead, 0);
                pbBuffer[cbRead] = '\0';

                strcat_s(strURL, sizeof(strURL), pbBuffer);
            } while (!fReceivedCR);

            // If an error occurred while reading, exit this loop
            if (fDone)
                break;

            // Strip the trailing CR and/or LF
            int i;
            for (i = (int)strlen(strURL)-1; i >= 0 && isspace(strURL[i]); i--)
            {
                strURL[i] = '\0';
            }

            if (i < 0)
            {
                // Sending a carriage return on a line by itself means that
                // the client is done making requests
                fDone = true;
            }
            else
            {
                // Send the browser to the requested URL
                CComVariant vNavFlags( navNoReadFromCache );
                CComVariant vTargetFrame("_self");
                CComVariant vPostData("");
                CComVariant vHTTPHeaders("");

                ResetEvent(m_hEvent);
                DWORD dwStartTime = GetTickCount();

                HRESULT hr = browser->Navigate(
                    CComBSTR(strURL),
                    &vNavFlags,
                    &vTargetFrame, // TargetFrameName
                    &vPostData, // PostData
                    &vHTTPHeaders // Headers
                    );

                // The main browser thread will call OnDocumentComplete() when
                // the page is done loading, which will in turn trigger 
                // m_hEvent. Wait here until then; the event will reset itself 
                // once this thread is released
                if (WaitForSingleObject(m_hEvent, MAX_PAGELOADTIME) == WAIT_TIMEOUT)
                {
                    sprintf_s(pbBuffer, sizeof(pbBuffer), "%s,timeout\n", strURL);

                    browser->Stop();
                }
                else
                {
                    // Format the elapsed time as a string
                    DWORD dwLoadTime = GetTickCount() - dwStartTime;
                    sprintf_s(
                        pbBuffer, sizeof(pbBuffer), "%s,%d\n", strURL, dwLoadTime);
                }

                // Send the result. Just in case the TCP buffer can't handle
                // the whole thing, send in parts if necessary
                char *chSend = pbBuffer;

                while (*chSend)
                {
                    cbWritten = send(
                        m_sockTransport, chSend, (int)strlen(chSend), 0);

                    // Error on send probably means connection reset by peer
                    if (cbWritten == 0)
                    {
                        fDone = true;
                        break;
                    }

                    chSend += cbWritten;
                }
            }
        }

        // Close the transport socket and wait for another connection
        closesocket(m_sockTransport);
        m_sockTransport = 0;
    }
}


void CMeasurePageLoadTimeBHO::ErrorExit()
{
    // Unlink from IE, close the sockets, then terminate this 
    // thread
    SetSite(NULL);

    if (m_sockTransport && m_sockTransport != SOCKET_ERROR)
    {
        closesocket(m_sockTransport);
        m_sockTransport = 0;
    }

    if (m_sockListen && m_sockListen != SOCKET_ERROR)
    {
        closesocket(m_sockListen);
        m_sockListen = 0;
    }

    TerminateThread(GetCurrentThread(), -1);
}