chrome-devtools-frontend 1.0.1706026 → 1.0.1708524
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/.agents/skills/code-review/SKILL.md +121 -0
- package/front_end/core/sdk/DOMModel.ts +13 -6
- package/front_end/core/sdk/NetworkRequest.ts +43 -7
- package/front_end/core/sdk/SourceMap.ts +4 -13
- package/front_end/core/sdk/SourceMapScopesInfo.ts +134 -86
- package/front_end/core/text_utils/ContentData.ts +18 -1
- package/front_end/entrypoints/heap_snapshot_worker/ContextAnalyzer.ts +102 -4
- package/front_end/generated/InspectorBackendCommands.ts +7 -1
- package/front_end/generated/SupportedCSSProperties.ts +11 -2
- package/front_end/generated/protocol-mapping.d.ts +31 -0
- package/front_end/generated/protocol-proxy-api.d.ts +32 -0
- package/front_end/generated/protocol.ts +11 -0
- package/front_end/models/ai_assistance/AiAgent2.ts +1 -0
- package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +26 -3
- package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +15 -16
- package/front_end/models/ai_assistance/skills/Skill.ts +1 -2
- package/front_end/models/ai_assistance/skills/SkillRegistry.ts +2 -0
- package/front_end/models/ai_assistance/skills/lighthouse.md +33 -0
- package/front_end/models/ai_assistance/tools/GetLighthouseAudits.ts +6 -6
- package/front_end/models/ai_assistance/tools/RunLighthouse.ts +8 -5
- package/front_end/models/bindings/CompilerScriptMapping.ts +102 -45
- package/front_end/models/bindings/DebuggerLanguagePlugins.ts +25 -33
- package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +33 -39
- package/front_end/models/bindings/StylesSourceMapping.ts +3 -0
- package/front_end/models/extensions/ExtensionEndpoint.ts +27 -6
- package/front_end/models/extensions/LanguageExtensionEndpoint.ts +106 -21
- package/front_end/models/extensions/RecorderExtensionEndpoint.ts +4 -2
- package/front_end/models/javascript_metadata/NativeFunctions.ts +6 -2
- package/front_end/models/persistence/NetworkPersistenceManager.ts +4 -3
- package/front_end/models/source_map_scopes/NamesResolver.ts +0 -8
- package/front_end/models/stack_trace/StackTraceImpl.ts +138 -47
- package/front_end/models/stack_trace/StackTraceModel.ts +117 -84
- package/front_end/models/stack_trace/Trie.ts +27 -0
- package/front_end/panels/accessibility/ARIAAttributesView.ts +6 -6
- package/front_end/panels/accessibility/AccessibilityAnnouncementRecordingView.ts +2 -2
- package/front_end/panels/accessibility/AccessibilityNodeView.ts +6 -9
- package/front_end/panels/accessibility/AccessibilitySidebarView.ts +83 -69
- package/front_end/panels/accessibility/AccessibilitySubPane.ts +24 -9
- package/front_end/panels/accessibility/SourceOrderView.ts +7 -3
- package/front_end/panels/accessibility/accessibilitySidebarView.css +5 -3
- package/front_end/panels/application/serviceWorkersView.css +1 -1
- package/front_end/panels/common/CommentThreadWidget.ts +30 -1
- package/front_end/panels/common/CommentsOverlayWidget.ts +42 -38
- package/front_end/panels/common/common-meta.ts +21 -9
- package/front_end/panels/console/ConsoleInsightTeaser.ts +25 -19
- package/front_end/panels/console/ConsoleViewMessage.ts +51 -21
- package/front_end/panels/console/console-meta.ts +1 -5
- package/front_end/panels/elements/DOMTreeWidget.ts +207 -586
- package/front_end/panels/elements/ElementsPanel.ts +1 -5
- package/front_end/panels/elements/StylePropertyTreeElement.ts +100 -5
- package/front_end/panels/lighthouse/LighthouseController.ts +9 -4
- package/front_end/panels/network/NetworkConfigView.ts +231 -187
- package/front_end/panels/network/RequestPayloadView.ts +22 -14
- package/front_end/panels/profiler/HeapDetachedElementsDataGrid.ts +0 -1
- package/front_end/panels/recorder/RecordingView.ts +42 -47
- package/front_end/panels/recorder/StepEditor.ts +1 -0
- package/front_end/panels/recorder/recordingView.css +4 -4
- package/front_end/panels/sources/CSSPlugin.ts +11 -0
- package/front_end/panels/sources/CoveragePlugin.ts +34 -23
- package/front_end/panels/sources/NavigatorView.ts +97 -52
- package/front_end/panels/sources/Plugin.ts +15 -1
- package/front_end/panels/sources/SourcesView.ts +47 -80
- package/front_end/panels/sources/TabbedEditorContainer.ts +27 -2
- package/front_end/panels/sources/UISourceCodeFrame.ts +11 -2
- package/front_end/panels/sources/navigatorTree.css +2 -2
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/comments/CommentAnchorResolver.ts +17 -0
- package/front_end/ui/components/suggestion_input/SuggestionInput.ts +10 -1
- package/front_end/ui/components/text_editor/AiCodeCompletionTeaser.ts +1 -1
- package/front_end/ui/components/text_editor/AiCodeGenerationTeaser.ts +2 -2
- package/front_end/ui/components/text_editor/aiCodeCompletionTeaser.css +0 -11
- package/front_end/ui/components/text_editor/aiCodeGenerationTeaser.css +0 -11
- package/front_end/ui/legacy/SoftDropDown.ts +17 -4
- package/front_end/ui/legacy/StackPane.ts +141 -0
- package/front_end/ui/legacy/TextPrompt.ts +37 -7
- package/front_end/ui/legacy/Toolbar.ts +7 -5
- package/front_end/ui/legacy/Widget.ts +3 -3
- package/front_end/ui/legacy/components/inline_editor/PositionAreaEditor.ts +85 -0
- package/front_end/ui/legacy/components/inline_editor/positionAreaEditor.css +61 -0
- package/front_end/ui/legacy/components/utils/Linkifier.ts +21 -45
- package/front_end/ui/legacy/inspectorCommon.css +11 -0
- package/front_end/ui/legacy/legacy.ts +2 -0
- package/front_end/ui/legacy/softContextMenu.css +2 -0
- package/front_end/ui/settings/ConsoleSettings.ts +7 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +10 -0
- package/package.json +1 -1
|
@@ -28,6 +28,11 @@ type ScopeInfoNodeIndex = Platform.Brand.Brand<number, 'ScopeInfoNodeIndex'>;
|
|
|
28
28
|
*/
|
|
29
29
|
type ContextNodeIndex = Platform.Brand.Brand<number, 'ContextNodeIndex'>;
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Index of a `system / Map` node within the heap snapshot's node array.
|
|
33
|
+
*/
|
|
34
|
+
type MapNodeIndex = Platform.Brand.Brand<number, 'MapNodeIndex'>;
|
|
35
|
+
|
|
31
36
|
/**
|
|
32
37
|
* Values addressed by scope. Since a `ScopeId` is only unique within its
|
|
33
38
|
* script, scopes are keyed by script first.
|
|
@@ -74,6 +79,8 @@ interface ScopeAccumulator {
|
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
interface LiveClosure {
|
|
82
|
+
// The JSFunction or generator object this entry was created for.
|
|
83
|
+
ownerNodeIndex: number;
|
|
77
84
|
contextNodeIndex: ContextNodeIndex;
|
|
78
85
|
scriptNodeIndex: ScriptNodeIndex;
|
|
79
86
|
scopeId: ScopeId;
|
|
@@ -99,6 +106,8 @@ interface HeapScan {
|
|
|
99
106
|
scripts: Map<ScriptNodeIndex, ScriptInfo>;
|
|
100
107
|
contextNodes: ContextNodeInfo[];
|
|
101
108
|
liveClosures: LiveClosure[];
|
|
109
|
+
// Top-level functions of modules that finished evaluating.
|
|
110
|
+
finishedModuleFunctionNodeIndexes: Set<number>;
|
|
102
111
|
// Maps a ScopeInfo to the script it belongs to. A stored `undefined` is a memoized
|
|
103
112
|
// failed lookup. It is therefore not the same as an absent entry, which merely means
|
|
104
113
|
// "not resolved yet".
|
|
@@ -114,7 +123,7 @@ export function analyzeContexts(snapshot: HeapSnapshot): HeapSnapshotModel.HeapS
|
|
|
114
123
|
const scan = scanHeap(snapshot);
|
|
115
124
|
|
|
116
125
|
// (3) Group live closures by function scope and record the context chains they can reach.
|
|
117
|
-
const liveFunctionsByScript = buildLiveFunctions(snapshot, scan.liveClosures);
|
|
126
|
+
const liveFunctionsByScript = buildLiveFunctions(snapshot, scan.liveClosures, scan.finishedModuleFunctionNodeIndexes);
|
|
118
127
|
|
|
119
128
|
// (4) Correlate contexts and their field values with embedded scopes.
|
|
120
129
|
const {scopes, scriptsWithoutScopes} = correlateContextsWithScopes(snapshot, scan, scopesByScript);
|
|
@@ -240,6 +249,9 @@ function scanHeap(snapshot: HeapSnapshot): HeapScan {
|
|
|
240
249
|
const scripts = new Map<ScriptNodeIndex, ScriptInfo>();
|
|
241
250
|
const contextNodes: ContextNodeInfo[] = [];
|
|
242
251
|
const liveClosures: LiveClosure[] = [];
|
|
252
|
+
const finishedModuleFunctionNodeIndexes = new Set<number>();
|
|
253
|
+
// Caches for each map whether it belongs to a generator object. See `isGeneratorObject`.
|
|
254
|
+
const generatorMapNodeIndexes = new Map<MapNodeIndex, boolean>();
|
|
243
255
|
const scopeInfoScriptNodeIndexes = new Map<ScopeInfoNodeIndex, ScriptNodeIndex|undefined>();
|
|
244
256
|
const node = snapshot.createNode();
|
|
245
257
|
const nodes = snapshot.nodes;
|
|
@@ -257,10 +269,12 @@ function scanHeap(snapshot: HeapSnapshot): HeapScan {
|
|
|
257
269
|
processContext(contextNodes, node);
|
|
258
270
|
} else if (node.rawType() === nodeClosureType) {
|
|
259
271
|
processClosure(liveClosures, node);
|
|
272
|
+
} else if (isGeneratorObject(generatorMapNodeIndexes, node)) {
|
|
273
|
+
processGeneratorObject(liveClosures, finishedModuleFunctionNodeIndexes, node);
|
|
260
274
|
}
|
|
261
275
|
}
|
|
262
276
|
|
|
263
|
-
return {scripts, contextNodes, liveClosures, scopeInfoScriptNodeIndexes};
|
|
277
|
+
return {scripts, contextNodes, liveClosures, finishedModuleFunctionNodeIndexes, scopeInfoScriptNodeIndexes};
|
|
264
278
|
}
|
|
265
279
|
|
|
266
280
|
function processScript(scripts: Map<ScriptNodeIndex, ScriptInfo>, node: HeapSnapshotNode): void {
|
|
@@ -426,6 +440,11 @@ function processClosure(liveClosures: LiveClosure[], node: HeapSnapshotNode): vo
|
|
|
426
440
|
if (!sharedFunctionInfo || !closureContext) {
|
|
427
441
|
return;
|
|
428
442
|
}
|
|
443
|
+
addLiveClosure(liveClosures, node, sharedFunctionInfo, closureContext);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
function addLiveClosure(liveClosures: LiveClosure[], ownerNode: HeapSnapshotNode, sharedFunctionInfo: HeapSnapshotNode,
|
|
447
|
+
contextNode: HeapSnapshotNode): void {
|
|
429
448
|
const script = sharedFunctionInfo.findInternalEdgeTarget('script');
|
|
430
449
|
if (!script) {
|
|
431
450
|
return;
|
|
@@ -433,18 +452,97 @@ function processClosure(liveClosures: LiveClosure[], node: HeapSnapshotNode): vo
|
|
|
433
452
|
const scopeId = sharedFunctionInfo.findInternalEdgeTarget('scope_id')?.nodeValueAsInt() as ScopeId | undefined;
|
|
434
453
|
if (scopeId !== undefined) {
|
|
435
454
|
liveClosures.push({
|
|
436
|
-
|
|
455
|
+
ownerNodeIndex: ownerNode.nodeIndex,
|
|
456
|
+
contextNodeIndex: contextNode.nodeIndex as ContextNodeIndex,
|
|
437
457
|
scriptNodeIndex: script.nodeIndex as ScriptNodeIndex,
|
|
438
458
|
scopeId,
|
|
439
459
|
});
|
|
440
460
|
}
|
|
441
461
|
}
|
|
442
462
|
|
|
443
|
-
|
|
463
|
+
// V8's `JSGeneratorObject::kGeneratorClosed`: the generator has finished and can't be resumed.
|
|
464
|
+
const GENERATOR_CLOSED = -1;
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Handles generator objects, which V8 uses for generators, async functions, async generators and
|
|
468
|
+
* the top-level code of modules.
|
|
469
|
+
*/
|
|
470
|
+
function processGeneratorObject(liveClosures: LiveClosure[], finishedModuleFunctionNodeIndexes: Set<number>,
|
|
471
|
+
node: HeapSnapshotNode): void {
|
|
472
|
+
const continuation = node.findInternalEdgeTarget('continuation')?.nodeValueAsInt();
|
|
473
|
+
const generatorFunction = node.findInternalEdgeTarget('function');
|
|
474
|
+
if (continuation === undefined || generatorFunction?.rawType() !== node.snapshot.nodeClosureType) {
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
const sharedFunctionInfo = generatorFunction.findInternalEdgeTarget('shared');
|
|
478
|
+
if (!sharedFunctionInfo) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
if (continuation === GENERATOR_CLOSED) {
|
|
482
|
+
const scopeId = sharedFunctionInfo.findInternalEdgeTarget('scope_id')?.nodeValueAsInt();
|
|
483
|
+
|
|
484
|
+
// Only the top-level code of a module is both a generator and a root scope.
|
|
485
|
+
if (scopeId !== undefined && isScriptRootScopeId(scopeId)) {
|
|
486
|
+
// A module stores a reference to its generator object. The generator object then
|
|
487
|
+
// references the top-level JSFunction. Once the generator object is finished its
|
|
488
|
+
// JSFunction can't run anymore and user code can't call it. Here we record such closures
|
|
489
|
+
// in order to skip them later in buildLiveFunctions().
|
|
490
|
+
finishedModuleFunctionNodeIndexes.add(generatorFunction.nodeIndex);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// A finished generator can't run again, so it doesn't get a LiveClosure. That way the fields
|
|
494
|
+
// of its context can be reported as dead.
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const generatorContext = node.findInternalEdgeTarget('context');
|
|
498
|
+
if (generatorContext) {
|
|
499
|
+
// The context of the generator's function is the one the function was created in. The body
|
|
500
|
+
// of the generator, however, runs in a context of its own, which only the generator object
|
|
501
|
+
// refers to. A generator therefore counts as a closure of its function with the context
|
|
502
|
+
// it resumes in.
|
|
503
|
+
addLiveClosure(liveClosures, node, sharedFunctionInfo, generatorContext);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// V8's instance types of `JSGeneratorObject` and its subclasses.
|
|
508
|
+
const GENERATOR_INSTANCE_TYPE_NAMES = new Set([
|
|
509
|
+
'JS_GENERATOR_OBJECT_TYPE',
|
|
510
|
+
'JS_ASYNC_FUNCTION_OBJECT_TYPE',
|
|
511
|
+
'JS_ASYNC_GENERATOR_OBJECT_TYPE',
|
|
512
|
+
]);
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Checks the instance type of the object's map, so that other objects with fields of the same name
|
|
516
|
+
* aren't mistaken for generator objects. The result is cached for each map.
|
|
517
|
+
*/
|
|
518
|
+
function isGeneratorObject(generatorMapNodeIndexes: Map<MapNodeIndex, boolean>, node: HeapSnapshotNode): boolean {
|
|
519
|
+
if (node.rawType() !== node.snapshot.nodeObjectType) {
|
|
520
|
+
return false;
|
|
521
|
+
}
|
|
522
|
+
const map = node.findInternalEdgeTarget('map');
|
|
523
|
+
if (!map) {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
const mapNodeIndex = map.nodeIndex as MapNodeIndex;
|
|
527
|
+
let isGenerator = generatorMapNodeIndexes.get(mapNodeIndex);
|
|
528
|
+
if (isGenerator === undefined) {
|
|
529
|
+
const instanceTypeName = map.findInternalEdgeTarget('instance_type_name')?.rawName();
|
|
530
|
+
isGenerator = instanceTypeName !== undefined && GENERATOR_INSTANCE_TYPE_NAMES.has(instanceTypeName);
|
|
531
|
+
generatorMapNodeIndexes.set(mapNodeIndex, isGenerator);
|
|
532
|
+
}
|
|
533
|
+
return isGenerator;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function buildLiveFunctions(snapshot: HeapSnapshot, liveClosures: LiveClosure[],
|
|
537
|
+
finishedModuleFunctionNodeIndexes: Set<number>): ByScope<LiveFunction> {
|
|
444
538
|
const liveFunctionsByScript: ByScope<LiveFunction> = new Map();
|
|
445
539
|
const node = snapshot.createNode();
|
|
446
540
|
|
|
447
541
|
for (const closure of liveClosures) {
|
|
542
|
+
if (finishedModuleFunctionNodeIndexes.has(closure.ownerNodeIndex)) {
|
|
543
|
+
// The top-level code of this module has finished and can't run anymore.
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
448
546
|
let scriptFunctions = liveFunctionsByScript.get(closure.scriptNodeIndex);
|
|
449
547
|
if (!scriptFunctions) {
|
|
450
548
|
scriptFunctions = new Map<ScopeId, LiveFunction>();
|
|
@@ -663,6 +663,12 @@ inspectorBackend.registerType("FileSystem.File", [{"name": "name", "type": "stri
|
|
|
663
663
|
inspectorBackend.registerType("FileSystem.Directory", [{"name": "name", "type": "string", "optional": false, "description": "", "typeRef": null}, {"name": "nestedDirectories", "type": "array", "optional": false, "description": "", "typeRef": "string"}, {"name": "nestedFiles", "type": "array", "optional": false, "description": "Files that are directly nested under this directory.", "typeRef": "FileSystem.File"}]);
|
|
664
664
|
inspectorBackend.registerType("FileSystem.BucketFileSystemLocator", [{"name": "storageKey", "type": "string", "optional": false, "description": "Storage key", "typeRef": "Storage.SerializedStorageKey"}, {"name": "bucketName", "type": "string", "optional": true, "description": "Bucket name. Not passing a `bucketName` will retrieve the default Bucket. (https://developer.mozilla.org/en-US/docs/Web/API/Storage_API#storage_buckets)", "typeRef": null}, {"name": "pathComponents", "type": "array", "optional": false, "description": "Path to the directory using each path component as an array item.", "typeRef": "string"}]);
|
|
665
665
|
|
|
666
|
+
// FindInPage.
|
|
667
|
+
inspectorBackend.registerCommand("FindInPage.findFirst", [{"name": "query", "type": "string", "optional": false, "description": "", "typeRef": null}], [], "Forwards `query` to the find-in-page facility, starting a new find session. Where exactly the search starts from is implementation-specific.");
|
|
668
|
+
inspectorBackend.registerCommand("FindInPage.findNext", [], [], "Moves to the next match for the query passed to the most recent findFirst() call.");
|
|
669
|
+
inspectorBackend.registerCommand("FindInPage.findPrev", [], [], "Moves to the previous match for the query passed to the most recent findFirst() call.");
|
|
670
|
+
inspectorBackend.registerCommand("FindInPage.stop", [], [], "Ends the current find session, if any, and clears its highlighting.");
|
|
671
|
+
|
|
666
672
|
// HeadlessExperimental.
|
|
667
673
|
inspectorBackend.registerEnum("HeadlessExperimental.ScreenshotParamsFormat", {Jpeg: "jpeg", Png: "png", Webp: "webp"});
|
|
668
674
|
inspectorBackend.registerCommand("HeadlessExperimental.beginFrame", [{"name": "frameTimeTicks", "type": "number", "optional": true, "description": "Timestamp of this BeginFrame in Renderer TimeTicks (milliseconds of uptime). If not set, the current time will be used.", "typeRef": null}, {"name": "interval", "type": "number", "optional": true, "description": "The interval between BeginFrames that is reported to the compositor, in milliseconds. Defaults to a 60 frames/second interval, i.e. about 16.666 milliseconds.", "typeRef": null}, {"name": "noDisplayUpdates", "type": "boolean", "optional": true, "description": "Whether updates should not be committed and drawn onto the display. False by default. If true, only side effects of the BeginFrame will be run, such as layout and animations, but any visual updates may not be visible on the display or in screenshots.", "typeRef": null}, {"name": "screenshot", "type": "object", "optional": true, "description": "If set, a screenshot of the frame will be captured and returned in the response. Otherwise, no screenshot will be captured. Note that capturing a screenshot can fail, for example, during renderer initialization. In such a case, no screenshot data will be returned.", "typeRef": "HeadlessExperimental.ScreenshotParams"}], ["hasDamage", "screenshotData"], "Sends a BeginFrame to the target and returns when the frame was completed. Optionally captures a screenshot from the resulting frame. Requires that the target was created with enabled BeginFrameControl. Designed for use with --run-all-compositor-stages-before-draw, see also https://goo.gle/chrome-headless-rendering for more background.");
|
|
@@ -1052,7 +1058,7 @@ inspectorBackend.registerEnum("Page.AdFrameExplanation", {ParentIsAd: "ParentIsA
|
|
|
1052
1058
|
inspectorBackend.registerEnum("Page.SecureContextType", {Secure: "Secure", SecureLocalhost: "SecureLocalhost", InsecureScheme: "InsecureScheme", InsecureAncestor: "InsecureAncestor"});
|
|
1053
1059
|
inspectorBackend.registerEnum("Page.CrossOriginIsolatedContextType", {Isolated: "Isolated", NotIsolated: "NotIsolated", NotIsolatedFeatureDisabled: "NotIsolatedFeatureDisabled"});
|
|
1054
1060
|
inspectorBackend.registerEnum("Page.GatedAPIFeatures", {SharedArrayBuffers: "SharedArrayBuffers", SharedArrayBuffersTransferAllowed: "SharedArrayBuffersTransferAllowed", PerformanceMeasureMemory: "PerformanceMeasureMemory", PerformanceProfile: "PerformanceProfile"});
|
|
1055
|
-
inspectorBackend.registerEnum("Page.PermissionsPolicyFeature", {Accelerometer: "accelerometer", AllScreensCapture: "all-screens-capture", AmbientLightSensor: "ambient-light-sensor", AriaNotify: "aria-notify", Autofill: "autofill", Autoplay: "autoplay", Bluetooth: "bluetooth", BrowsingTopics: "browsing-topics", Camera: "camera", CapturedSurfaceControl: "captured-surface-control", ChDpr: "ch-dpr", ChDeviceMemory: "ch-device-memory", ChDownlink: "ch-downlink", ChEct: "ch-ect", ChPrefersColorScheme: "ch-prefers-color-scheme", ChPrefersReducedMotion: "ch-prefers-reduced-motion", ChPrefersReducedTransparency: "ch-prefers-reduced-transparency", ChRtt: "ch-rtt", ChSaveData: "ch-save-data", ChUa: "ch-ua", ChUaArch: "ch-ua-arch", ChUaBitness: "ch-ua-bitness", ChUaHighEntropyValues: "ch-ua-high-entropy-values", ChUaPlatform: "ch-ua-platform", ChUaModel: "ch-ua-model", ChUaMobile: "ch-ua-mobile", ChUaFormFactors: "ch-ua-form-factors", ChUaFullVersion: "ch-ua-full-version", ChUaFullVersionList: "ch-ua-full-version-list", ChUaPlatformVersion: "ch-ua-platform-version", ChUaWow64: "ch-ua-wow64", ChViewportHeight: "ch-viewport-height", ChViewportWidth: "ch-viewport-width", ChWidth: "ch-width", ClipboardRead: "clipboard-read", ClipboardWrite: "clipboard-write", ComputePressure: "compute-pressure", ControlledFrame: "controlled-frame", CrossOriginIsolated: "cross-origin-isolated", DeferredFetch: "deferred-fetch", DeferredFetchMinimal: "deferred-fetch-minimal", DeviceAttributes: "device-attributes", DigitalCredentialsCreate: "digital-credentials-create", DigitalCredentialsGet: "digital-credentials-get", DirectSockets: "direct-sockets", DirectSocketsMulticast: "direct-sockets-multicast", DisplayCapture: "display-capture", DocumentDomain: "document-domain", EncryptedMedia: "encrypted-media", ExecutionWhileOutOfViewport: "execution-while-out-of-viewport", ExecutionWhileNotRendered: "execution-while-not-rendered", FocusWithoutUserActivation: "focus-without-user-activation", Fullscreen: "fullscreen", Frobulate: "frobulate", Gamepad: "gamepad", Geolocation: "geolocation", Gyroscope: "gyroscope", Haptics: "haptics", Hid: "hid", IdentityCredentialsGet: "identity-credentials-get", IdleDetection: "idle-detection", InterestCohort: "interest-cohort", KeyboardMap: "keyboard-map", LanguageDetector: "language-detector", LanguageModel: "language-model", LocalFonts: "local-fonts", LocalNetwork: "local-network", LocalNetworkAccess: "local-network-access", LoopbackNetwork: "loopback-network", Magnetometer: "magnetometer", ManualText: "manual-text", MediaPlaybackWhileNotVisible: "media-playback-while-not-visible", Microphone: "microphone", Midi: "midi", OnDeviceSpeechRecognition: "on-device-speech-recognition", OtpCredentials: "otp-credentials", Payment: "payment", PictureInPicture: "picture-in-picture", PrivateStateTokenIssuance: "private-state-token-issuance", PrivateStateTokenRedemption: "private-state-token-redemption", PublickeyCredentialsCreate: "publickey-credentials-create", PublickeyCredentialsGet: "publickey-credentials-get", Rewriter: "rewriter", ScreenWakeLock: "screen-wake-lock", Serial: "serial", SharedStorage: "shared-storage", SharedStorageSelectUrl: "shared-storage-select-url", SmartCard: "smart-card", SpeakerSelection: "speaker-selection", StorageAccess: "storage-access", SubApps: "sub-apps", Summarizer: "summarizer", SyncXhr: "sync-xhr", Tools: "tools", Translator: "translator", Unload: "unload", Usb: "usb", UsbUnrestricted: "usb-unrestricted", VerticalScroll: "vertical-scroll", WebAppInstallation: "web-app-installation", Webnn: "webnn", WebPrinting: "web-printing", WebShare: "web-share", WindowManagement: "window-management", Writer: "writer", XrSpatialTracking: "xr-spatial-tracking"});
|
|
1061
|
+
inspectorBackend.registerEnum("Page.PermissionsPolicyFeature", {Accelerometer: "accelerometer", AllScreensCapture: "all-screens-capture", AmbientLightSensor: "ambient-light-sensor", AriaNotify: "aria-notify", Autofill: "autofill", Autoplay: "autoplay", Bluetooth: "bluetooth", BrowsingTopics: "browsing-topics", Camera: "camera", CapturedSurfaceControl: "captured-surface-control", ChDpr: "ch-dpr", ChDeviceMemory: "ch-device-memory", ChDownlink: "ch-downlink", ChEct: "ch-ect", ChPrefersColorScheme: "ch-prefers-color-scheme", ChPrefersReducedMotion: "ch-prefers-reduced-motion", ChPrefersReducedTransparency: "ch-prefers-reduced-transparency", ChRtt: "ch-rtt", ChSaveData: "ch-save-data", ChUa: "ch-ua", ChUaArch: "ch-ua-arch", ChUaBitness: "ch-ua-bitness", ChUaHighEntropyValues: "ch-ua-high-entropy-values", ChUaPlatform: "ch-ua-platform", ChUaModel: "ch-ua-model", ChUaMobile: "ch-ua-mobile", ChUaFormFactors: "ch-ua-form-factors", ChUaFullVersion: "ch-ua-full-version", ChUaFullVersionList: "ch-ua-full-version-list", ChUaPlatformVersion: "ch-ua-platform-version", ChUaWow64: "ch-ua-wow64", ChViewportHeight: "ch-viewport-height", ChViewportWidth: "ch-viewport-width", ChWidth: "ch-width", ClipboardRead: "clipboard-read", ClipboardWrite: "clipboard-write", ComputePressure: "compute-pressure", ControlledFrame: "controlled-frame", CrossOriginIsolated: "cross-origin-isolated", DeferredFetch: "deferred-fetch", DeferredFetchMinimal: "deferred-fetch-minimal", DeviceAttributes: "device-attributes", DigitalCredentialsCreate: "digital-credentials-create", DigitalCredentialsGet: "digital-credentials-get", DirectSockets: "direct-sockets", DirectSocketsMulticast: "direct-sockets-multicast", DisplayCapture: "display-capture", DocumentDomain: "document-domain", EncryptedMedia: "encrypted-media", ExecutionWhileOutOfViewport: "execution-while-out-of-viewport", ExecutionWhileNotRendered: "execution-while-not-rendered", FocusWithoutUserActivation: "focus-without-user-activation", Fullscreen: "fullscreen", Frobulate: "frobulate", Gamepad: "gamepad", Geolocation: "geolocation", Gyroscope: "gyroscope", Haptics: "haptics", Hid: "hid", IdentityCredentialsGet: "identity-credentials-get", IdleDetection: "idle-detection", InterestCohort: "interest-cohort", KeyboardMap: "keyboard-map", LanguageDetector: "language-detector", LanguageModel: "language-model", LocalFonts: "local-fonts", LocalNetwork: "local-network", LocalNetworkAccess: "local-network-access", LoopbackNetwork: "loopback-network", Magnetometer: "magnetometer", ManualText: "manual-text", MediaPlaybackWhileNotVisible: "media-playback-while-not-visible", Microphone: "microphone", Midi: "midi", OnDeviceSpeechRecognition: "on-device-speech-recognition", OtpCredentials: "otp-credentials", Payment: "payment", PictureInPicture: "picture-in-picture", PrivateStateTokenIssuance: "private-state-token-issuance", PrivateStateTokenRedemption: "private-state-token-redemption", PublickeyCredentialsCreate: "publickey-credentials-create", PublickeyCredentialsGet: "publickey-credentials-get", PublickeyCredentialsRemoteClientDataJson: "publickey-credentials-remote-client-data-json", Rewriter: "rewriter", ScreenWakeLock: "screen-wake-lock", Serial: "serial", SharedStorage: "shared-storage", SharedStorageSelectUrl: "shared-storage-select-url", SmartCard: "smart-card", SpeakerSelection: "speaker-selection", StorageAccess: "storage-access", SubApps: "sub-apps", Summarizer: "summarizer", SyncXhr: "sync-xhr", Tools: "tools", Translator: "translator", Unload: "unload", Usb: "usb", UsbUnrestricted: "usb-unrestricted", VerticalScroll: "vertical-scroll", WebAppInstallation: "web-app-installation", Webnn: "webnn", WebPrinting: "web-printing", WebShare: "web-share", WindowManagement: "window-management", Writer: "writer", XrSpatialTracking: "xr-spatial-tracking"});
|
|
1056
1062
|
inspectorBackend.registerEnum("Page.PermissionsPolicyBlockReason", {Header: "Header", IframeAttribute: "IframeAttribute", InFencedFrameTree: "InFencedFrameTree", InIsolatedApp: "InIsolatedApp"});
|
|
1057
1063
|
inspectorBackend.registerEnum("Page.OriginTrialTokenStatus", {Success: "Success", NotSupported: "NotSupported", Insecure: "Insecure", Expired: "Expired", WrongOrigin: "WrongOrigin", InvalidSignature: "InvalidSignature", Malformed: "Malformed", WrongVersion: "WrongVersion", FeatureDisabled: "FeatureDisabled", TokenDisabled: "TokenDisabled", FeatureDisabledForUser: "FeatureDisabledForUser", UnknownTrial: "UnknownTrial"});
|
|
1058
1064
|
inspectorBackend.registerEnum("Page.OriginTrialStatus", {Enabled: "Enabled", ValidTokenNotProvided: "ValidTokenNotProvided", OSNotSupported: "OSNotSupported", TrialNotAllowed: "TrialNotAllowed"});
|
|
@@ -4603,8 +4603,16 @@ export const generatedProperties: CSSProperty[] = [
|
|
|
4603
4603
|
"name": "position-try-order"
|
|
4604
4604
|
},
|
|
4605
4605
|
{
|
|
4606
|
+
"devtools_keywords": [
|
|
4607
|
+
"always",
|
|
4608
|
+
"anchor-valid",
|
|
4609
|
+
"anchor-visible",
|
|
4610
|
+
"no-overflow"
|
|
4611
|
+
],
|
|
4606
4612
|
"keywords": [
|
|
4607
4613
|
"always",
|
|
4614
|
+
"anchor-valid",
|
|
4615
|
+
"anchor-visible",
|
|
4608
4616
|
"anchors-visible",
|
|
4609
4617
|
"no-overflow"
|
|
4610
4618
|
],
|
|
@@ -5482,7 +5490,7 @@ export const generatedProperties: CSSProperty[] = [
|
|
|
5482
5490
|
],
|
|
5483
5491
|
"name": "text-decoration-inset",
|
|
5484
5492
|
"runtime_flag": "CSSTextDecorationInset",
|
|
5485
|
-
"runtime_flag_status": "
|
|
5493
|
+
"runtime_flag_status": "stable"
|
|
5486
5494
|
},
|
|
5487
5495
|
{
|
|
5488
5496
|
"keywords": [
|
|
@@ -8484,7 +8492,8 @@ export const generatedPropertyValues: Record<string, CSSPropertyValue> = {
|
|
|
8484
8492
|
"position-visibility": {
|
|
8485
8493
|
"values": [
|
|
8486
8494
|
"always",
|
|
8487
|
-
"
|
|
8495
|
+
"anchor-valid",
|
|
8496
|
+
"anchor-visible",
|
|
8488
8497
|
"no-overflow"
|
|
8489
8498
|
]
|
|
8490
8499
|
},
|
|
@@ -3082,6 +3082,37 @@ export namespace ProtocolMapping {
|
|
|
3082
3082
|
paramsType: [Protocol.FileSystem.GetDirectoryRequest];
|
|
3083
3083
|
returnType: Protocol.FileSystem.GetDirectoryResponse;
|
|
3084
3084
|
};
|
|
3085
|
+
/**
|
|
3086
|
+
* Forwards `query` to the find-in-page facility, starting a new find session.
|
|
3087
|
+
* Where exactly the search starts from is implementation-specific.
|
|
3088
|
+
*/
|
|
3089
|
+
'FindInPage.findFirst': {
|
|
3090
|
+
paramsType: [Protocol.FindInPage.FindFirstRequest];
|
|
3091
|
+
returnType: void;
|
|
3092
|
+
};
|
|
3093
|
+
/**
|
|
3094
|
+
* Moves to the next match for the query passed to the most recent
|
|
3095
|
+
* findFirst() call.
|
|
3096
|
+
*/
|
|
3097
|
+
'FindInPage.findNext': {
|
|
3098
|
+
paramsType: [];
|
|
3099
|
+
returnType: void;
|
|
3100
|
+
};
|
|
3101
|
+
/**
|
|
3102
|
+
* Moves to the previous match for the query passed to the most recent
|
|
3103
|
+
* findFirst() call.
|
|
3104
|
+
*/
|
|
3105
|
+
'FindInPage.findPrev': {
|
|
3106
|
+
paramsType: [];
|
|
3107
|
+
returnType: void;
|
|
3108
|
+
};
|
|
3109
|
+
/**
|
|
3110
|
+
* Ends the current find session, if any, and clears its highlighting.
|
|
3111
|
+
*/
|
|
3112
|
+
'FindInPage.stop': {
|
|
3113
|
+
paramsType: [];
|
|
3114
|
+
returnType: void;
|
|
3115
|
+
};
|
|
3085
3116
|
/**
|
|
3086
3117
|
* Sends a BeginFrame to the target and returns when the frame was completed. Optionally captures a
|
|
3087
3118
|
* screenshot from the resulting frame. Requires that the target was created with enabled
|
|
@@ -68,6 +68,8 @@ declare namespace ProtocolProxyApi {
|
|
|
68
68
|
|
|
69
69
|
FileSystem: FileSystemApi;
|
|
70
70
|
|
|
71
|
+
FindInPage: FindInPageApi;
|
|
72
|
+
|
|
71
73
|
HeadlessExperimental: HeadlessExperimentalApi;
|
|
72
74
|
|
|
73
75
|
IO: IOApi;
|
|
@@ -185,6 +187,8 @@ declare namespace ProtocolProxyApi {
|
|
|
185
187
|
|
|
186
188
|
FileSystem: FileSystemDispatcher;
|
|
187
189
|
|
|
190
|
+
FindInPage: FindInPageDispatcher;
|
|
191
|
+
|
|
188
192
|
HeadlessExperimental: HeadlessExperimentalDispatcher;
|
|
189
193
|
|
|
190
194
|
IO: IODispatcher;
|
|
@@ -2181,6 +2185,34 @@ declare namespace ProtocolProxyApi {
|
|
|
2181
2185
|
export interface FileSystemDispatcher {
|
|
2182
2186
|
}
|
|
2183
2187
|
|
|
2188
|
+
export interface FindInPageApi {
|
|
2189
|
+
/**
|
|
2190
|
+
* Forwards `query` to the find-in-page facility, starting a new find session.
|
|
2191
|
+
* Where exactly the search starts from is implementation-specific.
|
|
2192
|
+
*/
|
|
2193
|
+
invoke_findFirst(params: Protocol.FindInPage.FindFirstRequest): Promise<Protocol.ProtocolResponseWithError>;
|
|
2194
|
+
|
|
2195
|
+
/**
|
|
2196
|
+
* Moves to the next match for the query passed to the most recent
|
|
2197
|
+
* findFirst() call.
|
|
2198
|
+
*/
|
|
2199
|
+
invoke_findNext(): Promise<Protocol.ProtocolResponseWithError>;
|
|
2200
|
+
|
|
2201
|
+
/**
|
|
2202
|
+
* Moves to the previous match for the query passed to the most recent
|
|
2203
|
+
* findFirst() call.
|
|
2204
|
+
*/
|
|
2205
|
+
invoke_findPrev(): Promise<Protocol.ProtocolResponseWithError>;
|
|
2206
|
+
|
|
2207
|
+
/**
|
|
2208
|
+
* Ends the current find session, if any, and clears its highlighting.
|
|
2209
|
+
*/
|
|
2210
|
+
invoke_stop(): Promise<Protocol.ProtocolResponseWithError>;
|
|
2211
|
+
|
|
2212
|
+
}
|
|
2213
|
+
export interface FindInPageDispatcher {
|
|
2214
|
+
}
|
|
2215
|
+
|
|
2184
2216
|
export interface HeadlessExperimentalApi {
|
|
2185
2217
|
/**
|
|
2186
2218
|
* Sends a BeginFrame to the target and returns when the frame was completed. Optionally captures a
|
|
@@ -8702,6 +8702,16 @@ export namespace FileSystem {
|
|
|
8702
8702
|
}
|
|
8703
8703
|
}
|
|
8704
8704
|
|
|
8705
|
+
/**
|
|
8706
|
+
* This domain provides commands to trigger the "Find in page" feature.
|
|
8707
|
+
*/
|
|
8708
|
+
export namespace FindInPage {
|
|
8709
|
+
|
|
8710
|
+
export interface FindFirstRequest {
|
|
8711
|
+
query: string;
|
|
8712
|
+
}
|
|
8713
|
+
}
|
|
8714
|
+
|
|
8705
8715
|
/**
|
|
8706
8716
|
* This domain provides experimental commands only supported in headless mode.
|
|
8707
8717
|
*/
|
|
@@ -14808,6 +14818,7 @@ export namespace Page {
|
|
|
14808
14818
|
PrivateStateTokenRedemption = 'private-state-token-redemption',
|
|
14809
14819
|
PublickeyCredentialsCreate = 'publickey-credentials-create',
|
|
14810
14820
|
PublickeyCredentialsGet = 'publickey-credentials-get',
|
|
14821
|
+
PublickeyCredentialsRemoteClientDataJson = 'publickey-credentials-remote-client-data-json',
|
|
14811
14822
|
Rewriter = 'rewriter',
|
|
14812
14823
|
ScreenWakeLock = 'screen-wake-lock',
|
|
14813
14824
|
Serial = 'serial',
|
|
@@ -41,6 +41,7 @@ const SKILL_DISPLAY_NAMES: Record<SkillName, string> = {
|
|
|
41
41
|
performance: 'Performance',
|
|
42
42
|
storage: 'Storage',
|
|
43
43
|
sources: 'Sources',
|
|
44
|
+
lighthouse: 'Lighthouse',
|
|
44
45
|
};
|
|
45
46
|
|
|
46
47
|
const preamble = `You are the most advanced unified AI assistant integrated into Chrome DevTools.
|
|
@@ -6,6 +6,12 @@ import type * as LHModel from '../../lighthouse/lighthouse.js';
|
|
|
6
6
|
|
|
7
7
|
import {bytes, millis} from './UnitFormatters.js';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Category argument for Lighthouse tools, accepting a specific category ID or `'all'`
|
|
11
|
+
* to target a full report across all standard categories.
|
|
12
|
+
*/
|
|
13
|
+
export type LighthouseCategoryArg = 'all'|LHModel.RunTypes.CategoryId;
|
|
14
|
+
|
|
9
15
|
/**
|
|
10
16
|
* A formatter that takes a raw Lighthouse report JSON and creates a markdown
|
|
11
17
|
* summary for an AI Agent.
|
|
@@ -29,14 +35,31 @@ export class LighthouseFormatter {
|
|
|
29
35
|
return lines.join('\n');
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Formats a Lighthouse report for an AI Agent. If categoryId is 'all', includes
|
|
40
|
+
* the overall summary followed by each category's audits. Otherwise, returns audits
|
|
41
|
+
* for the specified category.
|
|
42
|
+
*/
|
|
43
|
+
formatReport(report: LHModel.ReporterTypes.ReportJSON, categoryId: LighthouseCategoryArg): string {
|
|
44
|
+
if (categoryId === 'all') {
|
|
45
|
+
const sections: string[] = [this.summary(report)];
|
|
46
|
+
for (const category of Object.values(report.categories)) {
|
|
47
|
+
sections.push(this.audits(report, category));
|
|
48
|
+
}
|
|
49
|
+
return sections.join('\n\n');
|
|
50
|
+
}
|
|
51
|
+
return this.audits(report, categoryId);
|
|
52
|
+
}
|
|
53
|
+
|
|
32
54
|
/**
|
|
33
55
|
* Returns a markdown list of all audits in a given category.
|
|
34
56
|
* Highlight failing audits (score < 90).
|
|
35
57
|
*/
|
|
36
|
-
audits(report: LHModel.ReporterTypes.ReportJSON,
|
|
37
|
-
|
|
58
|
+
audits(report: LHModel.ReporterTypes.ReportJSON,
|
|
59
|
+
categoryOrId: LHModel.RunTypes.CategoryId|LHModel.ReporterTypes.CategoryJSON): string {
|
|
60
|
+
const category = typeof categoryOrId === 'string' ? report.categories[categoryOrId] : categoryOrId;
|
|
38
61
|
if (!category) {
|
|
39
|
-
return `Category "${
|
|
62
|
+
return `Category "${categoryOrId}" not found.`;
|
|
40
63
|
}
|
|
41
64
|
|
|
42
65
|
const lines: string[] = [];
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
// Use of this source code is governed by a BSD-style license that can be
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
|
-
import type * as Platform from '../../../core/platform/platform.js';
|
|
6
5
|
import * as SDK from '../../../core/sdk/sdk.js';
|
|
7
6
|
import * as TextUtils from '../../../core/text_utils/text_utils.js';
|
|
8
7
|
import * as Protocol from '../../../generated/protocol.js';
|
|
@@ -122,14 +121,19 @@ export class NetworkRequestFormatter {
|
|
|
122
121
|
return `${title}\n<binary data>`;
|
|
123
122
|
}
|
|
124
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Returns the URL of `initiator` if it is same-origin with `request`, or a redaction
|
|
126
|
+
* placeholder otherwise.
|
|
127
|
+
*
|
|
128
|
+
* Both sides use `requestURLSecurityOrigin()`, so imported HAR requests are compared
|
|
129
|
+
* using their `imported-har://` origins.
|
|
130
|
+
*/
|
|
125
131
|
static formatInitiatorUrl(
|
|
126
|
-
|
|
127
|
-
|
|
132
|
+
initiator: SDK.NetworkRequest.NetworkRequest,
|
|
133
|
+
request: SDK.NetworkRequest.NetworkRequest,
|
|
128
134
|
): string {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (initiatorOrigin.isSameOriginWith(targetOrigin)) {
|
|
132
|
-
return initiatorUrl;
|
|
135
|
+
if (initiator.requestURLSecurityOrigin().isSameOriginWith(request.requestURLSecurityOrigin())) {
|
|
136
|
+
return initiator.url();
|
|
133
137
|
}
|
|
134
138
|
return '<redacted cross-origin initiator URL>';
|
|
135
139
|
}
|
|
@@ -329,18 +333,15 @@ export function formatRequestInitiatorChain(
|
|
|
329
333
|
request: SDK.NetworkRequest.NetworkRequest,
|
|
330
334
|
networkLog: Logs.NetworkLog.NetworkLog,
|
|
331
335
|
): string {
|
|
332
|
-
const allowedOrigin = request.url();
|
|
333
336
|
let initiatorChain = '';
|
|
334
337
|
let lineStart = '- URL: ';
|
|
335
338
|
const graph = networkLog.initiatorGraphForRequest(request);
|
|
336
339
|
|
|
337
340
|
for (const initiator of Array.from(graph.initiators).reverse()) {
|
|
338
|
-
initiatorChain =
|
|
339
|
-
initiatorChain + lineStart + NetworkRequestFormatter.formatInitiatorUrl(initiator.url(), allowedOrigin) + '\n';
|
|
341
|
+
initiatorChain = initiatorChain + lineStart + NetworkRequestFormatter.formatInitiatorUrl(initiator, request) + '\n';
|
|
340
342
|
lineStart = '\t' + lineStart;
|
|
341
343
|
if (initiator === request) {
|
|
342
|
-
initiatorChain =
|
|
343
|
-
formatRequestInitiated(graph.initiated, request, request, initiatorChain, lineStart, allowedOrigin);
|
|
344
|
+
initiatorChain = formatRequestInitiated(graph.initiated, request, request, initiatorChain, lineStart);
|
|
344
345
|
}
|
|
345
346
|
}
|
|
346
347
|
|
|
@@ -353,7 +354,6 @@ function formatRequestInitiated(
|
|
|
353
354
|
parentRequest: SDK.NetworkRequest.NetworkRequest,
|
|
354
355
|
initiatorChain: string,
|
|
355
356
|
lineStart: string,
|
|
356
|
-
allowedOrigin: Platform.DevToolsPath.UrlString,
|
|
357
357
|
): string {
|
|
358
358
|
const visited = new Set<SDK.NetworkRequest.NetworkRequest>();
|
|
359
359
|
|
|
@@ -363,10 +363,9 @@ function formatRequestInitiated(
|
|
|
363
363
|
if (initiatedRequest === parentRequest) {
|
|
364
364
|
if (!visited.has(keyRequest)) {
|
|
365
365
|
visited.add(keyRequest);
|
|
366
|
-
initiatorChain = initiatorChain + lineStart +
|
|
367
|
-
NetworkRequestFormatter.formatInitiatorUrl(keyRequest.url(), allowedOrigin) + '\n';
|
|
368
366
|
initiatorChain =
|
|
369
|
-
|
|
367
|
+
initiatorChain + lineStart + NetworkRequestFormatter.formatInitiatorUrl(keyRequest, rootRequest) + '\n';
|
|
368
|
+
initiatorChain = formatRequestInitiated(initiated, rootRequest, keyRequest, initiatorChain, '\t' + lineStart);
|
|
370
369
|
}
|
|
371
370
|
}
|
|
372
371
|
}
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
// Use of this source code is governed by a BSD-style license that can be
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
export type SkillName = 'styling'|'network'|'accessibility'|'performance'|'storage'|'sources';
|
|
5
|
+
export type SkillName = 'styling'|'network'|'accessibility'|'performance'|'storage'|'sources'|'lighthouse';
|
|
7
6
|
|
|
8
7
|
export interface Skill {
|
|
9
8
|
name: SkillName;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import {skill as accessibilitySkill} from './accessibility.skill.js';
|
|
6
|
+
import {skill as lighthouseSkill} from './lighthouse.skill.js';
|
|
6
7
|
import {skill as networkSkill} from './network.skill.js';
|
|
7
8
|
import {skill as performanceSkill} from './performance.skill.js';
|
|
8
9
|
import type {Skill, SkillName} from './Skill.js';
|
|
@@ -17,4 +18,5 @@ export const SKILLS: Record<SkillName, Skill> = {
|
|
|
17
18
|
performance: performanceSkill,
|
|
18
19
|
storage: storageSkill,
|
|
19
20
|
sources: sourcesSkill,
|
|
21
|
+
lighthouse: lighthouseSkill,
|
|
20
22
|
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lighthouse
|
|
3
|
+
description: Running Lighthouse reports and audits, full-page audits (performance, accessibility, best practices, Search Engine Optimization (SEO)), and inspecting Lighthouse scores.
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- runLighthouse
|
|
6
|
+
- getLighthouseAudits
|
|
7
|
+
- resolveDevtoolsNodePath
|
|
8
|
+
---
|
|
9
|
+
You are an expert web quality and audit assistant integrated into Chrome DevTools.
|
|
10
|
+
Your role is to evaluate websites using Lighthouse audits across performance, accessibility, best practices, and SEO.
|
|
11
|
+
|
|
12
|
+
# Tools & Workflow
|
|
13
|
+
|
|
14
|
+
1. **Lighthouse Audits (`runLighthouse` & `getLighthouseAudits`)**:
|
|
15
|
+
- If an active Lighthouse report context already exists and no fresh audit is requested:
|
|
16
|
+
- To inspect the entire report or multiple categories, call `getLighthouseAudits` with `categoryId: 'all'`.
|
|
17
|
+
- For a specific category, call `getLighthouseAudits` with the corresponding category ID (e.g. `'performance'`, `'accessibility'`, `'best-practices'`, `'seo'`).
|
|
18
|
+
- If no active report exists or a fresh audit is requested, call `runLighthouse`:
|
|
19
|
+
- If the user asks for a general report or multiple categories, use `categoryId: 'all'`.
|
|
20
|
+
- For a single category, pass that category ID.
|
|
21
|
+
- Execution mode:
|
|
22
|
+
- Use `"navigation"` mode for full page-load audits (default for full site reviews).
|
|
23
|
+
- Use `"snapshot"` mode to re-evaluate live in-page DOM/CSS modifications without reloading.
|
|
24
|
+
- Use `"timespan"` mode for user interaction flows.
|
|
25
|
+
- Always honor explicit mode requests from the user.
|
|
26
|
+
|
|
27
|
+
2. **Resolving Node References (`resolveDevtoolsNodePath`)**:
|
|
28
|
+
- When an audit references failing elements by DevTools node path (e.g. `"1,HTML,1,BODY,2,BUTTON"`), use `resolveDevtoolsNodePath` to resolve the path to a `backendNodeId` to identify the failing element (or inspect it using tools from other skills if active).
|
|
29
|
+
|
|
30
|
+
# Considerations
|
|
31
|
+
|
|
32
|
+
- Base all analysis on empirical Lighthouse audit data. Never fabricate audit scores or results.
|
|
33
|
+
- When summarizing a full Lighthouse run, highlight overall category scores first, then detail failing audits (score < 90).
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
// found in the LICENSE file.
|
|
4
4
|
|
|
5
5
|
import * as Host from '../../../core/host/host.js';
|
|
6
|
-
import type
|
|
7
|
-
import {LighthouseFormatter} from '../data_formatters/LighthouseFormatter.js';
|
|
6
|
+
import {type LighthouseCategoryArg, LighthouseFormatter} from '../data_formatters/LighthouseFormatter.js';
|
|
8
7
|
|
|
9
8
|
import {
|
|
10
9
|
type BaseToolCapability,
|
|
@@ -16,14 +15,14 @@ import {
|
|
|
16
15
|
} from './Tool.js';
|
|
17
16
|
|
|
18
17
|
export interface GetLighthouseAuditsArgs extends ToolArgs {
|
|
19
|
-
categoryId:
|
|
18
|
+
categoryId: LighthouseCategoryArg;
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
export class GetLighthouseAuditsTool implements
|
|
23
22
|
DataTool<GetLighthouseAuditsArgs, {audits: string}, BaseToolCapability&LighthouseReportCapability> {
|
|
24
23
|
readonly name: ToolName = ToolName.GET_LIGHTHOUSE_AUDITS;
|
|
25
24
|
readonly description: string =
|
|
26
|
-
'Retrieves audit results and diagnostic details from the active Lighthouse report for a specific category (e.g., \'accessibility\').';
|
|
25
|
+
'Retrieves audit results and diagnostic details from the active Lighthouse report for all categories (using categoryId: "all") or a specific category (e.g., \'accessibility\').';
|
|
27
26
|
|
|
28
27
|
readonly parameters: Host.AidaClient.FunctionObjectParam<keyof GetLighthouseAuditsArgs> = {
|
|
29
28
|
type: Host.AidaClient.ParametersTypes.OBJECT,
|
|
@@ -32,7 +31,8 @@ export class GetLighthouseAuditsTool implements
|
|
|
32
31
|
properties: {
|
|
33
32
|
categoryId: {
|
|
34
33
|
type: Host.AidaClient.ParametersTypes.STRING,
|
|
35
|
-
description:
|
|
34
|
+
description:
|
|
35
|
+
'The category of audits to retrieve. Use "all" to retrieve the full report and all categories, or specify a category: "accessibility", "performance", "best-practices", "seo".',
|
|
36
36
|
nullable: false,
|
|
37
37
|
},
|
|
38
38
|
},
|
|
@@ -52,7 +52,7 @@ export class GetLighthouseAuditsTool implements
|
|
|
52
52
|
if (!report) {
|
|
53
53
|
return {error: 'Error: Active context is not a Lighthouse report.'};
|
|
54
54
|
}
|
|
55
|
-
const audits = new LighthouseFormatter().
|
|
55
|
+
const audits = new LighthouseFormatter().formatReport(report, params.categoryId);
|
|
56
56
|
return {
|
|
57
57
|
result: {audits},
|
|
58
58
|
widgets: [{name: 'LIGHTHOUSE_REPORT', data: {report}}],
|