chrome-devtools-frontend 1.0.1709383 → 1.0.1711155
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/AUTHORS +1 -0
- package/front_end/core/common/ParsedURL.ts +20 -19
- package/front_end/core/sdk/DOMModel.ts +6 -4
- package/front_end/core/sdk/PageFunctions.ts +1 -1
- package/front_end/generated/InspectorBackendCommands.ts +1 -0
- package/front_end/generated/protocol-mapping.d.ts +7 -0
- package/front_end/generated/protocol-proxy-api.d.ts +5 -0
- package/front_end/generated/protocol.ts +12 -0
- package/front_end/models/ai_assistance/agents/AiAgent.ts +7 -4
- package/front_end/models/ai_assistance/agents/StylingAgent.ts +1 -0
- package/front_end/models/ai_assistance/contexts/LighthouseContext.snapshot.txt +6 -1
- package/front_end/models/ai_assistance/contexts/LighthouseContext.ts +3 -2
- package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.snapshot.txt +11 -0
- package/front_end/models/ai_assistance/data_formatters/LighthouseFormatter.ts +47 -19
- package/front_end/models/ai_assistance/skills/accessibility.md +1 -0
- package/front_end/models/ai_assistance/skills/lighthouse.md +1 -0
- package/front_end/models/ai_assistance/skills/styling.md +1 -0
- package/front_end/models/ai_assistance/tools/ListSources.ts +11 -5
- package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +5 -1
- package/front_end/models/bindings/SourceMapStepping.ts +76 -4
- package/front_end/models/bindings/StylesSourceMapping.ts +3 -0
- package/front_end/models/emulation/DeviceModeModel.ts +9 -4
- package/front_end/models/har/HARFormat.ts +2 -7
- package/front_end/models/javascript_metadata/NativeFunctions.ts +52 -48
- package/front_end/models/persistence/Automapping.ts +8 -3
- package/front_end/models/trace/lantern/graph/PageDependencyGraph.ts +1 -1
- package/front_end/models/trace/lantern/metrics/TotalBlockingTime.ts +1 -1
- package/front_end/panels/application/BounceTrackingMitigationsTreeElement.ts +1 -1
- package/front_end/panels/application/CookieItemsView.ts +10 -10
- package/front_end/panels/application/DOMStorageItemsView.ts +7 -8
- package/front_end/panels/application/ExtensionStorageItemsView.ts +4 -5
- package/front_end/panels/application/IndexedDBViews.ts +29 -31
- package/front_end/panels/application/KeyValueStorageItemsView.ts +5 -5
- package/front_end/panels/application/ServiceWorkerCacheTreeElement.ts +6 -6
- package/front_end/panels/application/ServiceWorkerCacheViews.ts +17 -17
- package/front_end/panels/application/StorageBucketsTreeElement.ts +3 -10
- package/front_end/panels/application/StorageItemsToolbar.ts +6 -6
- package/front_end/panels/application/StorageView.ts +36 -34
- package/front_end/panels/application/TrustTokensTreeElement.ts +1 -2
- package/front_end/panels/common/CommentThreadWidget.ts +0 -17
- package/front_end/panels/common/CommentsOverlayWidget.ts +0 -4
- package/front_end/panels/common/ExtensionServer.ts +52 -50
- package/front_end/panels/common/commentThreadWidget.css +0 -4
- package/front_end/panels/elements/DOMPath.ts +7 -1
- package/front_end/panels/elements/DOMTreeWidget.ts +7 -3
- package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
- package/front_end/panels/elements/StylePropertyUtils.ts +11 -3
- package/front_end/panels/emulation/DeviceModeView.ts +1 -1
- package/front_end/panels/layer_viewer/Layers3DView.ts +7 -8
- package/front_end/panels/layer_viewer/layer_viewer-meta.ts +1 -5
- package/front_end/panels/network/NetworkDataGridNode.ts +3 -0
- package/front_end/panels/network/NetworkLogView.ts +8 -13
- package/front_end/panels/security/SecurityPanel.ts +27 -20
- package/front_end/panels/sources/DebuggerPlugin.ts +2 -2
- package/front_end/panels/sources/SourcesPanel.ts +9 -1
- package/front_end/panels/sources/UISourceCodeFrame.ts +69 -64
- package/front_end/panels/timeline/CompatibilityTracksAppender.ts +4 -1
- package/front_end/panels/timeline/CountersGraph.ts +19 -10
- package/front_end/panels/timeline/ModificationsManager.ts +3 -1
- package/front_end/panels/timeline/ThreadAppender.ts +4 -1
- package/front_end/panels/timeline/TimelineController.ts +9 -2
- package/front_end/panels/timeline/TimelineDetailsView.ts +4 -1
- package/front_end/panels/timeline/TimelineEventOverview.ts +3 -5
- package/front_end/panels/timeline/TimelineFlameChartView.ts +1 -4
- package/front_end/panels/timeline/TimelinePanel.ts +27 -16
- package/front_end/panels/timeline/TimelineTreeView.ts +0 -6
- package/front_end/panels/timeline/TimelineUIUtils.ts +5 -3
- package/front_end/panels/timeline/components/SidebarAnnotationsTab.ts +3 -1
- package/front_end/panels/timeline/overlays/OverlaysImpl.ts +3 -1
- package/front_end/panels/timeline/overlays/components/TimeRangeOverlay.ts +147 -97
- package/front_end/panels/timeline/timeline-meta.ts +22 -84
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/ui/components/text_editor/TextEditor.ts +18 -6
- package/front_end/ui/legacy/TabbedPane.ts +17 -1
- package/front_end/ui/legacy/TextPrompt.ts +80 -19
- package/front_end/ui/legacy/components/source_frame/ImageView.ts +11 -4
- package/front_end/ui/legacy/components/utils/Linkifier.ts +29 -17
- package/front_end/ui/settings/LayerViewerSettings.ts +14 -0
- package/front_end/ui/settings/TimelineSettings.ts +103 -0
- package/front_end/ui/settings/settings.ts +2 -0
- package/front_end/ui/visual_logging/KnownContextValues.ts +1 -0
- package/package.json +1 -1
package/AUTHORS
CHANGED
|
@@ -60,6 +60,7 @@ Jan Keitel <jan.keitel@gmail.com>
|
|
|
60
60
|
Jeffrey Chen <jeffreyca16@gmail.com>
|
|
61
61
|
Jerome Vouillon <jerome.vouillon@gmail.com>
|
|
62
62
|
Jesper van den Ende <jespertheend@gmail.com>
|
|
63
|
+
Jihyeon Lee <wlgus7464@gmail.com>
|
|
63
64
|
Jithil p Ponnan <jithil.p@gmail.com>
|
|
64
65
|
Joseph P <thepigrammer@gmail.com>
|
|
65
66
|
Juba Borgohain <chromiumjuba@gmail.com>
|
|
@@ -57,26 +57,34 @@ const WEB_SAFE_SCHEMES = new Set([
|
|
|
57
57
|
'data:',
|
|
58
58
|
]);
|
|
59
59
|
|
|
60
|
-
|
|
61
|
-
* Returns true if the URL uses an unprivileged web-safe scheme (or is a `blob:` URL wrapping one).
|
|
62
|
-
*/
|
|
63
|
-
export function hasWebSafeScheme(url: Platform.DevToolsPath.UrlString|URL|string): boolean {
|
|
60
|
+
function parseUnwrappedURL(url: Platform.DevToolsPath.UrlString|URL|string): URL|null {
|
|
64
61
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
let parsed = new URL(url);
|
|
63
|
+
// Iteratively unwrap nested `blob:` and `filesystem:` URLs (e.g. `blob:filesystem:https://...`)
|
|
64
|
+
// to inspect the underlying origin's protocol.
|
|
65
|
+
while (parsed.protocol === 'blob:' || parsed.protocol === 'filesystem:') {
|
|
66
|
+
parsed = new URL(parsed.href.slice(parsed.protocol.length));
|
|
68
67
|
}
|
|
69
|
-
return
|
|
68
|
+
return parsed;
|
|
70
69
|
} catch {
|
|
71
|
-
return
|
|
70
|
+
return null;
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Returns true if the URL uses an unprivileged web-safe scheme (or is a `blob:`/`filesystem:` URL wrapping one).
|
|
76
|
+
*/
|
|
77
|
+
export function hasWebSafeScheme(url: Platform.DevToolsPath.UrlString|URL|string): boolean {
|
|
78
|
+
const parsed = parseUnwrappedURL(url);
|
|
79
|
+
return parsed !== null && WEB_SAFE_SCHEMES.has(parsed.protocol);
|
|
80
|
+
}
|
|
81
|
+
|
|
75
82
|
/**
|
|
76
83
|
* Privileged browser and local schemes that must not be opened via external/new-tab links by default.
|
|
77
84
|
*/
|
|
78
85
|
const PRIVILEGED_SCHEMES = new Set([
|
|
79
86
|
'chrome:',
|
|
87
|
+
'chrome-error:',
|
|
80
88
|
'chrome-extension:',
|
|
81
89
|
'chrome-search:',
|
|
82
90
|
'chrome-untrusted:',
|
|
@@ -86,18 +94,11 @@ const PRIVILEGED_SCHEMES = new Set([
|
|
|
86
94
|
]);
|
|
87
95
|
|
|
88
96
|
/**
|
|
89
|
-
* Returns true if the URL uses a privileged scheme (or is a `blob:` URL wrapping one).
|
|
97
|
+
* Returns true if the URL uses a privileged scheme (or is a `blob:`/`filesystem:` URL wrapping one).
|
|
90
98
|
*/
|
|
91
99
|
export function isPrivilegedScheme(url: Platform.DevToolsPath.UrlString|URL|string): boolean {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (parsed.protocol === 'blob:') {
|
|
95
|
-
return isPrivilegedScheme(parsed.pathname);
|
|
96
|
-
}
|
|
97
|
-
return PRIVILEGED_SCHEMES.has(parsed.protocol);
|
|
98
|
-
} catch {
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
100
|
+
const parsed = parseUnwrappedURL(url);
|
|
101
|
+
return parsed !== null && PRIVILEGED_SCHEMES.has(parsed.protocol);
|
|
101
102
|
}
|
|
102
103
|
|
|
103
104
|
/**
|
|
@@ -1100,8 +1100,9 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
|
|
|
1100
1100
|
*/
|
|
1101
1101
|
async toggleHideElement(): Promise<void> {
|
|
1102
1102
|
let pseudoElementName = this.pseudoType() ? this.nodeName() : null;
|
|
1103
|
-
|
|
1104
|
-
|
|
1103
|
+
const pseudoIdentifier = this.pseudoIdentifier();
|
|
1104
|
+
if (pseudoElementName && pseudoIdentifier) {
|
|
1105
|
+
pseudoElementName += `(${cssEscape(pseudoIdentifier)})`;
|
|
1105
1106
|
}
|
|
1106
1107
|
|
|
1107
1108
|
let effectiveNode: DOMNode|null = this;
|
|
@@ -1358,8 +1359,9 @@ export class DOMNode extends Common.ObjectWrapper.ObjectWrapper<DOMNodeEventType
|
|
|
1358
1359
|
const classList = classes.trim().split(/\s+/g);
|
|
1359
1360
|
return (lowerCaseName === 'div' ? '' : lowerCaseName) + '.' + classList.map(cls => cssEscape(cls)).join('.');
|
|
1360
1361
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1362
|
+
const pseudoIdentifier = this.pseudoIdentifier();
|
|
1363
|
+
if (pseudoIdentifier) {
|
|
1364
|
+
return `${lowerCaseName}(${cssEscape(pseudoIdentifier)})`;
|
|
1363
1365
|
}
|
|
1364
1366
|
return lowerCaseName;
|
|
1365
1367
|
}
|
|
@@ -109,7 +109,7 @@ export function toggleClassAndInjectStyleRule(this: Element, pseudoElementName:
|
|
|
109
109
|
const classNamePrefix = '__web-inspector-hide';
|
|
110
110
|
const classNameSuffix = '-shortcut__';
|
|
111
111
|
const styleTagId = '__web-inspector-hide-shortcut-style__';
|
|
112
|
-
const pseudoElementNameEscaped = pseudoElementName ? pseudoElementName.replace(/[
|
|
112
|
+
const pseudoElementNameEscaped = pseudoElementName ? pseudoElementName.replace(/[^a-zA-Z0-9_-]/g, '_') : '';
|
|
113
113
|
const className = classNamePrefix + pseudoElementNameEscaped + classNameSuffix;
|
|
114
114
|
this.classList.toggle(className, hidden);
|
|
115
115
|
|
|
@@ -257,6 +257,7 @@ inspectorBackend.registerCommand("CSS.disable", [], [], "Disables the CSS agent
|
|
|
257
257
|
inspectorBackend.registerCommand("CSS.enable", [], [], "Enables the CSS agent for the given page. Clients should not assume that the CSS agent has been enabled until the result of this command is received.");
|
|
258
258
|
inspectorBackend.registerCommand("CSS.forcePseudoState", [{"name": "nodeId", "type": "number", "optional": false, "description": "The element id for which to force the pseudo state.", "typeRef": "DOM.NodeId"}, {"name": "forcedPseudoClasses", "type": "array", "optional": false, "description": "Element pseudo classes to force when computing the element's style.", "typeRef": "string"}], [], "Ensures that the given node will have specified pseudo-classes whenever its style is computed by the browser.");
|
|
259
259
|
inspectorBackend.registerCommand("CSS.forceStartingStyle", [{"name": "nodeId", "type": "number", "optional": false, "description": "The element id for which to force the starting-style state.", "typeRef": "DOM.NodeId"}, {"name": "forced", "type": "boolean", "optional": false, "description": "Boolean indicating if this is on or off.", "typeRef": null}], [], "Ensures that the given node is in its starting-style state.");
|
|
260
|
+
inspectorBackend.registerCommand("CSS.forcePositionTryOption", [{"name": "nodeId", "type": "number", "optional": false, "description": "The element id for which to force the position-try option.", "typeRef": "DOM.NodeId"}, {"name": "index", "type": "number", "optional": true, "description": "The 1-based index of the position-try fallback option, 0 for base position (no fallback), or omitted to clear the forced state.", "typeRef": null}], [], "Forces a position-try option for the given node.");
|
|
260
261
|
inspectorBackend.registerCommand("CSS.getBackgroundColors", [{"name": "nodeId", "type": "number", "optional": false, "description": "Id of the node to get background colors for.", "typeRef": "DOM.NodeId"}], ["backgroundColors", "computedFontSize", "computedFontWeight"], "");
|
|
261
262
|
inspectorBackend.registerCommand("CSS.getComputedStyleForNode", [{"name": "nodeId", "type": "number", "optional": false, "description": "", "typeRef": "DOM.NodeId"}], ["computedStyle", "extraFields"], "Returns the computed style for a DOM node identified by `nodeId`.");
|
|
262
263
|
inspectorBackend.registerCommand("CSS.resolveValues", [{"name": "values", "type": "array", "optional": false, "description": "Cascade-dependent keywords (revert/revert-layer) do not work.", "typeRef": "string"}, {"name": "nodeId", "type": "number", "optional": false, "description": "Id of the node in whose context the expression is evaluated", "typeRef": "DOM.NodeId"}, {"name": "propertyName", "type": "string", "optional": true, "description": "Only longhands and custom property names are accepted.", "typeRef": null}, {"name": "pseudoType", "type": "string", "optional": true, "description": "Pseudo element type, only works for pseudo elements that generate elements in the tree, such as ::before and ::after.", "typeRef": "DOM.PseudoType"}, {"name": "pseudoIdentifier", "type": "string", "optional": true, "description": "Pseudo element custom ident.", "typeRef": null}], ["results"], "Resolve the specified values in the context of the provided element. For example, a value of '1em' is evaluated according to the computed 'font-size' of the element and a value 'calc(1px + 2px)' will be resolved to '3px'. If the `propertyName` was specified the `values` are resolved as if they were property's declaration. If a value cannot be parsed according to the provided property syntax, the value is parsed using combined syntax as if null `propertyName` was provided. If the value cannot be resolved even then, return the provided value without any changes. Note: this function currently does not resolve CSS random() function, it returns unmodified random() function parts.`");
|
|
@@ -1567,6 +1567,13 @@ export namespace ProtocolMapping {
|
|
|
1567
1567
|
paramsType: [Protocol.CSS.ForceStartingStyleRequest];
|
|
1568
1568
|
returnType: void;
|
|
1569
1569
|
};
|
|
1570
|
+
/**
|
|
1571
|
+
* Forces a position-try option for the given node.
|
|
1572
|
+
*/
|
|
1573
|
+
'CSS.forcePositionTryOption': {
|
|
1574
|
+
paramsType: [Protocol.CSS.ForcePositionTryOptionRequest];
|
|
1575
|
+
returnType: void;
|
|
1576
|
+
};
|
|
1570
1577
|
'CSS.getBackgroundColors': {
|
|
1571
1578
|
paramsType: [Protocol.CSS.GetBackgroundColorsRequest];
|
|
1572
1579
|
returnType: Protocol.CSS.GetBackgroundColorsResponse;
|
|
@@ -796,6 +796,11 @@ declare namespace ProtocolProxyApi {
|
|
|
796
796
|
*/
|
|
797
797
|
invoke_forceStartingStyle(params: Protocol.CSS.ForceStartingStyleRequest): Promise<Protocol.ProtocolResponseWithError>;
|
|
798
798
|
|
|
799
|
+
/**
|
|
800
|
+
* Forces a position-try option for the given node.
|
|
801
|
+
*/
|
|
802
|
+
invoke_forcePositionTryOption(params: Protocol.CSS.ForcePositionTryOptionRequest): Promise<Protocol.ProtocolResponseWithError>;
|
|
803
|
+
|
|
799
804
|
invoke_getBackgroundColors(params: Protocol.CSS.GetBackgroundColorsRequest): Promise<Protocol.CSS.GetBackgroundColorsResponse>;
|
|
800
805
|
|
|
801
806
|
/**
|
|
@@ -4066,6 +4066,18 @@ export namespace CSS {
|
|
|
4066
4066
|
forced: boolean;
|
|
4067
4067
|
}
|
|
4068
4068
|
|
|
4069
|
+
export interface ForcePositionTryOptionRequest {
|
|
4070
|
+
/**
|
|
4071
|
+
* The element id for which to force the position-try option.
|
|
4072
|
+
*/
|
|
4073
|
+
nodeId: DOM.NodeId;
|
|
4074
|
+
/**
|
|
4075
|
+
* The 1-based index of the position-try fallback option, 0 for base position (no fallback),
|
|
4076
|
+
* or omitted to clear the forced state.
|
|
4077
|
+
*/
|
|
4078
|
+
index?: integer;
|
|
4079
|
+
}
|
|
4080
|
+
|
|
4069
4081
|
export interface GetBackgroundColorsRequest {
|
|
4070
4082
|
/**
|
|
4071
4083
|
* Id of the node to get background colors for.
|
|
@@ -189,7 +189,7 @@ export interface ParsedAnswer {
|
|
|
189
189
|
|
|
190
190
|
export type ParsedResponse = ParsedAnswer;
|
|
191
191
|
|
|
192
|
-
export const MAX_STEPS =
|
|
192
|
+
export const MAX_STEPS = 20;
|
|
193
193
|
|
|
194
194
|
export interface ConversationSuggestion {
|
|
195
195
|
title: string;
|
|
@@ -519,8 +519,6 @@ class CrossOriginError extends Error {
|
|
|
519
519
|
*
|
|
520
520
|
* TODO: missing a test that action code is yielded before the
|
|
521
521
|
* confirmation dialog.
|
|
522
|
-
* TODO: missing a test for an error if it took
|
|
523
|
-
* more than MAX_STEPS iterations.
|
|
524
522
|
*/
|
|
525
523
|
export abstract class AiAgent<T> {
|
|
526
524
|
/**
|
|
@@ -994,7 +992,12 @@ export abstract class AiAgent<T> {
|
|
|
994
992
|
break;
|
|
995
993
|
}
|
|
996
994
|
} else {
|
|
997
|
-
yield this.#createErrorResponse(
|
|
995
|
+
yield this.#createErrorResponse(ErrorType.UNKNOWN);
|
|
996
|
+
break;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
if (i === MAX_STEPS - 1) {
|
|
1000
|
+
yield this.#createErrorResponse(ErrorType.MAX_STEPS);
|
|
998
1001
|
break;
|
|
999
1002
|
}
|
|
1000
1003
|
}
|
|
@@ -47,6 +47,7 @@ First, examine the provided context, then use the functions to gather additional
|
|
|
47
47
|
* After applying a fix, please ask the user to confirm if the fix worked or not.
|
|
48
48
|
* ALWAYS OUTPUT a list of follow-up queries at the end of your text response. The format is SUGGESTIONS: ["suggestion1", "suggestion2", "suggestion3"]. Make sure that the array and the \`SUGGESTIONS: \` text is in the same line. You're also capable of executing the fix for the issue user mentioned. Reflect this in your suggestions.
|
|
49
49
|
* Use the precision of Strunk & White, the brevity of Hemingway, and the simple clarity of Vonnegut. Don't add repeated information, and keep the whole answer short.
|
|
50
|
+
* If the target element is in a cross-origin iframe, stop and ask the user to select it in the Elements panel and start a new chat with that context.
|
|
50
51
|
* **CRITICAL** NEVER write full Python programs - you should only write individual statements that invoke a single function from the provided library.
|
|
51
52
|
* **CRITICAL** NEVER output text before a function call. Always do a function call first.
|
|
52
53
|
* **CRITICAL** When answering questions about positioning or layout, ALWAYS inspect \`position\`, \`display\` and all other related properties. You MUST provide a specific list of CSS property names when calling functions to get styles. Do not use generic values like "all" or "*".
|
|
@@ -34,6 +34,11 @@ Lighthouse Version: 1.0.0
|
|
|
34
34
|
|
|
35
35
|
## Category Scores
|
|
36
36
|
- Accessibility: 50
|
|
37
|
+
|
|
38
|
+
## Failing audits
|
|
39
|
+
|
|
40
|
+
### Accessibility (categoryId: "accessibility")
|
|
41
|
+
- Accessibility Audit: 50
|
|
37
42
|
=== end content
|
|
38
43
|
|
|
39
44
|
Title: LighthouseContext with AI V2 architecture enabled returns the report summary as user facing details
|
|
@@ -41,7 +46,7 @@ Content:
|
|
|
41
46
|
[
|
|
42
47
|
{
|
|
43
48
|
"title": "Lighthouse report",
|
|
44
|
-
"text": "# Lighthouse Report Summary\nURL: https://example.com\nFetch Time: 2026-03-12\nLighthouse Version: 1.0.0\n\n## Category Scores\n- Accessibility: 50"
|
|
49
|
+
"text": "# Lighthouse Report Summary\nURL: https://example.com\nFetch Time: 2026-03-12\nLighthouse Version: 1.0.0\n\n## Category Scores\n- Accessibility: 50\n\n## Failing audits\n\n### Accessibility (categoryId: \"accessibility\")\n- Accessibility Audit: 50"
|
|
45
50
|
}
|
|
46
51
|
]
|
|
47
52
|
=== end content
|
|
@@ -58,8 +58,9 @@ export class LighthouseContext extends ConversationContext<LHModel.ReporterTypes
|
|
|
58
58
|
this.#cachedPayload =
|
|
59
59
|
'**CRITICAL**: The Lighthouse report failed to record or all category scores are error/unavailable (n/a). This indicates a failed run or missing data.';
|
|
60
60
|
} else if (Root.Runtime.hostConfig.devToolsAiV2Architecture?.enabled) {
|
|
61
|
-
// AI V2 sends
|
|
62
|
-
|
|
61
|
+
// AI V2 sends failing audit titles without their details to keep the prompt small. The agent
|
|
62
|
+
// uses the category IDs in the list to fetch full details with `getLighthouseAudits`.
|
|
63
|
+
this.#cachedPayload = `${formatter.summary(this.#lh)}\n\n${formatter.failingAuditsSummary(this.#lh)}`;
|
|
63
64
|
} else {
|
|
64
65
|
// The V1 `AccessibilityAgent` expects the accessibility audits up front. Remove this branch with V1.
|
|
65
66
|
this.#cachedPayload =
|
|
@@ -133,3 +133,14 @@ The following audits in this category have a score below 90 and may need attenti
|
|
|
133
133
|
* **Resource Size**: 22.2 kB
|
|
134
134
|
* **Est Savings**: 16.9 kB
|
|
135
135
|
=== end content
|
|
136
|
+
|
|
137
|
+
Title: LighthouseFormatter lists failing audits grouped by category with the category ID
|
|
138
|
+
Content:
|
|
139
|
+
## Failing audits
|
|
140
|
+
|
|
141
|
+
### Performance (categoryId: "performance")
|
|
142
|
+
- First Audit: 80
|
|
143
|
+
|
|
144
|
+
### Accessibility (categoryId: "accessibility")
|
|
145
|
+
- No failing audits.
|
|
146
|
+
=== end content
|
|
@@ -36,17 +36,37 @@ export class LighthouseFormatter {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
39
|
+
* Returns the title and score of every failing audit (score < 90), grouped by category.
|
|
40
|
+
* Descriptions and details tables are left out to keep the prompt small. Each category
|
|
41
|
+
* heading includes its category ID so the agent can request the full audit details.
|
|
42
|
+
*/
|
|
43
|
+
failingAuditsSummary(report: LHModel.ReporterTypes.ReportJSON): string {
|
|
44
|
+
const lines: string[] = [];
|
|
45
|
+
lines.push('## Failing audits');
|
|
46
|
+
for (const [categoryId, category] of Object.entries(report.categories)) {
|
|
47
|
+
lines.push('');
|
|
48
|
+
lines.push(`### ${category.title} (categoryId: "${categoryId}")`);
|
|
49
|
+
const failingAudits = this.#findFailingAudits(report, category);
|
|
50
|
+
if (failingAudits.length === 0) {
|
|
51
|
+
lines.push('- No failing audits.');
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
for (const audit of failingAudits) {
|
|
55
|
+
lines.push(`- ${audit.title}: ${this.#formatScore(audit.score)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Formats a Lighthouse report for an AI Agent. If categoryId is 'all', returns
|
|
63
|
+
* each category's audits. Otherwise, returns audits for the specified category.
|
|
64
|
+
* The output does not include the report summary, because the conversation
|
|
65
|
+
* context already sends it.
|
|
42
66
|
*/
|
|
43
67
|
formatReport(report: LHModel.ReporterTypes.ReportJSON, categoryId: LighthouseCategoryArg): string {
|
|
44
68
|
if (categoryId === 'all') {
|
|
45
|
-
|
|
46
|
-
for (const category of Object.values(report.categories)) {
|
|
47
|
-
sections.push(this.audits(report, category));
|
|
48
|
-
}
|
|
49
|
-
return sections.join('\n\n');
|
|
69
|
+
return Object.values(report.categories).map(category => this.audits(report, category)).join('\n\n');
|
|
50
70
|
}
|
|
51
71
|
return this.audits(report, categoryId);
|
|
52
72
|
}
|
|
@@ -69,10 +89,7 @@ export class LighthouseFormatter {
|
|
|
69
89
|
}
|
|
70
90
|
lines.push('');
|
|
71
91
|
|
|
72
|
-
const failingAudits =
|
|
73
|
-
const audit = report.audits[ref.id];
|
|
74
|
-
return audit && audit.score !== null && audit.score < 0.9;
|
|
75
|
-
});
|
|
92
|
+
const failingAudits = this.#findFailingAudits(report, category);
|
|
76
93
|
|
|
77
94
|
if (failingAudits.length === 0) {
|
|
78
95
|
lines.push('All audits in this category passed (score >= 90).');
|
|
@@ -80,13 +97,8 @@ export class LighthouseFormatter {
|
|
|
80
97
|
}
|
|
81
98
|
|
|
82
99
|
lines.push('The following audits in this category have a score below 90 and may need attention:');
|
|
83
|
-
for (const
|
|
84
|
-
|
|
85
|
-
if (!audit) {
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const score = audit.score !== null ? Math.round(audit.score * 100) : 'n/a';
|
|
89
|
-
let line = `- **${audit.title}**: ${score}`;
|
|
100
|
+
for (const audit of failingAudits) {
|
|
101
|
+
let line = `- **${audit.title}**: ${this.#formatScore(audit.score)}`;
|
|
90
102
|
if (audit.displayValue) {
|
|
91
103
|
line += ` (${audit.displayValue})`;
|
|
92
104
|
}
|
|
@@ -104,6 +116,22 @@ export class LighthouseFormatter {
|
|
|
104
116
|
return lines.join('\n');
|
|
105
117
|
}
|
|
106
118
|
|
|
119
|
+
/**
|
|
120
|
+
* Returns the audits in a category that scored below 90, in the category's order.
|
|
121
|
+
* Audits without a score (such as informative or manual audits) cannot fail, so they
|
|
122
|
+
* are excluded.
|
|
123
|
+
*/
|
|
124
|
+
#findFailingAudits(report: LHModel.ReporterTypes.ReportJSON, category: LHModel.ReporterTypes.CategoryJSON):
|
|
125
|
+
Array<LHModel.ReporterTypes.AuditResultJSON&{score: number}> {
|
|
126
|
+
return category.auditRefs.map(ref => report.audits[ref.id])
|
|
127
|
+
.filter((audit): audit is LHModel.ReporterTypes.AuditResultJSON&{score: number} =>
|
|
128
|
+
Boolean(audit) && audit.score !== null && audit.score < 0.9);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#formatScore(score: number): number {
|
|
132
|
+
return Math.round(score * 100);
|
|
133
|
+
}
|
|
134
|
+
|
|
107
135
|
#formatDetails(details: LHModel.ReporterTypes.DetailsJSON): string {
|
|
108
136
|
switch (details.type) {
|
|
109
137
|
case 'table': {
|
|
@@ -19,6 +19,7 @@ You are an expert accessibility debugging assistant.
|
|
|
19
19
|
- Use `getStyles` on the backend node ID to inspect layout, color contrast, or font properties.
|
|
20
20
|
|
|
21
21
|
2. **Lighthouse Accessibility Audits (`getLighthouseAudits` & `runLighthouse`)**:
|
|
22
|
+
- The Lighthouse report context contains only category scores and failing audit titles. To answer questions about failing accessibility audits, you must first call `getLighthouseAudits` with `categoryId: 'accessibility'` to fetch the full details. Never reply using only the initial summary.
|
|
22
23
|
- If the user asks for a Lighthouse audit or report (such as recording a report or checking accessibility scores), or if audits are needed:
|
|
23
24
|
- If an active Lighthouse report context already exists and no fresh audit is requested, query it via `getLighthouseAudits` with `categoryId: 'accessibility'`.
|
|
24
25
|
- If no active report exists or a fresh audit is requested, use `runLighthouse` with `categoryId: 'accessibility'`:
|
|
@@ -12,6 +12,7 @@ Your role is to evaluate websites using Lighthouse audits across performance, ac
|
|
|
12
12
|
# Tools & Workflow
|
|
13
13
|
|
|
14
14
|
1. **Lighthouse Audits (`runLighthouse` & `getLighthouseAudits`)**:
|
|
15
|
+
- The Lighthouse report context contains only category scores and failing audit titles. To answer questions about failing audits or how to improve a score, you must first call `getLighthouseAudits` for the relevant category to fetch the full details. Never reply using only the initial summary.
|
|
15
16
|
- If an active Lighthouse report context already exists and no fresh audit is requested:
|
|
16
17
|
- To inspect the entire report or multiple categories, call `getLighthouseAudits` with `categoryId: 'all'`.
|
|
17
18
|
- For a specific category, call `getLighthouseAudits` with the corresponding category ID (e.g. `'performance'`, `'accessibility'`, `'best-practices'`, `'seo'`).
|
|
@@ -39,3 +39,4 @@ The user selected a DOM element in DevTools and asks a query about the element o
|
|
|
39
39
|
|
|
40
40
|
* Meticulously investigate all potential causes for the observed behavior before concluding. Inspect parents, siblings, children, and overlapping elements where relevant.
|
|
41
41
|
* After applying a style fix, ask the user to verify if the visual change resolved their issue.
|
|
42
|
+
* If the target element is in a cross-origin iframe, stop and ask the user to select it in the Elements panel and start a new chat with that context.
|
|
@@ -43,10 +43,13 @@ export class ListSourcesTool implements
|
|
|
43
43
|
static lastSourceId = 0;
|
|
44
44
|
static uiSourceCodeId: WeakMap<Workspace.UISourceCode.UISourceCode, number> =
|
|
45
45
|
new WeakMap<Workspace.UISourceCode.UISourceCode, number>();
|
|
46
|
+
static idToUiSourceCode: Map<number, Workspace.UISourceCode.UISourceCode> =
|
|
47
|
+
new Map<number, Workspace.UISourceCode.UISourceCode>();
|
|
46
48
|
|
|
47
49
|
static reset(): void {
|
|
48
50
|
ListSourcesTool.lastSourceId = 0;
|
|
49
51
|
ListSourcesTool.uiSourceCodeId = new WeakMap();
|
|
52
|
+
ListSourcesTool.idToUiSourceCode = new Map();
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
static getUISourceCodes(
|
|
@@ -78,7 +81,9 @@ export class ListSourcesTool implements
|
|
|
78
81
|
if (!uiSourceCodes.get(url) || uiSourceCode.contentType().isFromSourceMap()) {
|
|
79
82
|
uiSourceCodes.set(url, uiSourceCode);
|
|
80
83
|
if (!ListSourcesTool.uiSourceCodeId.has(uiSourceCode)) {
|
|
81
|
-
|
|
84
|
+
const id = ++ListSourcesTool.lastSourceId;
|
|
85
|
+
ListSourcesTool.uiSourceCodeId.set(uiSourceCode, id);
|
|
86
|
+
ListSourcesTool.idToUiSourceCode.set(id, uiSourceCode);
|
|
82
87
|
}
|
|
83
88
|
}
|
|
84
89
|
}
|
|
@@ -90,14 +95,15 @@ export class ListSourcesTool implements
|
|
|
90
95
|
static getSourceById(
|
|
91
96
|
id: number,
|
|
92
97
|
originLock: OriginLockState,
|
|
93
|
-
// eslint-disable-next-line @devtools/no-instance-of-migrated-singletons
|
|
94
|
-
workspace: Workspace.Workspace.WorkspaceImpl = Workspace.Workspace.WorkspaceImpl.instance(),
|
|
95
98
|
): Workspace.UISourceCode.UISourceCode|undefined {
|
|
96
99
|
if (!Number.isInteger(id) || id <= 0) {
|
|
97
100
|
return undefined;
|
|
98
101
|
}
|
|
99
|
-
|
|
100
|
-
|
|
102
|
+
const file = ListSourcesTool.idToUiSourceCode.get(id);
|
|
103
|
+
if (!file || !isOriginAllowedByLock(originLock, file.securityOrigin())) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
return file;
|
|
101
107
|
}
|
|
102
108
|
|
|
103
109
|
readonly parameters: Host.AidaClient.FunctionObjectParam<never> = {
|
|
@@ -144,6 +144,10 @@ export class DebuggerWorkspaceBinding implements SDK.TargetManager.SDKModelObser
|
|
|
144
144
|
}
|
|
145
145
|
ranges = compilerMapping.getLocationRangesForSameSourceLocation(rawLocation);
|
|
146
146
|
ranges = ranges.filter(range => contained(rawLocation, range));
|
|
147
|
+
if (mode === SDK.DebuggerModel.StepMode.STEP_OVER) {
|
|
148
|
+
// Step over functions inlined by the compiler (from encoded source map scopes).
|
|
149
|
+
ranges = ranges.concat(SourceMapStepping.inlinedCalleeRanges(callFrame));
|
|
150
|
+
}
|
|
147
151
|
return ranges;
|
|
148
152
|
}
|
|
149
153
|
|
|
@@ -516,7 +520,7 @@ export class DebuggerWorkspaceBinding implements SDK.TargetManager.SDKModelObser
|
|
|
516
520
|
null :
|
|
517
521
|
await this.computeAutoStep(SDK.DebuggerModel.StepMode.STEP_OVER, callFrames);
|
|
518
522
|
}
|
|
519
|
-
return
|
|
523
|
+
return await SourceMapStepping.nextAutoStep(debuggerPausedDetails, context, this.computeAutoStep.bind(this));
|
|
520
524
|
}
|
|
521
525
|
|
|
522
526
|
async #shouldPauseInWasm(debuggerPausedDetails: SDK.DebuggerModel.DebuggerPausedDetails,
|
|
@@ -5,11 +5,13 @@
|
|
|
5
5
|
/**
|
|
6
6
|
* @file Stepping through code with encoded source map scopes: inlined functions, outlined functions and unmapped code.
|
|
7
7
|
*
|
|
8
|
-
* Mirrors the Wasm auto-stepping: user steps get a skip list (see `DebuggerWorkspaceBinding.computeAutoStep`)
|
|
8
|
+
* Mirrors the Wasm auto-stepping: user steps get a skip list (see `DebuggerWorkspaceBinding.computeAutoStep`), and
|
|
9
|
+
* pauses that don't correspond to a "logical" step are continued automatically ({@link nextAutoStep}).
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import * as Root from '../../core/root/root.js';
|
|
12
|
-
import
|
|
13
|
+
import * as SDK from '../../core/sdk/sdk.js';
|
|
14
|
+
import * as Protocol from '../../generated/protocol.js';
|
|
13
15
|
|
|
14
16
|
interface ScopedPosition {
|
|
15
17
|
sourceMap: SDK.SourceMap.SourceMap;
|
|
@@ -19,14 +21,17 @@ interface ScopedPosition {
|
|
|
19
21
|
column: number;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
/**
|
|
24
|
+
/**
|
|
25
|
+
* @returns the script-relative position of {@link frame}, if it's in a script with encoded source map scopes.
|
|
26
|
+
* Scopes without any mappings are ignored: every position would count as unmapped (see {@link isUnmapped}).
|
|
27
|
+
*/
|
|
23
28
|
function scopedPosition(frame: SDK.DebuggerModel.CallFrame): ScopedPosition|null {
|
|
24
29
|
if (!Root.Runtime.hostConfig.devToolsSourceMapScopesInSourcesPanel?.enabled) {
|
|
25
30
|
return null;
|
|
26
31
|
}
|
|
27
32
|
const script = frame.script;
|
|
28
33
|
const sourceMap = script.sourceMap();
|
|
29
|
-
if (!sourceMap?.hasEncodedScopeInfo()) {
|
|
34
|
+
if (!sourceMap?.hasEncodedScopeInfo() || sourceMap.mappings().length === 0) {
|
|
30
35
|
return null;
|
|
31
36
|
}
|
|
32
37
|
const {lineNumber, columnNumber} = script.rawLocationToRelativeLocation(frame.location());
|
|
@@ -42,9 +47,76 @@ function toLocationRanges({script}: ScopedPosition,
|
|
|
42
47
|
return ranges.map(({start, end}) => ({start: toLocation(start), end: toLocation(end)}));
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
export function isScopedFrame(frame: SDK.DebuggerModel.CallFrame): boolean {
|
|
51
|
+
return scopedPosition(frame) !== null;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** @returns true iff {@link frame} has encoded scopes, and its position is not mapped to any source. */
|
|
55
|
+
export function isUnmapped(frame: SDK.DebuggerModel.CallFrame): boolean {
|
|
56
|
+
const position = scopedPosition(frame);
|
|
57
|
+
return position !== null && position.sourceMap.findEntry(position.line, position.column)?.sourceURL === undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** @returns true iff both frames have encoded scopes and are mapped to the same original location. */
|
|
61
|
+
export function isSameOriginalLocation(a: SDK.DebuggerModel.CallFrame, b: SDK.DebuggerModel.CallFrame): boolean {
|
|
62
|
+
const entry = (frame: SDK.DebuggerModel.CallFrame): SDK.SourceMap.SourceMapEntry|null => {
|
|
63
|
+
const position = scopedPosition(frame);
|
|
64
|
+
return position?.sourceMap.findEntry(position.line, position.column) ?? null;
|
|
65
|
+
};
|
|
66
|
+
const entryA = entry(a);
|
|
67
|
+
const entryB = entry(b);
|
|
68
|
+
return entryA?.sourceURL !== undefined && entryA.sourceURL === entryB?.sourceURL &&
|
|
69
|
+
entryA.sourceLineNumber === entryB.sourceLineNumber && entryA.sourceColumnNumber === entryB.sourceColumnNumber;
|
|
70
|
+
}
|
|
71
|
+
|
|
45
72
|
/** @returns the body of the innermost inlined function that {@link frame} is paused in (empty if none). */
|
|
46
73
|
export function inlinedFunctionRanges(frame: SDK.DebuggerModel.CallFrame): SDK.DebuggerModel.LocationRange[] {
|
|
47
74
|
const position = scopedPosition(frame);
|
|
48
75
|
const range = position?.sourceMap.inlinedFunctionRange(position.line, position.column);
|
|
49
76
|
return position && range ? toLocationRanges(position, [range]) : [];
|
|
50
77
|
}
|
|
78
|
+
|
|
79
|
+
/** @returns the bodies of the functions inlined into the logical function that {@link frame} is paused in. */
|
|
80
|
+
export function inlinedCalleeRanges(frame: SDK.DebuggerModel.CallFrame): SDK.DebuggerModel.LocationRange[] {
|
|
81
|
+
const position = scopedPosition(frame);
|
|
82
|
+
return position ? toLocationRanges(position, position.sourceMap.inlinedCalleeRanges(position.line, position.column)) :
|
|
83
|
+
[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Decides whether the pause {@link details} completes the user's step {@link context}.
|
|
88
|
+
*
|
|
89
|
+
* @returns null to present the pause, or the step to issue instead.
|
|
90
|
+
*/
|
|
91
|
+
export async function nextAutoStep(
|
|
92
|
+
details: SDK.DebuggerModel.DebuggerPausedDetails, context: SDK.DebuggerModel.StepContext|null,
|
|
93
|
+
computeAutoStep: SDK.DebuggerModel.ComputeAutoStepCallback): Promise<SDK.DebuggerModel.AutoStep|null> {
|
|
94
|
+
if (!context || details.reason !== Protocol.Debugger.PausedEventReason.Step) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const frames = details.callFrames;
|
|
98
|
+
const start = context.callFrames;
|
|
99
|
+
if (frames.length === 0 || start.length === 0 || (!isScopedFrame(frames[0]) && !isScopedFrame(start[0]))) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
switch (context.mode) {
|
|
104
|
+
case SDK.DebuggerModel.StepMode.STEP_INTO:
|
|
105
|
+
return isUnmapped(frames[0]) ? await computeAutoStep(SDK.DebuggerModel.StepMode.STEP_INTO, frames) : null;
|
|
106
|
+
|
|
107
|
+
case SDK.DebuggerModel.StepMode.STEP_OVER: {
|
|
108
|
+
const startDepth = start.length;
|
|
109
|
+
const depth = frames.length;
|
|
110
|
+
if (isUnmapped(frames[0]) || (depth === startDepth && isSameOriginalLocation(frames[0], start[0]))) {
|
|
111
|
+
// Unmapped code, or still on the same original location (the skip list only covers the generated ranges that
|
|
112
|
+
// contain the start position): keep stepping over.
|
|
113
|
+
return await computeAutoStep(SDK.DebuggerModel.StepMode.STEP_OVER, frames);
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
case SDK.DebuggerModel.StepMode.STEP_OUT:
|
|
119
|
+
// Stepping into calls of the unmapped code would present a pause inside the callee, so continue as step over.
|
|
120
|
+
return isUnmapped(frames[0]) ? await computeAutoStep(SDK.DebuggerModel.StepMode.STEP_OVER, frames) : null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
@@ -191,6 +191,9 @@ export class StyleFile implements TextUtils.ContentProvider.ContentProvider {
|
|
|
191
191
|
|
|
192
192
|
addHeader(header: SDK.CSSStyleSheetHeader.CSSStyleSheetHeader): void {
|
|
193
193
|
this.headers.add(header);
|
|
194
|
+
if (header.hasSourceURL) {
|
|
195
|
+
NetworkProject.setSourceURLSynthesized(this.uiSourceCode);
|
|
196
|
+
}
|
|
194
197
|
NetworkProject.addFrameAttribution(this.uiSourceCode, header.frameId);
|
|
195
198
|
}
|
|
196
199
|
|
|
@@ -319,6 +319,8 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
319
319
|
if (this.#type === Type.Device && this.#device && this.#mode) {
|
|
320
320
|
const orientation = this.#device.orientationByName(this.#mode.orientation);
|
|
321
321
|
this.#scaleSetting.set(this.calculateFitScale(orientation.width, orientation.height));
|
|
322
|
+
} else if (this.#type === Type.Responsive) {
|
|
323
|
+
this.#scaleSetting.set(this.calculateFitScale(this.#widthSetting.get(), this.#heightSetting.get()));
|
|
322
324
|
}
|
|
323
325
|
}
|
|
324
326
|
|
|
@@ -377,7 +379,7 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
377
379
|
if (savedDevice) {
|
|
378
380
|
this.#autoAdjustScaleSetting.set(savedDevice.autoAdjust);
|
|
379
381
|
// An explicitly requested scale takes precedence over the saved one.
|
|
380
|
-
if (scale === undefined && !savedDevice.autoAdjust) {
|
|
382
|
+
if (scale === undefined && !savedDevice.autoAdjust && savedDevice.scale > 0) {
|
|
381
383
|
scale = savedDevice.scale;
|
|
382
384
|
}
|
|
383
385
|
} else {
|
|
@@ -404,10 +406,13 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
404
406
|
const savedDevice = map['Responsive'];
|
|
405
407
|
if (savedDevice) {
|
|
406
408
|
this.#autoAdjustScaleSetting.set(savedDevice.autoAdjust);
|
|
407
|
-
if (!savedDevice.autoAdjust) {
|
|
409
|
+
if (!savedDevice.autoAdjust && savedDevice.scale > 0) {
|
|
408
410
|
this.#scaleSetting.set(savedDevice.scale);
|
|
411
|
+
} else if (this.#initialized) {
|
|
412
|
+
this.#updateFitScale();
|
|
409
413
|
} else {
|
|
410
|
-
|
|
414
|
+
// Defer fit-scale calculation until setAvailableSize() initializes #preferredSize.
|
|
415
|
+
this.#autoFitScaleOnInitialize = true;
|
|
411
416
|
}
|
|
412
417
|
} else {
|
|
413
418
|
this.#autoAdjustScaleSetting.set(true);
|
|
@@ -775,7 +780,7 @@ export class DeviceModeModel extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
|
|
775
780
|
private calculateFitScale(screenWidth: number, screenHeight: number): number {
|
|
776
781
|
let scale = Math.min(screenWidth ? this.#preferredSize.width / screenWidth : 1,
|
|
777
782
|
screenHeight ? this.#preferredSize.height / screenHeight : 1);
|
|
778
|
-
scale = Math.min(Math.floor(scale * 100), 100);
|
|
783
|
+
scale = Math.max(Math.min(Math.floor(scale * 100), 100), 1);
|
|
779
784
|
|
|
780
785
|
let sharpScale = scale;
|
|
781
786
|
while (sharpScale > scale * 0.7) {
|