Skip to content

Commit 485d754

Browse files
J0pannt1m
authored andcommitted
Web Inspector: Show each individual requests when there are redirects
https://bugs.webkit.org/show_bug.cgi?id=293708 rdar://152606018 Reviewed by BJ Burg and Devin Rousso. This change surfaces individual redirect information in the Network tab by allowing users to click on redirect entries to view detailed information across multiple tabs (Preview, Headers, Cookies, Sizes, Timing, and Security) as well as the right click context utility menu. Limitations: Due to backend constraints, redirects have incomplete information compared to full resources (see https://webkit.org/b/190214): - Timing: Only a single timestamp is available (no breakdown of DNS, connection, etc.) - Security: No certificate or detailed security information - Sizes: Only estimated from header lengths (no actual body size data) * Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js: * Source/WebInspectorUI/UserInterface/Base/Main.js: * Source/WebInspectorUI/UserInterface/Controllers/HARBuilder.js: (WI.HARBuilder.async buildArchive): (WI.HARBuilder.entry): (WI.HARBuilder.requestForRedirect): (WI.HARBuilder.responseForRedirect): (WI.HARBuilder.timingsForRedirect): * Source/WebInspectorUI/UserInterface/Main.html: * Source/WebInspectorUI/UserInterface/Models/Redirect.js: (WI.Redirect): (WI.Redirect.prototype.generateFetchCode): (WI.Redirect.prototype.generateCURLCommand): (WI.Redirect.prototype.stringifyHTTPRequestHeaders): (WI.Redirect.prototype.stringifyHTTPResponseHeaders): * Source/WebInspectorUI/UserInterface/Models/Resource.js: (WI.Resource.prototype.updateForRedirectResponse): (WI.Resource.prototype.generateFetchCode): (WI.Resource.prototype.generateCURLCommand): (WI.Resource.prototype.stringifyHTTPRequestHeaders): (WI.Resource.prototype.stringifyHTTPResponseHeaders): (WI.Resource.prototype.generateCURLCommand.escapeStringPosix.escapeCharacter): Deleted. (WI.Resource.prototype.generateCURLCommand.escapeStringPosix): Deleted. * Source/WebInspectorUI/UserInterface/Models/ResourceUtilities.js: Added. (WI.ResourceUtilities.generateFetchCode): (WI.ResourceUtilities.generateCURLCommand.escapeStringPosix.escapeCharacter): (WI.ResourceUtilities.generateCURLCommand.escapeStringPosix): (WI.ResourceUtilities.generateCURLCommand): (WI.ResourceUtilities): * Source/WebInspectorUI/UserInterface/Test.html: * Source/WebInspectorUI/UserInterface/Views/CallFrameTreeElement.js: (WI.CallFrameTreeElement.prototype.populateContextMenu): * Source/WebInspectorUI/UserInterface/Views/ContextMenuUtilities.js: (WI.appendContextMenuItemsForNetworkResource): (WI.appendContextMenuItemsForSourceCode): * Source/WebInspectorUI/UserInterface/Views/NetworkRedirectDetailView.js: Added. (WI.NetworkRedirectDetailView): (WI.NetworkRedirectDetailView.prototype.showParentResource): (WI.NetworkRedirectDetailView.prototype.initialLayout): (WI.NetworkRedirectDetailView.prototype.showContentViewForIdentifier): * Source/WebInspectorUI/UserInterface/Views/NetworkRedirectHeadersContentView.js: Added. (WI.NetworkRedirectHeadersContentView): (WI.NetworkRedirectHeadersContentView.prototype.initialLayout): (WI.NetworkRedirectHeadersContentView.prototype._createSortedArrayForHeaders): * Source/WebInspectorUI/UserInterface/Views/NetworkResourceDetailView.js: (WI.NetworkResourceDetailView): (WI.NetworkResourceDetailView.prototype.showRedirect): (WI.NetworkResourceDetailView.prototype.showRepresentedObject): (WI.NetworkResourceDetailView.prototype.showContentViewForIdentifier): * Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css: (.network-table > .table .cell.name .disclosure): (body[dir=rtl] .network-table > .table .cell.name .disclosure): (.network-table:focus > .table li.selected .cell.name .disclosure): (.network-table > .table .cell.name .disclosure.expanded): (.network-table:focus > .table li.selected .cell.name .disclosure.expanded): (.network-table > .table .cell.name.child): (.table.network-table .data-container .data-list > li.redirect .cell:not(.name)): (.table.network-table.grouped .data-container .cell.name.parent): (.table.network-table.grouped .data-container .cell.name:not(.parent):not(.child)): (.network-table > .table .data-container .cell.name .redirect-count): (.network-table:focus > .table li.selected .cell.name .redirect-count): (.network-table > .table li.selected .cell.name .redirect-count): (@media (prefers-color-scheme: dark) .network-table > .table .data-container .cell.name .redirect-count): (.network-table > .table.grouped .data-container .cell.name): Deleted. (.network-table > .table.grouped .data-container .cell:not(.parent).name): Deleted. (.network-table > .table.grouped .data-container .cell.child.name): Deleted. * Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js: (WI.NetworkTableContentView): (WI.NetworkTableContentView.prototype.closed): (WI.NetworkTableContentView.prototype.showRepresentedObject): (WI.NetworkTableContentView.prototype.networkRedirectDetailViewShowParentResource): (WI.NetworkTableContentView.prototype.networkRedirectHeadersContentViewShowRedirect): (WI.NetworkTableContentView.prototype.networkRedirectHeadersContentViewShowParentResource): (WI.NetworkTableContentView.prototype.showRedirect): (WI.NetworkTableContentView.prototype.tableCellContextMenuClicked): (WI.NetworkTableContentView.prototype.tableSelectionDidChange): (WI.NetworkTableContentView.prototype.tablePopulateCell): (WI.NetworkTableContentView.prototype._populateNameCell): (WI.NetworkTableContentView.prototype._populateInitiatorCell): (WI.NetworkTableContentView.prototype._processPendingEntries): (WI.NetworkTableContentView.prototype._rowIndexForRepresentedObject): (WI.NetworkTableContentView.prototype._updateEntryForResource): (WI.NetworkTableContentView.prototype._populateRedirectEntriesForResourceEntry): (WI.NetworkTableContentView.prototype._showDetailView): (WI.NetworkTableContentView.prototype._resourceRedirectsDidChange): (WI.NetworkTableContentView.prototype._insertResourceAndReloadTable): (WI.NetworkTableContentView.prototype._entryForResource): (WI.NetworkTableContentView.prototype._entryForRedirect): (WI.NetworkTableContentView.prototype._estimateHeaderSize): (WI.NetworkTableContentView.prototype._updateFilteredEntries): (WI.NetworkTableContentView.prototype._reloadTable): (WI.NetworkTableContentView.prototype._HARResources): * Source/WebInspectorUI/UserInterface/Views/ResourceCookiesContentView.js: (WI.ResourceCookiesContentView): (WI.ResourceCookiesContentView.prototype._refreshRequestCookiesSection): (WI.ResourceCookiesContentView.prototype._refreshResponseCookiesSection): * Source/WebInspectorUI/UserInterface/Views/ResourceDetailsSection.css: (.resource-details > section > .title): (.resource-details > section > .title > .redirect-nav-link): (.resource-details > section:not(:hover) > .title > .redirect-nav-link): * Source/WebInspectorUI/UserInterface/Views/ResourceHeadersContentView.js: (WI.ResourceHeadersContentView): (WI.ResourceHeadersContentView.prototype._refreshSummarySection): (WI.ResourceHeadersContentView.prototype._refreshRedirectHeadersSections): (WI.ResourceHeadersContentView.prototype._refreshRequestHeadersSection): (WI.ResourceHeadersContentView.prototype._refreshResponseHeadersSection): * Source/WebInspectorUI/UserInterface/Views/ResourceSizesContentView.css: (.resource-sizes > .content > section.note-section): (.resource-sizes > .content > section.note-section .note): * Source/WebInspectorUI/UserInterface/Views/ResourceTimelineDataGridNode.js: (WI.ResourceTimelineDataGridNode.prototype.appendContextMenuItems): * Source/WebInspectorUI/UserInterface/Views/ResourceTreeElement.js: (WI.ResourceTreeElement.prototype.populateContextMenu): * Source/WebInspectorUI/UserInterface/Views/Table.css: (.table .cell): * Source/WebInspectorUI/UserInterface/Views/WorkerTreeElement.js: (WI.WorkerTreeElement.prototype.populateContextMenu): Canonical link: https://commits.webkit.org/306537@main
1 parent 84a28d9 commit 485d754

23 files changed

+1044
-218
lines changed

Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1722,6 +1722,8 @@ localizedStrings["Subscript @ Font Details Sidebar Property Value"] = "Subscript
17221722
localizedStrings["Subtree Modified @ DOM Breakpoint"] = "Subtree Modified";
17231723
localizedStrings["Suggest property names based on usage"] = "Suggest property names based on usage";
17241724
localizedStrings["Summary"] = "Summary";
1725+
/* Section header for network redirect details. */
1726+
localizedStrings["Summary @ Network Redirect Headers"] = "Summary";
17251727
/* Property value for `font-variant-position: super`. */
17261728
localizedStrings["Superscript @ Font Details Sidebar Property Value"] = "Superscript";
17271729
localizedStrings["Symbol"] = "Symbol";
@@ -1923,6 +1925,8 @@ localizedStrings["Video Details @ Media Sidebar"] = "Video Details";
19231925
localizedStrings["Video Format @ Media Sidebar"] = "Video Format";
19241926
/* Value string for Video Range color in the Media Sidebar */
19251927
localizedStrings["Video range @ Media Sidebar"] = "Video range";
1928+
localizedStrings["View Redirect"] = "View Redirect";
1929+
localizedStrings["View Response"] = "View Response";
19261930
localizedStrings["View Image"] = "View Image";
19271931
localizedStrings["View Recording"] = "View Recording";
19281932
localizedStrings["View Shader"] = "View Shader";

Source/WebInspectorUI/UserInterface/Base/Main.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2013-2020 Apple Inc. All rights reserved.
2+
* Copyright (C) 2013-2026 Apple Inc. All rights reserved.
33
*
44
* Redistribution and use in source and binary forms, with or without
55
* modification, are permitted provided that the following conditions
@@ -2814,7 +2814,7 @@ WI.linkifyElement = function(linkElement, sourceCodeLocation, options = {}) {
28142814
linkElement.addEventListener("click", showSourceCodeLocation);
28152815
linkElement.addEventListener("contextmenu", (event) => {
28162816
let contextMenu = WI.ContextMenu.createFromEvent(event);
2817-
WI.appendContextMenuItemsForSourceCode(contextMenu, sourceCodeLocation);
2817+
WI.appendContextMenuItemsForNetworkResource(contextMenu, sourceCodeLocation);
28182818
});
28192819
};
28202820

Source/WebInspectorUI/UserInterface/Controllers/HARBuilder.js

Lines changed: 84 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2017-2018 Apple Inc. All rights reserved.
2+
* Copyright (C) 2017-2026 Apple Inc. All rights reserved.
33
*
44
* Redistribution and use in source and binary forms, with or without
55
* modification, are permitted provided that the following conditions
@@ -29,29 +29,36 @@
2929

3030
WI.HARBuilder = class HARBuilder
3131
{
32-
static async buildArchive(resources)
32+
static async buildArchive(entries)
3333
{
3434
let promises = [];
35-
for (let resource of resources) {
36-
console.assert(resource.finished);
35+
for (let entry of entries) {
36+
// Handle redirect entries (which don't need content)
37+
if (entry.redirect) {
38+
promises.push(Promise.resolve());
39+
continue;
40+
}
41+
42+
// Handle regular resources
43+
console.assert(entry.finished);
3744
promises.push(new Promise((resolve, reject) => {
3845
// Always resolve.
39-
resource.requestContent().then(
46+
entry.requestContent().then(
4047
(x) => resolve(x),
4148
() => resolve(null)
4249
);
4350
}));
4451
}
4552

4653
let contents = await Promise.all(promises);
47-
console.assert(contents.length === resources.length);
54+
console.assert(contents.length === entries.length);
4855

4956
return {
5057
log: {
5158
version: "1.2",
5259
creator: HARBuilder.creator(),
5360
pages: HARBuilder.pages(),
54-
entries: resources.map((resource, index) => HARBuilder.entry(resource, contents[index])),
61+
entries: entries.map((entry, index) => HARBuilder.entry(entry, contents[index])),
5562
}
5663
};
5764
}
@@ -89,8 +96,29 @@ WI.HARBuilder = class HARBuilder
8996
return result;
9097
}
9198

92-
static entry(resource, content)
99+
static entry(entryOrResource, content)
93100
{
101+
// Handle redirect entries
102+
if (entryOrResource.redirect) {
103+
let redirect = entryOrResource.redirect;
104+
let parentResource = entryOrResource.parentResource;
105+
106+
return {
107+
pageref: "page_0",
108+
startedDateTime: HARBuilder.date(new Date(redirect.timestamp * 1000)),
109+
time: 0, // Redirects don't have detailed timing
110+
request: HARBuilder.requestForRedirect(redirect, parentResource),
111+
response: HARBuilder.responseForRedirect(redirect),
112+
cache: {},
113+
timings: HARBuilder.timingsForRedirect(),
114+
connection: String(parentResource.connectionIdentifier),
115+
serverIPAddress: parentResource.remoteAddress ? HARBuilder.ipAddress(parentResource.remoteAddress) : undefined,
116+
_serverPort: parentResource.remoteAddress ? HARBuilder.port(parentResource.remoteAddress) : undefined,
117+
};
118+
}
119+
120+
// Handle regular resources
121+
let resource = entryOrResource;
94122
let entry = {
95123
pageref: "page_0",
96124
startedDateTime: HARBuilder.date(resource.requestSentDate),
@@ -111,7 +139,7 @@ WI.HARBuilder = class HARBuilder
111139
entry._serverPort = HARBuilder.port(resource.remoteAddress);
112140
}
113141
if (resource.connectionIdentifier)
114-
entry.connection = "" + resource.connectionIdentifier;
142+
entry.connection = String(resource.connectionIdentifier);
115143

116144
// CFNetwork Custom Field `_fetchType`.
117145
if (resource.responseSource !== WI.Resource.ResponseSource.Unknown)
@@ -285,6 +313,53 @@ WI.HARBuilder = class HARBuilder
285313
return result;
286314
}
287315

316+
static requestForRedirect(redirect, parentResource)
317+
{
318+
return {
319+
method: redirect.requestMethod || "",
320+
url: redirect.url || "",
321+
httpVersion: "", // Not available for redirects
322+
cookies: [], // Not available for redirects
323+
headers: HARBuilder.headers(redirect.requestHeaders),
324+
queryString: [], // Could parse from URL if needed
325+
headersSize: -1,
326+
bodySize: -1,
327+
};
328+
}
329+
330+
static responseForRedirect(redirect)
331+
{
332+
return {
333+
status: redirect.responseStatusCode || 0,
334+
statusText: redirect.responseStatusText || "",
335+
httpVersion: "", // Not available for redirects
336+
cookies: [], // Not available for redirects
337+
headers: HARBuilder.headers(redirect.responseHeaders),
338+
content: {
339+
size: 0,
340+
mimeType: "x-unknown",
341+
},
342+
redirectURL: redirect.responseHeaders.Location || redirect.responseHeaders.location || "",
343+
headersSize: -1,
344+
bodySize: -1,
345+
};
346+
}
347+
348+
static timingsForRedirect()
349+
{
350+
// FIXME: <https://webkit.org/b/195694> Web Inspector: HAR Extension for Redirect Timing Info
351+
// Redirects don't currently have detailed timing breakdown
352+
return {
353+
blocked: -1,
354+
dns: -1,
355+
connect: -1,
356+
ssl: -1,
357+
send: 0,
358+
wait: 0,
359+
receive: 0,
360+
};
361+
}
362+
288363
// Helpers
289364

290365
static ipAddress(remoteAddress)

Source/WebInspectorUI/UserInterface/Main.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,7 @@
392392
<script src="Models/TimelineRecord.js"></script>
393393

394394
<script src="Models/Resource.js"></script>
395+
<script src="Models/ResourceUtilities.js"></script>
395396
<script src="Models/Script.js"></script>
396397
<script src="Models/LocalScript.js"></script>
397398

@@ -800,6 +801,8 @@
800801
<script src="Views/MultiSidebar.js"></script>
801802
<script src="Views/NavigationBar.js"></script>
802803
<script src="Views/NetworkDOMNodeDetailView.js"></script>
804+
<script src="Views/NetworkRedirectDetailView.js"></script>
805+
<script src="Views/NetworkRedirectHeadersContentView.js"></script>
803806
<script src="Views/NetworkResourceDetailView.js"></script>
804807
<script src="Views/NetworkTableContentView.js"></script>
805808
<script src="Views/NetworkTimelineOverviewGraph.js"></script>

Source/WebInspectorUI/UserInterface/Models/Redirect.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ WI.Redirect = class Redirect
3535
console.assert(typeof responseHeaders === "object");
3636
console.assert(!isNaN(timestamp));
3737

38+
// FIXME: <https://webkit.org/b/190214> Redirect model only stores basic information.
39+
// Missing from backend: detailed timing breakdown (ResourceTiming), size metrics (Metrics),
40+
// and security information (Security.Security). Protocol supports these fields in
41+
// Network.Response but they're not populated for redirectResponse.
42+
3843
this._url = url;
3944
this._urlComponents = null;
4045
this._requestMethod = requestMethod;
@@ -61,4 +66,24 @@ WI.Redirect = class Redirect
6166
this._urlComponents = parseURL(this._url);
6267
return this._urlComponents;
6368
}
69+
70+
generateFetchCode()
71+
{
72+
return WI.ResourceUtilities.generateFetchCode(this);
73+
}
74+
75+
generateCURLCommand()
76+
{
77+
return WI.ResourceUtilities.generateCURLCommand(this);
78+
}
79+
80+
stringifyHTTPRequestHeaders()
81+
{
82+
return WI.ResourceUtilities.stringifyHTTPRequestHeaders(this);
83+
}
84+
85+
stringifyHTTPResponseHeaders()
86+
{
87+
return WI.ResourceUtilities.stringifyHTTPResponseHeaders(this);
88+
}
6489
};

0 commit comments

Comments
 (0)