chrome-devtools-frontend 1.0.1664496 → 1.0.1664619

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 (78) hide show
  1. package/.agents/skills/devtools-ux-writing-refactor/SKILL.md +2 -2
  2. package/.agents/skills/repro-flaky-tests/SKILL.md +2 -2
  3. package/.agents/skills/version-control/SKILL.md +4 -3
  4. package/docs/ui_engineering.md +89 -0
  5. package/eslint.config.mjs +1 -2
  6. package/front_end/core/host/AidaClient.ts +19 -4
  7. package/front_end/core/host/InspectorFrontendHostStub.ts +1 -1
  8. package/front_end/core/host/ResourceLoader.ts +15 -15
  9. package/front_end/core/sdk/CSSMatchedStyles.ts +75 -0
  10. package/front_end/core/sdk/NetworkRequest.ts +4 -4
  11. package/front_end/entrypoints/main/MainImpl.ts +2 -1
  12. package/front_end/entrypoints/main/main-meta.ts +0 -15
  13. package/front_end/entrypoints/shell/shell.ts +1 -0
  14. package/front_end/entrypoints/trace_app/trace_app.ts +1 -0
  15. package/front_end/foundation/Universe.ts +1 -0
  16. package/front_end/models/ai_assistance/agents/AiAgent.ts +9 -1
  17. package/front_end/models/ai_assistance/data_formatters/FileFormatter.ts +11 -4
  18. package/front_end/models/ai_assistance/data_formatters/NetworkRequestFormatter.ts +8 -2
  19. package/front_end/models/ai_assistance/performance/AICallTree.ts +2 -2
  20. package/front_end/models/ai_assistance/tools/GetNetworkRequestDetails.ts +9 -2
  21. package/front_end/models/ai_assistance/tools/ListNetworkRequests.ts +2 -5
  22. package/front_end/models/badges/badges-meta.ts +27 -0
  23. package/front_end/models/bindings/CSSWorkspaceBinding.ts +4 -3
  24. package/front_end/models/bindings/DebuggerWorkspaceBinding.ts +4 -3
  25. package/front_end/models/bindings/PresentationConsoleMessageHelper.ts +18 -11
  26. package/front_end/models/bindings/ResourceMapping.ts +38 -32
  27. package/front_end/models/breakpoints/BreakpointManager.ts +2 -1
  28. package/front_end/models/har/Writer.ts +2 -2
  29. package/front_end/models/issues_manager/IssuesManager.ts +4 -2
  30. package/front_end/models/issues_manager/SourceFrameIssuesManager.ts +6 -2
  31. package/front_end/models/logs/logs-meta.ts +23 -16
  32. package/front_end/models/source_map_scopes/NamesResolver.ts +104 -86
  33. package/front_end/models/source_map_scopes/ScopeChainModel.ts +6 -2
  34. package/front_end/models/trace/handlers/UserTimingsHandler.ts +17 -6
  35. package/front_end/models/trace_source_maps_resolver/SourceMapsResolver.ts +2 -2
  36. package/front_end/models/workspace_diff/WorkspaceDiff.ts +12 -7
  37. package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
  38. package/front_end/panels/ai_assistance/components/ChatInput.ts +21 -17
  39. package/front_end/panels/ai_assistance/components/ChatMessage.ts +7 -0
  40. package/front_end/panels/ai_assistance/components/ImageResize.ts +74 -0
  41. package/front_end/panels/animation/animationTimeline.css +0 -2
  42. package/front_end/panels/changes/ChangesSidebar.ts +1 -1
  43. package/front_end/panels/changes/ChangesView.ts +3 -4
  44. package/front_end/panels/changes/CombinedDiffView.ts +2 -2
  45. package/front_end/panels/changes/changes-meta.ts +2 -2
  46. package/front_end/panels/common/aiCodeCompletionDisclaimer.css +0 -1
  47. package/front_end/panels/console/ConsolePrompt.ts +3 -1
  48. package/front_end/panels/elements/LayoutPane.ts +19 -9
  49. package/front_end/panels/elements/StylePropertiesSection.ts +33 -5
  50. package/front_end/panels/emulation/DeviceModeToolbar.ts +92 -32
  51. package/front_end/panels/emulation/DeviceModeView.ts +5 -4
  52. package/front_end/panels/emulation/MediaQueryInspector.ts +33 -17
  53. package/front_end/panels/network/NetworkLogViewColumns.ts +1 -1
  54. package/front_end/panels/network/networkTimingTable.css +2 -2
  55. package/front_end/panels/profiler/BottomUpProfileDataGrid.ts +0 -4
  56. package/front_end/panels/profiler/HeapProfileView.ts +53 -33
  57. package/front_end/panels/profiler/TopDownProfileDataGrid.ts +0 -4
  58. package/front_end/panels/profiler/profilesPanel.css +9 -13
  59. package/front_end/panels/sensors/LocationsSettingsTab.ts +245 -232
  60. package/front_end/panels/settings/SettingsScreen.ts +29 -24
  61. package/front_end/panels/sources/DebuggerPlugin.ts +6 -3
  62. package/front_end/panels/sources/ScopeChainSidebarPane.ts +3 -1
  63. package/front_end/panels/sources/WatchExpressionsSidebarPane.ts +3 -1
  64. package/front_end/panels/timeline/TimelineUIUtils.ts +2 -2
  65. package/front_end/panels/whats_new/ReleaseNoteText.ts +8 -8
  66. package/front_end/panels/whats_new/resources/WNDT.md +7 -8
  67. package/front_end/ui/components/highlighting/HighlightManager.ts +6 -1
  68. package/front_end/ui/components/settings/SettingCheckbox.ts +12 -7
  69. package/front_end/ui/components/text_editor/javascript.ts +3 -1
  70. package/front_end/ui/legacy/Toolbar.ts +4 -1
  71. package/front_end/ui/legacy/components/color_picker/spectrum.css +1 -2
  72. package/front_end/ui/legacy/components/data_grid/DataGridElement.ts +24 -1
  73. package/front_end/ui/legacy/components/object_ui/JavaScriptREPL.ts +3 -1
  74. package/front_end/ui/legacy/components/quick_open/CommandMenu.ts +14 -11
  75. package/front_end/ui/legacy/components/settings_ui/SettingsUI.ts +14 -4
  76. package/front_end/ui/settings/SettingUIRegistration.ts +8 -3
  77. package/mcp/mcp.ts +1 -0
  78. package/package.json +4 -4
@@ -112,9 +112,9 @@ When all tests and presubmit checks pass, commit your changes and upload the CL
112
112
  *(If you update an existing commit on this branch, use `git commit --amend`).*
113
113
 
114
114
  2. **Upload the CL to Gerrit**
115
- Upload the CL using `git cl upload`. Provide a dynamic summary of changes (depending on the actual content of the change) and the bug trailer:
115
+ Upload the CL using `git cl upload -f`. Always pass `-f` (`--force`) to prevent `git cl upload` from prompting or opening an interactive text editor in background agent shells:
116
116
  ```bash
117
- git cl upload -d --commit-description="Ensure consistent UI Strings in <folder_path>
117
+ git cl upload -f -d --commit-description="Ensure consistent UI Strings in <folder_path>
118
118
 
119
119
  Summary of changes:
120
120
  - <dynamically list specific words replaced, contractions adopted, or sentence case fixes>
@@ -43,7 +43,7 @@ The second bot should do the following:
43
43
  - Ensure you use the instructions from the `devtools-version-control` skill to create and switch branches appropriately.
44
44
  - Make a minor, harmless modification directly to the test file being investigated (e.g., adding a comment `// Trigger stressor bot`) to ensure the commit is not empty.
45
45
  - Add this file (with `git add`) and commit it using a meaningful commit message (e.g., `git commit -m "Deflake <test name>"`). Ask the user which bug number to use for the changelist description.
46
- - Upload this change using `git cl upload`. This creates a debugging CL that all agents (like the fix agent) will continue to work on.
46
+ - Upload this change using `git cl upload -f`. Always use `-f` (`--force`) so the upload runs non-interactively without opening a text editor or prompt. This creates a debugging CL that all agents (like the fix agent) will continue to work on.
47
47
  - Note the <issue number> created during upload.
48
48
  - To start the stressor bot, run this command (substituting `<test file>:exact_test_id` with the actual file and exact test ID):
49
49
  `git cl try -B devtools-frontend/try -b e2e_stressor_linux -b e2e_stressor_win64 -b e2e_stressor_mac -p runner_args='<test file>:exact_test_id --repeat=100'`
@@ -81,7 +81,7 @@ The test fixing sub-agent should do the following:
81
81
  - Compile and run the test locally, to make sure there are no obvious errors introduced.
82
82
  - Commit the changes (to the branch created earlier). Make sure to list (in the changelist description) the test name, the fix, reasoning for why the test failed, why the fix fixes the issue and the failure rate (if non-zero). If you were not able to reproduce the error, state that the fix is speculative. Ask the user to provide the bug number to use in the changelist description.
83
83
  - CRITICAL: Iff the 'local-repro' bot does not reproduce the error, the 'fix-test' agent MUST wait until the stressor bot finishes its run before uploading the fix to the server (so that the stressor try run isn't aborted prematurely). If the error reproduces locally, you can upload immediately.
84
- - Once it is safe to do so, upload the change with `git cl upload`.
84
+ - Once it is safe to do so, upload the change with `git cl upload -f`.
85
85
 
86
86
  ## Results
87
87
 
@@ -60,8 +60,9 @@ git rebase-update
60
60
  ### Initial upload
61
61
  When a CL is ready, upload it with:
62
62
  ```bash
63
- git cl upload -d --commit-description="<description>"
63
+ git cl upload -f -d --commit-description="<description>"
64
64
  ```
65
+ * Always include `-f` (`--force`) so that `git cl upload` runs non-interactively without opening a text editor or prompting.
65
66
  * Use the same writing style as the current committer
66
67
  * Keep line length below 72
67
68
  * Add a "Bug: <issue number>" or "Bug: None" trailer on a separate line.
@@ -70,7 +71,7 @@ git cl upload -d --commit-description="<description>"
70
71
  ### Subsequent upload
71
72
  To upload an updated CL:
72
73
  ```bash
73
- git cl upload -d -t "<one sentence patch set description>"
74
+ git cl upload -f -d -t "<one sentence patch set description>"
74
75
  ```
75
76
 
76
77
  ## Release and Roll Status
@@ -86,7 +87,7 @@ To check whether a DevTools commit (`devtools/devtools-frontend`) has rolled int
86
87
  | Create new CL from main | `git new-branch <name>` |
87
88
  | Create stacked CL | `git new-branch --upstream_current <name>` |
88
89
  | Update current CL | `git commit --amend` |
89
- | Upload to Gerrit | `git cl upload` |
90
+ | Upload to Gerrit | `git cl upload -f` |
90
91
  | Change branch parent | `git reparent-branch <new-parent>` |
91
92
  | Sync all branches | `git rebase-update` |
92
93
  | Check release & roll status | Query `https://chromiumdash.appspot.com/fetch_commit?commit=<sha>` |
@@ -1515,3 +1515,92 @@ The component will automatically sort and merge the ranges provided.
1515
1515
 
1516
1516
  In this example, the ranges `1,3` and `2,3` will be merged into `1,4`. The
1517
1517
  ranges `10,2` and the current range `5,3` will also be highlighted.
1518
+
1519
+ ## Migrating `UI.ListWidget.ListWidget`
1520
+
1521
+ Replace the legacy imperative `UI.ListWidget.ListWidget` (which rendered inline editing controls and required custom delegates) with the modern `<devtools-list>` component.
1522
+
1523
+ ### Guidelines
1524
+ 1. **Dialogue Modals for Complex Editors:** If the editor contains multiple input fields (e.g., location details, headers), show the editor in a modal card dialog powered by `UI.Dialog.Dialog`.
1525
+ 2. **Inline Prompts for Trivial Editors:** If the editor is trivial (i.e., a single text input, such as URL pattern blocking rules), handle editing inline using a `<devtools-prompt>` component. In this case, clicking the item focuses the prompt and toggles `editing` mode inline, dispatching `@commit` and `@cancel` events to update the state. See `RequestConditionsDrawer` for a reference implementation.
1526
+ 3. **Conditionally render list:** Use `<devtools-list>` and conditionally render it inside the card only if the list has elements (`input.locations.length > 0`). This ensures empty state layout rules are correctly applied and prevents extra spacing/gaps.
1527
+ 4. **Declarative dialog views:** Define the edit/add modal content in a separate, customizable view function (e.g. `DEFAULT_DIALOG_VIEW`). Instantiating `UI.Dialog.Dialog` in the class is fine, but its content rendering should be driven declaratively by the dialog view function.
1528
+ 5. **Common styles integration:** Ensure `{includeCommonStyles: true}` is passed when rendering elements into the DOM inside test setups to import proper layout variables and global typography styles (e.g. Roboto).
1529
+
1530
+ **Before:**
1531
+ ```typescript
1532
+ class SettingsTab extends UI.Widget.VBox implements UI.ListWidget.Delegate<Location> {
1533
+ private readonly listWidget: UI.ListWidget.ListWidget<Location>;
1534
+ constructor() {
1535
+ super();
1536
+ this.listWidget = new UI.ListWidget.ListWidget(this);
1537
+ this.listWidget.show(this.element);
1538
+ }
1539
+
1540
+ renderItem(item: Location, editable: boolean): Element {
1541
+ const element = document.createElement('div');
1542
+ element.createChild('span').textContent = item.title;
1543
+ return element;
1544
+ }
1545
+
1546
+ beginEdit(item: Location): UI.ListWidget.Editor<Location> {
1547
+ const editor = new UI.ListWidget.Editor<Location>();
1548
+ editor.createInput('title', 'text', 'Title', titleValidator);
1549
+ return editor;
1550
+ }
1551
+
1552
+ commitEdit(item: Location, editor: UI.ListWidget.Editor<Location>, isNew: boolean): void {
1553
+ item.title = editor.control('title').value;
1554
+ this.update();
1555
+ }
1556
+ }
1557
+ ```
1558
+
1559
+ **After:**
1560
+ ```typescript
1561
+ export interface SettingsTabInput {
1562
+ items: Location[];
1563
+ onAddClicked: () => void;
1564
+ onEdit: (index: number) => void;
1565
+ onDelete: (index: number) => void;
1566
+ }
1567
+
1568
+ export const DEFAULT_VIEW: View = (input, _output, target) => {
1569
+ render(html`
1570
+ <devtools-card heading="Locations">
1571
+ ${input.items.length > 0 ? html`
1572
+ <devtools-list
1573
+ .editable=${true}
1574
+ .deletable=${true}
1575
+ @edit=${(e: Lists.List.ItemEditEvent) => input.onEdit(e.detail.index)}
1576
+ @delete=${(e: Lists.List.ItemRemoveEvent) => input.onDelete(e.detail.index)}>
1577
+ ${input.items.map((item, index) => html`
1578
+ <div slot="slot-${index}">
1579
+ <span>${item.title}</span>
1580
+ </div>
1581
+ `)}
1582
+ </devtools-list>
1583
+ ` : nothing}
1584
+ <devtools-button @click=${input.onAddClicked}>Add location</devtools-button>
1585
+ </devtools-card>
1586
+ `, target);
1587
+ };
1588
+
1589
+ export interface SettingsTabDialogInput {
1590
+ editingValues: {title: string};
1591
+ validationErrors: {title: string | null};
1592
+ onTitleInput: (val: string) => void;
1593
+ onCancel: () => void;
1594
+ onSave: () => void;
1595
+ }
1596
+
1597
+ export const DEFAULT_DIALOG_VIEW = (input: SettingsTabDialogInput, target: HTMLElement) => {
1598
+ render(html`
1599
+ <div class="editor-container">
1600
+ <input type="text" .value=${input.editingValues.title} @input=${(e: Event) => input.onTitleInput((e.target as HTMLInputElement).value)}>
1601
+ <devtools-button @click=${input.onCancel}>Cancel</devtools-button>
1602
+ <devtools-button @click=${input.onSave}>Save</devtools-button>
1603
+ </div>
1604
+ `, target);
1605
+ };
1606
+ ```
package/eslint.config.mjs CHANGED
@@ -621,10 +621,9 @@ export default defineConfig([
621
621
  ],
622
622
  '@devtools/l10n-i18nString-call-only-with-uistrings': 'error',
623
623
  '@devtools/l10n-no-i18nString-calls-module-instantiation': 'error',
624
- '@devtools/l10n-no-locked-or-placeholder-only-phrase': 'error',
625
624
  '@devtools/l10n-no-uistrings-export': 'error',
626
625
  '@devtools/l10n-no-unused-message': 'error',
627
- '@devtools/l10n-use-curly-apostrophes': 'error',
626
+ '@devtools/l10n-uistrings-text-style': 'error',
628
627
  },
629
628
  },
630
629
  {
@@ -64,6 +64,7 @@ const AidaLanguageToMarkdown: Record<AidaInferenceLanguage, string> = {
64
64
  export class AidaAbortError extends Error {}
65
65
  export class AidaBlockError extends Error {}
66
66
  export class AidaQuotaError extends Error {}
67
+ export class AidaPayloadTooLargeError extends Error {}
67
68
 
68
69
  interface AiStream {
69
70
  write: (data: string) => Promise<void>;
@@ -195,13 +196,16 @@ export class AidaClient {
195
196
  return;
196
197
  }
197
198
  if ('error' in result && result.error) {
198
- const errorStr = typeof result.error === 'string' ? result.error : '';
199
- const detailStr = typeof result.detail === 'string' ? result.detail : '';
200
- if (errorStr.toLowerCase().includes('quota') || detailStr.toLowerCase().includes('quota')) {
199
+ if (isQuotaError(result.error, result.detail)) {
201
200
  stream.fail(new AidaQuotaError(
202
201
  `Cannot send request: ${result.error}${result.detail ? ` ${result.detail}` : ''}`));
203
202
  return;
204
203
  }
204
+ if (isPayloadTooLargeError(result.error, result.detail)) {
205
+ stream.fail(new AidaPayloadTooLargeError(
206
+ `Cannot send request: ${result.error}${result.detail ? ` ${result.detail}` : ''}`));
207
+ return;
208
+ }
205
209
  stream.fail(new Error(`Cannot send request: ${result.error}${result.detail ? ` ${result.detail}` : ''}`));
206
210
  return;
207
211
  }
@@ -261,9 +265,12 @@ export class AidaClient {
261
265
  thoughtSignature: result.functionCallChunk.functionCall.thoughtSignature,
262
266
  });
263
267
  } else if ('error' in result) {
264
- if (typeof result.error === 'string' && result.error.toLowerCase().includes('quota')) {
268
+ if (isQuotaError(result.error)) {
265
269
  throw new AidaQuotaError(`Server responded: ${JSON.stringify(result)}`);
266
270
  }
271
+ if (isPayloadTooLargeError(result.error)) {
272
+ throw new AidaPayloadTooLargeError(`Server responded: ${JSON.stringify(result)}`);
273
+ }
267
274
  throw new Error(`Server responded: ${JSON.stringify(result)}`);
268
275
  } else {
269
276
  throw new Error(`Unknown chunk result ${JSON.stringify(result)}`);
@@ -519,3 +526,11 @@ export const enum Events {
519
526
  export interface EventTypes {
520
527
  [Events.AIDA_AVAILABILITY_CHANGED]: void;
521
528
  }
529
+
530
+ function isQuotaError(...inputs: Array<string|undefined>): boolean {
531
+ return inputs.some(input => input?.toLowerCase().includes('quota'));
532
+ }
533
+
534
+ function isPayloadTooLargeError(...inputs: Array<string|undefined>): boolean {
535
+ return inputs.some(input => input?.toLowerCase().includes('payload size exceeds the limit'));
536
+ }
@@ -37,7 +37,7 @@ import {streamWrite as resourceLoaderStreamWrite} from './ResourceLoader.js';
37
37
 
38
38
  const UIStrings = {
39
39
  /**
40
- * @description Document title in Inspector Frontend Host of the DevTools window
40
+ * @description Document title in Inspector Frontend Host of the DevTools window.
41
41
  * @example {example.com} PH1
42
42
  */
43
43
  devtoolsS: 'DevTools - {PH1}',
@@ -12,59 +12,59 @@ import type {LoadNetworkResourceResult} from './InspectorFrontendHostAPI.js';
12
12
 
13
13
  const UIStrings = {
14
14
  /**
15
- * @description Name of an error category used in error messages
15
+ * @description Name of an error category used in error messages.
16
16
  */
17
17
  systemError: 'System error',
18
18
  /**
19
- * @description Name of an error category used in error messages
19
+ * @description Name of an error category used in error messages.
20
20
  */
21
21
  connectionError: 'Connection error',
22
22
  /**
23
- * @description Name of an error category used in error messages
23
+ * @description Name of an error category used in error messages.
24
24
  */
25
25
  certificateError: 'Certificate error',
26
26
  /**
27
- * @description Name of an error category used in error messages
27
+ * @description Name of an error category used in error messages.
28
28
  */
29
29
  httpError: 'HTTP error',
30
30
  /**
31
- * @description Name of an error category used in error messages
31
+ * @description Name of an error category used in error messages.
32
32
  */
33
33
  cacheError: 'Cache error',
34
34
  /**
35
- * @description Name of an error category used in error messages
35
+ * @description Name of an error category used in error messages.
36
36
  */
37
- signedExchangeError: 'Signed Exchange error',
37
+ signedExchangeError: '`Signed Exchange` error',
38
38
  /**
39
- * @description Name of an error category used in error messages
39
+ * @description Name of an error category used in error messages.
40
40
  */
41
41
  ftpError: 'FTP error',
42
42
  /**
43
- * @description Name of an error category used in error messages
43
+ * @description Name of an error category used in error messages.
44
44
  */
45
45
  certificateManagerError: 'Certificate manager error',
46
46
  /**
47
- * @description Name of an error category used in error messages
47
+ * @description Name of an error category used in error messages.
48
48
  */
49
49
  dnsResolverError: 'DNS resolver error',
50
50
  /**
51
- * @description Name of an error category used in error messages
51
+ * @description Name of an error category used in error messages.
52
52
  */
53
53
  unknownError: 'Unknown error',
54
54
  /**
55
- * @description Phrase used in error messages that carry a network error name
55
+ * @description Phrase used in error messages that carry a network error name.
56
56
  * @example {404} PH1
57
57
  * @example {net::ERR_INSUFFICIENT_RESOURCES} PH2
58
58
  */
59
59
  httpErrorStatusCodeSS: 'HTTP error: status code {PH1}, {PH2}',
60
60
  /**
61
- * @description Name of an error category used in error messages
61
+ * @description Name of an error category used in error messages.
62
62
  */
63
63
  invalidUrl: 'Invalid URL',
64
64
  /**
65
- * @description Name of an error category used in error messages
65
+ * @description Name of an error category used in error messages.
66
66
  */
67
- decodingDataUrlFailed: 'Decoding Data URL failed',
67
+ decodingDataUrlFailed: 'Decoding data URL failed',
68
68
  } as const;
69
69
  const str_ = i18n.i18n.registerUIStrings('core/host/ResourceLoader.ts', UIStrings);
70
70
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -886,6 +886,81 @@ export class CSSMatchedStyles {
886
886
  return new Set(this.#customHighlightPseudoDOMCascades.keys());
887
887
  }
888
888
 
889
+ /**
890
+ * Looks for a rule with the same selector chain as a specific parent rule of the current one.
891
+ * This finds a rule with the same guaranteed specificity, not necessarily THE parent rule,
892
+ * as the same selector string can be used on multiple rules in the same scope, in the same
893
+ * or different stylesheet.
894
+ *
895
+ * @param rule The (nested) rule whose parent rule selector should be matched
896
+ * @param nestingIndex Nesting depth of the parent selector to be matched, with 0 meaning direct parent, 1 grandparent rule and so on
897
+ * @returns A rule with the same selector chain and specificity as selected parent rule, if found. `null` otherwise
898
+ */
899
+ findParentRule(rule: CSSStyleRule, nestingIndex: number): CSSStyleRule|null {
900
+ const selectorText = rule.nestingSelectors?.[nestingIndex];
901
+ if (!selectorText) {
902
+ return null;
903
+ }
904
+ const nestingSelectors = rule.nestingSelectors?.slice(nestingIndex + 1) ?? [];
905
+
906
+ const matchCascade = (cascade: DOMInheritanceCascade): CSSStyleRule|null => {
907
+ for (const style of cascade.styles()) {
908
+ if (this.isInherited(style)) {
909
+ continue;
910
+ }
911
+ const parentRule = style.parentRule;
912
+ if (!(parentRule instanceof CSSStyleRule)) {
913
+ continue;
914
+ }
915
+ if (parentRule.selectorText() !== selectorText) {
916
+ continue;
917
+ }
918
+ const ruleNestingSelectors = parentRule.nestingSelectors ?? [];
919
+ if (ruleNestingSelectors.length !== nestingSelectors.length) {
920
+ continue;
921
+ }
922
+ let matchesChain = true;
923
+ for (let i = 0; i < nestingSelectors.length; i++) {
924
+ if (ruleNestingSelectors[i] !== nestingSelectors[i]) {
925
+ matchesChain = false;
926
+ break;
927
+ }
928
+ }
929
+ if (matchesChain) {
930
+ return parentRule;
931
+ }
932
+ }
933
+ return null;
934
+ };
935
+
936
+ if (this.#mainDOMCascade) {
937
+ const match = matchCascade(this.#mainDOMCascade);
938
+ if (match) {
939
+ return match;
940
+ }
941
+ }
942
+
943
+ if (this.#pseudoDOMCascades) {
944
+ for (const cascade of this.#pseudoDOMCascades.values()) {
945
+ const match = matchCascade(cascade);
946
+ if (match) {
947
+ return match;
948
+ }
949
+ }
950
+ }
951
+
952
+ if (this.#customHighlightPseudoDOMCascades) {
953
+ for (const cascade of this.#customHighlightPseudoDOMCascades.values()) {
954
+ const match = matchCascade(cascade);
955
+ if (match) {
956
+ return match;
957
+ }
958
+ }
959
+ }
960
+
961
+ return null;
962
+ }
963
+
889
964
  nodeForStyle(style: CSSStyleDeclaration): DOMNode|null {
890
965
  return this.#addedStyles.get(style) || this.#nodeForStyle.get(style) || null;
891
966
  }
@@ -33,7 +33,7 @@ const UIStrings = {
33
33
  /**
34
34
  * @description Tooltip to explain why a cookie was blocked
35
35
  */
36
- notOnPath: 'This cookie was blocked because its path was not an exact match for or a superdirectory of the request url’s path.',
36
+ notOnPath: 'This cookie was blocked because its path was not an exact match for or a superdirectory of the request URL’s path.',
37
37
  /**
38
38
  * @description Tooltip to explain why a cookie was blocked
39
39
  */
@@ -139,7 +139,7 @@ const UIStrings = {
139
139
  /**
140
140
  * @description Tooltip to explain why an attempt to set a cookie via a `Set-Cookie` HTTP header on a request's response was blocked.
141
141
  */
142
- blockedReasonInvalidDomain: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because its Domain attribute was invalid with regards to the current host url.',
142
+ blockedReasonInvalidDomain: 'This attempt to set a cookie via a `Set-Cookie` header was blocked because its Domain attribute was invalid with regards to the current host URL.',
143
143
  /**
144
144
  * @description Tooltip to explain why an attempt to set a cookie via a `Set-Cookie` HTTP header on a request's response was blocked.
145
145
  */
@@ -152,7 +152,7 @@ const UIStrings = {
152
152
  * @description Text in Network Manager
153
153
  * @example {https://example.com} PH1
154
154
  */
155
- setcookieHeaderIsIgnoredIn: 'Set-Cookie header is ignored in response from url: {PH1}. The combined size of the name and value must be less than or equal to 4096 characters.',
155
+ setcookieHeaderIsIgnoredIn: 'Set-Cookie header is ignored in response from URL: {PH1}. The combined size of the name and value must be less than or equal to 4096 characters.',
156
156
  /**
157
157
  * @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
158
158
  */
@@ -172,7 +172,7 @@ const UIStrings = {
172
172
  /**
173
173
  * @description Tooltip to explain why the cookie should have been blocked by third-party cookie phaseout but is exempted.
174
174
  */
175
- exemptionReasonScheme: 'This cookie is allowed by the top-level url scheme',
175
+ exemptionReasonScheme: 'This cookie is allowed by the top-level URL scheme',
176
176
  } as const;
177
177
  // clang-format on
178
178
 
@@ -460,7 +460,8 @@ export class MainImpl {
460
460
  targetManager.addEventListener(
461
461
  SDK.TargetManager.Events.SUSPEND_STATE_CHANGED, this.#onSuspendStateChanged.bind(this));
462
462
 
463
- new Bindings.PresentationConsoleMessageHelper.PresentationConsoleMessageManager(targetManager);
463
+ new Bindings.PresentationConsoleMessageHelper.PresentationConsoleMessageManager(targetManager,
464
+ this.#universe.workspace);
464
465
  targetManager.setScopeTarget(targetManager.primaryPageTarget());
465
466
  UI.Context.Context.instance().addFlavorChangeListener(SDK.Target.Target, ({data}) => {
466
467
  const outermostTarget = data?.outermostTarget();
@@ -192,11 +192,6 @@ const UIStrings = {
192
192
  * of saving settings to their Google account.
193
193
  */
194
194
  saveSettings: 'Save `DevTools` settings to your `Google` account',
195
- /**
196
- * @description Label for a checkbox in the settings UI. Allows developers to opt-in/opt-out
197
- * of receiving Google Developer Program (GDP) badges based on their activity in Chrome DevTools.
198
- */
199
- earnBadges: 'Earn badges',
200
195
  /**
201
196
  * @description A command available in the command menu to perform searches, for example in the
202
197
  * elements panel, as user types, rather than only when they press Enter.
@@ -795,16 +790,6 @@ Common.Settings.registerSettingExtension({
795
790
  reloadRequired: true,
796
791
  });
797
792
 
798
- Common.Settings.registerSettingExtension({
799
- category: Common.Settings.SettingCategory.ACCOUNT,
800
- settingName: 'receive-gdp-badges',
801
- settingType: Common.Settings.SettingType.BOOLEAN,
802
- storageType: Common.Settings.SettingStorageType.SYNCED,
803
- title: i18nLazyString(UIStrings.earnBadges),
804
- defaultValue: false,
805
- reloadRequired: true,
806
- });
807
-
808
793
  Common.Settings.registerSettingExtension({
809
794
  storageType: Common.Settings.SettingStorageType.SYNCED,
810
795
  settingName: 'user-shortcuts',
@@ -18,6 +18,7 @@ import '../../panels/settings/settings-meta.js';
18
18
  import '../../panels/protocol_monitor/protocol_monitor-meta.js';
19
19
  import '../../models/persistence/persistence-meta.js';
20
20
  import '../../models/logs/logs-meta.js';
21
+ import '../../models/badges/badges-meta.js';
21
22
  import '../main/main-meta.js';
22
23
  import '../../ui/legacy/components/perf_ui/perf_ui-meta.js';
23
24
  import '../../ui/legacy/components/quick_open/quick_open-meta.js';
@@ -13,6 +13,7 @@ import '../../models/workspace/workspace-meta.js';
13
13
  import '../../Images/Images.js';
14
14
  import '../../models/logs/logs-meta.js';
15
15
  import '../../models/persistence/persistence-meta.js';
16
+ import '../../models/badges/badges-meta.js';
16
17
  import '../../panels/browser_debugger/browser_debugger-meta.js';
17
18
  import '../../panels/developer_resources/developer_resources-meta.js';
18
19
  // panels/timeline depends on mobile_throttling for settings UI
@@ -173,6 +173,7 @@ export class Universe {
173
173
  workspace,
174
174
  persistence,
175
175
  networkPersistenceManager,
176
+ settings,
176
177
  );
177
178
  context.set(WorkspaceDiff.WorkspaceDiff.WorkspaceDiffImpl, workspaceDiff);
178
179
 
@@ -38,6 +38,7 @@ export const enum ErrorType {
38
38
  BLOCK = 'block',
39
39
  CROSS_ORIGIN = 'cross-origin',
40
40
  QUOTA = 'quota',
41
+ PAYLOAD_TOO_LARGE = 'payload-too-large',
41
42
  }
42
43
 
43
44
  export const enum MultimodalInputType {
@@ -806,6 +807,9 @@ export abstract class AiAgent<T> {
806
807
  } else if (err instanceof Host.AidaClient.AidaQuotaError ||
807
808
  (err instanceof Error && err.message.toLowerCase().includes('quota'))) {
808
809
  error = ErrorType.QUOTA;
810
+ } else if (err instanceof Host.AidaClient.AidaPayloadTooLargeError ||
811
+ (err instanceof Error && /payload size exceeds the limit/i.test(err.message))) {
812
+ error = ErrorType.PAYLOAD_TOO_LARGE;
809
813
  }
810
814
  yield this.#createErrorResponse(error);
811
815
 
@@ -1096,7 +1100,11 @@ export abstract class AiAgent<T> {
1096
1100
  request: structuredClone(request),
1097
1101
  aidaResponse,
1098
1102
  });
1099
- localStorage.setItem('aiAssistanceStructuredLog', JSON.stringify(this.#structuredLog));
1103
+ try {
1104
+ localStorage.setItem('aiAssistanceStructuredLog', JSON.stringify(this.#structuredLog));
1105
+ } catch (err) {
1106
+ console.warn('Failed to write to local storage "aiAssistanceStructuredLog":', err);
1107
+ }
1100
1108
  }
1101
1109
  }
1102
1110
 
@@ -3,6 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as Bindings from '../../bindings/bindings.js';
6
+ import * as Logs from '../../logs/logs.js';
6
7
  import * as NetworkTimeCalculator from '../../network_time_calculator/network_time_calculator.js';
7
8
  import type * as Workspace from '../../workspace/workspace.js';
8
9
 
@@ -51,12 +52,18 @@ export class FileFormatter {
51
52
 
52
53
  #file: Workspace.UISourceCode.UISourceCode;
53
54
  #debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding;
54
- constructor(file: Workspace.UISourceCode.UISourceCode,
55
- debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding =
56
- Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance()) {
55
+ #networkLog: Logs.NetworkLog.NetworkLog;
56
+ constructor(
57
+ file: Workspace.UISourceCode.UISourceCode,
58
+ debuggerWorkspaceBinding: Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding =
59
+ Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(),
60
+ networkLog: Logs.NetworkLog.NetworkLog = Logs.NetworkLog.NetworkLog.instance(),
61
+ ) {
57
62
  this.#file = file;
58
63
  this.#debuggerWorkspaceBinding = debuggerWorkspaceBinding;
64
+ this.#networkLog = networkLog;
59
65
  }
66
+
60
67
  formatFile(): string {
61
68
  const sourceMapDetails = FileFormatter.formatSourceMapDetails(this.#file, this.#debuggerWorkspaceBinding);
62
69
  const lines = [
@@ -69,7 +76,7 @@ export class FileFormatter {
69
76
  const calculator = new NetworkTimeCalculator.NetworkTransferTimeCalculator();
70
77
  calculator.updateBoundaries(resource.request);
71
78
  lines.push(`Request initiator chain:
72
- ${new NetworkRequestFormatter(resource.request, calculator).formatRequestInitiatorChain()}`);
79
+ ${new NetworkRequestFormatter(resource.request, calculator, this.#networkLog).formatRequestInitiatorChain()}`);
73
80
  }
74
81
  lines.push(`File content:
75
82
  ${this.#formatFileContent()}`);
@@ -131,10 +131,16 @@ export class NetworkRequestFormatter {
131
131
  return lines.length > 0 ? `${lines.join('\n')}\n` : '';
132
132
  }
133
133
 
134
+ readonly #networkLog: Logs.NetworkLog.NetworkLog;
135
+
134
136
  constructor(
135
- request: SDK.NetworkRequest.NetworkRequest, calculator: NetworkTimeCalculator.NetworkTransferTimeCalculator) {
137
+ request: SDK.NetworkRequest.NetworkRequest,
138
+ calculator: NetworkTimeCalculator.NetworkTransferTimeCalculator,
139
+ networkLog: Logs.NetworkLog.NetworkLog = Logs.NetworkLog.NetworkLog.instance(),
140
+ ) {
136
141
  this.#request = request;
137
142
  this.#calculator = calculator;
143
+ this.#networkLog = networkLog;
138
144
  }
139
145
 
140
146
  formatRequestHeaders(): string {
@@ -199,7 +205,7 @@ Request initiator chain:\n${this.formatRequestInitiatorChain()}`;
199
205
  const allowedOrigin = Common.ParsedURL.ParsedURL.extractOrigin(this.#request.url());
200
206
  let initiatorChain = '';
201
207
  let lineStart = '- URL: ';
202
- const graph = Logs.NetworkLog.NetworkLog.instance().initiatorGraphForRequest(this.#request);
208
+ const graph = this.#networkLog.initiatorGraphForRequest(this.#request);
203
209
 
204
210
  for (const initiator of Array.from(graph.initiators).reverse()) {
205
211
  initiatorChain = initiatorChain + lineStart +
@@ -33,6 +33,7 @@ export class AICallTree {
33
33
  public selectedNode: Trace.Extras.TraceTree.Node|null,
34
34
  public rootNode: Trace.Extras.TraceTree.TopDownRootNode,
35
35
  public parsedTrace: Trace.TraceModel.ParsedTrace,
36
+ public workspace: Workspace.Workspace.WorkspaceImpl = Workspace.Workspace.WorkspaceImpl.instance(),
36
37
  ) {
37
38
  }
38
39
 
@@ -336,8 +337,7 @@ export class AICallTree {
336
337
  const selfTimeStr = roundToTenths(node.selfTime);
337
338
 
338
339
  // 6. URL Index
339
- const location = SourceMapsResolver.SourceMapsResolver.codeLocationForEntry(
340
- parsedTrace, event, Workspace.Workspace.WorkspaceImpl.instance());
340
+ const location = SourceMapsResolver.SourceMapsResolver.codeLocationForEntry(parsedTrace, event, this.workspace);
341
341
  const url = location?.url;
342
342
  let urlIndexStr = '';
343
343
  if (url) {
@@ -38,6 +38,12 @@ export class GetNetworkRequestDetailsTool implements
38
38
  readonly description =
39
39
  'Retrieves the full headers, timing, status, and body details of a specific network request by ID.';
40
40
 
41
+ readonly #networkLog?: Logs.NetworkLog.NetworkLog;
42
+
43
+ constructor(networkLog?: Logs.NetworkLog.NetworkLog) {
44
+ this.#networkLog = networkLog;
45
+ }
46
+
41
47
  readonly parameters: Host.AidaClient.FunctionObjectParam<keyof GetNetworkRequestDetailsArgs> = {
42
48
  type: Host.AidaClient.ParametersTypes.OBJECT,
43
49
  description: 'Arguments for retrieving detailed information about a specific network request.',
@@ -81,7 +87,8 @@ export class GetNetworkRequestDetailsTool implements
81
87
  };
82
88
  }
83
89
 
84
- const request = Logs.NetworkLog.NetworkLog.instance().requests().find(req => {
90
+ const networkLog = this.#networkLog ?? Logs.NetworkLog.NetworkLog.instance();
91
+ const request = networkLog.requests().find(req => {
85
92
  if (req.requestId() !== args.id) {
86
93
  return false;
87
94
  }
@@ -102,7 +109,7 @@ export class GetNetworkRequestDetailsTool implements
102
109
  }
103
110
 
104
111
  const calculator = new NetworkTimeCalculator.NetworkTransferTimeCalculator();
105
- const formatter = new NetworkRequestFormatter(request, calculator);
112
+ const formatter = new NetworkRequestFormatter(request, calculator, networkLog);
106
113
  const formattedDetails = await formatter.formatNetworkRequest();
107
114
 
108
115
  return {