| /* |
| * 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 |