blob: a6601cae918cc76aab630e88c05acc445977169c [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
* Copyright (C) 2015-2019 Apple 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 "InspectorNetworkAgent.h"
#include "CachedCSSStyleSheet.h"
#include "CachedRawResource.h"
#include "CachedResource.h"
#include "CachedResourceLoader.h"
#include "CachedResourceRequestInitiators.h"
#include "CachedScript.h"
#include "CertificateInfo.h"
#include "CertificateSummary.h"
#include "DocumentInlines.h"
#include "DocumentLoader.h"
#include "DocumentThreadableLoader.h"
#include "FormData.h"
#include "Frame.h"
#include "FrameLoader.h"
#include "HTTPHeaderMap.h"
#include "HTTPHeaderNames.h"
#include "InspectorDOMAgent.h"
#include "InspectorTimelineAgent.h"
#include "InstrumentingAgents.h"
#include "JSDOMWindowCustom.h"
#include "JSExecState.h"
#include "JSWebSocket.h"
#include "LoaderStrategy.h"
#include "MIMETypeRegistry.h"
#include "MemoryCache.h"
#include "NetworkResourcesData.h"
#include "Page.h"
#include "PlatformStrategies.h"
#include "ProgressTracker.h"
#include "ResourceError.h"
#include "ResourceLoader.h"
#include "ResourceRequest.h"
#include "ResourceResponse.h"
#include "ScriptableDocumentParser.h"
#include "SubresourceLoader.h"
#include "TextResourceDecoder.h"
#include "ThreadableLoaderClient.h"
#include "WebConsoleAgent.h"
#include <wtf/URL.h>
#include "WebSocket.h"
#include "WebSocketChannel.h"
#include "WebSocketFrame.h"
#include <JavaScriptCore/ContentSearchUtilities.h>
#include <JavaScriptCore/IdentifiersFactory.h>
#include <JavaScriptCore/InjectedScript.h>
#include <JavaScriptCore/InjectedScriptManager.h>
#include <JavaScriptCore/InspectorProtocolObjects.h>
#include <JavaScriptCore/JSCInlines.h>
#include <JavaScriptCore/ScriptCallStack.h>
#include <JavaScriptCore/ScriptCallStackFactory.h>
#include <wtf/JSONValues.h>
#include <wtf/Lock.h>
#include <wtf/RefPtr.h>
#include <wtf/Stopwatch.h>
#include <wtf/persistence/PersistentEncoder.h>
#include <wtf/text/Base64.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/WTFString.h>
typedef Inspector::NetworkBackendDispatcherHandler::LoadResourceCallback LoadResourceCallback;
namespace WebCore {
using namespace Inspector;
namespace {
class InspectorThreadableLoaderClient final : public ThreadableLoaderClient {
WTF_MAKE_NONCOPYABLE(InspectorThreadableLoaderClient);
public:
InspectorThreadableLoaderClient(RefPtr<LoadResourceCallback>&& callback)
: m_callback(WTFMove(callback))
{
}
~InspectorThreadableLoaderClient() override = default;
void didReceiveResponse(ResourceLoaderIdentifier, const ResourceResponse& response) override
{
m_mimeType = response.mimeType();
m_statusCode = response.httpStatusCode();
// FIXME: This assumes text only responses. We should support non-text responses as well.
PAL::TextEncoding textEncoding(response.textEncodingName().string());
bool useDetector = false;
if (!textEncoding.isValid()) {
textEncoding = PAL::UTF8Encoding();
useDetector = true;
}
m_decoder = TextResourceDecoder::create("text/plain"_s, textEncoding, useDetector);
}
void didReceiveData(const SharedBuffer& buffer) override
{
if (buffer.isEmpty())
return;
m_responseText.append(m_decoder->decode(buffer.data(), buffer.size()));
}
void didFinishLoading(ResourceLoaderIdentifier, const NetworkLoadMetrics&) override
{
if (m_decoder)
m_responseText.append(m_decoder->flush());
m_callback->sendSuccess(m_responseText.toString(), m_mimeType, m_statusCode);
dispose();
}
void didFail(const ResourceError& error) override
{
m_callback->sendFailure(error.isAccessControl() ? "Loading resource for inspector failed access control check"_s : "Loading resource for inspector failed"_s);
dispose();
}
void setLoader(RefPtr<ThreadableLoader>&& loader)
{
m_loader = WTFMove(loader);
}
private:
void dispose()
{
m_loader = nullptr;
delete this;
}
RefPtr<LoadResourceCallback> m_callback;
RefPtr<ThreadableLoader> m_loader;
RefPtr<TextResourceDecoder> m_decoder;
String m_mimeType;
StringBuilder m_responseText;
int m_statusCode;
};
Ref<Protocol::Network::WebSocketFrame> buildWebSocketMessage(const WebSocketFrame& frame)
{
return Protocol::Network::WebSocketFrame::create()
.setOpcode(frame.opCode)
.setMask(frame.masked)
.setPayloadData(frame.opCode == 1 ? String::fromUTF8WithLatin1Fallback(frame.payload, frame.payloadLength) : base64EncodeToString(frame.payload, frame.payloadLength))
.setPayloadLength(frame.payloadLength)
.release();
}
} // namespace
InspectorNetworkAgent::InspectorNetworkAgent(WebAgentContext& context)
: InspectorAgentBase("Network"_s, context)
, m_frontendDispatcher(makeUnique<Inspector::NetworkFrontendDispatcher>(context.frontendRouter))
, m_backendDispatcher(Inspector::NetworkBackendDispatcher::create(context.backendDispatcher, this))
, m_injectedScriptManager(context.injectedScriptManager)
, m_resourcesData(makeUnique<NetworkResourcesData>())
{
}
InspectorNetworkAgent::~InspectorNetworkAgent() = default;
void InspectorNetworkAgent::didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*)
{
}
void InspectorNetworkAgent::willDestroyFrontendAndBackend(Inspector::DisconnectReason)
{
disable();
}
static Ref<Protocol::Network::Headers> buildObjectForHeaders(const HTTPHeaderMap& headers)
{
auto headersValue = Protocol::Network::Headers::create().release();
auto headersObject = headersValue->asObject();
for (const auto& header : headers)
headersObject->setString(header.key, header.value);
return headersValue;
}
Ref<Protocol::Network::ResourceTiming> InspectorNetworkAgent::buildObjectForTiming(const NetworkLoadMetrics& timing, ResourceLoader& resourceLoader)
{
auto elapsedTimeSince = [&] (const MonotonicTime& time) {
return m_environment.executionStopwatch().elapsedTimeSince(time).seconds();
};
auto millisecondsSinceFetchStart = [&] (const MonotonicTime& time) {
if (!time)
return 0.0;
return (time - timing.fetchStart).milliseconds();
};
return Protocol::Network::ResourceTiming::create()
.setStartTime(elapsedTimeSince(resourceLoader.loadTiming().startTime()))
.setRedirectStart(elapsedTimeSince(timing.redirectStart))
.setRedirectEnd(elapsedTimeSince(timing.fetchStart))
.setFetchStart(elapsedTimeSince(timing.fetchStart))
.setDomainLookupStart(millisecondsSinceFetchStart(timing.domainLookupStart))
.setDomainLookupEnd(millisecondsSinceFetchStart(timing.domainLookupEnd))
.setConnectStart(millisecondsSinceFetchStart(timing.connectStart))
.setConnectEnd(millisecondsSinceFetchStart(timing.connectEnd))
.setSecureConnectionStart(millisecondsSinceFetchStart(timing.secureConnectionStart))
.setRequestStart(millisecondsSinceFetchStart(timing.requestStart))
.setResponseStart(millisecondsSinceFetchStart(timing.responseStart))
.setResponseEnd(millisecondsSinceFetchStart(timing.responseEnd))
.release();
}
static Protocol::Network::Metrics::Priority toProtocol(NetworkLoadPriority priority)
{
switch (priority) {
case NetworkLoadPriority::Low:
return Protocol::Network::Metrics::Priority::Low;
case NetworkLoadPriority::Medium:
return Protocol::Network::Metrics::Priority::Medium;
case NetworkLoadPriority::High:
return Protocol::Network::Metrics::Priority::High;
case NetworkLoadPriority::Unknown:
break;
}
ASSERT_NOT_REACHED();
return Protocol::Network::Metrics::Priority::Medium;
}
Ref<Protocol::Network::Metrics> InspectorNetworkAgent::buildObjectForMetrics(const NetworkLoadMetrics& networkLoadMetrics)
{
auto metrics = Protocol::Network::Metrics::create().release();
if (!networkLoadMetrics.protocol.isNull())
metrics->setProtocol(networkLoadMetrics.protocol);
if (auto* additionalMetrics = networkLoadMetrics.additionalNetworkLoadMetricsForWebInspector.get()) {
if (additionalMetrics->priority != NetworkLoadPriority::Unknown)
metrics->setPriority(toProtocol(additionalMetrics->priority));
if (!additionalMetrics->remoteAddress.isNull())
metrics->setRemoteAddress(additionalMetrics->remoteAddress);
if (!additionalMetrics->connectionIdentifier.isNull())
metrics->setConnectionIdentifier(additionalMetrics->connectionIdentifier);
if (!additionalMetrics->requestHeaders.isEmpty())
metrics->setRequestHeaders(buildObjectForHeaders(additionalMetrics->requestHeaders));
if (additionalMetrics->requestHeaderBytesSent != std::numeric_limits<uint64_t>::max())
metrics->setRequestHeaderBytesSent(additionalMetrics->requestHeaderBytesSent);
if (additionalMetrics->requestBodyBytesSent != std::numeric_limits<uint64_t>::max())
metrics->setRequestBodyBytesSent(additionalMetrics->requestBodyBytesSent);
if (additionalMetrics->responseHeaderBytesReceived != std::numeric_limits<uint64_t>::max())
metrics->setResponseHeaderBytesReceived(additionalMetrics->responseHeaderBytesReceived);
metrics->setIsProxyConnection(additionalMetrics->isProxyConnection);
}
if (networkLoadMetrics.responseBodyBytesReceived != std::numeric_limits<uint64_t>::max())
metrics->setResponseBodyBytesReceived(networkLoadMetrics.responseBodyBytesReceived);
if (networkLoadMetrics.responseBodyDecodedSize != std::numeric_limits<uint64_t>::max())
metrics->setResponseBodyDecodedSize(networkLoadMetrics.responseBodyDecodedSize);
auto connectionPayload = Protocol::Security::Connection::create()
.release();
if (auto* additionalMetrics = networkLoadMetrics.additionalNetworkLoadMetricsForWebInspector.get()) {
if (!additionalMetrics->tlsProtocol.isEmpty())
connectionPayload->setProtocol(additionalMetrics->tlsProtocol);
if (!additionalMetrics->tlsCipher.isEmpty())
connectionPayload->setCipher(additionalMetrics->tlsCipher);
}
metrics->setSecurityConnection(WTFMove(connectionPayload));
return metrics;
}
static Ref<Protocol::Network::Request> buildObjectForResourceRequest(const ResourceRequest& request)
{
auto requestObject = Protocol::Network::Request::create()
.setUrl(request.url().string())
.setMethod(request.httpMethod())
.setHeaders(buildObjectForHeaders(request.httpHeaderFields()))
.release();
if (request.httpBody() && !request.httpBody()->isEmpty()) {
auto bytes = request.httpBody()->flatten();
requestObject->setPostData(String::fromUTF8WithLatin1Fallback(bytes.data(), bytes.size()));
}
return requestObject;
}
static Protocol::Network::Response::Source responseSource(ResourceResponse::Source source)
{
switch (source) {
case ResourceResponse::Source::DOMCache:
case ResourceResponse::Source::ApplicationCache:
// FIXME: Add support for ApplicationCache in inspector.
case ResourceResponse::Source::Unknown:
return Protocol::Network::Response::Source::Unknown;
case ResourceResponse::Source::Network:
return Protocol::Network::Response::Source::Network;
case ResourceResponse::Source::MemoryCache:
case ResourceResponse::Source::MemoryCacheAfterValidation:
return Protocol::Network::Response::Source::MemoryCache;
case ResourceResponse::Source::DiskCache:
case ResourceResponse::Source::DiskCacheAfterValidation:
return Protocol::Network::Response::Source::DiskCache;
case ResourceResponse::Source::ServiceWorker:
return Protocol::Network::Response::Source::ServiceWorker;
case ResourceResponse::Source::InspectorOverride:
return Protocol::Network::Response::Source::InspectorOverride;
}
ASSERT_NOT_REACHED();
return Protocol::Network::Response::Source::Unknown;
}
RefPtr<Protocol::Network::Response> InspectorNetworkAgent::buildObjectForResourceResponse(const ResourceResponse& response, ResourceLoader* resourceLoader)
{
if (response.isNull())
return nullptr;
auto responseObject = Protocol::Network::Response::create()
.setUrl(response.url().string())
.setStatus(response.httpStatusCode())
.setStatusText(response.httpStatusText())
.setHeaders(buildObjectForHeaders(response.httpHeaderFields()))
.setMimeType(response.mimeType())
.setSource(responseSource(response.source()))
.release();
if (resourceLoader) {
auto* metrics = response.deprecatedNetworkLoadMetricsOrNull();
responseObject->setTiming(buildObjectForTiming(metrics ? *metrics : NetworkLoadMetrics::emptyMetrics(), *resourceLoader));
}
if (auto& certificateInfo = response.certificateInfo()) {
auto securityPayload = Protocol::Security::Security::create()
.release();
if (auto certificateSummaryInfo = certificateInfo.value().summary()) {
auto certificatePayload = Protocol::Security::Certificate::create()
.release();
certificatePayload->setSubject(certificateSummaryInfo.value().subject);
if (auto validFrom = certificateSummaryInfo.value().validFrom)
certificatePayload->setValidFrom(validFrom.seconds());
if (auto validUntil = certificateSummaryInfo.value().validUntil)
certificatePayload->setValidUntil(validUntil.seconds());
auto dnsNamesPayload = JSON::ArrayOf<String>::create();
for (auto& dnsName : certificateSummaryInfo.value().dnsNames)
dnsNamesPayload->addItem(dnsName);
if (dnsNamesPayload->length())
certificatePayload->setDnsNames(WTFMove(dnsNamesPayload));
auto ipAddressesPayload = JSON::ArrayOf<String>::create();
for (auto& ipAddress : certificateSummaryInfo.value().ipAddresses)
ipAddressesPayload->addItem(ipAddress);
if (ipAddressesPayload->length())
certificatePayload->setIpAddresses(WTFMove(ipAddressesPayload));
securityPayload->setCertificate(WTFMove(certificatePayload));
}
responseObject->setSecurity(WTFMove(securityPayload));
}
return responseObject;
}
Ref<Protocol::Network::CachedResource> InspectorNetworkAgent::buildObjectForCachedResource(CachedResource* cachedResource)
{
auto resourceObject = Protocol::Network::CachedResource::create()
.setUrl(cachedResource->url().string())
.setType(InspectorPageAgent::cachedResourceTypeJSON(*cachedResource))
.setBodySize(cachedResource->encodedSize())
.release();
if (auto resourceResponse = buildObjectForResourceResponse(cachedResource->response(), cachedResource->loader()))
resourceObject->setResponse(resourceResponse.releaseNonNull());
String sourceMappingURL = InspectorPageAgent::sourceMapURLForResource(cachedResource);
if (!sourceMappingURL.isEmpty())
resourceObject->setSourceMapURL(sourceMappingURL);
return resourceObject;
}
double InspectorNetworkAgent::timestamp()
{
return m_environment.executionStopwatch().elapsedTime().seconds();
}
void InspectorNetworkAgent::willSendRequest(ResourceLoaderIdentifier identifier, DocumentLoader* loader, ResourceRequest& request, const ResourceResponse& redirectResponse, InspectorPageAgent::ResourceType type)
{
if (request.hiddenFromInspector()) {
m_hiddenRequestIdentifiers.add(identifier);
return;
}
double sendTimestamp = timestamp();
WallTime walltime = WallTime::now();
auto requestId = IdentifiersFactory::requestId(identifier.toUInt64());
auto frameId = frameIdentifier(loader);
auto loaderId = loaderIdentifier(loader);
String targetId = request.initiatorIdentifier();
if (type == InspectorPageAgent::OtherResource) {
if (m_loadingXHRSynchronously || request.requester() == ResourceRequest::Requester::XHR)
type = InspectorPageAgent::XHRResource;
else if (request.requester() == ResourceRequest::Requester::Fetch)
type = InspectorPageAgent::FetchResource;
else if (loader && equalIgnoringFragmentIdentifier(request.url(), loader->url()) && !loader->isCommitted())
type = InspectorPageAgent::DocumentResource;
else if (loader) {
for (auto& linkIcon : loader->linkIcons()) {
if (equalIgnoringFragmentIdentifier(request.url(), linkIcon.url)) {
type = InspectorPageAgent::ImageResource;
break;
}
}
}
}
m_resourcesData->resourceCreated(requestId, loaderId, type);
for (auto& entry : m_extraRequestHeaders)
request.setHTTPHeaderField(entry.key, entry.value);
auto protocolResourceType = InspectorPageAgent::resourceTypeJSON(type);
Document* document = loader && loader->frame() ? loader->frame()->document() : nullptr;
auto initiatorObject = buildInitiatorObject(document, &request);
String url = loader ? loader->url().string() : request.url().string();
std::optional<Protocol::Page::ResourceType> typePayload;
if (type != InspectorPageAgent::OtherResource)
typePayload = protocolResourceType;
m_frontendDispatcher->requestWillBeSent(requestId, frameId, loaderId, url, buildObjectForResourceRequest(request), sendTimestamp, walltime.secondsSinceEpoch().seconds(), WTFMove(initiatorObject), buildObjectForResourceResponse(redirectResponse, nullptr), WTFMove(typePayload), targetId);
}
static InspectorPageAgent::ResourceType resourceTypeForCachedResource(const CachedResource* resource)
{
if (resource)
return InspectorPageAgent::inspectorResourceType(*resource);
return InspectorPageAgent::OtherResource;
}
static InspectorPageAgent::ResourceType resourceTypeForLoadType(InspectorInstrumentation::LoadType loadType)
{
switch (loadType) {
case InspectorInstrumentation::LoadType::Ping:
return InspectorPageAgent::PingResource;
case InspectorInstrumentation::LoadType::Beacon:
return InspectorPageAgent::BeaconResource;
}
ASSERT_NOT_REACHED();
return InspectorPageAgent::OtherResource;
}
void InspectorNetworkAgent::willSendRequest(ResourceLoaderIdentifier identifier, DocumentLoader* loader, ResourceRequest& request, const ResourceResponse& redirectResponse, const CachedResource* cachedResource)
{
if (!cachedResource && loader)
cachedResource = InspectorPageAgent::cachedResource(loader->frame(), request.url());
willSendRequest(identifier, loader, request, redirectResponse, resourceTypeForCachedResource(cachedResource));
}
void InspectorNetworkAgent::willSendRequestOfType(ResourceLoaderIdentifier identifier, DocumentLoader* loader, ResourceRequest& request, InspectorInstrumentation::LoadType loadType)
{
willSendRequest(identifier, loader, request, ResourceResponse(), resourceTypeForLoadType(loadType));
}
void InspectorNetworkAgent::didReceiveResponse(ResourceLoaderIdentifier identifier, DocumentLoader* loader, const ResourceResponse& response, ResourceLoader* resourceLoader)
{
if (m_hiddenRequestIdentifiers.contains(identifier))
return;
String requestId = IdentifiersFactory::requestId(identifier.toUInt64());
std::optional<ResourceResponse> realResponse;
if (platformStrategies()->loaderStrategy()->havePerformedSecurityChecks(response)) {
callOnMainThreadAndWait([&] {
// We do not need to isolate response since it comes straight from IPC, but we might want to isolate it for extra safety.
auto response = platformStrategies()->loaderStrategy()->responseFromResourceLoadIdentifier(identifier);
if (!response.isNull())
realResponse = WTFMove(response);
});
}
auto resourceResponse = buildObjectForResourceResponse(realResponse ? *realResponse : response, resourceLoader);
ASSERT(resourceResponse);
bool isNotModified = response.httpStatusCode() == 304;
CachedResource* cachedResource = nullptr;
if (is<SubresourceLoader>(resourceLoader) && !isNotModified)
cachedResource = downcast<SubresourceLoader>(resourceLoader)->cachedResource();
if (!cachedResource && loader)
cachedResource = InspectorPageAgent::cachedResource(loader->frame(), response.url());
if (cachedResource) {
// Use mime type from cached resource in case the one in response is empty.
if (resourceResponse && response.mimeType().isEmpty())
resourceResponse->setString(Protocol::Network::Response::mimeTypeKey, cachedResource->response().mimeType());
m_resourcesData->addCachedResource(requestId, cachedResource);
}
InspectorPageAgent::ResourceType type = m_resourcesData->resourceType(requestId);
InspectorPageAgent::ResourceType newType = cachedResource ? InspectorPageAgent::inspectorResourceType(*cachedResource) : type;
// FIXME: XHRResource is returned for CachedResource::Type::RawResource, it should be OtherResource unless it truly is an XHR.
// RawResource is used for loading worker scripts, and those should stay as ScriptResource and not change to XHRResource.
if (type != newType && newType != InspectorPageAgent::XHRResource && newType != InspectorPageAgent::OtherResource)
type = newType;
// FIXME: <webkit.org/b/216125> 304 Not Modified responses for XHR/Fetch do not have all their information from the cache.
if (isNotModified && (type == InspectorPageAgent::XHRResource || type == InspectorPageAgent::FetchResource) && (!cachedResource || !cachedResource->encodedSize())) {
if (auto previousResourceData = m_resourcesData->dataForURL(response.url().string())) {
if (previousResourceData->hasContent())
m_resourcesData->setResourceContent(requestId, previousResourceData->content(), previousResourceData->base64Encoded());
else if (previousResourceData->hasBufferedData()) {
previousResourceData->buffer()->forEachSegmentAsSharedBuffer([&](auto&& buffer) {
m_resourcesData->maybeAddResourceData(requestId, buffer);
});
}
resourceResponse->setString(Protocol::Network::Response::mimeTypeKey, previousResourceData->mimeType());
resourceResponse->setInteger(Protocol::Network::Response::statusKey, previousResourceData->httpStatusCode());
resourceResponse->setString(Protocol::Network::Response::statusTextKey, previousResourceData->httpStatusText());
resourceResponse->setString(Protocol::Network::Response::sourceKey, Protocol::Helpers::getEnumConstantValue(Protocol::Network::Response::Source::DiskCache));
}
}
Protocol::Network::FrameId frameId = frameIdentifier(loader);
Protocol::Network::LoaderId loaderId = loaderIdentifier(loader);
m_resourcesData->responseReceived(requestId, frameId, response, type, shouldForceBufferingNetworkResourceData());
m_frontendDispatcher->responseReceived(requestId, frameId, loaderId, timestamp(), InspectorPageAgent::resourceTypeJSON(type), resourceResponse.releaseNonNull());
// If we revalidated the resource and got Not modified, send content length following didReceiveResponse
// as there will be no calls to didReceiveData from the network stack.
if (isNotModified && cachedResource && cachedResource->encodedSize())
didReceiveData(identifier, nullptr, cachedResource->encodedSize(), 0);
}
void InspectorNetworkAgent::didReceiveData(ResourceLoaderIdentifier identifier, const SharedBuffer* data, int expectedDataLength, int encodedDataLength)
{
if (m_hiddenRequestIdentifiers.contains(identifier))
return;
String requestId = IdentifiersFactory::requestId(identifier.toUInt64());
if (data) {
NetworkResourcesData::ResourceData const* resourceData = m_resourcesData->maybeAddResourceData(requestId, *data);
// For a synchronous XHR, if we didn't add data then we can apply it here as base64 encoded content.
// Often the data is text and we would have a decoder, but for non-text we won't have a decoder.
// Sync XHRs may not have a cached resource, while non-sync XHRs usually transfer data over on completion.
if (m_loadingXHRSynchronously && resourceData && !resourceData->hasBufferedData() && !resourceData->cachedResource())
m_resourcesData->setResourceContent(requestId, base64EncodeToString(data->data(), data->size()), true);
}
m_frontendDispatcher->dataReceived(requestId, timestamp(), expectedDataLength, encodedDataLength);
}
void InspectorNetworkAgent::didFinishLoading(ResourceLoaderIdentifier identifier, DocumentLoader* loader, const NetworkLoadMetrics& networkLoadMetrics, ResourceLoader*)
{
if (m_hiddenRequestIdentifiers.remove(identifier))
return;
double elapsedFinishTime;
if (networkLoadMetrics.responseEnd)
elapsedFinishTime = m_environment.executionStopwatch().elapsedTimeSince(networkLoadMetrics.responseEnd).seconds();
else
elapsedFinishTime = timestamp();
String requestId = IdentifiersFactory::requestId(identifier.toUInt64());
if (loader && m_resourcesData->resourceType(requestId) == InspectorPageAgent::DocumentResource)
m_resourcesData->addResourceSharedBuffer(requestId, loader->frameLoader()->documentLoader()->mainResourceData(), loader->frame()->document()->encoding());
m_resourcesData->maybeDecodeDataToContent(requestId);
String sourceMappingURL;
NetworkResourcesData::ResourceData const* resourceData = m_resourcesData->data(requestId);
if (resourceData && resourceData->cachedResource())
sourceMappingURL = InspectorPageAgent::sourceMapURLForResource(resourceData->cachedResource());
std::optional<NetworkLoadMetrics> realMetrics;
if (platformStrategies()->loaderStrategy()->shouldPerformSecurityChecks() && !networkLoadMetrics.isComplete()) {
callOnMainThreadAndWait([&] {
realMetrics = platformStrategies()->loaderStrategy()->networkMetricsFromResourceLoadIdentifier(identifier).isolatedCopy();
});
}
auto metrics = buildObjectForMetrics(realMetrics ? *realMetrics : networkLoadMetrics);
m_frontendDispatcher->loadingFinished(requestId, elapsedFinishTime, sourceMappingURL, WTFMove(metrics));
}
void InspectorNetworkAgent::didFailLoading(ResourceLoaderIdentifier identifier, DocumentLoader* loader, const ResourceError& error)
{
if (m_hiddenRequestIdentifiers.remove(identifier))
return;
String requestId = IdentifiersFactory::requestId(identifier.toUInt64());
if (loader && m_resourcesData->resourceType(requestId) == InspectorPageAgent::DocumentResource) {
Frame* frame = loader->frame();
if (frame && frame->loader().documentLoader() && frame->document()) {
m_resourcesData->addResourceSharedBuffer(requestId,
frame->loader().documentLoader()->mainResourceData(),
frame->document()->encoding());
}
}
m_frontendDispatcher->loadingFailed(requestId, timestamp(), error.localizedDescription(), error.isCancellation());
}
void InspectorNetworkAgent::didLoadResourceFromMemoryCache(DocumentLoader* loader, CachedResource& resource)
{
ASSERT(loader);
if (!loader)
return;
auto identifier = ResourceLoaderIdentifier::generate();
String requestId = IdentifiersFactory::requestId(identifier.toUInt64());
Protocol::Network::LoaderId loaderId = loaderIdentifier(loader);
Protocol::Network::FrameId frameId = frameIdentifier(loader);
m_resourcesData->resourceCreated(requestId, loaderId, resource);
auto initiatorObject = buildInitiatorObject(loader->frame() ? loader->frame()->document() : nullptr, &resource.resourceRequest());
// FIXME: It would be ideal to generate the Network.Response with the MemoryCache source
// instead of whatever ResourceResponse::Source the CachedResources's response has.
// The frontend already knows for certain that this was served from the memory cache.
m_frontendDispatcher->requestServedFromMemoryCache(requestId, frameId, loaderId, loader->url().string(), timestamp(), WTFMove(initiatorObject), buildObjectForCachedResource(&resource));
}
void InspectorNetworkAgent::setInitialScriptContent(ResourceLoaderIdentifier identifier, const String& sourceString)
{
m_resourcesData->setResourceContent(IdentifiersFactory::requestId(identifier.toUInt64()), sourceString);
}
void InspectorNetworkAgent::didReceiveScriptResponse(ResourceLoaderIdentifier identifier)
{
m_resourcesData->setResourceType(IdentifiersFactory::requestId(identifier.toUInt64()), InspectorPageAgent::ScriptResource);
}
void InspectorNetworkAgent::didReceiveThreadableLoaderResponse(ResourceLoaderIdentifier identifier, DocumentThreadableLoader& documentThreadableLoader)
{
String initiator = documentThreadableLoader.options().initiator;
if (initiator == cachedResourceRequestInitiators().fetch)
m_resourcesData->setResourceType(IdentifiersFactory::requestId(identifier.toUInt64()), InspectorPageAgent::FetchResource);
else if (initiator == cachedResourceRequestInitiators().xmlhttprequest)
m_resourcesData->setResourceType(IdentifiersFactory::requestId(identifier.toUInt64()), InspectorPageAgent::XHRResource);
}
void InspectorNetworkAgent::willLoadXHRSynchronously()
{
m_loadingXHRSynchronously = true;
}
void InspectorNetworkAgent::didLoadXHRSynchronously()
{
m_loadingXHRSynchronously = false;
}
void InspectorNetworkAgent::willDestroyCachedResource(CachedResource& cachedResource)
{
Vector<String> requestIds = m_resourcesData->removeCachedResource(&cachedResource);
if (!requestIds.size())
return;
String content;
bool base64Encoded;
if (!InspectorNetworkAgent::cachedResourceContent(cachedResource, &content, &base64Encoded))
return;
for (auto& id : requestIds)
m_resourcesData->setResourceContent(id, content, base64Encoded);
}
void InspectorNetworkAgent::willRecalculateStyle()
{
m_isRecalculatingStyle = true;
}
void InspectorNetworkAgent::didRecalculateStyle()
{
m_isRecalculatingStyle = false;
m_styleRecalculationInitiator = nullptr;
}
void InspectorNetworkAgent::didScheduleStyleRecalculation(Document& document)
{
if (!m_styleRecalculationInitiator)
m_styleRecalculationInitiator = buildInitiatorObject(&document);
}
Ref<Protocol::Network::Initiator> InspectorNetworkAgent::buildInitiatorObject(Document* document, const ResourceRequest* resourceRequest)
{
// FIXME: Worker support.
if (!isMainThread()) {
return Protocol::Network::Initiator::create()
.setType(Protocol::Network::Initiator::Type::Other)
.release();
}
RefPtr<Protocol::Network::Initiator> initiatorObject;
Ref<ScriptCallStack> stackTrace = createScriptCallStack(JSExecState::currentState());
if (stackTrace->size() > 0) {
initiatorObject = Protocol::Network::Initiator::create()
.setType(Protocol::Network::Initiator::Type::Script)
.release();
initiatorObject->setStackTrace(stackTrace->buildInspectorArray());
} else if (document && document->scriptableDocumentParser()) {
initiatorObject = Protocol::Network::Initiator::create()
.setType(Protocol::Network::Initiator::Type::Parser)
.release();
initiatorObject->setUrl(document->url().string());
initiatorObject->setLineNumber(document->scriptableDocumentParser()->textPosition().m_line.oneBasedInt());
}
auto domAgent = m_instrumentingAgents.persistentDOMAgent();
if (domAgent && resourceRequest) {
if (auto inspectorInitiatorNodeIdentifier = resourceRequest->inspectorInitiatorNodeIdentifier()) {
if (!initiatorObject) {
initiatorObject = Protocol::Network::Initiator::create()
.setType(Protocol::Network::Initiator::Type::Other)
.release();
}
initiatorObject->setNodeId(*inspectorInitiatorNodeIdentifier);
}
}
if (initiatorObject)
return initiatorObject.releaseNonNull();
if (m_isRecalculatingStyle && m_styleRecalculationInitiator)
return *m_styleRecalculationInitiator;
return Protocol::Network::Initiator::create()
.setType(Protocol::Network::Initiator::Type::Other)
.release();
}
void InspectorNetworkAgent::didCreateWebSocket(WebSocketChannelIdentifier identifier, const URL& requestURL)
{
m_frontendDispatcher->webSocketCreated(IdentifiersFactory::requestId(identifier.toUInt64()), requestURL.string());
}
void InspectorNetworkAgent::willSendWebSocketHandshakeRequest(WebSocketChannelIdentifier identifier, const ResourceRequest& request)
{
auto requestObject = Protocol::Network::WebSocketRequest::create()
.setHeaders(buildObjectForHeaders(request.httpHeaderFields()))
.release();
m_frontendDispatcher->webSocketWillSendHandshakeRequest(IdentifiersFactory::requestId(identifier.toUInt64()), timestamp(), WallTime::now().secondsSinceEpoch().seconds(), WTFMove(requestObject));
}
void InspectorNetworkAgent::didReceiveWebSocketHandshakeResponse(WebSocketChannelIdentifier identifier, const ResourceResponse& response)
{
auto responseObject = Protocol::Network::WebSocketResponse::create()
.setStatus(response.httpStatusCode())
.setStatusText(response.httpStatusText())
.setHeaders(buildObjectForHeaders(response.httpHeaderFields()))
.release();
m_frontendDispatcher->webSocketHandshakeResponseReceived(IdentifiersFactory::requestId(identifier.toUInt64()), timestamp(), WTFMove(responseObject));
}
void InspectorNetworkAgent::didCloseWebSocket(WebSocketChannelIdentifier identifier)
{
m_frontendDispatcher->webSocketClosed(IdentifiersFactory::requestId(identifier.toUInt64()), timestamp());
}
void InspectorNetworkAgent::didReceiveWebSocketFrame(WebSocketChannelIdentifier identifier, const WebSocketFrame& frame)
{
m_frontendDispatcher->webSocketFrameReceived(IdentifiersFactory::requestId(identifier.toUInt64()), timestamp(), buildWebSocketMessage(frame));
}
void InspectorNetworkAgent::didSendWebSocketFrame(WebSocketChannelIdentifier identifier, const WebSocketFrame& frame)
{
m_frontendDispatcher->webSocketFrameSent(IdentifiersFactory::requestId(identifier.toUInt64()), timestamp(), buildWebSocketMessage(frame));
}
void InspectorNetworkAgent::didReceiveWebSocketFrameError(WebSocketChannelIdentifier identifier, const String& errorMessage)
{
m_frontendDispatcher->webSocketFrameError(IdentifiersFactory::requestId(identifier.toUInt64()), timestamp(), errorMessage);
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::enable()
{
m_enabled = true;
m_instrumentingAgents.setEnabledNetworkAgent(this);
{
Locker locker { WebSocket::allActiveWebSocketsLock() };
for (auto* webSocket : activeWebSockets()) {
if (!is<Document>(webSocket->scriptExecutionContext()))
continue;
auto& document = downcast<Document>(*webSocket->scriptExecutionContext());
auto channel = webSocket->channel();
auto identifier = channel->progressIdentifier();
didCreateWebSocket(identifier, webSocket->url());
auto cookieRequestHeaderFieldValue = [document = WeakPtr { document }](const URL& url) -> String {
if (!document || !document->page())
return { };
return document->page()->cookieJar().cookieRequestHeaderFieldValue(*document, url);
};
willSendWebSocketHandshakeRequest(identifier, channel->clientHandshakeRequest(WTFMove(cookieRequestHeaderFieldValue)));
if (channel->isConnected())
didReceiveWebSocketHandshakeResponse(identifier, channel->serverHandshakeResponse());
if (webSocket->readyState() == WebSocket::CLOSED)
didCloseWebSocket(identifier);
}
}
return { };
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::disable()
{
m_enabled = false;
m_interceptionEnabled = false;
m_intercepts.clear();
m_instrumentingAgents.setEnabledNetworkAgent(nullptr);
m_resourcesData->clear();
m_extraRequestHeaders.clear();
continuePendingRequests();
continuePendingResponses();
setResourceCachingDisabled(false);
return { };
}
bool InspectorNetworkAgent::shouldIntercept(URL url, Protocol::Network::NetworkStage networkStage)
{
url.removeFragmentIdentifier();
String urlString = url.string();
if (urlString.isEmpty())
return false;
for (auto& intercept : m_intercepts) {
if (intercept.networkStage != networkStage)
continue;
if (intercept.url.isEmpty())
return true;
auto searchStringType = intercept.isRegex ? ContentSearchUtilities::SearchStringType::Regex : ContentSearchUtilities::SearchStringType::ExactString;
auto regex = ContentSearchUtilities::createRegularExpressionForSearchString(intercept.url, intercept.caseSensitive, searchStringType);
if (regex.match(urlString) != -1)
return true;
}
return false;
}
void InspectorNetworkAgent::continuePendingRequests()
{
for (auto& pendingRequest : m_pendingInterceptRequests.values())
pendingRequest->continueWithOriginalRequest();
m_pendingInterceptRequests.clear();
}
void InspectorNetworkAgent::continuePendingResponses()
{
for (auto& pendingInterceptResponse : m_pendingInterceptResponses.values())
pendingInterceptResponse->respondWithOriginalResponse();
m_pendingInterceptResponses.clear();
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::setExtraHTTPHeaders(Ref<JSON::Object>&& headers)
{
for (auto& entry : headers.get()) {
auto stringValue = entry.value->asString();
if (!!stringValue)
m_extraRequestHeaders.set(entry.key, stringValue);
}
return { };
}
Protocol::ErrorStringOr<std::tuple<String, bool /* base64Encoded */>> InspectorNetworkAgent::getResponseBody(const Protocol::Network::RequestId& requestId)
{
NetworkResourcesData::ResourceData const* resourceData = m_resourcesData->data(requestId);
if (!resourceData)
return makeUnexpected("Missing resource for given requestId"_s);
if (resourceData->hasContent())
return { { resourceData->content(), resourceData->base64Encoded() } };
if (resourceData->isContentEvicted())
return makeUnexpected("Resource content was evicted from inspector cache"_s);
if (resourceData->buffer() && !resourceData->textEncodingName().isNull()) {
String body;
if (InspectorPageAgent::sharedBufferContent(resourceData->buffer(), resourceData->textEncodingName(), false, &body))
return { { body, false } };
}
if (resourceData->cachedResource()) {
String body;
bool base64Encoded;
if (InspectorNetworkAgent::cachedResourceContent(*resourceData->cachedResource(), &body, &base64Encoded))
return { { body, base64Encoded } };
}
return makeUnexpected("Missing content of resource for given requestId"_s);
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::setResourceCachingDisabled(bool disabled)
{
setResourceCachingDisabledInternal(disabled);
return { };
}
void InspectorNetworkAgent::loadResource(const Protocol::Network::FrameId& frameId, const String& urlString, Ref<LoadResourceCallback>&& callback)
{
Protocol::ErrorString errorString;
auto* context = scriptExecutionContext(errorString, frameId);
if (!context) {
callback->sendFailure(errorString);
return;
}
URL url = context->completeURL(urlString);
ResourceRequest request(url);
request.setHTTPMethod("GET"_s);
request.setHiddenFromInspector(true);
ThreadableLoaderOptions options;
options.sendLoadCallbacks = SendCallbackPolicy::SendCallbacks; // So we remove this from m_hiddenRequestIdentifiers on completion.
options.defersLoadingPolicy = DefersLoadingPolicy::DisallowDefersLoading; // So the request is never deferred.
options.mode = FetchOptions::Mode::NoCors;
options.credentials = FetchOptions::Credentials::SameOrigin;
options.contentSecurityPolicyEnforcement = ContentSecurityPolicyEnforcement::DoNotEnforce;
// InspectorThreadableLoaderClient deletes itself when the load completes or fails.
InspectorThreadableLoaderClient* inspectorThreadableLoaderClient = new InspectorThreadableLoaderClient(callback.copyRef());
auto loader = ThreadableLoader::create(*context, *inspectorThreadableLoaderClient, WTFMove(request), options);
if (!loader) {
callback->sendFailure("Could not load requested resource."_s);
return;
}
// If the load already completed, inspectorThreadableLoaderClient will have been deleted and we will have already called the callback.
if (!callback->isActive())
return;
inspectorThreadableLoaderClient->setLoader(WTFMove(loader));
}
Protocol::ErrorStringOr<String> InspectorNetworkAgent::getSerializedCertificate(const Protocol::Network::RequestId& requestId)
{
auto* resourceData = m_resourcesData->data(requestId);
if (!resourceData)
return makeUnexpected("Missing resource for given requestId"_s);
auto& certificate = resourceData->certificateInfo();
if (!certificate || certificate.value().isEmpty())
return makeUnexpected("Missing certificate of resource for given requestId"_s);
WTF::Persistence::Encoder encoder;
WTF::Persistence::Coder<WebCore::CertificateInfo>::encode(encoder, certificate.value());
return base64EncodeToString(encoder.buffer(), encoder.bufferSize());
}
WebSocket* InspectorNetworkAgent::webSocketForRequestId(const Protocol::Network::RequestId& requestId)
{
Locker locker { WebSocket::allActiveWebSocketsLock() };
for (auto* webSocket : activeWebSockets()) {
if (IdentifiersFactory::requestId(webSocket->channel()->progressIdentifier().toUInt64()) == requestId)
return webSocket;
}
return nullptr;
}
static JSC::JSValue webSocketAsScriptValue(JSC::JSGlobalObject& state, WebSocket* webSocket)
{
JSC::JSLockHolder lock(&state);
return toJS(&state, deprecatedGlobalObjectForPrototype(&state), webSocket);
}
Protocol::ErrorStringOr<Ref<Protocol::Runtime::RemoteObject>> InspectorNetworkAgent::resolveWebSocket(const Protocol::Network::RequestId& requestId, const String& objectGroup)
{
WebSocket* webSocket = webSocketForRequestId(requestId);
if (!webSocket)
return makeUnexpected("Missing web socket for given requestId"_s);
// FIXME: <https://webkit.org/b/168475> Web Inspector: Correctly display iframe's and worker's WebSockets
if (!is<Document>(webSocket->scriptExecutionContext()))
return makeUnexpected("Not supported"_s);
auto* document = downcast<Document>(webSocket->scriptExecutionContext());
auto* frame = document->frame();
if (!frame)
return makeUnexpected("Missing frame of web socket for given requestId"_s);
auto& globalObject = mainWorldGlobalObject(*frame);
auto injectedScript = m_injectedScriptManager.injectedScriptFor(&globalObject);
ASSERT(!injectedScript.hasNoValue());
auto object = injectedScript.wrapObject(webSocketAsScriptValue(globalObject, webSocket), objectGroup);
if (!object)
return makeUnexpected("Internal error: unable to cast WebSocket"_s);
return object.releaseNonNull();
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::setInterceptionEnabled(bool enabled)
{
if (m_interceptionEnabled == enabled)
return makeUnexpected(m_interceptionEnabled ? "Interception already enabled"_s : "Interception already disabled"_s);
m_interceptionEnabled = enabled;
if (!m_interceptionEnabled) {
continuePendingRequests();
continuePendingResponses();
}
return { };
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::addInterception(const String& url, Protocol::Network::NetworkStage networkStage, std::optional<bool>&& caseSensitive, std::optional<bool>&& isRegex)
{
Intercept intercept;
intercept.url = url;
if (caseSensitive)
intercept.caseSensitive = *caseSensitive;
if (isRegex)
intercept.isRegex = *isRegex;
intercept.networkStage = networkStage;
if (!m_intercepts.appendIfNotContains(intercept))
return makeUnexpected("Intercept for given url, given isRegex, and given stage already exists"_s);
return { };
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::removeInterception(const String& url, Protocol::Network::NetworkStage networkStage, std::optional<bool>&& caseSensitive, std::optional<bool>&& isRegex)
{
Intercept intercept;
intercept.url = url;
if (caseSensitive)
intercept.caseSensitive = *caseSensitive;
if (isRegex)
intercept.isRegex = *isRegex;
intercept.networkStage = networkStage;
if (!m_intercepts.removeAll(intercept))
return makeUnexpected("Missing intercept for given url, given isRegex, and given stage"_s);
return { };
}
bool InspectorNetworkAgent::willIntercept(const ResourceRequest& request)
{
if (!m_interceptionEnabled)
return false;
return shouldIntercept(request.url(), Protocol::Network::NetworkStage::Request)
|| shouldIntercept(request.url(), Protocol::Network::NetworkStage::Response);
}
bool InspectorNetworkAgent::shouldInterceptRequest(const ResourceLoader& loader)
{
if (!m_interceptionEnabled)
return false;
#if ENABLE(SERVICE_WORKER)
if (loader.options().serviceWorkerRegistrationIdentifier)
return false;
#endif
return shouldIntercept(loader.url(), Protocol::Network::NetworkStage::Request);
}
bool InspectorNetworkAgent::shouldInterceptResponse(const ResourceResponse& response)
{
if (!m_interceptionEnabled)
return false;
return shouldIntercept(response.url(), Protocol::Network::NetworkStage::Response);
}
void InspectorNetworkAgent::interceptRequest(ResourceLoader& loader, Function<void(const ResourceRequest&)>&& handler)
{
ASSERT(m_enabled);
ASSERT(m_interceptionEnabled);
String requestId = IdentifiersFactory::requestId(loader.identifier().toUInt64());
if (m_pendingInterceptRequests.contains(requestId)) {
handler(loader.request());
return;
}
m_pendingInterceptRequests.set(requestId, makeUnique<PendingInterceptRequest>(&loader, WTFMove(handler)));
m_frontendDispatcher->requestIntercepted(requestId, buildObjectForResourceRequest(loader.request()));
}
void InspectorNetworkAgent::interceptResponse(const ResourceResponse& response, ResourceLoaderIdentifier identifier, CompletionHandler<void(const ResourceResponse&, RefPtr<FragmentedSharedBuffer>)>&& handler)
{
ASSERT(m_enabled);
ASSERT(m_interceptionEnabled);
String requestId = IdentifiersFactory::requestId(identifier.toUInt64());
if (m_pendingInterceptResponses.contains(requestId)) {
ASSERT_NOT_REACHED();
handler(response, nullptr);
return;
}
m_pendingInterceptResponses.set(requestId, makeUnique<PendingInterceptResponse>(response, WTFMove(handler)));
auto resourceResponse = buildObjectForResourceResponse(response, nullptr);
if (!resourceResponse)
return;
m_frontendDispatcher->responseIntercepted(requestId, resourceResponse.releaseNonNull());
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::interceptContinue(const Protocol::Network::RequestId& requestId, Protocol::Network::NetworkStage networkStage)
{
switch (networkStage) {
case Protocol::Network::NetworkStage::Request:
if (auto pendingInterceptRequest = m_pendingInterceptRequests.take(requestId)) {
pendingInterceptRequest->continueWithOriginalRequest();
return { };
}
return makeUnexpected("Missing pending intercept request for given requestId"_s);
case Protocol::Network::NetworkStage::Response:
if (auto pendingInterceptResponse = m_pendingInterceptResponses.take(requestId)) {
pendingInterceptResponse->respondWithOriginalResponse();
return { };
}
return makeUnexpected("Missing pending intercept response for given requestId"_s);
}
ASSERT_NOT_REACHED();
return { };
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::interceptWithRequest(const Protocol::Network::RequestId& requestId, const String& url, const String& method, RefPtr<JSON::Object>&& headers, const String& postData)
{
auto pendingRequest = m_pendingInterceptRequests.take(requestId);
if (!pendingRequest)
return makeUnexpected("Missing pending intercept request for given requestId"_s);
auto& loader = *pendingRequest->m_loader;
ResourceRequest request = loader.request();
if (!!url)
request.setURL(URL({ }, url));
if (!!method)
request.setHTTPMethod(method);
if (headers) {
HTTPHeaderMap explicitHeaders;
for (auto& [key, value] : *headers) {
auto headerValue = value->asString();
if (!!headerValue)
explicitHeaders.add(key, headerValue);
}
request.setHTTPHeaderFields(WTFMove(explicitHeaders));
}
if (!!postData) {
auto buffer = base64Decode(postData);
if (!buffer)
return makeUnexpected("Unable to decode given postData"_s);
request.setHTTPBody(FormData::create(WTFMove(*buffer)));
}
// FIXME: figure out how to identify when a request has been overridden when we add this to the frontend.
pendingRequest->continueWithRequest(request);
return { };
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::interceptWithResponse(const Protocol::Network::RequestId& requestId, const String& content, bool base64Encoded, const String& mimeType, std::optional<int>&& status, const String& statusText, RefPtr<JSON::Object>&& headers)
{
auto pendingInterceptResponse = m_pendingInterceptResponses.take(requestId);
if (!pendingInterceptResponse)
return makeUnexpected("Missing pending intercept response for given requestId"_s);
ResourceResponse overrideResponse(pendingInterceptResponse->originalResponse());
overrideResponse.setSource(ResourceResponse::Source::InspectorOverride);
if (status)
overrideResponse.setHTTPStatusCode(*status);
if (!!statusText)
overrideResponse.setHTTPStatusText(AtomString { statusText });
if (!!mimeType)
overrideResponse.setMimeType(AtomString { mimeType });
if (headers) {
HTTPHeaderMap explicitHeaders;
for (auto& header : *headers) {
auto headerValue = header.value->asString();
if (!!headerValue)
explicitHeaders.add(header.key, headerValue);
}
overrideResponse.setHTTPHeaderFields(WTFMove(explicitHeaders));
overrideResponse.setHTTPHeaderField(HTTPHeaderName::ContentType, overrideResponse.mimeType());
}
RefPtr<FragmentedSharedBuffer> overrideData;
if (base64Encoded) {
auto buffer = base64Decode(content);
if (!buffer)
return makeUnexpected("Unable to decode given content"_s);
overrideData = SharedBuffer::create(WTFMove(*buffer));
} else {
auto utf8Content = content.utf8();
overrideData = SharedBuffer::create(utf8Content.data(), utf8Content.length());
}
pendingInterceptResponse->respond(overrideResponse, overrideData);
return { };
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::interceptRequestWithResponse(const Protocol::Network::RequestId& requestId, const String& content, bool base64Encoded, const String& mimeType, int status, const String& statusText, Ref<JSON::Object>&& headers)
{
auto pendingRequest = m_pendingInterceptRequests.take(requestId);
if (!pendingRequest)
return makeUnexpected("Missing pending intercept request for given requestId"_s);
// Loader will be retained in the didReceiveResponse lambda below.
RefPtr<ResourceLoader> loader = pendingRequest->m_loader.get();
if (loader->reachedTerminalState())
return makeUnexpected("Unable to fulfill request, it has already been processed"_s);
RefPtr<SharedBuffer> data;
if (base64Encoded) {
auto buffer = base64Decode(content);
if (!buffer)
return makeUnexpected("Unable to decode given content"_s);
data = SharedBuffer::create(WTFMove(*buffer));
} else {
auto utf8Content = content.utf8();
data = SharedBuffer::create(utf8Content.data(), utf8Content.length());
}
// Mimic data URL load behavior - report didReceiveResponse & didFinishLoading.
ResourceResponse response(pendingRequest->m_loader->url(), mimeType, data->size(), String());
response.setSource(ResourceResponse::Source::InspectorOverride);
response.setHTTPStatusCode(status);
response.setHTTPStatusText(AtomString { statusText });
HTTPHeaderMap explicitHeaders;
for (auto& header : headers.get()) {
auto headerValue = header.value->asString();
if (!!headerValue)
explicitHeaders.add(header.key, headerValue);
}
response.setHTTPHeaderFields(WTFMove(explicitHeaders));
response.setHTTPHeaderField(HTTPHeaderName::ContentType, response.mimeType());
loader->didReceiveResponse(response, [loader, buffer = data.releaseNonNull()]() {
if (loader->reachedTerminalState())
return;
if (buffer->size())
loader->didReceiveData(buffer, buffer->size(), DataPayloadWholeResource);
loader->didFinishLoading(NetworkLoadMetrics());
});
return { };
}
static ResourceError::Type toResourceErrorType(Protocol::Network::ResourceErrorType protocolResourceErrorType)
{
switch (protocolResourceErrorType) {
case Protocol::Network::ResourceErrorType::General:
return ResourceError::Type::General;
case Protocol::Network::ResourceErrorType::AccessControl:
return ResourceError::Type::AccessControl;
case Protocol::Network::ResourceErrorType::Cancellation:
return ResourceError::Type::Cancellation;
case Protocol::Network::ResourceErrorType::Timeout:
return ResourceError::Type::Timeout;
}
ASSERT_NOT_REACHED();
return ResourceError::Type::Null;
}
Protocol::ErrorStringOr<void> InspectorNetworkAgent::interceptRequestWithError(const Protocol::Network::RequestId& requestId, Protocol::Network::ResourceErrorType errorType)
{
auto pendingRequest = m_pendingInterceptRequests.take(requestId);
if (!pendingRequest)
return makeUnexpected("Missing pending intercept request for given requestId"_s);
auto& loader = *pendingRequest->m_loader;
if (loader.reachedTerminalState())
return makeUnexpected("Unable to abort request, it has already been processed"_s);
addConsoleMessage(makeUnique<Inspector::ConsoleMessage>(MessageSource::Network, MessageType::Log, MessageLevel::Info, makeString("Web Inspector blocked ", loader.url().string(), " from loading"), loader.identifier().toUInt64()));
loader.didFail(ResourceError(InspectorNetworkAgent::errorDomain(), 0, loader.url(), "Blocked by Web Inspector"_s, toResourceErrorType(errorType)));
return { };
}
bool InspectorNetworkAgent::shouldTreatAsText(const String& mimeType)
{
return startsWithLettersIgnoringASCIICase(mimeType, "text/"_s)
|| MIMETypeRegistry::isSupportedJavaScriptMIMEType(mimeType)
|| MIMETypeRegistry::isSupportedJSONMIMEType(mimeType)
|| MIMETypeRegistry::isXMLMIMEType(mimeType)
|| MIMETypeRegistry::isTextMediaPlaylistMIMEType(mimeType);
}
Ref<TextResourceDecoder> InspectorNetworkAgent::createTextDecoder(const String& mimeType, const String& textEncodingName)
{
if (!textEncodingName.isEmpty())
return TextResourceDecoder::create("text/plain"_s, textEncodingName);
if (MIMETypeRegistry::isTextMIMEType(mimeType))
return TextResourceDecoder::create(mimeType, "UTF-8");
if (MIMETypeRegistry::isXMLMIMEType(mimeType)) {
auto decoder = TextResourceDecoder::create("application/xml"_s);
decoder->useLenientXMLDecoding();
return decoder;
}
return TextResourceDecoder::create("text/plain"_s, "UTF-8");
}
std::optional<String> InspectorNetworkAgent::textContentForCachedResource(CachedResource& cachedResource)
{
if (!InspectorNetworkAgent::shouldTreatAsText(cachedResource.mimeType()))
return std::nullopt;
String result;
bool base64Encoded;
if (InspectorNetworkAgent::cachedResourceContent(cachedResource, &result, &base64Encoded)) {
ASSERT(!base64Encoded);
return result;
}
return std::nullopt;
}
bool InspectorNetworkAgent::cachedResourceContent(CachedResource& resource, String* result, bool* base64Encoded)
{
ASSERT(result);
ASSERT(base64Encoded);
if (!resource.encodedSize()) {
*base64Encoded = false;
*result = String();
return true;
}
switch (resource.type()) {
case CachedResource::Type::CSSStyleSheet:
*base64Encoded = false;
*result = downcast<CachedCSSStyleSheet>(resource).sheetText();
// The above can return a null String if the MIME type is invalid.
return !result->isNull();
case CachedResource::Type::Script:
*base64Encoded = false;
*result = downcast<CachedScript>(resource).script().toString();
return true;
default:
auto* buffer = resource.resourceBuffer();
if (!buffer)
return false;
if (InspectorNetworkAgent::shouldTreatAsText(resource.mimeType())) {
auto decoder = InspectorNetworkAgent::createTextDecoder(resource.mimeType(), resource.response().textEncodingName());
*base64Encoded = false;
*result = decoder->decodeAndFlush(buffer->makeContiguous()->data(), buffer->size());
return true;
}
*base64Encoded = true;
*result = base64EncodeToString(buffer->makeContiguous()->data(), buffer->size());
return true;
}
}
static Ref<Protocol::Page::SearchResult> buildObjectForSearchResult(const Protocol::Network::RequestId& requestId, const Protocol::Network::FrameId& frameId, const String& url, int matchesCount)
{
auto searchResult = Protocol::Page::SearchResult::create()
.setUrl(url)
.setFrameId(frameId)
.setMatchesCount(matchesCount)
.release();
searchResult->setRequestId(requestId);
return searchResult;
}
static std::optional<String> textContentForResourceData(const NetworkResourcesData::ResourceData& resourceData)
{
if (resourceData.hasContent() && !resourceData.base64Encoded())
return resourceData.content();
if (resourceData.cachedResource())
return InspectorNetworkAgent::textContentForCachedResource(*resourceData.cachedResource());
return std::nullopt;
}
void InspectorNetworkAgent::searchOtherRequests(const JSC::Yarr::RegularExpression& regex, Ref<JSON::ArrayOf<Protocol::Page::SearchResult>>& result)
{
Vector<NetworkResourcesData::ResourceData*> resources = m_resourcesData->resources();
for (auto* resourceData : resources) {
if (auto textContent = textContentForResourceData(*resourceData)) {
int matchesCount = ContentSearchUtilities::countRegularExpressionMatches(regex, resourceData->content());
if (matchesCount)
result->addItem(buildObjectForSearchResult(resourceData->requestId(), resourceData->frameId(), resourceData->url(), matchesCount));
}
}
}
void InspectorNetworkAgent::searchInRequest(Protocol::ErrorString& errorString, const Protocol::Network::RequestId& requestId, const String& query, bool caseSensitive, bool isRegex, RefPtr<JSON::ArrayOf<Protocol::GenericTypes::SearchMatch>>& results)
{
NetworkResourcesData::ResourceData const* resourceData = m_resourcesData->data(requestId);
if (!resourceData) {
errorString = "Missing resource for given requestId"_s;
return;
}
if (!resourceData->hasContent()) {
errorString = "Missing content of resource for given requestId"_s;
return;
}
results = ContentSearchUtilities::searchInTextByLines(resourceData->content(), query, caseSensitive, isRegex);
}
void InspectorNetworkAgent::mainFrameNavigated(DocumentLoader& loader)
{
m_resourcesData->clear(loaderIdentifier(&loader));
}
} // namespace WebCore