chrome-devtools-frontend 1.0.1530564 → 1.0.1532228

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 (59) hide show
  1. package/AUTHORS +2 -0
  2. package/front_end/core/protocol_client/InspectorBackend.ts +15 -6
  3. package/front_end/core/protocol_client/protocol_client.ts +0 -10
  4. package/front_end/core/sdk/NetworkManager.ts +155 -41
  5. package/front_end/core/sdk/SourceMap.ts +6 -1
  6. package/front_end/core/sdk/SourceMapScopesInfo.ts +73 -7
  7. package/front_end/generated/ARIAProperties.js +1301 -174
  8. package/front_end/generated/Deprecation.ts +7 -0
  9. package/front_end/generated/InspectorBackendCommands.js +1 -0
  10. package/front_end/generated/protocol-mapping.d.ts +4 -0
  11. package/front_end/generated/protocol-proxy-api.d.ts +5 -0
  12. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.snapshot.txt +1 -1
  13. package/front_end/models/ai_assistance/data_formatters/PerformanceInsightFormatter.ts +1 -1
  14. package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatterBounds.snapshot.txt +4 -0
  15. package/front_end/models/bindings/CompilerScriptMapping.ts +16 -14
  16. package/front_end/models/issues_manager/AttributionReportingIssue.ts +4 -3
  17. package/front_end/models/issues_manager/BounceTrackingIssue.ts +4 -3
  18. package/front_end/models/issues_manager/ClientHintIssue.ts +4 -3
  19. package/front_end/models/issues_manager/ContentSecurityPolicyIssue.ts +4 -3
  20. package/front_end/models/issues_manager/CookieDeprecationMetadataIssue.ts +5 -3
  21. package/front_end/models/issues_manager/CookieIssue.ts +4 -4
  22. package/front_end/models/issues_manager/CorsIssue.ts +3 -3
  23. package/front_end/models/issues_manager/CrossOriginEmbedderPolicyIssue.ts +2 -1
  24. package/front_end/models/issues_manager/DeprecationIssue.ts +4 -3
  25. package/front_end/models/issues_manager/ElementAccessibilityIssue.ts +4 -3
  26. package/front_end/models/issues_manager/FederatedAuthRequestIssue.ts +4 -3
  27. package/front_end/models/issues_manager/GenericIssue.ts +3 -3
  28. package/front_end/models/issues_manager/HeavyAdIssue.ts +3 -3
  29. package/front_end/models/issues_manager/IssuesManager.ts +3 -3
  30. package/front_end/models/issues_manager/LowTextContrastIssue.ts +5 -3
  31. package/front_end/models/issues_manager/MixedContentIssue.ts +4 -3
  32. package/front_end/models/issues_manager/PartitioningBlobURLIssue.ts +5 -3
  33. package/front_end/models/issues_manager/PropertyRuleIssue.ts +4 -3
  34. package/front_end/models/issues_manager/QuirksModeIssue.ts +4 -3
  35. package/front_end/models/issues_manager/SRIMessageSignatureIssue.ts +5 -3
  36. package/front_end/models/issues_manager/SharedArrayBufferIssue.ts +5 -3
  37. package/front_end/models/issues_manager/SharedDictionaryIssue.ts +5 -3
  38. package/front_end/models/issues_manager/StylesheetLoadingIssue.ts +5 -3
  39. package/front_end/models/issues_manager/UnencodedDigestIssue.ts +5 -3
  40. package/front_end/models/issues_manager/UserReidentificationIssue.ts +4 -3
  41. package/front_end/models/javascript_metadata/NativeFunctions.js +8 -2
  42. package/front_end/models/trace/insights/ForcedReflow.ts +1 -1
  43. package/front_end/panels/ai_assistance/AiAssistancePanel.ts +17 -4
  44. package/front_end/panels/console/ConsoleInsightTeaser.ts +111 -60
  45. package/front_end/panels/console/ConsoleSidebar.ts +3 -3
  46. package/front_end/panels/network/NetworkLogView.ts +135 -33
  47. package/front_end/panels/network/{BlockedURLsPane.ts → RequestConditionsDrawer.ts} +64 -23
  48. package/front_end/panels/network/network-meta.ts +33 -9
  49. package/front_end/panels/network/network.ts +3 -3
  50. package/front_end/panels/network/{blockedURLsPane.css → requestConditionsDrawer.css} +5 -0
  51. package/front_end/panels/recorder/components/stepView.css +2 -2
  52. package/front_end/panels/sources/SourcesSearchScope.ts +5 -0
  53. package/front_end/panels/sources/sources-meta.ts +1 -0
  54. package/front_end/panels/timeline/components/insights/ForcedReflow.ts +2 -2
  55. package/front_end/third_party/chromium/README.chromium +1 -1
  56. package/front_end/ui/legacy/components/cookie_table/CookiesTable.ts +31 -5
  57. package/front_end/ui/legacy/components/utils/TargetDetachedDialog.ts +3 -0
  58. package/front_end/ui/visual_logging/KnownContextValues.ts +5 -0
  59. package/package.json +1 -1
@@ -15,7 +15,8 @@ export const lateImportStylesheetLoadingCode = [
15
15
 
16
16
  export class StylesheetLoadingIssue extends Issue {
17
17
  #issueDetails: Protocol.Audits.StylesheetLoadingIssueDetails;
18
- constructor(issueDetails: Protocol.Audits.StylesheetLoadingIssueDetails, issuesModel: SDK.IssuesModel.IssuesModel) {
18
+ constructor(
19
+ issueDetails: Protocol.Audits.StylesheetLoadingIssueDetails, issuesModel: SDK.IssuesModel.IssuesModel|null) {
19
20
  const code =
20
21
  `${Protocol.Audits.InspectorIssueCode.StylesheetLoadingIssue}::${issueDetails.styleSheetLoadingIssueReason}`;
21
22
  super(code, issuesModel);
@@ -67,8 +68,9 @@ export class StylesheetLoadingIssue extends Issue {
67
68
  return IssueKind.PAGE_ERROR;
68
69
  }
69
70
 
70
- static fromInspectorIssue(issueModel: SDK.IssuesModel.IssuesModel, inspectorIssue: Protocol.Audits.InspectorIssue):
71
- StylesheetLoadingIssue[] {
71
+ static fromInspectorIssue(
72
+ issueModel: SDK.IssuesModel.IssuesModel|null,
73
+ inspectorIssue: Protocol.Audits.InspectorIssue): StylesheetLoadingIssue[] {
72
74
  const stylesheetLoadingDetails = inspectorIssue.details.stylesheetLoadingIssueDetails;
73
75
  if (!stylesheetLoadingDetails) {
74
76
  console.warn('Stylesheet loading issue without details received');
@@ -29,7 +29,8 @@ const i18nLazyString = i18n.i18n.getLazilyComputedLocalizedString.bind(undefined
29
29
  export class UnencodedDigestIssue extends Issue<string> {
30
30
  readonly #issueDetails: Protocol.Audits.UnencodedDigestIssueDetails;
31
31
 
32
- constructor(issueDetails: Protocol.Audits.UnencodedDigestIssueDetails, issuesModel: SDK.IssuesModel.IssuesModel) {
32
+ constructor(
33
+ issueDetails: Protocol.Audits.UnencodedDigestIssueDetails, issuesModel: SDK.IssuesModel.IssuesModel|null) {
33
34
  super(
34
35
  {
35
36
  code: `${Protocol.Audits.InspectorIssueCode.UnencodedDigestIssue}::${issueDetails.error}`,
@@ -76,8 +77,9 @@ export class UnencodedDigestIssue extends Issue<string> {
76
77
  return this.details().request ? [this.details().request] : [];
77
78
  }
78
79
 
79
- static fromInspectorIssue(issuesModel: SDK.IssuesModel.IssuesModel, inspectorIssue: Protocol.Audits.InspectorIssue):
80
- UnencodedDigestIssue[] {
80
+ static fromInspectorIssue(
81
+ issuesModel: SDK.IssuesModel.IssuesModel|null,
82
+ inspectorIssue: Protocol.Audits.InspectorIssue): UnencodedDigestIssue[] {
81
83
  const details = inspectorIssue.details.unencodedDigestIssueDetails;
82
84
  if (!details) {
83
85
  console.warn('Unencoded-Digest issue without details received.');
@@ -16,7 +16,7 @@ export class UserReidentificationIssue extends Issue {
16
16
  #issueDetails: Protocol.Audits.UserReidentificationIssueDetails;
17
17
 
18
18
  constructor(
19
- issueDetails: Protocol.Audits.UserReidentificationIssueDetails, issuesModel: SDK.IssuesModel.IssuesModel) {
19
+ issueDetails: Protocol.Audits.UserReidentificationIssueDetails, issuesModel: SDK.IssuesModel.IssuesModel|null) {
20
20
  super('UserReidentificationIssue', issuesModel);
21
21
  this.#issueDetails = issueDetails;
22
22
  }
@@ -46,8 +46,9 @@ export class UserReidentificationIssue extends Issue {
46
46
  return IssueKind.IMPROVEMENT;
47
47
  }
48
48
 
49
- static fromInspectorIssue(issuesModel: SDK.IssuesModel.IssuesModel, inspectorIssue: Protocol.Audits.InspectorIssue):
50
- UserReidentificationIssue[] {
49
+ static fromInspectorIssue(
50
+ issuesModel: SDK.IssuesModel.IssuesModel|null,
51
+ inspectorIssue: Protocol.Audits.InspectorIssue): UserReidentificationIssue[] {
51
52
  const userReidentificationIssueDetails = inspectorIssue.details.userReidentificationIssueDetails;
52
53
  if (!userReidentificationIssueDetails) {
53
54
  console.warn('User Reidentification issue without details received.');
@@ -6041,7 +6041,13 @@ export const NativeFunctions = [
6041
6041
  },
6042
6042
  {
6043
6043
  name: "waitUntil",
6044
- signatures: [["f"]]
6044
+ signatures: [["f"]],
6045
+ receivers: ["ExtendableEvent"]
6046
+ },
6047
+ {
6048
+ name: "waitUntil",
6049
+ signatures: [["promise"]],
6050
+ receivers: ["ViewTransition"]
6045
6051
  },
6046
6052
  {
6047
6053
  name: "respondWith",
@@ -8237,7 +8243,7 @@ export const NativeFunctions = [
8237
8243
  },
8238
8244
  {
8239
8245
  name: "softmax",
8240
- signatures: [["input","?options"],["input","axis","?options"]]
8246
+ signatures: [["input","axis","?options"]]
8241
8247
  },
8242
8248
  {
8243
8249
  name: "softplus",
@@ -31,7 +31,7 @@ export const UIStrings = {
31
31
  /**
32
32
  * @description Title of a list to provide related stack trace data
33
33
  */
34
- relatedStackTrace: 'Stack trace',
34
+ reflowCallFrames: 'Call frames that trigger reflow',
35
35
  /**
36
36
  * @description Text to describe the top time-consuming function call
37
37
  */
@@ -1091,7 +1091,16 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1091
1091
  // Node picker is using linkifier.
1092
1092
  }
1093
1093
 
1094
- handleAction(actionId: string, opts?: Record<string, unknown>): void {
1094
+ #canExecuteQuery(): boolean {
1095
+ const isBrandedBuild = Boolean(Root.Runtime.hostConfig.aidaAvailability?.enabled);
1096
+ const isBlockedByAge = Boolean(Root.Runtime.hostConfig.aidaAvailability?.blockedByAge);
1097
+ const isAidaAvailable = Boolean(this.#aidaAvailability === Host.AidaClient.AidaAccessPreconditions.AVAILABLE);
1098
+ const isUserOptedIn = Boolean(this.#aiAssistanceEnabledSetting?.getIfNotDisabled());
1099
+
1100
+ return isBrandedBuild && isAidaAvailable && isUserOptedIn && !isBlockedByAge;
1101
+ }
1102
+
1103
+ async handleAction(actionId: string, opts?: Record<string, unknown>): Promise<void> {
1095
1104
  if (this.#isLoading && !opts?.['prompt']) {
1096
1105
  // If running some queries already, and this action doesn't contain a predefined prompt, focus the input with the abort
1097
1106
  // button and do nothing.
@@ -1160,13 +1169,17 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1160
1169
  this.#updateConversationState({agent});
1161
1170
  const predefinedPrompt = opts?.['prompt'];
1162
1171
  if (predefinedPrompt && typeof predefinedPrompt === 'string') {
1172
+ if (!this.#canExecuteQuery()) {
1173
+ return;
1174
+ }
1175
+
1163
1176
  this.#imageInput = undefined;
1164
1177
  this.#isTextInputEmpty = true;
1165
1178
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceQuerySubmitted);
1166
1179
  if (this.#blockedByCrossOrigin) {
1167
1180
  this.#handleNewChatRequest();
1168
1181
  }
1169
- void this.#startConversation(predefinedPrompt);
1182
+ await this.#startConversation(predefinedPrompt);
1170
1183
  } else {
1171
1184
  this.#viewOutput.chatView?.focusTextInput();
1172
1185
  }
@@ -1187,7 +1200,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
1187
1200
 
1188
1201
  contextMenu.defaultSection().appendCheckboxItem(title, () => {
1189
1202
  void this.#openHistoricConversation(conversation);
1190
- }, {checked: (this.#conversation === conversation)});
1203
+ }, {checked: (this.#conversation === conversation), jslogContext: 'freestyler.history-item'});
1191
1204
  }
1192
1205
 
1193
1206
  const historyEmpty = contextMenu.defaultSection().items.length === 0;
@@ -1671,7 +1684,7 @@ export class ActionDelegate implements UI.ActionRegistration.ActionDelegate {
1671
1684
  }
1672
1685
 
1673
1686
  const widget = (await view.widget()) as AiAssistancePanel;
1674
- widget.handleAction(actionId, opts);
1687
+ void widget.handleAction(actionId, opts);
1675
1688
  })();
1676
1689
  return true;
1677
1690
  }
@@ -61,6 +61,10 @@ const UIStringsNotTranslate = {
61
61
  * @description Header text during loading state while an AI summary is being generated
62
62
  */
63
63
  summarizing: 'Summarizing…',
64
+ /**
65
+ * @description Header text during longer lasting loading state while an AI summary is being generated
66
+ */
67
+ summarizingTakesABitLonger: 'Summarizing takes a bit longer…',
64
68
  /**
65
69
  * @description Label for an animation shown while an AI response is being generated
66
70
  */
@@ -77,6 +81,10 @@ const UIStringsNotTranslate = {
77
81
  * @description Aria-label for an infor-button triggering a tooltip with more info about data usage
78
82
  */
79
83
  learnDataUsage: 'Learn more about how your data is used',
84
+ /**
85
+ * @description Header text if there was an error during AI summary generation
86
+ */
87
+ summaryNotAvailable: 'Summary not available',
80
88
  } as const;
81
89
 
82
90
  const lockedString = i18n.i18n.lockedString;
@@ -84,6 +92,7 @@ const lockedString = i18n.i18n.lockedString;
84
92
  const CODE_SNIPPET_WARNING_URL = 'https://support.google.com/legal/answer/13505487';
85
93
  const DATA_USAGE_URL = 'https://developer.chrome.com/docs/devtools/ai-assistance/get-started#data-use';
86
94
  const EXPLAIN_TEASER_ACTION_ID = 'explain.console-message.teaser';
95
+ const SLOW_GENERATION_CUTOFF_MILLISECONDS = 3500;
87
96
 
88
97
  interface ViewInput {
89
98
  onTellMeMoreClick: (event: Event) => void;
@@ -95,6 +104,8 @@ interface ViewInput {
95
104
  isInactive: boolean;
96
105
  dontShowChanged: (e: Event) => void;
97
106
  hasTellMeMoreButton: boolean;
107
+ isSlowGeneration: boolean;
108
+ isError: boolean;
98
109
  }
99
110
 
100
111
  export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLElement): void => {
@@ -104,6 +115,51 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLE
104
115
  }
105
116
 
106
117
  const showPlaceholder = !Boolean(input.mainText);
118
+ const renderFooter = (): Lit.LitTemplate => {
119
+ // clang-format off
120
+ return html`
121
+ <div class="tooltip-footer">
122
+ ${input.hasTellMeMoreButton ? html`
123
+ <devtools-button
124
+ title=${lockedString(UIStringsNotTranslate.tellMeMore)}
125
+ .jslogContext=${'insights-teaser-tell-me-more'},
126
+ .variant=${Buttons.Button.Variant.PRIMARY}
127
+ @click=${input.onTellMeMoreClick}
128
+ >
129
+ <devtools-icon class="lightbulb-icon" name="lightbulb-spark"></devtools-icon>
130
+ ${lockedString(UIStringsNotTranslate.tellMeMore)}
131
+ </devtools-button>
132
+ ` : Lit.nothing}
133
+ ${showPlaceholder ? Lit.nothing : html`
134
+ <devtools-button
135
+ .iconName=${'info'}
136
+ .variant=${Buttons.Button.Variant.ICON}
137
+ aria-details=${'teaser-info-tooltip-' + input.uuid}
138
+ aria-label=${lockedString(UIStringsNotTranslate.learnDataUsage)}
139
+ ></devtools-button>
140
+ <devtools-tooltip id=${'teaser-info-tooltip-' + input.uuid} variant="rich">
141
+ <div class="info-tooltip-text">${lockedString(UIStringsNotTranslate.infoTooltipText)}</div>
142
+ <div class="learn-more">
143
+ <x-link
144
+ class="devtools-link"
145
+ title=${lockedString(UIStringsNotTranslate.learnMoreAboutAiSummaries)}
146
+ href=${DATA_USAGE_URL}
147
+ jslog=${VisualLogging.link().track({click: true, keydown:'Enter|Space'}).context('explain.teaser.learn-more')}
148
+ >${lockedString(UIStringsNotTranslate.learnMoreAboutAiSummaries)}</x-link>
149
+ </div>
150
+ </devtools-tooltip>
151
+ `}
152
+ <devtools-checkbox
153
+ aria-label=${lockedString(UIStringsNotTranslate.dontShow)}
154
+ @change=${input.dontShowChanged}
155
+ jslog=${VisualLogging.toggle('explain.teaser.dont-show').track({ change: true })}>
156
+ ${lockedString(UIStringsNotTranslate.dontShow)}
157
+ </devtools-checkbox>
158
+ </div>
159
+ `;
160
+ // clang-format on
161
+ };
162
+
107
163
  // clang-format off
108
164
  render(html`
109
165
  <style>${consoleInsightTeaserStyles}</style>
@@ -115,64 +171,36 @@ export const DEFAULT_VIEW = (input: ViewInput, _output: undefined, target: HTMLE
115
171
  prefer-span-left
116
172
  >
117
173
  <div class="teaser-tooltip-container">
118
- ${showPlaceholder ? html`
119
- <h2>${lockedString(UIStringsNotTranslate.summarizing)}</h2>
120
- <div
121
- role="presentation"
122
- aria-label=${lockedString(UIStringsNotTranslate.loading)}
123
- class="loader"
124
- style="clip-path: url(${'#clipPath-' + input.uuid});"
125
- >
126
- <svg width="100%" height="52">
127
- <defs>
128
- <clipPath id=${'clipPath-' + input.uuid}>
129
- <rect x="0" y="0" width="100%" height="12" rx="8"></rect>
130
- <rect x="0" y="20" width="100%" height="12" rx="8"></rect>
131
- <rect x="0" y="40" width="100%" height="12" rx="8"></rect>
132
- </clipPath>
133
- </defs>
134
- </svg>
135
- </div>
136
- ` : html`
137
- <h2>${input.headerText}</h2>
138
- <div>${input.mainText}</div>
139
- <div class="tooltip-footer">
140
- ${input.hasTellMeMoreButton ? html`
141
- <devtools-button
142
- title=${lockedString(UIStringsNotTranslate.tellMeMore)}
143
- .jslogContext=${'insights-teaser-tell-me-more'},
144
- .variant=${Buttons.Button.Variant.PRIMARY}
145
- @click=${input.onTellMeMoreClick}
146
- >
147
- <devtools-icon class="lightbulb-icon" name="lightbulb-spark"></devtools-icon>
148
- ${lockedString(UIStringsNotTranslate.tellMeMore)}
149
- </devtools-button>
150
- ` : Lit.nothing}
151
- <devtools-button
152
- .iconName=${'info'}
153
- .variant=${Buttons.Button.Variant.ICON}
154
- aria-details=${'teaser-info-tooltip-' + input.uuid}
155
- aria-label=${lockedString(UIStringsNotTranslate.learnDataUsage)}
156
- ></devtools-button>
157
- <devtools-tooltip id=${'teaser-info-tooltip-' + input.uuid} variant="rich">
158
- <div class="info-tooltip-text">${lockedString(UIStringsNotTranslate.infoTooltipText)}</div>
159
- <div class="learn-more">
160
- <x-link
161
- class="devtools-link"
162
- title=${lockedString(UIStringsNotTranslate.learnMoreAboutAiSummaries)}
163
- href=${DATA_USAGE_URL}
164
- jslog=${VisualLogging.link().track({click: true, keydown:'Enter|Space'}).context('explain.teaser.learn-more')}
165
- >${lockedString(UIStringsNotTranslate.learnMoreAboutAiSummaries)}</x-link>
166
- </div>
167
- </devtools-tooltip>
168
- <devtools-checkbox
169
- aria-label=${lockedString(UIStringsNotTranslate.dontShow)}
170
- @change=${input.dontShowChanged}
171
- jslog=${VisualLogging.toggle('explain.teaser.dont-show').track({ change: true })}>
172
- ${lockedString(UIStringsNotTranslate.dontShow)}
173
- </devtools-checkbox>
174
- </div>
175
- `}
174
+ ${input.isError ? html`
175
+ <h2>${lockedString(UIStringsNotTranslate.summaryNotAvailable)}</h2>
176
+ ` :
177
+ showPlaceholder ? html`
178
+ <h2>${input.isSlowGeneration ?
179
+ lockedString(UIStringsNotTranslate.summarizingTakesABitLonger) :
180
+ lockedString(UIStringsNotTranslate.summarizing)
181
+ }</h2>
182
+ <div
183
+ role="presentation"
184
+ aria-label=${lockedString(UIStringsNotTranslate.loading)}
185
+ class="loader"
186
+ style="clip-path: url(${'#clipPath-' + input.uuid});"
187
+ >
188
+ <svg width="100%" height="52">
189
+ <defs>
190
+ <clipPath id=${'clipPath-' + input.uuid}>
191
+ <rect x="0" y="0" width="100%" height="12" rx="8"></rect>
192
+ <rect x="0" y="20" width="100%" height="12" rx="8"></rect>
193
+ <rect x="0" y="40" width="100%" height="12" rx="8"></rect>
194
+ </clipPath>
195
+ </defs>
196
+ </svg>
197
+ </div>
198
+ ` : html`
199
+ <h2>${input.headerText}</h2>
200
+ <div>${input.mainText}</div>
201
+ `
202
+ }
203
+ ${input.isError || input.isSlowGeneration || !showPlaceholder ? renderFooter() : Lit.nothing}
176
204
  </div>
177
205
  </devtools-tooltip>
178
206
  `, target);
@@ -192,6 +220,9 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
192
220
  #consoleViewMessage: ConsoleViewMessage;
193
221
  #isInactive = false;
194
222
  #abortController: null|AbortController = null;
223
+ #isSlow = false;
224
+ #timeoutId: ReturnType<typeof setTimeout>|null = null;
225
+ #isError = false;
195
226
 
196
227
  constructor(uuid: string, consoleViewMessage: ConsoleViewMessage, element?: HTMLElement, view?: View) {
197
228
  super(element);
@@ -285,6 +316,9 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
285
316
  this.#abortController.abort();
286
317
  }
287
318
  this.#isGenerating = false;
319
+ if (this.#timeoutId) {
320
+ clearTimeout(this.#timeoutId);
321
+ }
288
322
  }
289
323
 
290
324
  setInactive(isInactive: boolean): void {
@@ -295,8 +329,14 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
295
329
  this.requestUpdate();
296
330
  }
297
331
 
332
+ #setSlow(): void {
333
+ this.#isSlow = true;
334
+ this.requestUpdate();
335
+ }
336
+
298
337
  async #generateTeaserText(): Promise<void> {
299
338
  this.#isGenerating = true;
339
+ this.#timeoutId = setTimeout(this.#setSlow.bind(this), SLOW_GENERATION_CUTOFF_MILLISECONDS);
300
340
  let teaserText = '';
301
341
  try {
302
342
  for await (const chunk of this.#getOnDeviceInsight()) {
@@ -306,12 +346,16 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
306
346
  // Ignore `AbortError` errors, which are thrown on mouse leave.
307
347
  if (err.name !== 'AbortError') {
308
348
  console.error(err.name, err.message);
349
+ this.#isError = true;
309
350
  }
310
351
  this.#isGenerating = false;
352
+ clearTimeout(this.#timeoutId);
353
+ this.requestUpdate();
311
354
  return;
312
355
  }
313
356
 
314
- // TODO(crbug.com/443618746): Add user-facing error message instead of staying in loading state
357
+ clearTimeout(this.#timeoutId);
358
+ this.#isGenerating = false;
315
359
  let responseObject = {
316
360
  header: null,
317
361
  explanation: null,
@@ -320,10 +364,15 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
320
364
  responseObject = JSON.parse(teaserText);
321
365
  } catch (err) {
322
366
  console.error(err.name, err.message);
367
+ this.#isError = true;
368
+ this.requestUpdate();
369
+ return;
323
370
  }
324
371
  this.#headerText = responseObject.header || '';
325
372
  this.#mainText = responseObject.explanation || '';
326
- this.#isGenerating = false;
373
+ if (!this.#headerText || !this.#mainText) {
374
+ this.#isError = true;
375
+ }
327
376
  this.requestUpdate();
328
377
  }
329
378
 
@@ -373,6 +422,8 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
373
422
  !Common.Settings.Settings.instance().moduleSetting('console-insight-teasers-enabled').get(),
374
423
  dontShowChanged: this.#dontShowChanged.bind(this),
375
424
  hasTellMeMoreButton: this.#hasTellMeMoreButton(),
425
+ isSlowGeneration: this.#isSlow,
426
+ isError: this.#isError,
376
427
  },
377
428
  undefined, this.contentElement);
378
429
  }
@@ -52,7 +52,7 @@ const str_ = i18n.i18n.registerUIStrings('panels/console/ConsoleSidebar.ts', UIS
52
52
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
53
53
  const {render, html, nothing, Directives} = Lit;
54
54
 
55
- const enum GroupName {
55
+ export const enum GroupName {
56
56
  CONSOLE_API = 'user message',
57
57
  ALL = 'message',
58
58
  ERROR = 'error',
@@ -112,7 +112,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
112
112
  <ul role="group" hidden>
113
113
  ${group.urlGroups.values().map(urlGroup => html`
114
114
  <li
115
- ${Directives.ref(element => element && nodeFilterMap.set(element, group.filter))}
115
+ ${Directives.ref(element => element && nodeFilterMap.set(element, urlGroup.filter))}
116
116
  role="treeitem"
117
117
  ?selected=${urlGroup.filter === input.selectedFilter}
118
118
  title=${urlGroup.url ?? ''}>
@@ -126,7 +126,7 @@ export const DEFAULT_VIEW: View = (input, output, target) => {
126
126
  target);
127
127
  };
128
128
 
129
- class ConsoleFilterGroup {
129
+ export class ConsoleFilterGroup {
130
130
  readonly urlGroups = new Map<string|null, {filter: ConsoleFilter, url: string|null, count: number}>();
131
131
  messageCount = 0;
132
132
  readonly name: GroupName;
@@ -432,6 +432,27 @@ const UIStrings = {
432
432
  * @description A context menu item in the Network Log View of the Network panel
433
433
  */
434
434
  clearBrowserCookies: 'Clear browser cookies',
435
+ /**
436
+ * @description A context menu item in the Network Log View of the Network panel
437
+ */
438
+ throttleRequests: 'Throttle requests',
439
+ /**
440
+ * @description A context menu item in the Network Log View of the Network panel
441
+ */
442
+ throttleRequestUrl: 'Throttle request URL',
443
+ /**
444
+ * @description A context menu item in the Network Log View of the Network panel
445
+ * @example {example.com} PH1
446
+ */
447
+ unthrottleS: 'Stop throttling {PH1}',
448
+ /**
449
+ * @description A context menu item in the Network Log View of the Network panel
450
+ */
451
+ throttleRequestDomain: 'Throttle request domain',
452
+ /**
453
+ * @description A context menu item in the Network Log View of the Network panel
454
+ */
455
+ blockRequests: 'Block requests',
435
456
  /**
436
457
  * @description A context menu item in the Network Log View of the Network panel
437
458
  */
@@ -1849,43 +1870,124 @@ export class NetworkLogView extends Common.ObjectWrapper.eventMixin<EventTypes,
1849
1870
  const maxBlockedURLLength = 20;
1850
1871
  const manager = SDK.NetworkManager.MultitargetNetworkManager.instance();
1851
1872
 
1852
- function addBlockedURL(url: string): void {
1853
- manager.requestConditions.add(
1854
- new SDK.NetworkManager.RequestCondition({enabled: true, url: url as Platform.DevToolsPath.UrlString}));
1855
- manager.setBlockingEnabled(true);
1856
- void UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
1857
- }
1873
+ if (!Root.Runtime.hostConfig.devToolsIndividualRequestThrottling?.enabled) {
1874
+ function addBlockedURL(url: string): void {
1875
+ manager.requestConditions.add(SDK.NetworkManager.RequestCondition.createFromSetting(
1876
+ {enabled: true, url: url as Platform.DevToolsPath.UrlString}));
1877
+ manager.requestConditions.conditionsEnabled = true;
1878
+ void UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
1879
+ }
1858
1880
 
1859
- function removeBlockedURL(url: string): void {
1860
- const entry = manager.requestConditions.findCondition(url);
1861
- if (entry) {
1862
- manager.requestConditions.delete(entry);
1881
+ function removeBlockedURL(url: string): void {
1882
+ const entry = manager.requestConditions.findCondition(url);
1883
+ if (entry) {
1884
+ manager.requestConditions.delete(entry);
1885
+ }
1886
+ void UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
1863
1887
  }
1864
- void UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
1865
- }
1866
1888
 
1867
- const urlWithoutScheme = request.parsedURL.urlWithoutScheme();
1868
- if (urlWithoutScheme && !manager.requestConditions.has(urlWithoutScheme)) {
1869
- contextMenu.debugSection().appendItem(
1870
- i18nString(UIStrings.blockRequestUrl), addBlockedURL.bind(null, urlWithoutScheme),
1871
- {jslogContext: 'block-request-url'});
1872
- } else if (urlWithoutScheme) {
1873
- const croppedURL = Platform.StringUtilities.trimMiddle(urlWithoutScheme, maxBlockedURLLength);
1874
- contextMenu.debugSection().appendItem(
1875
- i18nString(UIStrings.unblockS, {PH1: croppedURL}), removeBlockedURL.bind(null, urlWithoutScheme),
1876
- {jslogContext: 'unblock'});
1877
- }
1889
+ const urlWithoutScheme = request.parsedURL.urlWithoutScheme();
1890
+ if (urlWithoutScheme && !manager.requestConditions.has(urlWithoutScheme)) {
1891
+ contextMenu.debugSection().appendItem(
1892
+ i18nString(UIStrings.blockRequestUrl), addBlockedURL.bind(null, urlWithoutScheme),
1893
+ {jslogContext: 'block-request-url'});
1894
+ } else if (urlWithoutScheme) {
1895
+ const croppedURL = Platform.StringUtilities.trimMiddle(urlWithoutScheme, maxBlockedURLLength);
1896
+ contextMenu.debugSection().appendItem(
1897
+ i18nString(UIStrings.unblockS, {PH1: croppedURL}), removeBlockedURL.bind(null, urlWithoutScheme),
1898
+ {jslogContext: 'unblock'});
1899
+ }
1878
1900
 
1879
- const domain = request.parsedURL.domain();
1880
- if (domain && !manager.requestConditions.has(domain)) {
1881
- contextMenu.debugSection().appendItem(
1882
- i18nString(UIStrings.blockRequestDomain), addBlockedURL.bind(null, domain),
1883
- {jslogContext: 'block-request-domain'});
1884
- } else if (domain) {
1885
- const croppedDomain = Platform.StringUtilities.trimMiddle(domain, maxBlockedURLLength);
1886
- contextMenu.debugSection().appendItem(
1887
- i18nString(UIStrings.unblockS, {PH1: croppedDomain}), removeBlockedURL.bind(null, domain),
1888
- {jslogContext: 'unblock'});
1901
+ const domain = request.parsedURL.domain();
1902
+ if (domain && !manager.requestConditions.has(domain)) {
1903
+ contextMenu.debugSection().appendItem(
1904
+ i18nString(UIStrings.blockRequestDomain), addBlockedURL.bind(null, domain),
1905
+ {jslogContext: 'block-request-domain'});
1906
+ } else if (domain) {
1907
+ const croppedDomain = Platform.StringUtilities.trimMiddle(domain, maxBlockedURLLength);
1908
+ contextMenu.debugSection().appendItem(
1909
+ i18nString(UIStrings.unblockS, {PH1: croppedDomain}), removeBlockedURL.bind(null, domain),
1910
+ {jslogContext: 'unblock'});
1911
+ }
1912
+ } else {
1913
+ function removeRequestCondition(pattern: SDK.NetworkManager.RequestURLPattern): void {
1914
+ const entry = manager.requestConditions.findCondition(pattern.constructorString);
1915
+ if (entry) {
1916
+ manager.requestConditions.delete(entry);
1917
+ void UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
1918
+ }
1919
+ }
1920
+
1921
+ function addRequestCondition(
1922
+ pattern: SDK.NetworkManager.RequestURLPattern,
1923
+ conditions: SDK.NetworkManager.ThrottlingConditions,
1924
+ ): void {
1925
+ const entry = manager.requestConditions.findCondition(pattern.constructorString);
1926
+ if (entry) {
1927
+ entry.conditions = conditions;
1928
+ } else {
1929
+ manager.requestConditions.add(SDK.NetworkManager.RequestCondition.create(pattern, conditions));
1930
+ }
1931
+ manager.requestConditions.conditionsEnabled = true;
1932
+ void UI.ViewManager.ViewManager.instance().showView('network.blocked-urls');
1933
+ }
1934
+
1935
+ const blockingMenu =
1936
+ contextMenu.debugSection().appendSubMenuItem(i18nString(UIStrings.blockRequests), /* disabled=*/ true);
1937
+ const throttlingMenu =
1938
+ contextMenu.debugSection().appendSubMenuItem(i18nString(UIStrings.throttleRequests), /* disabled=*/ true);
1939
+
1940
+ const urlWithoutScheme = request.parsedURL.urlWithoutScheme();
1941
+ const urlPattern = urlWithoutScheme &&
1942
+ SDK.NetworkManager.RequestURLPattern.create(
1943
+ `*://${urlWithoutScheme}` as SDK.NetworkManager.URLPatternConstructorString);
1944
+ if (urlPattern) {
1945
+ throttlingMenu.setEnabled(true);
1946
+ blockingMenu.setEnabled(true);
1947
+ const existingConditions = manager.requestConditions.findCondition(urlPattern.constructorString);
1948
+ const isBlocking = existingConditions?.conditions === SDK.NetworkManager.BlockingConditions;
1949
+ const isThrottling = existingConditions &&
1950
+ existingConditions.conditions !== SDK.NetworkManager.BlockingConditions &&
1951
+ existingConditions.conditions !== SDK.NetworkManager.NoThrottlingConditions;
1952
+ blockingMenu.debugSection().appendItem(
1953
+ isBlocking ? i18nString(UIStrings.unblockS, {PH1: urlPattern.constructorString}) :
1954
+ i18nString(UIStrings.blockRequestUrl),
1955
+ () => isBlocking ? removeRequestCondition(urlPattern) :
1956
+ addRequestCondition(urlPattern, SDK.NetworkManager.BlockingConditions),
1957
+ {jslogContext: 'block-request-url'});
1958
+ throttlingMenu.debugSection().appendItem(
1959
+ isThrottling ? i18nString(UIStrings.unthrottleS, {PH1: urlPattern.constructorString}) :
1960
+ i18nString(UIStrings.throttleRequestUrl),
1961
+ () => isThrottling ? removeRequestCondition(urlPattern) :
1962
+ addRequestCondition(urlPattern, SDK.NetworkManager.Slow3GConditions),
1963
+ {jslogContext: 'throttle-request-url'});
1964
+ }
1965
+
1966
+ const domain = request.parsedURL.domain();
1967
+ const domainPattern = domain &&
1968
+ SDK.NetworkManager.RequestURLPattern.create(
1969
+ `*://${domain}` as SDK.NetworkManager.URLPatternConstructorString);
1970
+ if (domainPattern) {
1971
+ throttlingMenu.setEnabled(true);
1972
+ blockingMenu.setEnabled(true);
1973
+ const existingConditions = manager.requestConditions.findCondition(domainPattern.constructorString);
1974
+ const isBlocking = existingConditions?.conditions === SDK.NetworkManager.BlockingConditions;
1975
+ const isThrottling = existingConditions &&
1976
+ existingConditions.conditions !== SDK.NetworkManager.BlockingConditions &&
1977
+ existingConditions.conditions !== SDK.NetworkManager.NoThrottlingConditions;
1978
+ blockingMenu.debugSection().appendItem(
1979
+ isBlocking ? i18nString(UIStrings.unblockS, {PH1: domainPattern.constructorString}) :
1980
+ i18nString(UIStrings.blockRequestDomain),
1981
+ () => isBlocking ? removeRequestCondition(domainPattern) :
1982
+ addRequestCondition(domainPattern, SDK.NetworkManager.BlockingConditions),
1983
+ {jslogContext: 'block-request-domain'});
1984
+ throttlingMenu.debugSection().appendItem(
1985
+ isThrottling ? i18nString(UIStrings.unthrottleS, {PH1: domainPattern.constructorString}) :
1986
+ i18nString(UIStrings.throttleRequestDomain),
1987
+ () => isThrottling ? removeRequestCondition(domainPattern) :
1988
+ addRequestCondition(domainPattern, SDK.NetworkManager.Slow3GConditions),
1989
+ {jslogContext: 'throttle-request-domain'});
1990
+ }
1889
1991
  }
1890
1992
 
1891
1993
  if (SDK.NetworkManager.NetworkManager.canReplayRequest(request)) {