Commit 943aa1d7 authored by Songtao Xia's avatar Songtao Xia Committed by Commit Bot

[DevTools]Plumb async chain from network requests to sw 'fetch'

This is based on
https://chromium-review.googlesource.com/c/chromium/src/+/1725223
by Dmitry Gozman. Part to support "stepping back from fetch" is
in a different CL.

Explainer:
https://docs.google.com/document/d/1WJcNA9PymdWQXOZ4b0xSPmqQCDplWO6c01Ve47lQ2xo/edit?usp=sharing

This patch introduces debug header "X-Debug-Stack-Trace-Id", which
is added when Network.setDebugHeaderEnabled is called. This header
plumbs stack trace id and can be used to stitch async stacks between
page and service worker, or between page and node.js backend (future
work).

The header is added by InspectorNetworkAgent and parsed by
ServiceWorkerGlobalScope.

Frontend changes to enable 'step into service workers':
https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2292897

Screenshot before:
https://imgur.com/pIxDfHa
After:
https://imgur.com/MHXM5BI

Change-Id: I4aeeca29802318fe063459562c31e147e7b0d9e2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2335644
Commit-Queue: Songtao Xia <soxia@microsoft.com>
Reviewed-by: default avatarAndrey Kosyakov <caseq@chromium.org>
Cr-Commit-Position: refs/heads/master@{#802088}
parent 5bea3010
......@@ -5050,6 +5050,12 @@ domain Network
# Map with extra HTTP headers.
Headers headers
# Specifies whether to sned a debug header to all outgoing requests.
experimental command setAttachDebugHeader
parameters
# Whether to send a debug header.
boolean enabled
# Sets the requests to intercept that match the provided patterns and optionally resource types.
# Deprecated, please use Fetch.enable instead.
experimental deprecated command setRequestInterception
......
......@@ -107,6 +107,8 @@ blink_core_sources("inspector") {
"main_thread_debugger.h",
"network_resources_data.cc",
"network_resources_data.h",
"request_debug_header_scope.cc",
"request_debug_header_scope.h",
"resolve_node.cc",
"resolve_node.h",
"thread_debugger.cc",
......
......@@ -58,6 +58,7 @@
#include "third_party/blink/renderer/core/inspector/identifiers_factory.h"
#include "third_party/blink/renderer/core/inspector/inspected_frames.h"
#include "third_party/blink/renderer/core/inspector/network_resources_data.h"
#include "third_party/blink/renderer/core/inspector/request_debug_header_scope.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
#include "third_party/blink/renderer/core/loader/frame_loader.h"
#include "third_party/blink/renderer/core/loader/mixed_content_checker.h"
......@@ -1009,6 +1010,22 @@ void InspectorNetworkAgent::PrepareRequest(
}
if (bypass_service_worker_.Get())
request.SetSkipServiceWorker(true);
if (debug_header_enabled_.Get() &&
request.HttpHeaderField(RequestDebugHeaderScope::kHeaderName).IsNull()) {
ExecutionContext* context = nullptr;
if (worker_global_scope_) {
context = worker_global_scope_.Get();
} else if (loader && loader->GetFrame()) {
context = loader->GetFrame()->GetDocument()->ExecutingWindow();
}
String header =
RequestDebugHeaderScope::CaptureHeaderForCurrentLocation(context);
if (!header.IsNull()) {
request.SetHttpHeaderField(RequestDebugHeaderScope::kHeaderName,
AtomicString(header));
}
}
}
void InspectorNetworkAgent::WillSendRequest(
......@@ -1522,6 +1539,13 @@ Response InspectorNetworkAgent::setExtraHTTPHeaders(
return Response::Success();
}
Response InspectorNetworkAgent::setAttachDebugHeader(bool enabled) {
if (enabled && !enabled_.Get())
return Response::InvalidParams("Domain must be enabled");
debug_header_enabled_.Set(enabled);
return Response::Success();
}
bool InspectorNetworkAgent::CanGetResponseBodyBlob(const String& request_id) {
NetworkResourcesData::ResourceData const* resource_data =
resources_data_->Data(request_id);
......@@ -1857,6 +1881,7 @@ InspectorNetworkAgent::InspectorNetworkAgent(
bypass_service_worker_(&agent_state_, /*default_value=*/false),
blocked_urls_(&agent_state_, /*default_value=*/false),
extra_request_headers_(&agent_state_, /*default_value=*/WTF::String()),
debug_header_enabled_(&agent_state_, /*default_value=*/false),
total_buffer_size_(&agent_state_,
/*default_value=*/kDefaultTotalBufferSize),
resource_buffer_size_(&agent_state_,
......
......@@ -204,6 +204,7 @@ class CORE_EXPORT InspectorNetworkAgent final
protocol::Response disable() override;
protocol::Response setExtraHTTPHeaders(
std::unique_ptr<protocol::Network::Headers>) override;
protocol::Response setAttachDebugHeader(bool enabled) override;
void getResponseBody(const String& request_id,
std::unique_ptr<GetResponseBodyCallback>) override;
protocol::Response searchInResponseBody(
......@@ -293,6 +294,7 @@ class CORE_EXPORT InspectorNetworkAgent final
InspectorAgentState::Boolean bypass_service_worker_;
InspectorAgentState::BooleanMap blocked_urls_;
InspectorAgentState::StringMap extra_request_headers_;
InspectorAgentState::Boolean debug_header_enabled_;
InspectorAgentState::Integer total_buffer_size_;
InspectorAgentState::Integer resource_buffer_size_;
InspectorAgentState::Integer max_post_data_size_;
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/core/inspector/request_debug_header_scope.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/inspector/main_thread_debugger.h"
#include "third_party/blink/renderer/core/inspector/protocol/Protocol.h"
#include "third_party/blink/renderer/core/inspector/v8_inspector_string.h"
#include "third_party/blink/renderer/core/inspector/worker_thread_debugger.h"
#include "third_party/blink/renderer/core/workers/worker_global_scope.h"
#include "third_party/blink/renderer/core/workers/worker_thread.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
namespace blink {
// static
const char RequestDebugHeaderScope::kHeaderName[] = "X-Debug-Stack-Trace-Id";
// static
String RequestDebugHeaderScope::CaptureHeaderForCurrentLocation(
ExecutionContext* context) {
ThreadDebugger* debugger = nullptr;
if (auto* scope = DynamicTo<WorkerGlobalScope>(context))
debugger = WorkerThreadDebugger::From(scope->GetThread()->GetIsolate());
else if (LocalDOMWindow* dom_window = DynamicTo<LocalDOMWindow>(context))
debugger = MainThreadDebugger::Instance();
if (!debugger)
return String();
auto stack = debugger->StoreCurrentStackTrace("network request").ToString();
return stack ? ToCoreString(std::move(stack)) : String();
}
RequestDebugHeaderScope::RequestDebugHeaderScope(ExecutionContext* context,
const String& header) {
if (header.IsEmpty())
return;
stack_trace_id_ =
v8_inspector::V8StackTraceId(ToV8InspectorStringView(header));
if (stack_trace_id_.IsInvalid())
return;
if (auto* scope = DynamicTo<WorkerGlobalScope>(context))
debugger_ = WorkerThreadDebugger::From(scope->GetThread()->GetIsolate());
else if (auto* window = DynamicTo<LocalDOMWindow>(context))
debugger_ = MainThreadDebugger::Instance();
if (debugger_)
debugger_->ExternalAsyncTaskStarted(stack_trace_id_);
}
RequestDebugHeaderScope::~RequestDebugHeaderScope() {
if (debugger_)
debugger_->ExternalAsyncTaskFinished(stack_trace_id_);
}
} // namespace blink
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_REQUEST_DEBUG_HEADER_SCOPE_H_
#define THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_REQUEST_DEBUG_HEADER_SCOPE_H_
#include "third_party/blink/renderer/core/core_export.h"
#include "third_party/blink/renderer/platform/wtf/allocator/allocator.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
#include "v8/include/v8-inspector.h"
namespace blink {
class ExecutionContext;
class ThreadDebugger;
class CORE_EXPORT RequestDebugHeaderScope {
STACK_ALLOCATED();
public:
static const char kHeaderName[];
static String CaptureHeaderForCurrentLocation(ExecutionContext*);
RequestDebugHeaderScope(ExecutionContext*, const String& header);
~RequestDebugHeaderScope();
private:
ThreadDebugger* debugger_ = nullptr;
v8_inspector::V8StackTraceId stack_trace_id_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_CORE_INSPECTOR_REQUEST_DEBUG_HEADER_SCOPE_H_
......@@ -64,6 +64,7 @@
#include "third_party/blink/renderer/core/fetch/global_fetch.h"
#include "third_party/blink/renderer/core/frame/reporting_context.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/core/inspector/request_debug_header_scope.h"
#include "third_party/blink/renderer/core/inspector/worker_inspector_controller.h"
#include "third_party/blink/renderer/core/inspector/worker_thread_debugger.h"
#include "third_party/blink/renderer/core/loader/threadable_loader.h"
......@@ -1509,6 +1510,12 @@ void ServiceWorkerGlobalScope::StartFetchEvent(
!params->request->is_main_resource_load ? String() : params->client_id);
event_init->setIsReload(params->request->is_reload);
mojom::blink::FetchAPIRequest& fetch_request = *params->request;
auto debug_header_it =
fetch_request.headers.find(RequestDebugHeaderScope::kHeaderName);
auto stack_string = debug_header_it == fetch_request.headers.end()
? String()
: debug_header_it->value;
Request* request = Request::Create(
ScriptController()->GetScriptState(), std::move(params->request),
Request::ForServiceWorkerFetchEvent::kTrue);
......@@ -1532,6 +1539,7 @@ void ServiceWorkerGlobalScope::StartFetchEvent(
NoteNewFetchEvent(request->url());
RequestDebugHeaderScope debug_header_scope(this, stack_string);
DispatchExtendableEventWithRespondWith(fetch_event, wait_until_observer,
respond_with_observer);
}
......
self.addEventListener('fetch', fetchEvent => {
console.log(fetchEvent); // Should pause here.
fetchEvent.respondWith(fetch(fetchEvent.request));
});
Async stack trace for service worker fetch.
Navigated, registering service worker
Service worker activated, reloading
Reloaded, continue
enable debug header: {
id : <number>
result : {
}
sessionId : <string>
}
Evaluate:
debugger;
fetch("foo.json");
//# sourceURL=test.js
Paused in page, step over
Run stepInto with breakOnAsyncCall flag
Paused in service worker
(anonymous) at http://127.0.0.1:8000/inspector-protocol/service-worker/resources/service-worker-fetch.js:1:2
--network request--
(anonymous) at test.js:2:6
(async function(testRunner) {
var {page, session, dp} =
await testRunner.startBlank('Async stack trace for service worker fetch.');
let swdp;
let swDebuggerId;
await dp.Target.setAutoAttach({autoAttach: true, waitForDebuggerOnStart: true, flatten: true});
const onAttached = async event => {
swdp = session.createChild(event.params.sessionId).protocol;
swDebuggerId = (await swdp.Debugger.enable()).result.debuggerId;
await swdp.Debugger.setAsyncCallStackDepth({maxDepth: 32});
swdp.Runtime.runIfWaitingForDebugger();
};
dp.Target.onAttachedToTarget(onAttached);
await dp.ServiceWorker.enable();
await session.navigate('resources/service-worker-fetch.html');
testRunner.log('Navigated, registering service worker');
session.evaluateAsync(`navigator.serviceWorker.register('service-worker-fetch.js')`);
async function waitForServiceWorkerActivation() {
let versions;
do {
const result = await dp.ServiceWorker.onceWorkerVersionUpdated();
versions = result.params.versions;
} while (!versions.length || versions[0].status !== "activated");
return versions[0].registrationId;
}
await waitForServiceWorkerActivation();
testRunner.log('\nService worker activated, reloading');
dp.Page.reload();
await dp.Page.onceLifecycleEvent(event => event.params.name === 'load');
testRunner.log('\nReloaded, continue');
const pageDebuggerId = (await dp.Debugger.enable()).result.debuggerId;
await dp.Debugger.setAsyncCallStackDepth({maxDepth: 32});
await dp.Network.enable();
testRunner.log(await dp.Network.setAttachDebugHeader({enabled: true}), 'enable debug header: ');
const code = `
debugger;
fetch("foo.json");
//# sourceURL=test.js`;
testRunner.log('\nEvaluate:\n' + code);
session.evaluate(code);
await dp.Debugger.oncePaused();
testRunner.log('\nPaused in page, step over');
dp.Debugger.stepOver();
await dp.Debugger.oncePaused();
testRunner.log('Run stepInto with breakOnAsyncCall flag');
dp.Debugger.stepInto({breakOnAsyncCall: true});
const {callFrames, asyncStackTrace, asyncStackTraceId} = (await swdp.Debugger.oncePaused()).params;
testRunner.log('\nPaused in service worker');
await testRunner.logStackTrace(
new Map([[swDebuggerId, swdp.Debugger], [pageDebuggerId, dp.Debugger]]),
{callFrames, parent: asyncStackTrace, parentId: asyncStackTraceId},
swDebuggerId);
testRunner.completeTest();
})
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment