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.
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.
12 #include "MeasurePageLoadTimeBHO.h"
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
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();
29 STDMETHODIMP
CMeasurePageLoadTimeBHO::SetSite(IUnknown
* pUnkSite
)
33 // Cache the pointer to IWebBrowser2.
34 HRESULT hr
= pUnkSite
->QueryInterface(IID_IWebBrowser2
, (void **)&m_spWebBrowser
);
37 // Register to sink events from DWebBrowserEvents2.
38 hr
= DispEventAdvise(m_spWebBrowser
);
44 // Stash the interface in the global interface table
45 CComGITPtr
<IWebBrowser2
> git(m_spWebBrowser
);
46 m_dwCookie
= git
.Detach();
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
);
53 // Create a thread to wait on the socket
54 HANDLE hThread
= CreateThread(NULL
, 0, ::ProcessPageTimeRequests
, this, 0, NULL
);
59 // Unregister event sink.
62 DispEventUnadvise(m_spWebBrowser
);
66 // Release cached pointers and other resources here.
67 m_spWebBrowser
.Release();
70 // Call base class implementation.
71 return IObjectWithSiteImpl
<CMeasurePageLoadTimeBHO
>::SetSite(pUnkSite
);
75 void STDMETHODCALLTYPE
CMeasurePageLoadTimeBHO::OnDocumentComplete(IDispatch
*pDisp
, VARIANT
*pvarURL
)
77 if (pDisp
== m_spWebBrowser
)
79 // Fire the event when the page is done loading
80 // to unblock the other thread.
86 void CMeasurePageLoadTimeBHO::ProcessPageTimeRequests()
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.
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
98 WaitForSingleObject(m_hEvent
, MAX_PAGELOADTIME
);
100 // Retrieve the web browser interface from the global table
101 CComGITPtr
<IWebBrowser2
> git(m_dwCookie
);
102 IWebBrowser2
* browser
;
103 git
.CopyTo(&browser
);
105 // Create a listening socket
106 m_sockListen
= socket(AF_INET
, SOCK_STREAM
, IPPROTO_TCP
);
107 if (m_sockListen
== SOCKET_ERROR
)
111 if (setsockopt(m_sockListen
, SOL_SOCKET
, SO_REUSEADDR
,
112 (const char*)&on
, sizeof(on
)))
115 // Bind the listening socket
116 SOCKADDR_IN addrBind
;
118 addrBind
.sin_family
= AF_INET
;
119 addrBind
.sin_addr
.s_addr
= htonl(INADDR_LOOPBACK
);
120 addrBind
.sin_port
= htons(PORT
);
122 if (bind(m_sockListen
, (sockaddr
*)&addrBind
, sizeof(addrBind
)))
125 // Listen for incoming connections
126 if (listen(m_sockListen
, 1))
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
))
137 // Loop indefinitely waiting for connections
140 SOCKADDR_IN addrConnected
;
141 int sConnected
= sizeof(addrConnected
);
143 // Wait for a client to connect and send a URL
144 m_sockTransport
= accept(
145 m_sockListen
, (sockaddr
*)&addrConnected
, &sConnected
);
147 if (m_sockTransport
== SOCKET_ERROR
)
150 char pbBuffer
[MAX_URL
], strURL
[MAX_URL
];
151 DWORD cbRead
, cbWritten
;
155 // Loop until we're done with this client
159 bool fReceivedCR
= false;
163 // Only receive up to the first carriage return
164 cbRead
= recv(m_sockTransport
, pbBuffer
, MAX_URL
-1, MSG_PEEK
);
166 // An error on read most likely means that the remote peer
167 // closed the connection. Go back to waiting
174 // Null terminate the received characters so strchr() is safe
175 pbBuffer
[cbRead
] = '\0';
177 if(char* pchFirstCR
= strchr(pbBuffer
, '\n'))
179 cbRead
= (DWORD
)(pchFirstCR
- pbBuffer
+ 1);
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';
188 strcat_s(strURL
, sizeof(strURL
), pbBuffer
);
189 } while (!fReceivedCR
);
191 // If an error occurred while reading, exit this loop
195 // Strip the trailing CR and/or LF
197 for (i
= (int)strlen(strURL
)-1; i
>= 0 && isspace(strURL
[i
]); i
--)
204 // Sending a carriage return on a line by itself means that
205 // the client is done making requests
210 // Send the browser to the requested URL
211 CComVariant
vNavFlags( navNoReadFromCache
);
212 CComVariant
vTargetFrame("_self");
213 CComVariant
vPostData("");
214 CComVariant
vHTTPHeaders("");
216 ResetEvent(m_hEvent
);
217 DWORD dwStartTime
= GetTickCount();
219 HRESULT hr
= browser
->Navigate(
222 &vTargetFrame
, // TargetFrameName
223 &vPostData
, // PostData
224 &vHTTPHeaders
// Headers
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
)
233 sprintf_s(pbBuffer
, sizeof(pbBuffer
), "%s,timeout\n", strURL
);
239 // Format the elapsed time as a string
240 DWORD dwLoadTime
= GetTickCount() - dwStartTime
;
242 pbBuffer
, sizeof(pbBuffer
), "%s,%d\n", strURL
, dwLoadTime
);
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
;
252 m_sockTransport
, chSend
, (int)strlen(chSend
), 0);
254 // Error on send probably means connection reset by peer
266 // Close the transport socket and wait for another connection
267 closesocket(m_sockTransport
);
273 void CMeasurePageLoadTimeBHO::ErrorExit()
275 // Unlink from IE, close the sockets, then terminate this
279 if (m_sockTransport
&& m_sockTransport
!= SOCKET_ERROR
)
281 closesocket(m_sockTransport
);
285 if (m_sockListen
&& m_sockListen
!= SOCKET_ERROR
)
287 closesocket(m_sockListen
);
291 TerminateThread(GetCurrentThread(), -1);