2011-02-24 Zan Dobersek <zandobersek@gmail.com>
[WebKit-https.git] / Source / WebCore / inspector / InspectorResourceAgent.cpp
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 #include "config.h"
32 #include "InspectorResourceAgent.h"
33
34 #if ENABLE(INSPECTOR)
35
36 #include "Base64.h"
37 #include "MemoryCache.h"
38 #include "CachedResource.h"
39 #include "CachedResourceLoader.h"
40 #include "Document.h"
41 #include "DocumentLoader.h"
42 #include "Frame.h"
43 #include "FrameLoader.h"
44 #include "HTMLFrameOwnerElement.h"
45 #include "HTMLNames.h"
46 #include "HTTPHeaderMap.h"
47 #include "InspectorFrontend.h"
48 #include "InspectorState.h"
49 #include "InspectorValues.h"
50 #include "KURL.h"
51 #include "Page.h"
52 #include "ProgressTracker.h"
53 #include "ResourceError.h"
54 #include "ResourceRequest.h"
55 #include "ResourceResponse.h"
56 #include "ScriptCallStack.h"
57 #include "ScriptCallStackFactory.h"
58 #include "SharedBuffer.h"
59 #include "TextEncoding.h"
60 #include "WebSocketHandshakeRequest.h"
61 #include "WebSocketHandshakeResponse.h"
62
63 #include <wtf/CurrentTime.h>
64 #include <wtf/ListHashSet.h>
65 #include <wtf/RefPtr.h>
66 #include <wtf/text/StringBuffer.h>
67
68 namespace WebCore {
69
70 namespace ResourceAgentState {
71 static const char resourceAgentEnabled[] = "resourceAgentEnabled";
72 static const char extraRequestHeaders[] = "extraRequestHeaders";
73 }
74
75 PassRefPtr<InspectorResourceAgent> InspectorResourceAgent::restore(Page* page, InspectorState* state, InspectorFrontend* frontend)
76 {
77     if (state->getBoolean(ResourceAgentState::resourceAgentEnabled))
78         return create(page, state, frontend);
79     return 0;
80 }
81
82 bool InspectorResourceAgent::resourceContent(Frame* frame, const KURL& url, String* result)
83 {
84     if (!frame)
85         return false;
86
87     String textEncodingName;
88     RefPtr<SharedBuffer> buffer = InspectorResourceAgent::resourceData(frame, url, &textEncodingName);
89
90     if (buffer) {
91         TextEncoding encoding(textEncodingName);
92         if (!encoding.isValid())
93             encoding = WindowsLatin1Encoding();
94         *result = encoding.decode(buffer->data(), buffer->size());
95         return true;
96     }
97
98     return false;
99 }
100
101 bool InspectorResourceAgent::resourceContentBase64(Frame* frame, const KURL& url, String* result)
102 {
103     String textEncodingName;
104     RefPtr<SharedBuffer> data = InspectorResourceAgent::resourceData(frame, url, &textEncodingName);
105     if (!data) {
106         *result = String();
107         return false;
108     }
109
110     *result = base64Encode(data->data(), data->size());
111     return true;
112 }
113
114 PassRefPtr<SharedBuffer> InspectorResourceAgent::resourceData(Frame* frame, const KURL& url, String* textEncodingName)
115 {
116     FrameLoader* frameLoader = frame->loader();
117     DocumentLoader* loader = frameLoader->documentLoader();
118     if (equalIgnoringFragmentIdentifier(url, loader->url())) {
119         *textEncodingName = frame->document()->inputEncoding();
120         return frameLoader->documentLoader()->mainResourceData();
121     }
122
123     CachedResource* cachedResource = InspectorResourceAgent::cachedResource(frame, url);
124     if (!cachedResource)
125         return 0;
126
127     // Zero-sized resources don't have data at all -- so fake the empty buffer, insted of indicating error by returning 0.
128     if (!cachedResource->encodedSize())
129         return SharedBuffer::create();
130
131     if (cachedResource->isPurgeable()) {
132         // If the resource is purgeable then make it unpurgeable to get
133         // get its data. This might fail, in which case we return an
134         // empty String.
135         // FIXME: should we do something else in the case of a purged
136         // resource that informs the user why there is no data in the
137         // inspector?
138         if (!cachedResource->makePurgeable(false))
139             return 0;
140     }
141
142     *textEncodingName = cachedResource->encoding();
143     return cachedResource->data();
144 }
145
146 CachedResource* InspectorResourceAgent::cachedResource(Frame* frame, const KURL& url)
147 {
148     CachedResource* cachedResource = frame->document()->cachedResourceLoader()->cachedResource(url);
149     if (!cachedResource)
150         cachedResource = memoryCache()->resourceForURL(url);
151     return cachedResource;
152 }
153
154 static PassRefPtr<InspectorObject> buildObjectForHeaders(const HTTPHeaderMap& headers)
155 {
156     RefPtr<InspectorObject> headersObject = InspectorObject::create();
157     HTTPHeaderMap::const_iterator end = headers.end();
158     for (HTTPHeaderMap::const_iterator it = headers.begin(); it != end; ++it)
159         headersObject->setString(it->first.string(), it->second);
160     return headersObject;
161 }
162
163 static PassRefPtr<InspectorObject> buildObjectForTiming(const ResourceLoadTiming& timing)
164 {
165     RefPtr<InspectorObject> timingObject = InspectorObject::create();
166     timingObject->setNumber("requestTime", timing.requestTime);
167     timingObject->setNumber("proxyStart", timing.proxyStart);
168     timingObject->setNumber("proxyEnd", timing.proxyEnd);
169     timingObject->setNumber("dnsStart", timing.dnsStart);
170     timingObject->setNumber("dnsEnd", timing.dnsEnd);
171     timingObject->setNumber("connectStart", timing.connectStart);
172     timingObject->setNumber("connectEnd", timing.connectEnd);
173     timingObject->setNumber("sslStart", timing.sslStart);
174     timingObject->setNumber("sslEnd", timing.sslEnd);
175     timingObject->setNumber("sendStart", timing.sendStart);
176     timingObject->setNumber("sendEnd", timing.sendEnd);
177     timingObject->setNumber("receiveHeadersEnd", timing.receiveHeadersEnd);
178     return timingObject;
179 }
180
181 static PassRefPtr<InspectorObject> buildObjectForResourceRequest(const ResourceRequest& request)
182 {
183     RefPtr<InspectorObject> requestObject = InspectorObject::create();
184     requestObject->setString("url", request.url().string());
185     requestObject->setString("httpMethod", request.httpMethod());
186     requestObject->setObject("httpHeaderFields", buildObjectForHeaders(request.httpHeaderFields()));
187     if (request.httpBody() && !request.httpBody()->isEmpty())
188         requestObject->setString("requestFormData", request.httpBody()->flattenToString());
189     return requestObject;
190 }
191
192 static PassRefPtr<InspectorObject> buildObjectForResourceResponse(const ResourceResponse& response)
193 {
194     RefPtr<InspectorObject> responseObject = InspectorObject::create();
195     if (response.isNull()) {
196         responseObject->setBoolean("isNull", true);
197         return responseObject;
198     }
199     responseObject->setString("url", response.url().string());
200     responseObject->setString("mimeType", response.mimeType());
201     responseObject->setNumber("expectedContentLength", response.expectedContentLength());
202     responseObject->setString("textEncodingName", response.textEncodingName());
203     responseObject->setString("suggestedFilename", response.suggestedFilename());
204     responseObject->setNumber("httpStatusCode", response.httpStatusCode());
205     responseObject->setString("httpStatusText", response.httpStatusText());
206     responseObject->setObject("httpHeaderFields", buildObjectForHeaders(response.httpHeaderFields()));
207     responseObject->setBoolean("connectionReused", response.connectionReused());
208     responseObject->setNumber("connectionID", response.connectionID());
209     responseObject->setBoolean("wasCached", response.wasCached());
210     if (response.resourceLoadTiming())
211         responseObject->setObject("timing", buildObjectForTiming(*response.resourceLoadTiming()));
212     if (response.resourceLoadInfo()) {
213         RefPtr<InspectorObject> loadInfoObject = InspectorObject::create();
214         loadInfoObject->setNumber("httpStatusCode", response.resourceLoadInfo()->httpStatusCode);
215         loadInfoObject->setString("httpStatusText", response.resourceLoadInfo()->httpStatusText);
216         loadInfoObject->setObject("requestHeaders", buildObjectForHeaders(response.resourceLoadInfo()->requestHeaders));
217         loadInfoObject->setObject("responseHeaders", buildObjectForHeaders(response.resourceLoadInfo()->responseHeaders));
218         responseObject->setObject("loadInfo", loadInfoObject);
219     }
220     return responseObject;
221 }
222
223 static unsigned long frameId(Frame* frame)
224 {
225     return reinterpret_cast<uintptr_t>(frame);
226 }
227
228 static PassRefPtr<InspectorObject> buildObjectForDocumentLoader(DocumentLoader* loader)
229 {
230     RefPtr<InspectorObject> documentLoaderObject = InspectorObject::create();
231     documentLoaderObject->setNumber("frameId", frameId(loader->frame()));
232     documentLoaderObject->setNumber("loaderId", reinterpret_cast<uintptr_t>(loader));
233     documentLoaderObject->setString("url", loader->requestURL().string());
234     return documentLoaderObject;
235 }
236
237 static PassRefPtr<InspectorObject> buildObjectForFrameResource(Frame* frame)
238 {
239     FrameLoader* frameLoader = frame->loader();
240     DocumentLoader* loader = frameLoader->documentLoader();
241
242     RefPtr<InspectorObject> resourceObject = InspectorObject::create();
243     resourceObject->setString("url", loader->url().string());
244     resourceObject->setObject("loader", buildObjectForDocumentLoader(loader));
245     resourceObject->setObject("request", buildObjectForResourceRequest(loader->request()));
246     resourceObject->setObject("response", buildObjectForResourceResponse(loader->response()));
247     return resourceObject;
248 }
249
250 static String cachedResourceTypeString(const CachedResource& cachedResource)
251 {
252     switch (cachedResource.type()) {
253     case CachedResource::ImageResource:
254         return "Image";
255     case CachedResource::FontResource:
256         return "Font";
257     case CachedResource::CSSStyleSheet:
258         // Fall through.
259 #if ENABLE(XSLT)
260     case CachedResource::XSLStyleSheet:
261 #endif
262         return "Stylesheet";
263     case CachedResource::Script:
264         return "Script";
265     default:
266         break;
267     }
268     return "Other";
269 }
270
271 static PassRefPtr<InspectorObject> buildObjectForCachedResource(DocumentLoader* loader, const CachedResource& cachedResource)
272 {
273     RefPtr<InspectorObject> resourceObject = InspectorObject::create();
274     resourceObject->setString("url", cachedResource.url());
275     resourceObject->setString("type", cachedResourceTypeString(cachedResource));
276     resourceObject->setNumber("encodedSize", cachedResource.encodedSize());
277     resourceObject->setObject("response", buildObjectForResourceResponse(cachedResource.response()));
278     resourceObject->setObject("loader", buildObjectForDocumentLoader(loader));
279     return resourceObject;
280 }
281
282 static void populateObjectWithFrameResources(Frame* frame, PassRefPtr<InspectorObject> frameResources)
283 {
284     frameResources->setObject("resource", buildObjectForFrameResource(frame));
285     RefPtr<InspectorArray> subresources = InspectorArray::create();
286     frameResources->setArray("subresources", subresources);
287
288     const CachedResourceLoader::DocumentResourceMap& allResources = frame->document()->cachedResourceLoader()->allCachedResources();
289     CachedResourceLoader::DocumentResourceMap::const_iterator end = allResources.end();
290     for (CachedResourceLoader::DocumentResourceMap::const_iterator it = allResources.begin(); it != end; ++it) {
291         CachedResource* cachedResource = it->second.get();
292         RefPtr<InspectorObject> cachedResourceObject = buildObjectForCachedResource(frame->loader()->documentLoader(), *cachedResource);
293         subresources->pushValue(cachedResourceObject);
294     }
295 }
296
297 InspectorResourceAgent::~InspectorResourceAgent()
298 {
299     m_state->setBoolean(ResourceAgentState::resourceAgentEnabled, false);
300 }
301
302 void InspectorResourceAgent::identifierForInitialRequest(unsigned long identifier, const KURL& url, DocumentLoader* loader)
303 {
304     RefPtr<InspectorObject> loaderObject = buildObjectForDocumentLoader(loader);
305     RefPtr<ScriptCallStack> callStack = createScriptCallStack(ScriptCallStack::maxCallStackSizeToCapture, true);
306     RefPtr<InspectorValue> callStackValue;
307     if (callStack)
308         callStackValue = callStack->buildInspectorObject();
309     else
310         callStackValue = InspectorValue::null();
311     m_frontend->identifierForInitialRequest(identifier, url.string(), loaderObject, callStackValue);
312 }
313
314 void InspectorResourceAgent::setExtraHeaders(ErrorString*, PassRefPtr<InspectorObject> headers)
315 {
316     m_state->setObject(ResourceAgentState::extraRequestHeaders, headers);
317 }
318
319
320 void InspectorResourceAgent::willSendRequest(unsigned long identifier, ResourceRequest& request, const ResourceResponse& redirectResponse)
321 {
322     RefPtr<InspectorObject> headers = m_state->getObject(ResourceAgentState::extraRequestHeaders);
323
324     if (headers) {
325         InspectorObject::const_iterator end = headers->end();
326         for (InspectorObject::const_iterator it = headers->begin(); it != end; ++it) {
327             String value;
328             if (it->second->asString(&value))
329                 request.setHTTPHeaderField(it->first, value);
330         }
331     }
332
333     request.setReportLoadTiming(true);
334     request.setReportRawHeaders(true);
335
336     m_frontend->willSendRequest(identifier, currentTime(), buildObjectForResourceRequest(request), buildObjectForResourceResponse(redirectResponse));
337 }
338
339 void InspectorResourceAgent::markResourceAsCached(unsigned long identifier)
340 {
341     m_frontend->markResourceAsCached(identifier);
342 }
343
344 void InspectorResourceAgent::didReceiveResponse(unsigned long identifier, DocumentLoader* loader, const ResourceResponse& response)
345 {
346     RefPtr<InspectorObject> resourceResponse = buildObjectForResourceResponse(response);
347     String type = "Other";
348     long cachedResourceSize = 0;
349
350     if (loader) {
351         CachedResource* cachedResource = InspectorResourceAgent::cachedResource(loader->frame(), response.url());
352         if (cachedResource) {
353             type = cachedResourceTypeString(*cachedResource);
354             cachedResourceSize = cachedResource->encodedSize();
355             // Use mime type from cached resource in case the one in response is empty.
356             if (response.mimeType().isEmpty())
357                 resourceResponse->setString("mimeType", cachedResource->response().mimeType());
358         }
359         if (equalIgnoringFragmentIdentifier(response.url(), loader->frameLoader()->iconURL()))
360             type = "Image";
361         else if (equalIgnoringFragmentIdentifier(response.url(), loader->url()) && type == "Other")
362             type = "Document";
363     }
364     m_frontend->didReceiveResponse(identifier, currentTime(), type, resourceResponse);
365     // If we revalidated the resource and got Not modified, send content length following didReceiveResponse
366     // as there will be no calls to didReceiveContentLength from the network stack.
367     if (cachedResourceSize && response.httpStatusCode() == 304)
368         didReceiveContentLength(identifier, cachedResourceSize);
369 }
370
371 void InspectorResourceAgent::didReceiveContentLength(unsigned long identifier, int lengthReceived)
372 {
373     m_frontend->didReceiveContentLength(identifier, currentTime(), lengthReceived);
374 }
375
376 void InspectorResourceAgent::didFinishLoading(unsigned long identifier, double finishTime)
377 {
378     if (!finishTime)
379         finishTime = currentTime();
380
381     m_frontend->didFinishLoading(identifier, finishTime);
382 }
383
384 void InspectorResourceAgent::didFailLoading(unsigned long identifier, const ResourceError& error)
385 {
386     m_frontend->didFailLoading(identifier, currentTime(), error.localizedDescription());
387 }
388
389 void InspectorResourceAgent::didLoadResourceFromMemoryCache(DocumentLoader* loader, const CachedResource* resource)
390 {
391     m_frontend->didLoadResourceFromMemoryCache(currentTime(), buildObjectForCachedResource(loader, *resource));
392 }
393
394 void InspectorResourceAgent::setInitialContent(unsigned long identifier, const String& sourceString, const String& type)
395 {
396     m_frontend->setInitialContent(identifier, sourceString, type);
397 }
398
399 static PassRefPtr<InspectorObject> buildObjectForFrame(Frame* frame)
400 {
401     RefPtr<InspectorObject> frameObject = InspectorObject::create();
402     frameObject->setNumber("id", frameId(frame));
403     frameObject->setNumber("parentId", frameId(frame->tree()->parent()));
404     if (frame->ownerElement()) {
405         String name = frame->ownerElement()->getAttribute(HTMLNames::nameAttr);
406         if (name.isEmpty())
407             name = frame->ownerElement()->getAttribute(HTMLNames::idAttr);
408         frameObject->setString("name", name);
409     }
410     frameObject->setString("url", frame->document()->url().string());
411     return frameObject;
412 }
413
414 static PassRefPtr<InspectorObject> buildObjectForFrameTree(Frame* frame, bool dumpResources)
415 {
416     RefPtr<InspectorObject> frameObject = buildObjectForFrame(frame);
417
418     if (dumpResources)
419         populateObjectWithFrameResources(frame, frameObject);
420     RefPtr<InspectorArray> childrenArray;
421     for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
422         if (!childrenArray) {
423             childrenArray = InspectorArray::create();
424             frameObject->setArray("children", childrenArray);
425         }
426         childrenArray->pushObject(buildObjectForFrameTree(child, dumpResources));
427     }
428     return frameObject;
429 }
430
431 void InspectorResourceAgent::didCommitLoad(DocumentLoader* loader)
432 {
433     m_frontend->didCommitLoadForFrame(buildObjectForFrame(loader->frame()), buildObjectForDocumentLoader(loader));
434 }
435
436 void InspectorResourceAgent::frameDetachedFromParent(Frame* frame)
437 {
438     m_frontend->frameDetachedFromParent(frameId(frame));
439 }
440
441 #if ENABLE(WEB_SOCKETS)
442
443 // FIXME: More this into the front-end?
444 // Create human-readable binary representation, like "01:23:45:67:89:AB:CD:EF".
445 static String createReadableStringFromBinary(const unsigned char* value, size_t length)
446 {
447     ASSERT(length > 0);
448     static const char hexDigits[17] = "0123456789ABCDEF";
449     size_t bufferSize = length * 3 - 1;
450     StringBuffer buffer(bufferSize);
451     size_t index = 0;
452     for (size_t i = 0; i < length; ++i) {
453         if (i > 0)
454             buffer[index++] = ':';
455         buffer[index++] = hexDigits[value[i] >> 4];
456         buffer[index++] = hexDigits[value[i] & 0xF];
457     }
458     ASSERT(index == bufferSize);
459     return String::adopt(buffer);
460 }
461
462 void InspectorResourceAgent::didCreateWebSocket(unsigned long identifier, const KURL& requestURL)
463 {
464     m_frontend->didCreateWebSocket(identifier, requestURL.string());
465 }
466
467 void InspectorResourceAgent::willSendWebSocketHandshakeRequest(unsigned long identifier, const WebSocketHandshakeRequest& request)
468 {
469     RefPtr<InspectorObject> requestObject = InspectorObject::create();
470     requestObject->setObject("webSocketHeaderFields", buildObjectForHeaders(request.headerFields()));
471     requestObject->setString("webSocketRequestKey3", createReadableStringFromBinary(request.key3().value, sizeof(request.key3().value)));
472     m_frontend->willSendWebSocketHandshakeRequest(identifier, currentTime(), requestObject);
473 }
474
475 void InspectorResourceAgent::didReceiveWebSocketHandshakeResponse(unsigned long identifier, const WebSocketHandshakeResponse& response)
476 {
477     RefPtr<InspectorObject> responseObject = InspectorObject::create();
478     responseObject->setNumber("statusCode", response.statusCode());
479     responseObject->setString("statusText", response.statusText());
480     responseObject->setObject("webSocketHeaderFields", buildObjectForHeaders(response.headerFields()));
481     responseObject->setString("webSocketChallengeResponse", createReadableStringFromBinary(response.challengeResponse().value, sizeof(response.challengeResponse().value)));
482     m_frontend->didReceiveWebSocketHandshakeResponse(identifier, currentTime(), responseObject);
483 }
484
485 void InspectorResourceAgent::didCloseWebSocket(unsigned long identifier)
486 {
487     m_frontend->didCloseWebSocket(identifier, currentTime());
488 }
489 #endif // ENABLE(WEB_SOCKETS)
490
491 Frame* InspectorResourceAgent::frameForId(unsigned long frameId)
492 {
493     Frame* mainFrame = m_page->mainFrame();
494     for (Frame* frame = mainFrame; frame; frame = frame->tree()->traverseNext(mainFrame)) {
495         if (reinterpret_cast<uintptr_t>(frame) == frameId)
496             return frame;
497     }
498     return 0;
499 }
500
501 void InspectorResourceAgent::cachedResources(ErrorString* error, RefPtr<InspectorObject>* object)
502 {
503     *object = buildObjectForFrameTree(m_page->mainFrame(), true);
504 }
505
506 void InspectorResourceAgent::resourceContent(ErrorString*, unsigned long frameId, const String& url, bool base64Encode, bool* success, String* content)
507 {
508     Frame* frame = frameForId(frameId);
509     if (!frame) {
510         *success = false;
511         return;
512     }
513     if (base64Encode)
514         *success = InspectorResourceAgent::resourceContentBase64(frame, KURL(ParsedURLString, url), content);
515     else
516         *success = InspectorResourceAgent::resourceContent(frame, KURL(ParsedURLString, url), content);
517 }
518
519 InspectorResourceAgent::InspectorResourceAgent(Page* page, InspectorState* state, InspectorFrontend* frontend)
520     : m_page(page)
521     , m_state(state)
522     , m_frontend(frontend)
523 {
524     m_state->setBoolean(ResourceAgentState::resourceAgentEnabled, true);
525 }
526
527 } // namespace WebCore
528
529 #endif // ENABLE(INSPECTOR)