blob: f200702eee99bc9c0d2c52ed74bb0ea42fe93527 [file] [log] [blame]
/*
* Copyright (C) 2011 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.
*/
#include "config.h"
#include "InspectorResourceAgent.h"
#if ENABLE(INSPECTOR)
#include "Base64.h"
#include "CachedResource.h"
#include "CachedResourceLoader.h"
#include "Document.h"
#include "DocumentLoader.h"
#include "EventsCollector.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "HTMLFrameOwnerElement.h"
#include "HTMLNames.h"
#include "HTTPHeaderMap.h"
#include "InspectorFrontend.h"
#include "InspectorFrontendChannel.h"
#include "InspectorFrontendProxy.h"
#include "InspectorState.h"
#include "InspectorValues.h"
#include "InstrumentingAgents.h"
#include "KURL.h"
#include "MemoryCache.h"
#include "Page.h"
#include "ProgressTracker.h"
#include "ResourceError.h"
#include "ResourceRequest.h"
#include "ResourceResponse.h"
#include "ScriptCallStack.h"
#include "ScriptCallStackFactory.h"
#include "SharedBuffer.h"
#include "TextEncoding.h"
#include "WebSocketHandshakeRequest.h"
#include "WebSocketHandshakeResponse.h"
#include <wtf/CurrentTime.h>
#include <wtf/HexNumber.h>
#include <wtf/ListHashSet.h>
#include <wtf/RefPtr.h>
#include <wtf/text/StringBuilder.h>
namespace WebCore {
namespace ResourceAgentState {
static const char resourceAgentEnabled[] = "resourceAgentEnabled";
static const char extraRequestHeaders[] = "extraRequestHeaders";
}
namespace ResourceType {
static const char document[] = "Document";
static const char stylesheet[] = "Stylesheet";
static const char image[] = "Image";
static const char font[] = "Font";
static const char script[] = "Script";
static const char xhr[] = "XHR";
static const char websocket[] = "WebSocket";
static const char other[] = "Other";
}
void InspectorResourceAgent::setFrontend(InspectorFrontend* frontend)
{
m_frontend = frontend->network();
if (backgroundEventsCollectionEnabled()) {
// Insert Message Proxy in receiver chain.
InspectorFrontendChannel* client = m_frontend->getInspectorFrontendChannel();
m_inspectorFrontendProxy->setInspectorFrontendChannel(client);
m_frontend->setInspectorFrontendChannel(m_inspectorFrontendProxy.get());
m_eventsCollector->sendCollectedEvents(client);
}
}
void InspectorResourceAgent::clearFrontend()
{
if (backgroundEventsCollectionEnabled()) {
m_inspectorFrontendProxy->setInspectorFrontendChannel(0);
m_frontend = m_mockFrontend.get();
} else
m_frontend = 0;
disable(0);
}
void InspectorResourceAgent::restore()
{
if (m_state->getBoolean(ResourceAgentState::resourceAgentEnabled))
enable();
}
void InspectorResourceAgent::resourceContent(ErrorString* errorString, Frame* frame, const KURL& url, String* result)
{
if (!frame) {
*errorString = "No frame to get resource content for";
return;
}
String textEncodingName;
RefPtr<SharedBuffer> buffer = InspectorResourceAgent::resourceData(frame, url, &textEncodingName);
if (buffer) {
TextEncoding encoding(textEncodingName);
if (!encoding.isValid())
encoding = WindowsLatin1Encoding();
*result = encoding.decode(buffer->data(), buffer->size());
return;
}
*errorString = "No resource with given URL found";
}
void InspectorResourceAgent::resourceContentBase64(ErrorString* errorString, Frame* frame, const KURL& url, String* result)
{
String textEncodingName;
RefPtr<SharedBuffer> data = InspectorResourceAgent::resourceData(frame, url, &textEncodingName);
if (!data) {
*result = String();
*errorString = "No resource with given URL found";
return;
}
*result = base64Encode(data->data(), data->size());
}
PassRefPtr<SharedBuffer> InspectorResourceAgent::resourceData(Frame* frame, const KURL& url, String* textEncodingName)
{
FrameLoader* frameLoader = frame->loader();
DocumentLoader* loader = frameLoader->documentLoader();
if (equalIgnoringFragmentIdentifier(url, loader->url())) {
*textEncodingName = frame->document()->inputEncoding();
return frameLoader->documentLoader()->mainResourceData();
}
CachedResource* cachedResource = InspectorResourceAgent::cachedResource(frame, url);
if (!cachedResource)
return 0;
// Zero-sized resources don't have data at all -- so fake the empty buffer, insted of indicating error by returning 0.
if (!cachedResource->encodedSize())
return SharedBuffer::create();
if (cachedResource->isPurgeable()) {
// If the resource is purgeable then make it unpurgeable to get
// get its data. This might fail, in which case we return an
// empty String.
// FIXME: should we do something else in the case of a purged
// resource that informs the user why there is no data in the
// inspector?
if (!cachedResource->makePurgeable(false))
return 0;
}
*textEncodingName = cachedResource->encoding();
return cachedResource->data();
}
CachedResource* InspectorResourceAgent::cachedResource(Frame* frame, const KURL& url)
{
CachedResource* cachedResource = frame->document()->cachedResourceLoader()->cachedResource(url);
if (!cachedResource)
cachedResource = memoryCache()->resourceForURL(url);
return cachedResource;
}
static PassRefPtr<InspectorObject> buildObjectForHeaders(const HTTPHeaderMap& headers)
{
RefPtr<InspectorObject> headersObject = InspectorObject::create();
HTTPHeaderMap::const_iterator end = headers.end();
for (HTTPHeaderMap::const_iterator it = headers.begin(); it != end; ++it)
headersObject->setString(it->first.string(), it->second);
return headersObject;
}
static PassRefPtr<InspectorObject> buildObjectForTiming(const ResourceLoadTiming& timing)
{
RefPtr<InspectorObject> timingObject = InspectorObject::create();
timingObject->setNumber("requestTime", timing.requestTime);
timingObject->setNumber("proxyStart", timing.proxyStart);
timingObject->setNumber("proxyEnd", timing.proxyEnd);
timingObject->setNumber("dnsStart", timing.dnsStart);
timingObject->setNumber("dnsEnd", timing.dnsEnd);
timingObject->setNumber("connectStart", timing.connectStart);
timingObject->setNumber("connectEnd", timing.connectEnd);
timingObject->setNumber("sslStart", timing.sslStart);
timingObject->setNumber("sslEnd", timing.sslEnd);
timingObject->setNumber("sendStart", timing.sendStart);
timingObject->setNumber("sendEnd", timing.sendEnd);
timingObject->setNumber("receiveHeadersEnd", timing.receiveHeadersEnd);
return timingObject;
}
static PassRefPtr<InspectorObject> buildObjectForResourceRequest(const ResourceRequest& request)
{
RefPtr<InspectorObject> requestObject = InspectorObject::create();
requestObject->setString("url", request.url().string());
requestObject->setString("method", request.httpMethod());
requestObject->setObject("headers", buildObjectForHeaders(request.httpHeaderFields()));
if (request.httpBody() && !request.httpBody()->isEmpty())
requestObject->setString("postData", request.httpBody()->flattenToString());
return requestObject;
}
static PassRefPtr<InspectorObject> buildObjectForResourceResponse(const ResourceResponse& response)
{
if (response.isNull())
return 0;
RefPtr<InspectorObject> responseObject = InspectorObject::create();
responseObject->setNumber("status", response.resourceLoadInfo() ? response.resourceLoadInfo()->httpStatusCode : response.httpStatusCode());
responseObject->setString("statusText", response.resourceLoadInfo() ? response.resourceLoadInfo()->httpStatusText : response.httpStatusText());
responseObject->setString("mimeType", response.mimeType());
responseObject->setBoolean("connectionReused", response.connectionReused());
responseObject->setNumber("connectionID", response.connectionID());
responseObject->setBoolean("fromDiskCache", response.wasCached());
if (response.resourceLoadTiming())
responseObject->setObject("timing", buildObjectForTiming(*response.resourceLoadTiming()));
if (response.resourceLoadInfo()) {
responseObject->setObject("headers", buildObjectForHeaders(response.resourceLoadInfo()->responseHeaders));
if (!response.resourceLoadInfo()->responseHeadersText.isEmpty())
responseObject->setString("headersText", response.resourceLoadInfo()->responseHeadersText);
responseObject->setObject("requestHeaders", buildObjectForHeaders(response.resourceLoadInfo()->requestHeaders));
if (!response.resourceLoadInfo()->requestHeadersText.isEmpty())
responseObject->setString("requestHeadersText", response.resourceLoadInfo()->requestHeadersText);
} else
responseObject->setObject("headers", buildObjectForHeaders(response.httpHeaderFields()));
return responseObject;
}
static String pointerAsId(void* pointer)
{
unsigned long long address = reinterpret_cast<uintptr_t>(pointer);
// We want 0 to be "", so that JavaScript checks for if (frameId) worked.
return String::format("%.0llX", address);
}
static String cachedResourceTypeString(const CachedResource& cachedResource)
{
switch (cachedResource.type()) {
case CachedResource::ImageResource:
return ResourceType::image;
case CachedResource::FontResource:
return ResourceType::font;
case CachedResource::CSSStyleSheet:
// Fall through.
#if ENABLE(XSLT)
case CachedResource::XSLStyleSheet:
#endif
return ResourceType::stylesheet;
case CachedResource::Script:
return ResourceType::script;
default:
break;
}
return ResourceType::other;
}
static PassRefPtr<InspectorObject> buildObjectForCachedResource(const CachedResource& cachedResource)
{
RefPtr<InspectorObject> resourceObject = InspectorObject::create();
resourceObject->setString("url", cachedResource.url());
resourceObject->setString("type", cachedResourceTypeString(cachedResource));
resourceObject->setNumber("bodySize", cachedResource.encodedSize());
RefPtr<InspectorObject> resourceResponse = buildObjectForResourceResponse(cachedResource.response());
if (resourceResponse)
resourceObject->setObject("response", resourceResponse);
return resourceObject;
}
InspectorResourceAgent::~InspectorResourceAgent()
{
if (m_state->getBoolean(ResourceAgentState::resourceAgentEnabled)) {
ErrorString error;
disable(&error);
}
ASSERT(!m_instrumentingAgents->inspectorResourceAgent());
}
void InspectorResourceAgent::willSendRequest(unsigned long identifier, DocumentLoader* loader, ResourceRequest& request, const ResourceResponse& redirectResponse)
{
RefPtr<InspectorObject> headers = m_state->getObject(ResourceAgentState::extraRequestHeaders);
if (headers) {
InspectorObject::const_iterator end = headers->end();
for (InspectorObject::const_iterator it = headers->begin(); it != end; ++it) {
String value;
if (it->second->asString(&value))
request.setHTTPHeaderField(it->first, value);
}
}
request.setReportLoadTiming(true);
request.setReportRawHeaders(true);
RefPtr<ScriptCallStack> callStack = createScriptCallStack(ScriptCallStack::maxCallStackSizeToCapture, true);
RefPtr<InspectorArray> callStackValue;
if (callStack)
callStackValue = callStack->buildInspectorArray();
else
callStackValue = InspectorArray::create();
m_frontend->requestWillBeSent(static_cast<int>(identifier), pointerAsId(loader->frame()), pointerAsId(loader), loader->url().string(), buildObjectForResourceRequest(request), currentTime(), callStackValue, buildObjectForResourceResponse(redirectResponse));
}
void InspectorResourceAgent::markResourceAsCached(unsigned long identifier)
{
m_frontend->resourceMarkedAsCached(static_cast<int>(identifier));
}
void InspectorResourceAgent::didReceiveResponse(unsigned long identifier, DocumentLoader* loader, const ResourceResponse& response)
{
RefPtr<InspectorObject> resourceResponse = buildObjectForResourceResponse(response);
String type = "Other";
long cachedResourceSize = 0;
if (loader) {
CachedResource* cachedResource = InspectorResourceAgent::cachedResource(loader->frame(), response.url());
if (cachedResource) {
type = cachedResourceTypeString(*cachedResource);
cachedResourceSize = cachedResource->encodedSize();
// Use mime type from cached resource in case the one in response is empty.
if (response.mimeType().isEmpty())
resourceResponse->setString("mimeType", cachedResource->response().mimeType());
}
if (equalIgnoringFragmentIdentifier(response.url(), loader->frameLoader()->iconURL()))
type = ResourceType::image;
else if (equalIgnoringFragmentIdentifier(response.url(), loader->url()) && type == "Other")
type = ResourceType::document;
}
m_frontend->responseReceived(static_cast<int>(identifier), currentTime(), type, resourceResponse);
// If we revalidated the resource and got Not modified, send content length following didReceiveResponse
// as there will be no calls to didReceiveContentLength from the network stack.
if (cachedResourceSize && response.httpStatusCode() == 304)
didReceiveContentLength(identifier, cachedResourceSize, 0);
}
void InspectorResourceAgent::didReceiveContentLength(unsigned long identifier, int dataLength, int encodedDataLength)
{
m_frontend->dataReceived(static_cast<int>(identifier), currentTime(), dataLength, encodedDataLength);
}
void InspectorResourceAgent::didFinishLoading(unsigned long identifier, double finishTime)
{
if (!finishTime)
finishTime = currentTime();
m_frontend->loadingFinished(static_cast<int>(identifier), finishTime);
}
void InspectorResourceAgent::didFailLoading(unsigned long identifier, const ResourceError& error)
{
m_frontend->loadingFailed(static_cast<int>(identifier), currentTime(), error.localizedDescription(), error.isCancellation());
}
void InspectorResourceAgent::didLoadResourceFromMemoryCache(DocumentLoader* loader, const CachedResource* resource)
{
m_frontend->resourceLoadedFromMemoryCache(pointerAsId(loader->frame()), pointerAsId(loader), loader->url().string(), currentTime(), buildObjectForCachedResource(*resource));
}
void InspectorResourceAgent::setInitialScriptContent(unsigned long identifier, const String& sourceString)
{
m_frontend->initialContentSet(static_cast<int>(identifier), sourceString, ResourceType::script);
}
void InspectorResourceAgent::setInitialXHRContent(unsigned long identifier, const String& sourceString)
{
m_frontend->initialContentSet(static_cast<int>(identifier), sourceString, ResourceType::xhr);
}
void InspectorResourceAgent::domContentEventFired()
{
m_frontend->domContentEventFired(currentTime());
}
void InspectorResourceAgent::loadEventFired()
{
m_frontend->loadEventFired(currentTime());
}
static PassRefPtr<InspectorObject> buildObjectForFrame(Frame* frame)
{
RefPtr<InspectorObject> frameObject = InspectorObject::create();
frameObject->setString("id", pointerAsId(frame));
frameObject->setString("parentId", pointerAsId(frame->tree()->parent()));
if (frame->ownerElement()) {
String name = frame->ownerElement()->getAttribute(HTMLNames::nameAttr);
if (name.isEmpty())
name = frame->ownerElement()->getAttribute(HTMLNames::idAttr);
frameObject->setString("name", name);
}
frameObject->setString("url", frame->document()->url().string());
frameObject->setString("loaderId", pointerAsId(frame->loader()->documentLoader()));
return frameObject;
}
static PassRefPtr<InspectorObject> buildObjectForFrameTree(Frame* frame)
{
RefPtr<InspectorObject> result = InspectorObject::create();
RefPtr<InspectorObject> frameObject = buildObjectForFrame(frame);
result->setObject("frame", frameObject);
RefPtr<InspectorArray> subresources = InspectorArray::create();
result->setArray("resources", subresources);
const CachedResourceLoader::DocumentResourceMap& allResources = frame->document()->cachedResourceLoader()->allCachedResources();
CachedResourceLoader::DocumentResourceMap::const_iterator end = allResources.end();
for (CachedResourceLoader::DocumentResourceMap::const_iterator it = allResources.begin(); it != end; ++it) {
CachedResource* cachedResource = it->second.get();
RefPtr<InspectorObject> resourceObject = InspectorObject::create();
resourceObject->setString("url", cachedResource->url());
resourceObject->setString("type", cachedResourceTypeString(*cachedResource));
subresources->pushValue(resourceObject);
}
RefPtr<InspectorArray> childrenArray;
for (Frame* child = frame->tree()->firstChild(); child; child = child->tree()->nextSibling()) {
if (!childrenArray) {
childrenArray = InspectorArray::create();
result->setArray("childFrames", childrenArray);
}
childrenArray->pushObject(buildObjectForFrameTree(child));
}
return result;
}
void InspectorResourceAgent::didCommitLoad(DocumentLoader* loader)
{
m_frontend->frameNavigated(buildObjectForFrame(loader->frame()), pointerAsId(loader));
}
void InspectorResourceAgent::frameDetachedFromParent(Frame* frame)
{
m_frontend->frameDetached(pointerAsId(frame));
}
#if ENABLE(WEB_SOCKETS)
// FIXME: More this into the front-end?
// Create human-readable binary representation, like "01:23:45:67:89:AB:CD:EF".
static String createReadableStringFromBinary(const unsigned char* value, size_t length)
{
ASSERT(length > 0);
StringBuilder builder;
builder.reserveCapacity(length * 3 - 1);
for (size_t i = 0; i < length; ++i) {
if (i > 0)
builder.append(':');
appendByteAsHex(value[i], builder);
}
return builder.toString();
}
void InspectorResourceAgent::didCreateWebSocket(unsigned long identifier, const KURL& requestURL)
{
m_frontend->webSocketCreated(static_cast<int>(identifier), requestURL.string());
}
void InspectorResourceAgent::willSendWebSocketHandshakeRequest(unsigned long identifier, const WebSocketHandshakeRequest& request)
{
RefPtr<InspectorObject> requestObject = InspectorObject::create();
requestObject->setObject("headers", buildObjectForHeaders(request.headerFields()));
requestObject->setString("requestKey3", createReadableStringFromBinary(request.key3().value, sizeof(request.key3().value)));
m_frontend->webSocketWillSendHandshakeRequest(static_cast<int>(identifier), currentTime(), requestObject);
}
void InspectorResourceAgent::didReceiveWebSocketHandshakeResponse(unsigned long identifier, const WebSocketHandshakeResponse& response)
{
RefPtr<InspectorObject> responseObject = InspectorObject::create();
responseObject->setNumber("status", response.statusCode());
responseObject->setString("statusText", response.statusText());
responseObject->setObject("headers", buildObjectForHeaders(response.headerFields()));
responseObject->setString("challengeResponse", createReadableStringFromBinary(response.challengeResponse().value, sizeof(response.challengeResponse().value)));
m_frontend->webSocketHandshakeResponseReceived(static_cast<int>(identifier), currentTime(), responseObject);
}
void InspectorResourceAgent::didCloseWebSocket(unsigned long identifier)
{
m_frontend->webSocketClosed(static_cast<int>(identifier), currentTime());
}
#endif // ENABLE(WEB_SOCKETS)
Frame* InspectorResourceAgent::frameForId(const String& frameId)
{
Frame* mainFrame = m_page->mainFrame();
for (Frame* frame = mainFrame; frame; frame = frame->tree()->traverseNext(mainFrame)) {
if (pointerAsId(frame) == frameId)
return frame;
}
return 0;
}
bool InspectorResourceAgent::backgroundEventsCollectionEnabled()
{
// FIXME (https://bugs.webkit.org/show_bug.cgi?id=58652)
// Add here condition to enable background events collection.
// Now this function is disable.
return false;
}
void InspectorResourceAgent::enable(ErrorString*)
{
enable();
}
void InspectorResourceAgent::enable()
{
if (!m_frontend)
return;
m_state->setBoolean(ResourceAgentState::resourceAgentEnabled, true);
m_instrumentingAgents->setInspectorResourceAgent(this);
}
void InspectorResourceAgent::disable(ErrorString*)
{
m_state->setBoolean(ResourceAgentState::resourceAgentEnabled, false);
m_instrumentingAgents->setInspectorResourceAgent(0);
}
void InspectorResourceAgent::getCachedResources(ErrorString*, RefPtr<InspectorObject>* object)
{
*object = buildObjectForFrameTree(m_page->mainFrame());
}
void InspectorResourceAgent::getResourceContent(ErrorString* errorString, const String& frameId, const String& url, const bool* const optionalBase64Encode, String* content)
{
Frame* frame = frameForId(frameId);
if (!frame) {
*errorString = "No frame for given id found";
return;
}
if (optionalBase64Encode ? *optionalBase64Encode : false)
InspectorResourceAgent::resourceContentBase64(errorString, frame, KURL(ParsedURLString, url), content);
else
InspectorResourceAgent::resourceContent(errorString, frame, KURL(ParsedURLString, url), content);
}
void InspectorResourceAgent::setExtraHeaders(ErrorString*, PassRefPtr<InspectorObject> headers)
{
m_state->setObject(ResourceAgentState::extraRequestHeaders, headers);
}
InspectorResourceAgent::InspectorResourceAgent(InstrumentingAgents* instrumentingAgents, Page* page, InspectorState* state)
: m_instrumentingAgents(instrumentingAgents)
, m_page(page)
, m_state(state)
{
if (backgroundEventsCollectionEnabled()) {
m_eventsCollector = new EventsCollector();
m_inspectorFrontendProxy = new InspectorFrontendProxy(m_eventsCollector.get());
// Create mock frontend, so we can collect network events.
m_mockFrontend = new InspectorFrontend::Network(m_inspectorFrontendProxy.get());
m_frontend = m_mockFrontend.get();
enable();
} else
m_frontend = 0;
}
} // namespace WebCore
#endif // ENABLE(INSPECTOR)