chrome-devtools-frontend 1.0.1585664 → 1.0.1587572
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/front_end/core/common/Srcset.ts +61 -0
- package/front_end/core/common/common.ts +2 -0
- package/front_end/entrypoint_template.html +5 -1
- package/front_end/generated/InspectorBackendCommands.ts +1 -1
- package/front_end/generated/protocol.ts +2 -0
- package/front_end/models/ai_assistance/AiConversation.ts +6 -2
- package/front_end/models/javascript_metadata/NativeFunctions.js +2 -2
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +1 -0
- package/front_end/panels/application/ServiceWorkerCacheViews.ts +1 -2
- package/front_end/panels/console/PromptBuilder.ts +15 -3
- package/front_end/panels/elements/ElementsPanel.ts +114 -15
- package/front_end/panels/elements/ElementsTreeElement.ts +4 -56
- package/front_end/panels/network/NetworkItemView.ts +4 -3
- package/front_end/panels/network/{components/RequestHeadersView.ts → RequestHeadersView.ts} +112 -184
- package/front_end/panels/network/components/RequestHeaderSection.ts +3 -0
- package/front_end/panels/network/components/components.ts +0 -2
- package/front_end/panels/network/network.ts +3 -0
- package/front_end/panels/sources/CallStackSidebarPane.ts +3 -7
- package/front_end/panels/utils/utils.ts +13 -5
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/FrameManager.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/FrameManager.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/util.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/util.js +7 -7
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/common/util.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +7 -7
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/FrameManager.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/FrameManager.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/util.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/util.js +7 -7
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/common/util.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/package.json +2 -2
- package/front_end/third_party/puppeteer/package/src/cdp/FrameManager.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/common/util.ts +9 -8
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/legacy/UIUtils.ts +31 -14
- package/front_end/ui/legacy/components/utils/JSPresentationUtils.ts +135 -142
- package/package.json +1 -1
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// Copyright 2026 The Chromium Authors
|
|
2
|
+
// Use of this source code is governed by a BSD-style license that can be
|
|
3
|
+
// found in the LICENSE file.
|
|
4
|
+
|
|
5
|
+
export const enum TokenType {
|
|
6
|
+
LITERAL = 0,
|
|
7
|
+
URL = 1
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface Token {
|
|
11
|
+
type: TokenType;
|
|
12
|
+
value: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Parsing of
|
|
17
|
+
* https://html.spec.whatwg.org/multipage/images.html#srcset-attribute and href
|
|
18
|
+
* attributes to identify URLs vs other text in the srcset.
|
|
19
|
+
*
|
|
20
|
+
* Note: this is probably not spec compliant.
|
|
21
|
+
*/
|
|
22
|
+
export function parseSrcset(value: string): Token[] {
|
|
23
|
+
const result: Token[] = [];
|
|
24
|
+
let i = 0;
|
|
25
|
+
while (value.length) {
|
|
26
|
+
if (i++ > 0) {
|
|
27
|
+
result.push({value: ' ', type: TokenType.LITERAL});
|
|
28
|
+
}
|
|
29
|
+
value = value.trim();
|
|
30
|
+
let url = '';
|
|
31
|
+
let descriptor = '';
|
|
32
|
+
const indexOfSpace = value.search(/\s/);
|
|
33
|
+
if (indexOfSpace === -1) {
|
|
34
|
+
url = value;
|
|
35
|
+
} else if (indexOfSpace > 0 && value[indexOfSpace - 1] === ',') {
|
|
36
|
+
url = value.substring(0, indexOfSpace);
|
|
37
|
+
} else {
|
|
38
|
+
url = value.substring(0, indexOfSpace);
|
|
39
|
+
const indexOfComma = value.indexOf(',', indexOfSpace);
|
|
40
|
+
if (indexOfComma !== -1) {
|
|
41
|
+
descriptor = value.substring(indexOfSpace, indexOfComma + 1);
|
|
42
|
+
} else {
|
|
43
|
+
descriptor = value.substring(indexOfSpace);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (url) {
|
|
48
|
+
if (url.endsWith(',')) {
|
|
49
|
+
result.push({value: url.substring(0, url.length - 1), type: TokenType.URL});
|
|
50
|
+
result.push({type: TokenType.LITERAL, value: ','});
|
|
51
|
+
} else {
|
|
52
|
+
result.push({value: url, type: TokenType.URL});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (descriptor) {
|
|
56
|
+
result.push({type: TokenType.LITERAL, value: descriptor});
|
|
57
|
+
}
|
|
58
|
+
value = value.substring(url.length + descriptor.length);
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
@@ -29,6 +29,7 @@ import * as SegmentedRange from './SegmentedRange.js';
|
|
|
29
29
|
import * as SettingRegistration from './SettingRegistration.js';
|
|
30
30
|
import * as Settings from './Settings.js';
|
|
31
31
|
import * as SimpleHistoryManager from './SimpleHistoryManager.js';
|
|
32
|
+
import * as Srcset from './Srcset.js';
|
|
32
33
|
import * as StringOutputStream from './StringOutputStream.js';
|
|
33
34
|
import * as TextDictionary from './TextDictionary.js';
|
|
34
35
|
import * as Throttler from './Throttler.js';
|
|
@@ -68,6 +69,7 @@ export {
|
|
|
68
69
|
SettingRegistration,
|
|
69
70
|
Settings,
|
|
70
71
|
SimpleHistoryManager,
|
|
72
|
+
Srcset,
|
|
71
73
|
StringOutputStream,
|
|
72
74
|
TextDictionary,
|
|
73
75
|
Throttler,
|
|
@@ -14,7 +14,11 @@
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
</style>
|
|
17
|
-
<meta
|
|
17
|
+
<meta
|
|
18
|
+
http-equiv="Content-Security-Policy"
|
|
19
|
+
content="default-src 'self' devtools: data:; style-src 'self' 'unsafe-inline' devtools:; object-src 'none'; script-src
|
|
20
|
+
'self' https://chrome-devtools-frontend.appspot.com; img-src 'self' data:; frame-src * data:; connect-src data:
|
|
21
|
+
https://chromeuxreport.googleapis.com 'self' devtools:;">
|
|
18
22
|
<meta name="referrer" content="no-referrer">
|
|
19
23
|
<script type="module" src="./entrypoints/%ENTRYPOINT_NAME%/%ENTRYPOINT_NAME%.js"></script>
|
|
20
24
|
<link href="./application_tokens.css" rel="stylesheet">
|
|
@@ -857,7 +857,7 @@ inspectorBackend.registerCommand("Network.disable", [], [], "Disables network tr
|
|
|
857
857
|
inspectorBackend.registerCommand("Network.emulateNetworkConditions", [{"name": "offline", "type": "boolean", "optional": false, "description": "True to emulate internet disconnection.", "typeRef": null}, {"name": "latency", "type": "number", "optional": false, "description": "Minimum latency from request sent to response headers received (ms).", "typeRef": null}, {"name": "downloadThroughput", "type": "number", "optional": false, "description": "Maximal aggregated download throughput (bytes/sec). -1 disables download throttling.", "typeRef": null}, {"name": "uploadThroughput", "type": "number", "optional": false, "description": "Maximal aggregated upload throughput (bytes/sec). -1 disables upload throttling.", "typeRef": null}, {"name": "connectionType", "type": "string", "optional": true, "description": "Connection type if known.", "typeRef": "Network.ConnectionType"}, {"name": "packetLoss", "type": "number", "optional": true, "description": "WebRTC packet loss (percent, 0-100). 0 disables packet loss emulation, 100 drops all the packets.", "typeRef": null}, {"name": "packetQueueLength", "type": "number", "optional": true, "description": "WebRTC packet queue length (packet). 0 removes any queue length limitations.", "typeRef": null}, {"name": "packetReordering", "type": "boolean", "optional": true, "description": "WebRTC packetReordering feature.", "typeRef": null}], [], "Activates emulation of network conditions. This command is deprecated in favor of the emulateNetworkConditionsByRule and overrideNetworkState commands, which can be used together to the same effect.");
|
|
858
858
|
inspectorBackend.registerCommand("Network.emulateNetworkConditionsByRule", [{"name": "offline", "type": "boolean", "optional": false, "description": "True to emulate internet disconnection.", "typeRef": null}, {"name": "matchedNetworkConditions", "type": "array", "optional": false, "description": "Configure conditions for matching requests. If multiple entries match a request, the first entry wins. Global conditions can be configured by leaving the urlPattern for the conditions empty. These global conditions are also applied for throttling of p2p connections.", "typeRef": "Network.NetworkConditions"}], ["ruleIds"], "Activates emulation of network conditions for individual requests using URL match patterns. Unlike the deprecated Network.emulateNetworkConditions this method does not affect `navigator` state. Use Network.overrideNetworkState to explicitly modify `navigator` behavior.");
|
|
859
859
|
inspectorBackend.registerCommand("Network.overrideNetworkState", [{"name": "offline", "type": "boolean", "optional": false, "description": "True to emulate internet disconnection.", "typeRef": null}, {"name": "latency", "type": "number", "optional": false, "description": "Minimum latency from request sent to response headers received (ms).", "typeRef": null}, {"name": "downloadThroughput", "type": "number", "optional": false, "description": "Maximal aggregated download throughput (bytes/sec). -1 disables download throttling.", "typeRef": null}, {"name": "uploadThroughput", "type": "number", "optional": false, "description": "Maximal aggregated upload throughput (bytes/sec). -1 disables upload throttling.", "typeRef": null}, {"name": "connectionType", "type": "string", "optional": true, "description": "Connection type if known.", "typeRef": "Network.ConnectionType"}], [], "Override the state of navigator.onLine and navigator.connection.");
|
|
860
|
-
inspectorBackend.registerCommand("Network.enable", [{"name": "maxTotalBufferSize", "type": "number", "optional": true, "description": "Buffer size in bytes to use when preserving network payloads (XHRs, etc).", "typeRef": null}, {"name": "maxResourceBufferSize", "type": "number", "optional": true, "description": "Per-resource buffer size in bytes to use when preserving network payloads (XHRs, etc).", "typeRef": null}, {"name": "maxPostDataSize", "type": "number", "optional": true, "description": "Longest post body size (in bytes) that would be included in requestWillBeSent notification", "typeRef": null}, {"name": "reportDirectSocketTraffic", "type": "boolean", "optional": true, "description": "Whether DirectSocket chunk send/receive events should be reported.", "typeRef": null}, {"name": "enableDurableMessages", "type": "boolean", "optional": true, "description": "Enable storing response bodies outside of renderer, so that these survive a cross-process navigation. Requires maxTotalBufferSize to be set. Currently defaults to false. This field is being deprecated in favor of the dedicated configureDurableMessages command, due to the possibility of deadlocks when awaiting Network.enable before issuing Runtime.runIfWaitingForDebugger.", "typeRef": null}], [], "Enables network tracking, network events will now be delivered to the client.");
|
|
860
|
+
inspectorBackend.registerCommand("Network.enable", [{"name": "maxTotalBufferSize", "type": "number", "optional": true, "description": "Buffer size in bytes to use when preserving network payloads (XHRs, etc). This is the maximum number of bytes that will be collected by this DevTools session.", "typeRef": null}, {"name": "maxResourceBufferSize", "type": "number", "optional": true, "description": "Per-resource buffer size in bytes to use when preserving network payloads (XHRs, etc).", "typeRef": null}, {"name": "maxPostDataSize", "type": "number", "optional": true, "description": "Longest post body size (in bytes) that would be included in requestWillBeSent notification", "typeRef": null}, {"name": "reportDirectSocketTraffic", "type": "boolean", "optional": true, "description": "Whether DirectSocket chunk send/receive events should be reported.", "typeRef": null}, {"name": "enableDurableMessages", "type": "boolean", "optional": true, "description": "Enable storing response bodies outside of renderer, so that these survive a cross-process navigation. Requires maxTotalBufferSize to be set. Currently defaults to false. This field is being deprecated in favor of the dedicated configureDurableMessages command, due to the possibility of deadlocks when awaiting Network.enable before issuing Runtime.runIfWaitingForDebugger.", "typeRef": null}], [], "Enables network tracking, network events will now be delivered to the client.");
|
|
861
861
|
inspectorBackend.registerCommand("Network.configureDurableMessages", [{"name": "maxTotalBufferSize", "type": "number", "optional": true, "description": "Buffer size in bytes to use when preserving network payloads (XHRs, etc).", "typeRef": null}, {"name": "maxResourceBufferSize", "type": "number", "optional": true, "description": "Per-resource buffer size in bytes to use when preserving network payloads (XHRs, etc).", "typeRef": null}], [], "Configures storing response bodies outside of renderer, so that these survive a cross-process navigation. If maxTotalBufferSize is not set, durable messages are disabled.");
|
|
862
862
|
inspectorBackend.registerCommand("Network.getAllCookies", [], ["cookies"], "Returns all browser cookies. Depending on the backend support, will return detailed cookie information in the `cookies` field. Deprecated. Use Storage.getCookies instead.");
|
|
863
863
|
inspectorBackend.registerCommand("Network.getCertificate", [{"name": "origin", "type": "string", "optional": false, "description": "Origin to get certificate for.", "typeRef": null}], ["tableNames"], "Returns the DER-encoded certificate.");
|
|
@@ -11896,6 +11896,8 @@ export namespace Network {
|
|
|
11896
11896
|
export interface EnableRequest {
|
|
11897
11897
|
/**
|
|
11898
11898
|
* Buffer size in bytes to use when preserving network payloads (XHRs, etc).
|
|
11899
|
+
* This is the maximum number of bytes that will be collected by this
|
|
11900
|
+
* DevTools session.
|
|
11899
11901
|
*/
|
|
11900
11902
|
maxTotalBufferSize?: integer;
|
|
11901
11903
|
/**
|
|
@@ -456,16 +456,20 @@ Time: ${micros(time)}`;
|
|
|
456
456
|
},
|
|
457
457
|
options.multimodalInput,
|
|
458
458
|
)) {
|
|
459
|
+
// Add to history if relevant
|
|
459
460
|
if (shouldAddToHistory(data)) {
|
|
460
461
|
void this.addHistoryItem(data);
|
|
461
462
|
}
|
|
463
|
+
// Always yield the data
|
|
464
|
+
yield data;
|
|
465
|
+
|
|
466
|
+
// If we change the context
|
|
467
|
+
// requery with the specialized agent.
|
|
462
468
|
if (data.type === ResponseType.CONTEXT_CHANGE) {
|
|
463
469
|
this.setContext(data.context);
|
|
464
470
|
yield* this.#runAgent(initialQuery, options);
|
|
465
471
|
return;
|
|
466
472
|
}
|
|
467
|
-
|
|
468
|
-
yield data;
|
|
469
473
|
}
|
|
470
474
|
}
|
|
471
475
|
|
|
@@ -6624,8 +6624,8 @@ export const NativeFunctions = [
|
|
|
6624
6624
|
signatures: [["type","?eventInitDict"]]
|
|
6625
6625
|
},
|
|
6626
6626
|
{
|
|
6627
|
-
name: "
|
|
6628
|
-
signatures: [["
|
|
6627
|
+
name: "getValueRange",
|
|
6628
|
+
signatures: [["start","end"]]
|
|
6629
6629
|
},
|
|
6630
6630
|
{
|
|
6631
6631
|
name: "getBoxQuads",
|
|
@@ -15,7 +15,6 @@ import * as LegacyWrapper from '../../ui/components/legacy_wrapper/legacy_wrappe
|
|
|
15
15
|
import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js';
|
|
16
16
|
import * as UI from '../../ui/legacy/legacy.js';
|
|
17
17
|
import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
|
|
18
|
-
import * as NetworkComponents from '../network/components/components.js';
|
|
19
18
|
import * as Network from '../network/network.js';
|
|
20
19
|
|
|
21
20
|
import * as ApplicationComponents from './components/components.js';
|
|
@@ -545,7 +544,7 @@ export class RequestView extends UI.Widget.VBox {
|
|
|
545
544
|
this.tabbedPane.appendTab(
|
|
546
545
|
'headers', i18nString(UIStrings.headers),
|
|
547
546
|
LegacyWrapper.LegacyWrapper.legacyWrapper(
|
|
548
|
-
UI.Widget.VBox, new
|
|
547
|
+
UI.Widget.VBox, new Network.RequestHeadersView.RequestHeadersView(request)));
|
|
549
548
|
this.tabbedPane.appendTab(
|
|
550
549
|
'preview', i18nString(UIStrings.preview), new Network.RequestPreviewView.RequestPreviewView(request));
|
|
551
550
|
this.tabbedPane.show(this.element);
|
|
@@ -9,6 +9,7 @@ import * as Formatter from '../../models/formatter/formatter.js';
|
|
|
9
9
|
import * as Logs from '../../models/logs/logs.js';
|
|
10
10
|
import * as TextUtils from '../../models/text_utils/text_utils.js';
|
|
11
11
|
import * as Components from '../../ui/legacy/components/utils/utils.js';
|
|
12
|
+
import * as UI from '../../ui/legacy/legacy.js';
|
|
12
13
|
|
|
13
14
|
import type {ConsoleViewMessage} from './ConsoleViewMessage.js';
|
|
14
15
|
|
|
@@ -85,7 +86,7 @@ export class PromptBuilder {
|
|
|
85
86
|
|
|
86
87
|
const relatedCode = sourceCode?.text ? formatRelatedCode(sourceCode) : '';
|
|
87
88
|
const relatedRequest = request ? formatNetworkRequest(request) : '';
|
|
88
|
-
const stacktrace = sourcesTypes.includes(SourceType.STACKTRACE) ? formatStackTrace(this.#consoleMessage) : '';
|
|
89
|
+
const stacktrace = sourcesTypes.includes(SourceType.STACKTRACE) ? await formatStackTrace(this.#consoleMessage) : '';
|
|
89
90
|
|
|
90
91
|
const message = formatConsoleMessage(this.#consoleMessage);
|
|
91
92
|
|
|
@@ -290,14 +291,25 @@ export function formatConsoleMessage(message: ConsoleViewMessage): string {
|
|
|
290
291
|
* This formats the stacktrace from the console message which might or might not
|
|
291
292
|
* match the content of stacktrace(s) in the console message arguments.
|
|
292
293
|
*/
|
|
293
|
-
|
|
294
|
+
async function formatStackTrace(message: ConsoleViewMessage): Promise<string> {
|
|
294
295
|
const previewContainer = message.contentElement().querySelector('.stack-preview-container');
|
|
295
296
|
|
|
296
297
|
if (!previewContainer) {
|
|
297
298
|
return '';
|
|
298
299
|
}
|
|
299
300
|
|
|
300
|
-
const
|
|
301
|
+
const widget =
|
|
302
|
+
UI.Widget.Widget.get(previewContainer) as Components.JSPresentationUtils.StackTracePreviewContent | undefined;
|
|
303
|
+
if (!widget) {
|
|
304
|
+
return '';
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
await widget.updateComplete;
|
|
308
|
+
|
|
309
|
+
// TODO(crbug.com/433162438): Get the `StackTrace` from the widget and render that instead
|
|
310
|
+
// of crawling the DOM. `StackTrace` is source mapped and has ignore listing.
|
|
311
|
+
|
|
312
|
+
const preview = widget.contentElement.querySelector('.stack-preview-container') as HTMLElement;
|
|
301
313
|
|
|
302
314
|
const nodes = preview.childTextNodes();
|
|
303
315
|
// Gets application-level source mapped stack trace taking the ignore list
|
|
@@ -227,6 +227,7 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
|
|
|
227
227
|
domTreeButton?: HTMLElement;
|
|
228
228
|
private selectedNodeOnReset?: SDK.DOMModel.DOMNode;
|
|
229
229
|
private hasNonDefaultSelectedNode?: boolean;
|
|
230
|
+
#restorationGeneration = 0;
|
|
230
231
|
private searchConfig?: UI.SearchableView.SearchConfig;
|
|
231
232
|
private omitDefaultSelection?: boolean;
|
|
232
233
|
private notFirstInspectElement?: boolean;
|
|
@@ -609,6 +610,7 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
|
|
|
609
610
|
if (focus) {
|
|
610
611
|
this.selectedNodeOnReset = selectedNode;
|
|
611
612
|
this.hasNonDefaultSelectedNode = true;
|
|
613
|
+
this.#restorationGeneration++;
|
|
612
614
|
}
|
|
613
615
|
|
|
614
616
|
const executionContexts = selectedNode.domModel().runtimeModel().executionContexts();
|
|
@@ -645,32 +647,128 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
|
|
|
645
647
|
}
|
|
646
648
|
|
|
647
649
|
const savedSelectedNodeOnReset = this.selectedNodeOnReset;
|
|
648
|
-
void
|
|
650
|
+
void this.restoreSelectedNodeAfterUpdate(domModel, this.selectedNodeOnReset || null, savedSelectedNodeOnReset);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Best-effort restoration of the previously focused node after a reload.
|
|
655
|
+
*
|
|
656
|
+
* The CDP path-based mechanism works well for stable DOMs, but can be
|
|
657
|
+
* unreliable for pages that render asynchronously after the initial
|
|
658
|
+
* document update. To improve reliability we retry a few times, and also
|
|
659
|
+
* fall back to evaluating a JS path (document.querySelector(...)) when
|
|
660
|
+
* possible.
|
|
661
|
+
*
|
|
662
|
+
* Node resolution (computation) is separated from view state updates:
|
|
663
|
+
* resolveNode returns a DOMNode|null, and this method handles selection.
|
|
664
|
+
*/
|
|
665
|
+
private async restoreSelectedNodeAfterUpdate(
|
|
666
|
+
domModel: SDK.DOMModel.DOMModel, staleNode: SDK.DOMModel.DOMNode|null,
|
|
667
|
+
savedSelectedNodeOnReset: SDK.DOMModel.DOMNode|undefined): Promise<void> {
|
|
668
|
+
// Fast path: no previous node to restore -- just select the fallback
|
|
669
|
+
// synchronously so callers that check selection immediately still work.
|
|
670
|
+
if (!staleNode) {
|
|
671
|
+
this.trySetFallbackSelection(domModel);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const nodePath = staleNode.path();
|
|
676
|
+
|
|
677
|
+
// Keep the panel usable quickly by selecting a reasonable default node as
|
|
678
|
+
// soon as we can, but continue trying to restore the stale node.
|
|
679
|
+
let didSetFallbackSelection = false;
|
|
649
680
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
const restoredNodeId = nodePath ? await domModel.pushNodeByPathToFrontend(nodePath) : null;
|
|
681
|
+
// Retry with exponential-ish backoff, capping total wait at ~3s.
|
|
682
|
+
// Most async-rendered pages settle well within this window.
|
|
683
|
+
const attemptDelaysMs = [0, 250, 500, 1000, 1500];
|
|
654
684
|
|
|
685
|
+
// Capture the restoration generation so any user interaction (node
|
|
686
|
+
// selection, style editing, node reveal, etc.) cancels pending retries.
|
|
687
|
+
const restorationGeneration = this.#restorationGeneration;
|
|
688
|
+
|
|
689
|
+
for (let attempt = 0; attempt < attemptDelaysMs.length; ++attempt) {
|
|
655
690
|
if (savedSelectedNodeOnReset !== this.selectedNodeOnReset) {
|
|
656
691
|
return;
|
|
657
692
|
}
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
node = inspectedDocument ? inspectedDocument.body || inspectedDocument.documentElement : null;
|
|
693
|
+
if (this.hasNonDefaultSelectedNode || this.pendingNodeReveal ||
|
|
694
|
+
restorationGeneration !== this.#restorationGeneration) {
|
|
695
|
+
return;
|
|
662
696
|
}
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
697
|
+
|
|
698
|
+
if (attemptDelaysMs[attempt]) {
|
|
699
|
+
await new Promise<void>(resolve => window.setTimeout(resolve, attemptDelaysMs[attempt]));
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (savedSelectedNodeOnReset !== this.selectedNodeOnReset) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (this.hasNonDefaultSelectedNode || this.pendingNodeReveal ||
|
|
706
|
+
restorationGeneration !== this.#restorationGeneration) {
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Computation: resolve the node without touching view state.
|
|
711
|
+
const restoredNode = await this.resolveNodeForRestoration(domModel, nodePath);
|
|
712
|
+
|
|
713
|
+
if (restoredNode) {
|
|
714
|
+
this.setDefaultSelectedNode(restoredNode);
|
|
669
715
|
this.lastSelectedNodeSelectedForTest();
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (!didSetFallbackSelection) {
|
|
720
|
+
// If we cannot compute a fallback selection yet, the document likely
|
|
721
|
+
// has not been transmitted from the backend and isn't in a valid state
|
|
722
|
+
// to have a default-selected node. Another document update should be
|
|
723
|
+
// forthcoming. In the meantime, don't notify tests that selection is
|
|
724
|
+
// ready, because it isn't.
|
|
725
|
+
if (!this.trySetFallbackSelection(domModel)) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
didSetFallbackSelection = true;
|
|
670
729
|
}
|
|
671
730
|
}
|
|
672
731
|
}
|
|
673
732
|
|
|
733
|
+
/**
|
|
734
|
+
* Attempts to resolve a DOM node by its CDP path.
|
|
735
|
+
* Pure computation -- does not modify view state.
|
|
736
|
+
*/
|
|
737
|
+
private async resolveNodeForRestoration(domModel: SDK.DOMModel.DOMModel, nodePath: string|null):
|
|
738
|
+
Promise<SDK.DOMModel.DOMNode|null> {
|
|
739
|
+
try {
|
|
740
|
+
if (nodePath) {
|
|
741
|
+
const restoredNodeId = await domModel.pushNodeByPathToFrontend(nodePath);
|
|
742
|
+
const restoredNode = domModel.nodeForId(restoredNodeId);
|
|
743
|
+
if (restoredNode) {
|
|
744
|
+
return restoredNode;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
} catch {
|
|
748
|
+
// CDP calls (pushNodeByPathToFrontend) can reject when the target or
|
|
749
|
+
// session is closed, e.g. if the page navigates again while we are
|
|
750
|
+
// retrying. Safe to swallow: we either retry on the next iteration or
|
|
751
|
+
// fall through to the fallback node.
|
|
752
|
+
}
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
private trySetFallbackSelection(domModel: SDK.DOMModel.DOMModel): boolean {
|
|
757
|
+
const inspectedDocument = domModel.existingDocument();
|
|
758
|
+
const fallbackNode = inspectedDocument ? inspectedDocument.body || inspectedDocument.documentElement : null;
|
|
759
|
+
if (!fallbackNode) {
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
this.setDefaultSelectedNode(fallbackNode);
|
|
764
|
+
this.lastSelectedNodeSelectedForTest();
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
cancelPendingRestoration(): void {
|
|
769
|
+
this.#restorationGeneration++;
|
|
770
|
+
}
|
|
771
|
+
|
|
674
772
|
private lastSelectedNodeSelectedForTest(): void {
|
|
675
773
|
}
|
|
676
774
|
|
|
@@ -1373,6 +1471,7 @@ export class DOMNodeRevealer implements Common.Revealer.Revealer<
|
|
|
1373
1471
|
omitFocus?: boolean): Promise<void> {
|
|
1374
1472
|
const panel = ElementsPanel.instance();
|
|
1375
1473
|
panel.pendingNodeReveal = true;
|
|
1474
|
+
panel.cancelPendingRestoration();
|
|
1376
1475
|
|
|
1377
1476
|
return (new Promise<void>(revealPromise)).catch((reason: Error) => {
|
|
1378
1477
|
let message: string;
|
|
@@ -634,64 +634,12 @@ function renderTitle(
|
|
|
634
634
|
}
|
|
635
635
|
}
|
|
636
636
|
|
|
637
|
-
|
|
638
|
-
LITERAL = 0,
|
|
639
|
-
LINK = 1
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
interface SrcsetToken {
|
|
643
|
-
type: SrcsetTokenType;
|
|
644
|
-
value: string;
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// FIXME: find a home for this in SDK.
|
|
648
|
-
function parseSrcset(value: string): SrcsetToken[] {
|
|
649
|
-
const result: SrcsetToken[] = [];
|
|
650
|
-
let i = 0;
|
|
651
|
-
while (value.length) {
|
|
652
|
-
if (i++ > 0) {
|
|
653
|
-
result.push({value: ' ', type: SrcsetTokenType.LITERAL});
|
|
654
|
-
}
|
|
655
|
-
value = value.trim();
|
|
656
|
-
let url = '';
|
|
657
|
-
let descriptor = '';
|
|
658
|
-
const indexOfSpace = value.search(/\s/);
|
|
659
|
-
if (indexOfSpace === -1) {
|
|
660
|
-
url = value;
|
|
661
|
-
} else if (indexOfSpace > 0 && value[indexOfSpace - 1] === ',') {
|
|
662
|
-
url = value.substring(0, indexOfSpace);
|
|
663
|
-
} else {
|
|
664
|
-
url = value.substring(0, indexOfSpace);
|
|
665
|
-
const indexOfComma = value.indexOf(',', indexOfSpace);
|
|
666
|
-
if (indexOfComma !== -1) {
|
|
667
|
-
descriptor = value.substring(indexOfSpace, indexOfComma + 1);
|
|
668
|
-
} else {
|
|
669
|
-
descriptor = value.substring(indexOfSpace);
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
if (url) {
|
|
674
|
-
if (url.endsWith(',')) {
|
|
675
|
-
result.push({value: url.substring(0, url.length - 1), type: SrcsetTokenType.LINK});
|
|
676
|
-
result.push({type: SrcsetTokenType.LITERAL, value: ','});
|
|
677
|
-
} else {
|
|
678
|
-
result.push({value: url, type: SrcsetTokenType.LINK});
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
if (descriptor) {
|
|
682
|
-
result.push({type: SrcsetTokenType.LITERAL, value: descriptor});
|
|
683
|
-
}
|
|
684
|
-
value = value.substring(url.length + descriptor.length);
|
|
685
|
-
}
|
|
686
|
-
return result;
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
function renderLinkifiedSrcset(tokens: SrcsetToken[], node: SDK.DOMModel.DOMNode): Lit.TemplateResult {
|
|
637
|
+
function renderLinkifiedSrcset(tokens: Common.Srcset.Token[], node: SDK.DOMModel.DOMNode): Lit.TemplateResult {
|
|
690
638
|
return html`${repeat(tokens, token => {
|
|
691
639
|
switch (token.type) {
|
|
692
|
-
case
|
|
640
|
+
case Common.Srcset.TokenType.URL:
|
|
693
641
|
return renderLinkifiedValue(token.value, node);
|
|
694
|
-
case
|
|
642
|
+
case Common.Srcset.TokenType.LITERAL:
|
|
695
643
|
return token.value;
|
|
696
644
|
}
|
|
697
645
|
})}`;
|
|
@@ -849,7 +797,7 @@ function renderAttribute(
|
|
|
849
797
|
Boolean(updateRecord?.isAttributeModified(name) && hasText),
|
|
850
798
|
DOM_UPDATE_ANIMATION_CLASS_NAME)} ${valueRelationRefDirective} ${withEntitiesRef}>
|
|
851
799
|
${valueType === ValueType.SRC ? renderLinkifiedValue(value, node) : nothing}
|
|
852
|
-
${valueType === ValueType.SRCSET ? renderLinkifiedSrcset(parseSrcset(value), node) : nothing}
|
|
800
|
+
${valueType === ValueType.SRCSET ? renderLinkifiedSrcset(Common.Srcset.parseSrcset(value), node) : nothing}
|
|
853
801
|
</span>"` :
|
|
854
802
|
nothing}</span>`;
|
|
855
803
|
// clang-format on
|
|
@@ -22,6 +22,7 @@ import * as NetworkComponents from './components/components.js';
|
|
|
22
22
|
import {EventSourceMessagesView} from './EventSourceMessagesView.js';
|
|
23
23
|
import {RequestCookiesView} from './RequestCookiesView.js';
|
|
24
24
|
import {RequestDeviceBoundSessionsView} from './RequestDeviceBoundSessionsView.js';
|
|
25
|
+
import * as RequestHeadersView from './RequestHeadersView.js';
|
|
25
26
|
import {RequestInitiatorView} from './RequestInitiatorView.js';
|
|
26
27
|
import {RequestPayloadView} from './RequestPayloadView.js';
|
|
27
28
|
import {RequestPreviewView} from './RequestPreviewView.js';
|
|
@@ -148,7 +149,7 @@ const requestToPreviewView = new WeakMap<SDK.NetworkRequest.NetworkRequest, Requ
|
|
|
148
149
|
export class NetworkItemView extends UI.TabbedPane.TabbedPane {
|
|
149
150
|
#request: SDK.NetworkRequest.NetworkRequest;
|
|
150
151
|
readonly #resourceViewTabSetting: Common.Settings.Setting<NetworkForward.UIRequestLocation.UIRequestTabs>;
|
|
151
|
-
readonly #headersViewComponent:
|
|
152
|
+
readonly #headersViewComponent: RequestHeadersView.RequestHeadersView|undefined;
|
|
152
153
|
#payloadView: RequestPayloadView|null = null;
|
|
153
154
|
readonly #responseView: RequestResponseView|undefined;
|
|
154
155
|
#cookiesView: RequestCookiesView|null = null;
|
|
@@ -174,7 +175,7 @@ export class NetworkItemView extends UI.TabbedPane.TabbedPane {
|
|
|
174
175
|
i18nString(UIStrings.headers));
|
|
175
176
|
} else {
|
|
176
177
|
this.#firstTab = NetworkForward.UIRequestLocation.UIRequestTabs.HEADERS_COMPONENT;
|
|
177
|
-
this.#headersViewComponent = new
|
|
178
|
+
this.#headersViewComponent = new RequestHeadersView.RequestHeadersView(request);
|
|
178
179
|
this.appendTab(
|
|
179
180
|
NetworkForward.UIRequestLocation.UIRequestTabs.HEADERS_COMPONENT, i18nString(UIStrings.headers),
|
|
180
181
|
LegacyWrapper.LegacyWrapper.legacyWrapper(UI.Widget.VBox, this.#headersViewComponent),
|
|
@@ -426,7 +427,7 @@ export class NetworkItemView extends UI.TabbedPane.TabbedPane {
|
|
|
426
427
|
this.#headersViewComponent?.revealHeader(section, header);
|
|
427
428
|
}
|
|
428
429
|
|
|
429
|
-
getHeadersViewComponent():
|
|
430
|
+
getHeadersViewComponent(): RequestHeadersView.RequestHeadersView|undefined {
|
|
430
431
|
return this.#headersViewComponent;
|
|
431
432
|
}
|
|
432
433
|
}
|