/* * 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 #include #include #include #include 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 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 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 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 buildObjectForHeaders(const HTTPHeaderMap& headers) { RefPtr 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 buildObjectForTiming(const ResourceLoadTiming& timing) { RefPtr 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 buildObjectForResourceRequest(const ResourceRequest& request) { RefPtr 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 buildObjectForResourceResponse(const ResourceResponse& response) { if (response.isNull()) return 0; RefPtr 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(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 buildObjectForCachedResource(const CachedResource& cachedResource) { RefPtr resourceObject = InspectorObject::create(); resourceObject->setString("url", cachedResource.url()); resourceObject->setString("type", cachedResourceTypeString(cachedResource)); resourceObject->setNumber("bodySize", cachedResource.encodedSize()); RefPtr 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 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 callStack = createScriptCallStack(ScriptCallStack::maxCallStackSizeToCapture, true); RefPtr callStackValue; if (callStack) callStackValue = callStack->buildInspectorArray(); else callStackValue = InspectorArray::create(); m_frontend->requestWillBeSent(static_cast(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(identifier)); } void InspectorResourceAgent::didReceiveResponse(unsigned long identifier, DocumentLoader* loader, const ResourceResponse& response) { RefPtr 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(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(identifier), currentTime(), dataLength, encodedDataLength); } void InspectorResourceAgent::didFinishLoading(unsigned long identifier, double finishTime) { if (!finishTime) finishTime = currentTime(); m_frontend->loadingFinished(static_cast(identifier), finishTime); } void InspectorResourceAgent::didFailLoading(unsigned long identifier, const ResourceError& error) { m_frontend->loadingFailed(static_cast(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(identifier), sourceString, ResourceType::script); } void InspectorResourceAgent::setInitialXHRContent(unsigned long identifier, const String& sourceString) { m_frontend->initialContentSet(static_cast(identifier), sourceString, ResourceType::xhr); } void InspectorResourceAgent::domContentEventFired() { m_frontend->domContentEventFired(currentTime()); } void InspectorResourceAgent::loadEventFired() { m_frontend->loadEventFired(currentTime()); } static PassRefPtr buildObjectForFrame(Frame* frame) { RefPtr 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 buildObjectForFrameTree(Frame* frame) { RefPtr result = InspectorObject::create(); RefPtr frameObject = buildObjectForFrame(frame); result->setObject("frame", frameObject); RefPtr 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 resourceObject = InspectorObject::create(); resourceObject->setString("url", cachedResource->url()); resourceObject->setString("type", cachedResourceTypeString(*cachedResource)); subresources->pushValue(resourceObject); } RefPtr 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(identifier), requestURL.string()); } void InspectorResourceAgent::willSendWebSocketHandshakeRequest(unsigned long identifier, const WebSocketHandshakeRequest& request) { RefPtr 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(identifier), currentTime(), requestObject); } void InspectorResourceAgent::didReceiveWebSocketHandshakeResponse(unsigned long identifier, const WebSocketHandshakeResponse& response) { RefPtr 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(identifier), currentTime(), responseObject); } void InspectorResourceAgent::didCloseWebSocket(unsigned long identifier) { m_frontend->webSocketClosed(static_cast(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* 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 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)