chrome-devtools-frontend 1.0.1590494 → 1.0.1592129

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.
Files changed (79) hide show
  1. package/docs/ui_engineering.md +34 -1
  2. package/front_end/core/common/Gzip.ts +17 -2
  3. package/front_end/core/host/AidaClient.ts +38 -15
  4. package/front_end/core/host/DispatchHttpRequestClient.ts +7 -4
  5. package/front_end/core/host/InspectorFrontendHostAPI.ts +2 -0
  6. package/front_end/core/host/UserMetrics.ts +2 -1
  7. package/front_end/core/root/ExperimentNames.ts +1 -0
  8. package/front_end/core/root/Runtime.ts +5 -0
  9. package/front_end/core/sdk/CSSMetadata.ts +18 -0
  10. package/front_end/core/sdk/EmulationModel.ts +6 -1
  11. package/front_end/core/sdk/NetworkManager.ts +49 -2
  12. package/front_end/core/sdk/NetworkRequest.ts +4 -0
  13. package/front_end/core/sdk/RuntimeModel.ts +2 -1
  14. package/front_end/core/sdk/sdk-meta.ts +26 -0
  15. package/front_end/entrypoints/inspector_main/RenderingOptions.ts +34 -5
  16. package/front_end/entrypoints/main/MainImpl.ts +8 -0
  17. package/front_end/generated/InspectorBackendCommands.ts +1 -0
  18. package/front_end/generated/protocol-mapping.d.ts +10 -0
  19. package/front_end/generated/protocol-proxy-api.d.ts +8 -0
  20. package/front_end/generated/protocol.ts +6 -2
  21. package/front_end/models/ai_assistance/AiConversation.ts +9 -0
  22. package/front_end/models/ai_assistance/AiHistoryStorage.ts +1 -0
  23. package/front_end/models/ai_assistance/agents/AiAgent.ts +6 -1
  24. package/front_end/models/ai_assistance/agents/BreakpointDebuggerAgent.ts +722 -0
  25. package/front_end/models/ai_assistance/agents/PerformanceAgent.ts +1 -1
  26. package/front_end/models/ai_assistance/ai_assistance.ts +2 -0
  27. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +12 -12
  28. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +7 -7
  29. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +7 -7
  30. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.ts +1 -1
  31. package/front_end/models/greendev/Prototypes.ts +7 -1
  32. package/front_end/models/javascript_metadata/NativeFunctions.js +5 -1
  33. package/front_end/models/trace/handlers/NetworkRequestsHandler.ts +3 -3
  34. package/front_end/models/trace/helpers/Network.ts +1 -1
  35. package/front_end/models/trace/insights/NetworkDependencyTree.ts +1 -1
  36. package/front_end/models/trace/insights/RenderBlocking.ts +5 -5
  37. package/front_end/models/trace/lantern/metrics/FirstContentfulPaint.ts +6 -6
  38. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +63 -0
  39. package/front_end/panels/ai_assistance/README.md +14 -0
  40. package/front_end/panels/ai_assistance/components/ChatInput.ts +1 -4
  41. package/front_end/panels/ai_assistance/components/ChatMessage.ts +45 -27
  42. package/front_end/panels/ai_assistance/components/chatInput.css +0 -8
  43. package/front_end/panels/ai_assistance/components/chatMessage.css +6 -0
  44. package/front_end/panels/browser_debugger/CategorizedBreakpointsSidebarPane.ts +25 -46
  45. package/front_end/panels/common/AiCodeGenerationTeaser.ts +9 -4
  46. package/front_end/panels/console/ConsoleViewMessage.ts +100 -4
  47. package/front_end/panels/elements/ComputedStyleWidget.ts +46 -18
  48. package/front_end/panels/elements/ElementsPanel.ts +2 -2
  49. package/front_end/panels/elements/StyleEditorWidget.ts +10 -2
  50. package/front_end/panels/elements/StylePropertyTreeElement.ts +1 -1
  51. package/front_end/panels/elements/StylesContainer.ts +2 -0
  52. package/front_end/panels/elements/StylesSidebarPane.ts +8 -0
  53. package/front_end/panels/network/NetworkDataGridNode.ts +1 -1
  54. package/front_end/panels/network/NetworkLogViewColumns.ts +1 -1
  55. package/front_end/panels/network/RequestPayloadView.ts +7 -6
  56. package/front_end/panels/network/RequestTimingView.ts +3 -3
  57. package/front_end/panels/search/SearchResultsPane.ts +2 -1
  58. package/front_end/panels/settings/SettingsScreen.ts +3 -2
  59. package/front_end/panels/settings/WorkspaceSettingsTab.ts +1 -1
  60. package/front_end/panels/sources/BreakpointsView.ts +1 -1
  61. package/front_end/panels/sources/CallStackSidebarPane.ts +1 -1
  62. package/front_end/panels/timeline/NetworkTrackAppender.ts +1 -1
  63. package/front_end/panels/timeline/components/IgnoreListSetting.ts +0 -1
  64. package/front_end/panels/timeline/components/NetworkRequestDetails.ts +4 -4
  65. package/front_end/panels/timeline/components/NetworkRequestTooltip.ts +2 -2
  66. package/front_end/panels/timeline/components/SidebarAnnotationsTab.ts +4 -4
  67. package/front_end/third_party/chromium/README.chromium +4 -2
  68. package/front_end/third_party/lighthouse/lighthouse-dt-bundle.js +5 -1
  69. package/front_end/third_party/lighthouse/locales/en-GB.json +2 -2
  70. package/front_end/third_party/lighthouse/locales/en-US.json +2 -2
  71. package/front_end/ui/components/text_editor/AiCodeGenerationProvider.ts +1 -1
  72. package/front_end/ui/legacy/Treeoutline.ts +39 -0
  73. package/front_end/ui/legacy/components/object_ui/ObjectPropertiesSection.ts +31 -8
  74. package/front_end/ui/legacy/components/object_ui/RemoteObjectPreviewFormatter.ts +10 -3
  75. package/front_end/ui/legacy/components/source_frame/XMLView.ts +20 -5
  76. package/front_end/ui/visual_logging/Debugging.ts +51 -34
  77. package/front_end/ui/visual_logging/KnownContextValues.ts +7 -1
  78. package/front_end/ui/visual_logging/LoggingEvents.ts +5 -2
  79. package/package.json +1 -1
@@ -611,7 +611,7 @@ export const DEFAULT_VIEW = (input, _output, target) => {
611
611
  <a href="https://www.google.com" data-some-key="some-value" role="some-role">some-text</a>
612
612
  <img src="https://www.google.com/some-image.png" alt="some-alt" draggable="true" height="100"
613
613
  hidden="hidden" href="https://www.google.com" id="some-id" name="some-name" rel="some-rel"
614
- scope="some-scope"></img>
614
+ scope="some-scope">
615
615
  <input type="text" placeholder="some-placeholder" value="some-value"
616
616
  ?disabled=${!this.enabled} checked>
617
617
  </div>`,
@@ -1383,6 +1383,39 @@ export const DEFAULT_VIEW = (input, _output, target) => {
1383
1383
  };
1384
1384
  ```
1385
1385
 
1386
+ In a more complex case you might want to skip rendering of the collapsed nodes
1387
+ subtrees and temporarily expand nodes to show search matches. This could be done
1388
+ as follows:
1389
+
1390
+ ```typescript
1391
+ export const DEFAULT_VIEW = (input, _output, target) => {
1392
+ render(html`
1393
+ <div>
1394
+ <devtools-tree .template=${html`
1395
+ <ul role="tree">
1396
+ ${input.topLevelNodes.map(node => html`
1397
+ <li role="treeitem" ?open=${containsSearchResult(node)}>
1398
+ ${node.name}
1399
+ ${node.children.length ? html`
1400
+ <ul role="group">
1401
+ ${ifExpanded(node.children.map(renderChild))}
1402
+ </ul>
1403
+ ` : nothing}
1404
+ </li>`)}
1405
+ </ul>
1406
+ `}></devtools-tree>
1407
+ </div>`,
1408
+ target, {host: input});
1409
+ };
1410
+ ```
1411
+
1412
+ Here `open` attribute overrides the expansion state set by the user. Once `open`
1413
+ attribute is removed, the expansion state is reversed to the last state set by
1414
+ the user.
1415
+
1416
+ `ifExpanded` is a lit directive provided on `UI.TreeOutline` and it makes its
1417
+ argument render only if the current tree node is expanded.
1418
+
1386
1419
  ## Refactoring UI.Toolbar.Provider
1387
1420
 
1388
1421
  As part of the migration, sometimes classes need to be broken up into smaller pieces. Classes implementing
@@ -37,11 +37,26 @@ export async function fileToString(file: File): Promise<string> {
37
37
  * Decompress a gzipped ArrayBuffer to a string.
38
38
  * Consider using `arrayBufferToString` instead, which can handle both gzipped and plain text buffers.
39
39
  */
40
- export async function decompress(gzippedBuffer: ArrayBufferLike): Promise<string> {
40
+ export async function decompress(gzippedBuffer: ArrayBufferLike, charset = 'utf-8'): Promise<string> {
41
41
  const buffer = await gzipCodec(gzippedBuffer, new DecompressionStream('gzip'));
42
- const str = new TextDecoder('utf-8').decode(buffer);
42
+ const str = new TextDecoder(charset).decode(buffer);
43
43
  return str;
44
44
  }
45
+
46
+ /**
47
+ * Decompress a deflate-encoded ArrayBuffer to a string.
48
+ * Tries 'deflate' (zlib wrapper) first, then falls back to 'deflate-raw'.
49
+ */
50
+ export async function decompressDeflate(buffer: ArrayBufferLike, charset = 'utf-8'): Promise<string> {
51
+ let decompressedBuffer: ArrayBuffer;
52
+ try {
53
+ decompressedBuffer = await gzipCodec(buffer, new DecompressionStream('deflate'));
54
+ } catch {
55
+ // Try deflate-raw format if zlib-wrapped deflate fails.
56
+ decompressedBuffer = await gzipCodec(buffer, new DecompressionStream('deflate-raw'));
57
+ }
58
+ return new TextDecoder(charset).decode(decompressedBuffer);
59
+ }
45
60
  export async function compress(str: string): Promise<ArrayBuffer> {
46
61
  const encoded = new TextEncoder().encode(str);
47
62
  const buffer = await gzipCodec(encoded, new CompressionStream('gzip'));
@@ -528,8 +528,8 @@ export class AidaClient {
528
528
  async *
529
529
  doConversation(request: DoConversationRequest, options?: {signal?: AbortSignal}):
530
530
  AsyncGenerator<DoConversationResponse, void, void> {
531
- if (!InspectorFrontendHostInstance.doAidaConversation) {
532
- throw new Error('doAidaConversation is not available');
531
+ if (!InspectorFrontendHostInstance.dispatchHttpRequest) {
532
+ throw new Error('dispatchHttpRequest is not available');
533
533
  }
534
534
 
535
535
  // Disable logging for now.
@@ -559,19 +559,42 @@ export class AidaClient {
559
559
  };
560
560
  })();
561
561
  const streamId = bindOutputStream(stream);
562
- InspectorFrontendHostInstance.doAidaConversation(JSON.stringify(request), streamId, result => {
563
- if (result.statusCode === 403) {
564
- stream.fail(new Error('Server responded: permission denied'));
565
- } else if (result.error) {
566
- stream.fail(new Error(`Cannot send request: ${result.error} ${result.detail || ''}`));
567
- } else if (result.netErrorName === 'net::ERR_TIMED_OUT') {
568
- stream.fail(new Error('doAidaConversation timed out'));
569
- } else if (result.statusCode !== 200) {
570
- stream.fail(new Error(`Request failed: ${JSON.stringify(result)}`));
571
- } else {
572
- void stream.close();
573
- }
574
- });
562
+ DispatchHttpRequestClient
563
+ .makeHttpRequest(
564
+ {
565
+ service: SERVICE_NAME,
566
+ path: '/v1/aida:doConversation',
567
+ method: 'POST',
568
+ body: JSON.stringify(request),
569
+ streamId,
570
+ },
571
+ options)
572
+ .then(
573
+ () => {
574
+ void stream.close();
575
+ },
576
+ err => {
577
+ if (err instanceof DispatchHttpRequestClient.DispatchHttpRequestError && err.response) {
578
+ const result = err.response;
579
+ if (result.statusCode === 403) {
580
+ stream.fail(new Error('Server responded: permission denied'));
581
+ return;
582
+ }
583
+ if ('error' in result && result.error) {
584
+ stream.fail(new Error(`Cannot send request: ${result.error} ${result.detail || ''}`));
585
+ return;
586
+ }
587
+ if ('netErrorName' in result && result.netErrorName === 'net::ERR_TIMED_OUT') {
588
+ stream.fail(new Error('doAidaConversation timed out'));
589
+ return;
590
+ }
591
+ if (result.statusCode !== 200) {
592
+ stream.fail(new Error(`Request failed: ${JSON.stringify(result)}`));
593
+ return;
594
+ }
595
+ }
596
+ stream.fail(err);
597
+ });
575
598
  let chunk;
576
599
  const text = [];
577
600
  let inCodeChunk = false;
@@ -12,7 +12,7 @@ export enum ErrorType {
12
12
  }
13
13
 
14
14
  export class DispatchHttpRequestError extends Error {
15
- constructor(readonly type: ErrorType, options?: ErrorOptions) {
15
+ constructor(readonly type: ErrorType, readonly response?: DispatchHttpRequestResult, options?: ErrorOptions) {
16
16
  super(undefined, options);
17
17
  }
18
18
  }
@@ -38,18 +38,21 @@ export async function makeHttpRequest<R>(
38
38
 
39
39
  debugLog({request, response});
40
40
  if (response.statusCode === 404) {
41
- throw new DispatchHttpRequestError(ErrorType.NOT_FOUND);
41
+ throw new DispatchHttpRequestError(ErrorType.NOT_FOUND, response);
42
42
  }
43
43
 
44
44
  if ('response' in response && response.statusCode === 200) {
45
+ if (request.streamId && !response.response) {
46
+ return null as R;
47
+ }
45
48
  try {
46
49
  return JSON.parse(response.response) as R;
47
50
  } catch (err) {
48
- throw new DispatchHttpRequestError(ErrorType.HTTP_RESPONSE_UNAVAILABLE, {cause: err});
51
+ throw new DispatchHttpRequestError(ErrorType.HTTP_RESPONSE_UNAVAILABLE, response, {cause: err});
49
52
  }
50
53
  }
51
54
 
52
- throw new DispatchHttpRequestError(ErrorType.HTTP_RESPONSE_UNAVAILABLE);
55
+ throw new DispatchHttpRequestError(ErrorType.HTTP_RESPONSE_UNAVAILABLE, response);
53
56
  }
54
57
 
55
58
  function isDebugMode(): boolean {
@@ -263,12 +263,14 @@ export type DispatchHttpRequestRequest = {
263
263
  path: string,
264
264
  method: 'GET',
265
265
  queryParams?: Record<string, string|string[]>,
266
+ streamId?: number,
266
267
  body?: never,
267
268
  }|{
268
269
  service: string,
269
270
  path: string,
270
271
  method: 'POST',
271
272
  queryParams?: Record<string, string|string[]>,
273
+ streamId?: number,
272
274
  // A JSON string containing the request body.
273
275
  body?: string,
274
276
  };
@@ -825,10 +825,11 @@ export enum DevtoolsExperiments {
825
825
  'use-source-map-scopes' = 76,
826
826
  'timeline-show-postmessage-events' = 86,
827
827
  'timeline-debug-mode' = 93,
828
+ 'durable-messages' = 110,
828
829
  /* eslint-enable @typescript-eslint/naming-convention */
829
830
 
830
831
  // Increment this when new experiments are added.
831
- MAX_VALUE = 110,
832
+ MAX_VALUE = 111,
832
833
  }
833
834
 
834
835
  /** Update DevToolsIssuesPanelIssueExpanded from tools/metrics/histograms/enums.xml if new enum is added. **/
@@ -23,6 +23,7 @@ export enum ExperimentName {
23
23
  USE_SOURCE_MAP_SCOPES = 'use-source-map-scopes',
24
24
  TIMELINE_SHOW_POST_MESSAGE_EVENTS = 'timeline-show-postmessage-events',
25
25
  TIMELINE_DEBUG_MODE = 'timeline-debug-mode',
26
+ DURABLE_MESSAGES = 'durable-messages',
26
27
  // Adding or removing an entry from this enum?
27
28
  // You will need to update:
28
29
  // 1. DevToolsExperiments enum in host/UserMetrics.ts
@@ -534,6 +534,10 @@ export interface HostConfigAnimationStylesInStylesTab {
534
534
  enabled: boolean;
535
535
  }
536
536
 
537
+ export interface HostConfigJpegXlImageFormat {
538
+ enabled: boolean;
539
+ }
540
+
537
541
  export interface HostConfigThirdPartyCookieControls {
538
542
  thirdPartyCookieRestrictionEnabled: boolean;
539
543
  thirdPartyCookieMetadataEnabled: boolean;
@@ -641,6 +645,7 @@ export type HostConfig = Platform.TypeScriptUtilities.RecursivePartial<{
641
645
  isOffTheRecord: boolean,
642
646
  devToolsEnableOriginBoundCookies: HostConfigEnableOriginBoundCookies,
643
647
  devToolsAnimationStylesInStylesTab: HostConfigAnimationStylesInStylesTab,
648
+ devToolsJpegXlImageFormat: HostConfigJpegXlImageFormat,
644
649
  thirdPartyCookieControls: HostConfigThirdPartyCookieControls,
645
650
  devToolsAiGeneratedTimelineLabels: AiGeneratedTimelineLabels,
646
651
  devToolsAllowPopoverForcing: AllowPopoverForcing,
@@ -1344,6 +1344,24 @@ const extraPropertyValues = new Map<string, Set<string>>([
1344
1344
  ['-webkit-transform-origin-x', new Set(['left', 'right', 'center'])],
1345
1345
  ['-webkit-transform-origin-y', new Set(['top', 'bottom', 'center'])],
1346
1346
  ['width', new Set(['-webkit-fill-available', 'stretch'])],
1347
+ [
1348
+ 'animation-trigger',
1349
+ new Set([
1350
+ 'play',
1351
+ 'pause',
1352
+ 'play-once',
1353
+ 'play-alternate',
1354
+ 'play-forwards',
1355
+ 'play-backwards',
1356
+ 'play-pause',
1357
+ 'replay',
1358
+ ]),
1359
+ ],
1360
+ ['timeline-trigger-activation-range-start', new Set(['normal'])],
1361
+ ['timeline-trigger-activation-range-end', new Set(['normal'])],
1362
+ ['timeline-trigger-active-range-start', new Set(['normal'])],
1363
+ ['timeline-trigger-active-range-end', new Set(['normal'])],
1364
+
1347
1365
  ['contain-intrinsic-width', new Set(['auto none', 'auto 100px'])],
1348
1366
  ['contain-intrinsic-height', new Set(['auto none', 'auto 100px'])],
1349
1367
  ['contain-intrinsic-size', new Set(['auto none', 'auto 100px'])],
@@ -191,6 +191,7 @@ export class EmulationModel extends SDKModel<void> {
191
191
  }
192
192
 
193
193
  const avifFormatDisabledSetting = Common.Settings.Settings.instance().moduleSetting('avif-format-disabled');
194
+ const jpegXlFormatDisabledSetting = Common.Settings.Settings.instance().moduleSetting('jpeg-xl-format-disabled');
194
195
  const webpFormatDisabledSetting = Common.Settings.Settings.instance().moduleSetting('webp-format-disabled');
195
196
 
196
197
  const updateDisabledImageFormats = (): void => {
@@ -198,6 +199,9 @@ export class EmulationModel extends SDKModel<void> {
198
199
  if (avifFormatDisabledSetting.get()) {
199
200
  types.push(Protocol.Emulation.DisabledImageType.Avif);
200
201
  }
202
+ if (jpegXlFormatDisabledSetting.get()) {
203
+ types.push(Protocol.Emulation.DisabledImageType.Jxl);
204
+ }
201
205
  if (webpFormatDisabledSetting.get()) {
202
206
  types.push(Protocol.Emulation.DisabledImageType.Webp);
203
207
  }
@@ -205,9 +209,10 @@ export class EmulationModel extends SDKModel<void> {
205
209
  };
206
210
 
207
211
  avifFormatDisabledSetting.addChangeListener(updateDisabledImageFormats);
212
+ jpegXlFormatDisabledSetting.addChangeListener(updateDisabledImageFormats);
208
213
  webpFormatDisabledSetting.addChangeListener(updateDisabledImageFormats);
209
214
 
210
- if (avifFormatDisabledSetting.get() || webpFormatDisabledSetting.get()) {
215
+ if (avifFormatDisabledSetting.get() || jpegXlFormatDisabledSetting.get() || webpFormatDisabledSetting.get()) {
211
216
  updateDisabledImageFormats();
212
217
  }
213
218
 
@@ -277,13 +277,56 @@ export class NetworkManager extends SDKModel<EventTypes> {
277
277
  return null;
278
278
  }
279
279
  try {
280
- const {postData} = await manager.#networkAgent.invoke_getRequestPostData({requestId});
280
+ const {postData, base64Encoded} = await manager.#networkAgent.invoke_getRequestPostData({requestId});
281
+ if (base64Encoded && postData) {
282
+ // Decode base64 to get raw bytes as an ArrayBuffer.
283
+ const binaryString = window.atob(postData);
284
+ const bytes = new Uint8Array(binaryString.length);
285
+ for (let i = 0; i < binaryString.length; i++) {
286
+ bytes[i] = binaryString.charCodeAt(i);
287
+ }
288
+
289
+ // Extract charset from request Content-Type header, defaulting to utf-8.
290
+ const requestContentType = request.requestContentType();
291
+ const charset =
292
+ requestContentType ? Platform.MimeType.parseContentType(requestContentType).charset ?? 'utf-8' : 'utf-8';
293
+
294
+ // If the request body is compressed, attempt to decompress it.
295
+ const contentEncoding = request.requestContentEncoding()?.toLowerCase();
296
+ if (contentEncoding) {
297
+ const decompressed = await NetworkManager.#tryDecompressBody(bytes.buffer, contentEncoding, charset);
298
+ if (decompressed !== null) {
299
+ return decompressed;
300
+ }
301
+ }
302
+
303
+ // Not compressed or decompression not applicable -- decode as text.
304
+ return new TextDecoder(charset).decode(bytes);
305
+ }
281
306
  return postData;
282
307
  } catch (e) {
283
308
  return e.message;
284
309
  }
285
310
  }
286
311
 
312
+ /**
313
+ * Attempts to decompress a compressed request body.
314
+ * Returns the decompressed string, or null if decompression is not applicable.
315
+ */
316
+ static async #tryDecompressBody(buffer: ArrayBuffer, encoding: string, charset: string): Promise<string|null> {
317
+ try {
318
+ if (encoding.includes('gzip') && Common.Gzip.isGzip(buffer)) {
319
+ return await Common.Gzip.decompress(buffer, charset);
320
+ }
321
+ if (encoding.includes('deflate')) {
322
+ return await Common.Gzip.decompressDeflate(buffer, charset);
323
+ }
324
+ } catch (e) {
325
+ console.warn('Failed to decompress request body:', e);
326
+ }
327
+ return null;
328
+ }
329
+
287
330
  static connectionType(conditions: Conditions): Protocol.Network.ConnectionType {
288
331
  if (!conditions.download && !conditions.upload) {
289
332
  return Protocol.Network.ConnectionType.None;
@@ -567,7 +610,11 @@ export class NetworkDispatcher implements ProtocolProxyApi.NetworkDispatcher {
567
610
  private updateNetworkRequestWithRequest(networkRequest: NetworkRequest, request: Protocol.Network.Request): void {
568
611
  networkRequest.requestMethod = request.method;
569
612
  networkRequest.setRequestHeaders(this.headersMapToHeadersArray(request.headers));
570
- networkRequest.setRequestFormData(Boolean(request.hasPostData), request.postData || null);
613
+ // If the request body is compressed, discard the inline postData which is
614
+ // garbled (binary-as-text). The getRequestPostData command will provide
615
+ // properly base64-encoded data that we can decompress.
616
+ const isCompressed = Boolean(networkRequest.requestContentEncoding());
617
+ networkRequest.setRequestFormData(Boolean(request.hasPostData), isCompressed ? null : (request.postData || null));
571
618
  networkRequest.setInitialPriority(request.initialPriority);
572
619
  networkRequest.mixedContentType = request.mixedContentType || Protocol.Security.MixedContentType.None;
573
620
  networkRequest.setReferrerPolicy(request.referrerPolicy);
@@ -1471,6 +1471,10 @@ export class NetworkRequest extends Common.ObjectWrapper.ObjectWrapper<EventType
1471
1471
  return this.requestHeaderValue('Content-Type');
1472
1472
  }
1473
1473
 
1474
+ requestContentEncoding(): string|undefined {
1475
+ return this.requestHeaderValue('Content-Encoding');
1476
+ }
1477
+
1474
1478
  hasErrorStatusCode(): boolean {
1475
1479
  return this.statusCode >= 400;
1476
1480
  }
@@ -262,7 +262,8 @@ export class RuntimeModel extends SDKModel<EventTypes> {
262
262
  }
263
263
 
264
264
  if (object.isNode()) {
265
- void Common.Revealer.reveal(object).then(object.release.bind(object));
265
+ const omitFocus = hints !== null && typeof hints === 'object' && 'omitFocus' in hints && Boolean(hints.omitFocus);
266
+ void Common.Revealer.reveal(object, omitFocus).then(object.release.bind(object));
266
267
  return;
267
268
  }
268
269
 
@@ -313,6 +313,14 @@ const UIStrings = {
313
313
  * @description Title of a setting that enables AVIF format
314
314
  */
315
315
  enableAvifFormat: 'Enable `AVIF` format',
316
+ /**
317
+ * @description Title of a setting that disables JPEG XL format
318
+ */
319
+ disableJpegXlFormat: 'Disable `JPEG XL` format',
320
+ /**
321
+ * @description Title of a setting that enables JPEG XL format
322
+ */
323
+ enableJpegXlFormat: 'Enable `JPEG XL` format',
316
324
  /**
317
325
  * @description Title of a setting that disables WebP format
318
326
  */
@@ -1072,6 +1080,24 @@ Common.Settings.registerSettingExtension({
1072
1080
  defaultValue: false,
1073
1081
  });
1074
1082
 
1083
+ Common.Settings.registerSettingExtension({
1084
+ category: Common.Settings.SettingCategory.RENDERING,
1085
+ settingName: 'jpeg-xl-format-disabled',
1086
+ settingType: Common.Settings.SettingType.BOOLEAN,
1087
+ storageType: Common.Settings.SettingStorageType.SESSION,
1088
+ options: [
1089
+ {
1090
+ value: true,
1091
+ title: i18nLazyString(UIStrings.disableJpegXlFormat),
1092
+ },
1093
+ {
1094
+ value: false,
1095
+ title: i18nLazyString(UIStrings.enableJpegXlFormat),
1096
+ },
1097
+ ],
1098
+ defaultValue: false,
1099
+ });
1100
+
1075
1101
  Common.Settings.registerSettingExtension({
1076
1102
  category: Common.Settings.SettingCategory.RENDERING,
1077
1103
  settingName: 'webp-format-disabled',
@@ -7,6 +7,7 @@
7
7
  import * as Common from '../../core/common/common.js';
8
8
  import * as Host from '../../core/host/host.js';
9
9
  import * as i18n from '../../core/i18n/i18n.js';
10
+ import * as Root from '../../core/root/root.js';
10
11
  import * as SettingsUI from '../../ui/legacy/components/settings_ui/settings_ui.js';
11
12
  import * as UI from '../../ui/legacy/legacy.js';
12
13
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
@@ -145,10 +146,14 @@ const UIStrings = {
145
146
  */
146
147
  disableAvifImageFormat: 'Disable `AVIF` image format',
147
148
  /**
148
- * @description Explanation text for both the 'Disable AVIF image format' and 'Disable WebP image
149
- * format' settings in the Rendering tool.
149
+ * @description Explanation text for the image format disabling settings in the Rendering tool.
150
150
  */
151
151
  requiresAPageReloadToApplyAnd: 'Requires a page reload to apply and disables caching for image requests.',
152
+ /**
153
+ * @description The name of a checkbox setting in the Rendering tool. This setting disables the
154
+ * page from loading images with the JPEG XL format.
155
+ */
156
+ disableJpegXlImageFormat: 'Disable `JPEG XL` image format',
152
157
  /**
153
158
  * @description The name of a checkbox setting in the Rendering tool. This setting disables the
154
159
  * page from loading images with the WebP format.
@@ -184,7 +189,13 @@ const supportsPrefersContrast = (): boolean => {
184
189
  return window.matchMedia(query).matches;
185
190
  };
186
191
 
192
+ const supportsJpegXl = (): boolean => {
193
+ return Boolean(Root.Runtime.hostConfig.devToolsJpegXlImageFormat?.enabled);
194
+ };
195
+
187
196
  export class RenderingOptionsView extends UI.Widget.VBox {
197
+ #jpegXlCheckboxAdded = false;
198
+
188
199
  constructor() {
189
200
  super({useShadowDom: true});
190
201
  this.registerRequiredCSS(renderingOptionsStyles);
@@ -266,13 +277,19 @@ export class RenderingOptionsView extends UI.Widget.VBox {
266
277
 
267
278
  this.contentElement.createChild('div').classList.add('panel-section-separator');
268
279
 
280
+ const avifFormatDisabledSetting = Common.Settings.Settings.instance().moduleSetting('avif-format-disabled');
281
+ const jpegXlFormatDisabledSetting = Common.Settings.Settings.instance().moduleSetting('jpeg-xl-format-disabled');
282
+ const webpFormatDisabledSetting = Common.Settings.Settings.instance().moduleSetting('webp-format-disabled');
283
+
269
284
  this.#appendCheckbox(
270
285
  i18nString(UIStrings.disableAvifImageFormat), i18nString(UIStrings.requiresAPageReloadToApplyAnd),
271
- Common.Settings.Settings.instance().moduleSetting('avif-format-disabled'));
286
+ avifFormatDisabledSetting);
272
287
 
273
- this.#appendCheckbox(
288
+ const webpCheckbox = this.#appendCheckbox(
274
289
  i18nString(UIStrings.disableWebpImageFormat), i18nString(UIStrings.requiresAPageReloadToApplyAnd),
275
- Common.Settings.Settings.instance().moduleSetting('webp-format-disabled'));
290
+ webpFormatDisabledSetting);
291
+
292
+ this.#appendJpegXlCheckboxWhenSupported(webpCheckbox, jpegXlFormatDisabledSetting);
276
293
 
277
294
  this.contentElement.createChild('div').classList.add('panel-section-separator');
278
295
  }
@@ -286,6 +303,18 @@ export class RenderingOptionsView extends UI.Widget.VBox {
286
303
  return checkbox;
287
304
  }
288
305
 
306
+ #appendJpegXlCheckboxWhenSupported(
307
+ webpCheckbox: UI.UIUtils.CheckboxLabel, jpegXlFormatDisabledSetting: Common.Settings.Setting<boolean>): void {
308
+ if (this.#jpegXlCheckboxAdded || !supportsJpegXl()) {
309
+ return;
310
+ }
311
+
312
+ this.#jpegXlCheckboxAdded = true;
313
+ webpCheckbox.before(this.#appendCheckbox(
314
+ i18nString(UIStrings.disableJpegXlImageFormat), i18nString(UIStrings.requiresAPageReloadToApplyAnd),
315
+ jpegXlFormatDisabledSetting));
316
+ }
317
+
289
318
  #appendSelect(label: string, setting: Common.Settings.Setting<unknown>): void {
290
319
  const control = SettingsUI.SettingsUI.createControlForSetting(setting, label);
291
320
  if (control) {
@@ -423,6 +423,14 @@ export class MainImpl {
423
423
  'Performance panel: show postMessage dispatch and handling flows',
424
424
  );
425
425
 
426
+ Root.Runtime.experiments.registerHostExperiment({
427
+ name: Root.ExperimentNames.ExperimentName.DURABLE_MESSAGES,
428
+ title: 'Durable Messages',
429
+ aboutFlag: 'devtools-enable-durable-messages',
430
+ isEnabled: Root.Runtime.hostConfig.devToolsEnableDurableMessages?.enabled ?? false,
431
+ requiresChromeRestart: false,
432
+ });
433
+
426
434
  Root.Runtime.experiments.enableExperimentsByDefault([
427
435
  Root.ExperimentNames.ExperimentName.FULL_ACCESSIBILITY_TREE,
428
436
  Root.ExperimentNames.ExperimentName.USE_SOURCE_MAP_SCOPES,
@@ -557,6 +557,7 @@ inspectorBackend.registerCommand("Emulation.setSmallViewportHeightDifferenceOver
557
557
  inspectorBackend.registerCommand("Emulation.getScreenInfos", [], ["screenInfos"], "Returns device's screen configuration. In headful mode, the physical screens configuration is returned, whereas in headless mode, a virtual headless screen configuration is provided instead.");
558
558
  inspectorBackend.registerCommand("Emulation.addScreen", [{"name": "left", "type": "number", "optional": false, "description": "Offset of the left edge of the screen in pixels.", "typeRef": null}, {"name": "top", "type": "number", "optional": false, "description": "Offset of the top edge of the screen in pixels.", "typeRef": null}, {"name": "width", "type": "number", "optional": false, "description": "The width of the screen in pixels.", "typeRef": null}, {"name": "height", "type": "number", "optional": false, "description": "The height of the screen in pixels.", "typeRef": null}, {"name": "workAreaInsets", "type": "object", "optional": true, "description": "Specifies the screen's work area. Default is entire screen.", "typeRef": "Emulation.WorkAreaInsets"}, {"name": "devicePixelRatio", "type": "number", "optional": true, "description": "Specifies the screen's device pixel ratio. Default is 1.", "typeRef": null}, {"name": "rotation", "type": "number", "optional": true, "description": "Specifies the screen's rotation angle. Available values are 0, 90, 180 and 270. Default is 0.", "typeRef": null}, {"name": "colorDepth", "type": "number", "optional": true, "description": "Specifies the screen's color depth in bits. Default is 24.", "typeRef": null}, {"name": "label", "type": "string", "optional": true, "description": "Specifies the descriptive label for the screen. Default is none.", "typeRef": null}, {"name": "isInternal", "type": "boolean", "optional": true, "description": "Indicates whether the screen is internal to the device or external, attached to the device. Default is false.", "typeRef": null}], ["screenInfo"], "Add a new screen to the device. Only supported in headless mode.");
559
559
  inspectorBackend.registerCommand("Emulation.removeScreen", [{"name": "screenId", "type": "string", "optional": false, "description": "", "typeRef": "Emulation.ScreenId"}], [], "Remove screen from the device. Only supported in headless mode.");
560
+ inspectorBackend.registerCommand("Emulation.setPrimaryScreen", [{"name": "screenId", "type": "string", "optional": false, "description": "", "typeRef": "Emulation.ScreenId"}], [], "Set primary screen. Only supported in headless mode. Note that this changes the coordinate system origin to the top-left of the new primary screen, updating the bounds and work areas of all existing screens accordingly.");
560
561
  inspectorBackend.registerType("Emulation.SafeAreaInsets", [{"name": "top", "type": "number", "optional": true, "description": "Overrides safe-area-inset-top.", "typeRef": null}, {"name": "topMax", "type": "number", "optional": true, "description": "Overrides safe-area-max-inset-top.", "typeRef": null}, {"name": "left", "type": "number", "optional": true, "description": "Overrides safe-area-inset-left.", "typeRef": null}, {"name": "leftMax", "type": "number", "optional": true, "description": "Overrides safe-area-max-inset-left.", "typeRef": null}, {"name": "bottom", "type": "number", "optional": true, "description": "Overrides safe-area-inset-bottom.", "typeRef": null}, {"name": "bottomMax", "type": "number", "optional": true, "description": "Overrides safe-area-max-inset-bottom.", "typeRef": null}, {"name": "right", "type": "number", "optional": true, "description": "Overrides safe-area-inset-right.", "typeRef": null}, {"name": "rightMax", "type": "number", "optional": true, "description": "Overrides safe-area-max-inset-right.", "typeRef": null}]);
561
562
  inspectorBackend.registerType("Emulation.ScreenOrientation", [{"name": "type", "type": "string", "optional": false, "description": "Orientation type.", "typeRef": null}, {"name": "angle", "type": "number", "optional": false, "description": "Orientation angle.", "typeRef": null}]);
562
563
  inspectorBackend.registerType("Emulation.DisplayFeature", [{"name": "orientation", "type": "string", "optional": false, "description": "Orientation of a display feature in relation to screen", "typeRef": null}, {"name": "offset", "type": "number", "optional": false, "description": "The offset from the screen origin in either the x (for vertical orientation) or y (for horizontal orientation) direction.", "typeRef": null}, {"name": "maskLength", "type": "number", "optional": false, "description": "A display feature may mask content such that it is not physically displayed - this length along with the offset describes this area. A display feature that only splits content will have a 0 mask_length.", "typeRef": null}]);
@@ -2782,6 +2782,16 @@ export namespace ProtocolMapping {
2782
2782
  paramsType: [Protocol.Emulation.RemoveScreenRequest];
2783
2783
  returnType: void;
2784
2784
  };
2785
+ /**
2786
+ * Set primary screen. Only supported in headless mode.
2787
+ * Note that this changes the coordinate system origin to the top-left
2788
+ * of the new primary screen, updating the bounds and work areas
2789
+ * of all existing screens accordingly.
2790
+ */
2791
+ 'Emulation.setPrimaryScreen': {
2792
+ paramsType: [Protocol.Emulation.SetPrimaryScreenRequest];
2793
+ returnType: void;
2794
+ };
2785
2795
  /**
2786
2796
  * Sets breakpoint on particular native event.
2787
2797
  */
@@ -1852,6 +1852,14 @@ declare namespace ProtocolProxyApi {
1852
1852
  */
1853
1853
  invoke_removeScreen(params: Protocol.Emulation.RemoveScreenRequest): Promise<Protocol.ProtocolResponseWithError>;
1854
1854
 
1855
+ /**
1856
+ * Set primary screen. Only supported in headless mode.
1857
+ * Note that this changes the coordinate system origin to the top-left
1858
+ * of the new primary screen, updating the bounds and work areas
1859
+ * of all existing screens accordingly.
1860
+ */
1861
+ invoke_setPrimaryScreen(params: Protocol.Emulation.SetPrimaryScreenRequest): Promise<Protocol.ProtocolResponseWithError>;
1862
+
1855
1863
  }
1856
1864
  export interface EmulationDispatcher {
1857
1865
  /**
@@ -7550,6 +7550,10 @@ export namespace Emulation {
7550
7550
  export interface RemoveScreenRequest {
7551
7551
  screenId: ScreenId;
7552
7552
  }
7553
+
7554
+ export interface SetPrimaryScreenRequest {
7555
+ screenId: ScreenId;
7556
+ }
7553
7557
  }
7554
7558
 
7555
7559
  /**
@@ -10153,7 +10157,7 @@ export namespace Network {
10153
10157
  }
10154
10158
 
10155
10159
  /**
10156
- * The render blocking behavior of a resource request.
10160
+ * The render-blocking behavior of a resource request.
10157
10161
  */
10158
10162
  export const enum RenderBlockingBehavior {
10159
10163
  Blocking = 'Blocking',
@@ -12670,7 +12674,7 @@ export namespace Network {
12670
12674
  */
12671
12675
  hasUserGesture?: boolean;
12672
12676
  /**
12673
- * The render blocking behavior of the request.
12677
+ * The render-blocking behavior of the request.
12674
12678
  */
12675
12679
  renderBlockingBehavior?: RenderBlockingBehavior;
12676
12680
  }
@@ -6,6 +6,7 @@ import * as Host from '../../core/host/host.js';
6
6
  import * as Root from '../../core/root/root.js';
7
7
  import * as SDK from '../../core/sdk/sdk.js';
8
8
  import * as Trace from '../../models/trace/trace.js';
9
+ import * as Greendev from '../greendev/greendev.js';
9
10
  import * as NetworkTimeCalculator from '../network_time_calculator/network_time_calculator.js';
10
11
 
11
12
  import {
@@ -17,6 +18,7 @@ import {
17
18
  ResponseType,
18
19
  type UserQuery
19
20
  } from './agents/AiAgent.js';
21
+ import {BreakpointDebuggerAgent} from './agents/BreakpointDebuggerAgent.js';
20
22
  import {ContextSelectionAgent} from './agents/ContextSelectionAgent.js';
21
23
  import {FileAgent, FileContext} from './agents/FileAgent.js';
22
24
  import {NetworkAgent, RequestContext} from './agents/NetworkAgent.js';
@@ -326,6 +328,13 @@ export class AiConversation {
326
328
  this.#agent = new PerformanceAgent(options);
327
329
  break;
328
330
  }
331
+ case ConversationType.BREAKPOINT: {
332
+ const breakpointAgentEnabled = Greendev.Prototypes.instance().isEnabled('breakpointDebuggerAgent');
333
+ if (breakpointAgentEnabled) {
334
+ this.#agent = new BreakpointDebuggerAgent(options);
335
+ }
336
+ break;
337
+ }
329
338
  case ConversationType.NONE: {
330
339
  this.#agent = new ContextSelectionAgent(options);
331
340
  break;
@@ -13,6 +13,7 @@ export const enum ConversationType {
13
13
  FILE = 'drjones-file',
14
14
  NETWORK = 'drjones-network-request',
15
15
  PERFORMANCE = 'drjones-performance-full',
16
+ BREAKPOINT = 'breakpoint',
16
17
  }
17
18
 
18
19
  export interface SerializedConversation {