chrome-devtools-frontend 1.0.1007307 → 1.0.1008562

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 (28) hide show
  1. package/config/gni/devtools_grd_files.gni +3 -0
  2. package/extension-api/ExtensionAPI.d.ts +10 -0
  3. package/front_end/core/common/ParsedURL.ts +9 -1
  4. package/front_end/core/i18n/locales/en-US.json +14 -5
  5. package/front_end/core/i18n/locales/en-XL.json +14 -5
  6. package/front_end/core/sdk/CSSFontFace.ts +8 -0
  7. package/front_end/entrypoints/lighthouse_worker/LighthouseWorkerService.ts +1 -4
  8. package/front_end/generated/InspectorBackendCommands.js +2 -2
  9. package/front_end/generated/protocol.ts +2 -1
  10. package/front_end/legacy_test_runner/lighthouse_test_runner/lighthouse_test_runner.js +16 -0
  11. package/front_end/models/extensions/ExtensionAPI.ts +95 -12
  12. package/front_end/models/extensions/ExtensionEndpoint.ts +69 -0
  13. package/front_end/models/extensions/ExtensionServer.ts +21 -0
  14. package/front_end/models/extensions/LanguageExtensionEndpoint.ts +46 -78
  15. package/front_end/models/extensions/RecorderExtensionEndpoint.ts +43 -0
  16. package/front_end/models/extensions/RecorderPluginManager.ts +30 -0
  17. package/front_end/models/extensions/extensions.ts +2 -0
  18. package/front_end/models/issues_manager/DeprecationIssue.ts +0 -14
  19. package/front_end/panels/application/AppManifestView.ts +2 -1
  20. package/front_end/panels/browser_debugger/DOMBreakpointsSidebarPane.ts +15 -1
  21. package/front_end/panels/lighthouse/LighthouseController.ts +25 -10
  22. package/front_end/panels/lighthouse/LighthouseStartView.ts +25 -1
  23. package/front_end/panels/lighthouse/LighthouseStartViewFR.ts +1 -1
  24. package/front_end/panels/network/components/RequestHeadersView.css +5 -0
  25. package/front_end/panels/network/components/RequestHeadersView.ts +56 -6
  26. package/front_end/ui/components/tree_outline/TreeOutline.ts +4 -0
  27. package/front_end/ui/components/tree_outline/treeOutline.css +6 -1
  28. package/package.json +1 -1
@@ -5,9 +5,30 @@
5
5
  import type * as SDK from '../../core/sdk/sdk.js';
6
6
  import * as Bindings from '../bindings/bindings.js';
7
7
  import type {Chrome} from '../../../extension-api/ExtensionAPI.js'; // eslint-disable-line rulesdir/es_modules_import
8
+ import {ExtensionEndpoint} from './ExtensionEndpoint.js';
8
9
 
9
10
  import {PrivateAPI} from './ExtensionAPI.js';
10
11
 
12
+ class LanguageExtensionEndpointImpl extends ExtensionEndpoint {
13
+ private plugin: LanguageExtensionEndpoint;
14
+ constructor(plugin: LanguageExtensionEndpoint, port: MessagePort) {
15
+ super(port);
16
+ this.plugin = plugin;
17
+ }
18
+ protected handleEvent({event}: {event: string}): void {
19
+ switch (event) {
20
+ case PrivateAPI.LanguageExtensionPluginEvents.UnregisteredLanguageExtensionPlugin: {
21
+ this.disconnect();
22
+ const {pluginManager} = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
23
+ if (pluginManager) {
24
+ pluginManager.removePlugin(this.plugin);
25
+ }
26
+ break;
27
+ }
28
+ }
29
+ }
30
+ }
31
+
11
32
  export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.DebuggerLanguagePlugin {
12
33
  private readonly supportedScriptTypes: {
13
34
  language: string,
@@ -15,13 +36,7 @@ export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.
15
36
  // eslint-disable-next-line @typescript-eslint/naming-convention
16
37
  symbol_types: Array<string>,
17
38
  };
18
- private readonly port: MessagePort;
19
- private nextRequestId: number;
20
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
21
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
23
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
- private pendingRequests: Map<any, any>;
39
+ private endpoint: LanguageExtensionEndpointImpl;
25
40
  constructor(
26
41
  name: string, supportedScriptTypes: {
27
42
  language: string,
@@ -32,63 +47,7 @@ export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.
32
47
  port: MessagePort) {
33
48
  super(name);
34
49
  this.supportedScriptTypes = supportedScriptTypes;
35
- this.port = port;
36
- this.port.onmessage = this.onResponse.bind(this);
37
- this.nextRequestId = 0;
38
- this.pendingRequests = new Map();
39
- }
40
-
41
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
42
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
44
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
45
- private sendRequest(method: string, parameters: any): Promise<any> {
46
- return new Promise((resolve, reject) => {
47
- const requestId = this.nextRequestId++;
48
- this.pendingRequests.set(requestId, {resolve, reject});
49
- this.port.postMessage({requestId, method, parameters});
50
- });
51
- }
52
-
53
- private onResponse({data}: MessageEvent<{
54
- requestId: number,
55
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
56
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
- result: any,
58
- error: Error|null,
59
- }|{
60
- event: string,
61
- }>): void {
62
- if ('event' in data) {
63
- const {event} = data;
64
- switch (event) {
65
- case PrivateAPI.LanguageExtensionPluginEvents.UnregisteredLanguageExtensionPlugin: {
66
- for (const {reject} of this.pendingRequests.values()) {
67
- reject(new Error('Language extension endpoint disconnected'));
68
- }
69
- this.pendingRequests.clear();
70
- this.port.close();
71
- const {pluginManager} = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
72
- if (pluginManager) {
73
- pluginManager.removePlugin(this);
74
- }
75
- break;
76
- }
77
- }
78
- return;
79
- }
80
- const {requestId, result, error} = data;
81
- if (!this.pendingRequests.has(requestId)) {
82
- console.error(`No pending request ${requestId}`);
83
- return;
84
- }
85
- const {resolve, reject} = this.pendingRequests.get(requestId);
86
- this.pendingRequests.delete(requestId);
87
- if (error) {
88
- reject(new Error(error.message));
89
- } else {
90
- resolve(result);
91
- }
50
+ this.endpoint = new LanguageExtensionEndpointImpl(this, port);
92
51
  }
93
52
 
94
53
  handleScript(script: SDK.Script.Script): boolean {
@@ -100,7 +59,7 @@ export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.
100
59
  /** Notify the plugin about a new script
101
60
  */
102
61
  addRawModule(rawModuleId: string, symbolsURL: string, rawModule: Chrome.DevTools.RawModule): Promise<string[]> {
103
- return this.sendRequest(
62
+ return this.endpoint.sendRequest(
104
63
  PrivateAPI.LanguageExtensionPluginCommands.AddRawModule, {rawModuleId, symbolsURL, rawModule}) as
105
64
  Promise<string[]>;
106
65
  }
@@ -109,33 +68,36 @@ export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.
109
68
  * Notifies the plugin that a script is removed.
110
69
  */
111
70
  removeRawModule(rawModuleId: string): Promise<void> {
112
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.RemoveRawModule, {rawModuleId}) as Promise<void>;
71
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.RemoveRawModule, {rawModuleId}) as
72
+ Promise<void>;
113
73
  }
114
74
 
115
75
  /** Find locations in raw modules from a location in a source file
116
76
  */
117
77
  sourceLocationToRawLocation(sourceLocation: Chrome.DevTools.SourceLocation):
118
78
  Promise<Chrome.DevTools.RawLocationRange[]> {
119
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.SourceLocationToRawLocation, {sourceLocation}) as
79
+ return this.endpoint.sendRequest(
80
+ PrivateAPI.LanguageExtensionPluginCommands.SourceLocationToRawLocation, {sourceLocation}) as
120
81
  Promise<Chrome.DevTools.RawLocationRange[]>;
121
82
  }
122
83
 
123
84
  /** Find locations in source files from a location in a raw module
124
85
  */
125
86
  rawLocationToSourceLocation(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.SourceLocation[]> {
126
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.RawLocationToSourceLocation, {rawLocation}) as
87
+ return this.endpoint.sendRequest(
88
+ PrivateAPI.LanguageExtensionPluginCommands.RawLocationToSourceLocation, {rawLocation}) as
127
89
  Promise<Chrome.DevTools.SourceLocation[]>;
128
90
  }
129
91
 
130
92
  getScopeInfo(type: string): Promise<Chrome.DevTools.ScopeInfo> {
131
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetScopeInfo, {type}) as
93
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetScopeInfo, {type}) as
132
94
  Promise<Chrome.DevTools.ScopeInfo>;
133
95
  }
134
96
 
135
97
  /** List all variables in lexical scope at a given location in a raw module
136
98
  */
137
99
  listVariablesInScope(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.Variable[]> {
138
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.ListVariablesInScope, {rawLocation}) as
100
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.ListVariablesInScope, {rawLocation}) as
139
101
  Promise<Chrome.DevTools.Variable[]>;
140
102
  }
141
103
 
@@ -144,7 +106,8 @@ export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.
144
106
  getFunctionInfo(rawLocation: Chrome.DevTools.RawLocation): Promise<{
145
107
  frames: Array<Chrome.DevTools.FunctionInfo>,
146
108
  }> {
147
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetFunctionInfo, {rawLocation}) as Promise<{
109
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetFunctionInfo, {rawLocation}) as
110
+ Promise<{
148
111
  frames: Array<Chrome.DevTools.FunctionInfo>,
149
112
  }>;
150
113
  }
@@ -153,7 +116,8 @@ export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.
153
116
  * that rawLocation is in.
154
117
  */
155
118
  getInlinedFunctionRanges(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.RawLocationRange[]> {
156
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInlinedFunctionRanges, {rawLocation}) as
119
+ return this.endpoint.sendRequest(
120
+ PrivateAPI.LanguageExtensionPluginCommands.GetInlinedFunctionRanges, {rawLocation}) as
157
121
  Promise<Chrome.DevTools.RawLocationRange[]>;
158
122
  }
159
123
 
@@ -161,7 +125,8 @@ export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.
161
125
  * called by the function or inline frame that rawLocation is in.
162
126
  */
163
127
  getInlinedCalleesRanges(rawLocation: Chrome.DevTools.RawLocation): Promise<Chrome.DevTools.RawLocationRange[]> {
164
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInlinedCalleesRanges, {rawLocation}) as
128
+ return this.endpoint.sendRequest(
129
+ PrivateAPI.LanguageExtensionPluginCommands.GetInlinedCalleesRanges, {rawLocation}) as
165
130
  Promise<Chrome.DevTools.RawLocationRange[]>;
166
131
  }
167
132
 
@@ -169,7 +134,8 @@ export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.
169
134
  typeInfos: Array<Chrome.DevTools.TypeInfo>,
170
135
  base: Chrome.DevTools.EvalBase,
171
136
  }|null> {
172
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetTypeInfo, {expression, context}) as Promise<{
137
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetTypeInfo, {expression, context}) as
138
+ Promise<{
173
139
  typeInfos: Array<Chrome.DevTools.TypeInfo>,
174
140
  base: Chrome.DevTools.EvalBase,
175
141
  }|null>;
@@ -183,8 +149,8 @@ export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.
183
149
  context: Chrome.DevTools.RawLocation): Promise<{
184
150
  js: string,
185
151
  }> {
186
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetFormatter, {expressionOrField, context}) as
187
- Promise<{
152
+ return this.endpoint.sendRequest(
153
+ PrivateAPI.LanguageExtensionPluginCommands.GetFormatter, {expressionOrField, context}) as Promise<{
188
154
  js: string,
189
155
  }>;
190
156
  }
@@ -195,13 +161,15 @@ export class LanguageExtensionEndpoint extends Bindings.DebuggerLanguagePlugins.
195
161
  }): Promise<{
196
162
  js: string,
197
163
  }> {
198
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInspectableAddress, {field}) as Promise<{
164
+ return this.endpoint.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetInspectableAddress, {field}) as
165
+ Promise<{
199
166
  js: string,
200
167
  }>;
201
168
  }
202
169
 
203
170
  async getMappedLines(rawModuleId: string, sourceFileURL: string): Promise<number[]|undefined> {
204
- return this.sendRequest(PrivateAPI.LanguageExtensionPluginCommands.GetMappedLines, {rawModuleId, sourceFileURL});
171
+ return this.endpoint.sendRequest(
172
+ PrivateAPI.LanguageExtensionPluginCommands.GetMappedLines, {rawModuleId, sourceFileURL});
205
173
  }
206
174
 
207
175
  dispose(): void {
@@ -0,0 +1,43 @@
1
+ // Copyright 2022 The Chromium Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import {PrivateAPI} from './ExtensionAPI.js';
6
+ import {ExtensionEndpoint} from './ExtensionEndpoint.js';
7
+ import {RecorderPluginManager} from './RecorderPluginManager.js';
8
+
9
+ export class RecorderExtensionEndpoint extends ExtensionEndpoint {
10
+ private readonly name: string;
11
+
12
+ constructor(name: string, port: MessagePort) {
13
+ super(port);
14
+ this.name = name;
15
+ }
16
+
17
+ getName(): string {
18
+ return this.name;
19
+ }
20
+
21
+ protected handleEvent({event}: {event: string}): void {
22
+ switch (event) {
23
+ case PrivateAPI.RecorderExtensionPluginEvents.UnregisteredRecorderExtensionPlugin: {
24
+ this.disconnect();
25
+ RecorderPluginManager.instance().removePlugin(this);
26
+ break;
27
+ }
28
+ default:
29
+ throw new Error(`Unrecognized Recorder extension endpoint event: ${event}`);
30
+ }
31
+ }
32
+
33
+ /**
34
+ * In practice, `recording` is a UserFlow[1], but we avoid defining this type on the
35
+ * API in order to prevent dependencies between Chrome and puppeteer. Extensions
36
+ * are responsible for working out potential compatibility issues.
37
+ *
38
+ * [1]: https://github.com/puppeteer/replay/blob/main/src/Schema.ts#L245
39
+ */
40
+ stringify(recording: Object): Promise<string> {
41
+ return this.sendRequest(PrivateAPI.RecorderExtensionPluginCommands.Stringify, {recording});
42
+ }
43
+ }
@@ -0,0 +1,30 @@
1
+ // Copyright 2022 The Chromium Authors. All rights reserved.
2
+ // Use of this source code is governed by a BSD-style license that can be
3
+ // found in the LICENSE file.
4
+
5
+ import type {RecorderExtensionEndpoint} from './RecorderExtensionEndpoint.js';
6
+
7
+ let instance: RecorderPluginManager|null = null;
8
+
9
+ export class RecorderPluginManager {
10
+ #plugins: Set<RecorderExtensionEndpoint> = new Set();
11
+
12
+ static instance(): RecorderPluginManager {
13
+ if (!instance) {
14
+ instance = new RecorderPluginManager();
15
+ }
16
+ return instance;
17
+ }
18
+
19
+ addPlugin(plugin: RecorderExtensionEndpoint): void {
20
+ this.#plugins.add(plugin);
21
+ }
22
+
23
+ removePlugin(plugin: RecorderExtensionEndpoint): void {
24
+ this.#plugins.delete(plugin);
25
+ }
26
+
27
+ plugins(): RecorderExtensionEndpoint[] {
28
+ return Array.from(this.#plugins.values());
29
+ }
30
+ }
@@ -7,6 +7,7 @@ import * as ExtensionPanel from './ExtensionPanel.js';
7
7
  import * as ExtensionServer from './ExtensionServer.js';
8
8
  import * as ExtensionTraceProvider from './ExtensionTraceProvider.js';
9
9
  import * as ExtensionView from './ExtensionView.js';
10
+ import * as RecorderPluginManager from './RecorderPluginManager.js';
10
11
 
11
12
  export {
12
13
  ExtensionAPI,
@@ -14,4 +15,5 @@ export {
14
15
  ExtensionServer,
15
16
  ExtensionTraceProvider,
16
17
  ExtensionView,
18
+ RecorderPluginManager,
17
19
  };
@@ -243,16 +243,6 @@ const UIStrings = {
243
243
  * `RTCP MUX` policy.
244
244
  */
245
245
  rtcpMuxPolicyNegotiate: 'The `rtcpMuxPolicy` option is deprecated and will be removed.',
246
- /**
247
- * @description A deprecation warning shown in the DevTools Issues tab.
248
- * It's shown when a video conferencing website attempts to turn on or
249
- * off a feature that has been removed, `RTP data channels`.
250
- * `RTP data channels` are used to send and receive arbitrary data,
251
- * but have been removed in favor of standardized versions of
252
- * `data channels`: `SCTP data channels`.
253
- */
254
- rtpDataChannel:
255
- '`RTP data channels` are no longer supported. The `RtpDataChannels` constraint is currently ignored, and may cause an error at a later date.',
256
246
  /**
257
247
  * @description TODO(crbug.com/1318878): Description needed for translation
258
248
  */
@@ -500,10 +490,6 @@ export class DeprecationIssue extends Issue {
500
490
  feature = 5654810086866944;
501
491
  milestone = 62;
502
492
  break;
503
- case Protocol.Audits.DeprecationIssueType.RTPDataChannel:
504
- messageFunction = i18nLazyString(UIStrings.rtpDataChannel);
505
- milestone = 88;
506
- break;
507
493
  case Protocol.Audits.DeprecationIssueType.SharedArrayBufferConstructedWithoutIsolation:
508
494
  messageFunction = i18nLazyString(UIStrings.sharedArrayBufferConstructedWithoutIsolation);
509
495
  milestone = 106;
@@ -663,7 +663,8 @@ export class AppManifestView extends UI.Widget.VBox implements SDK.TargetManager
663
663
  }
664
664
 
665
665
  const userPreferences = parsedManifest['user_preferences'] || {};
666
- const colorSchemeDark = userPreferences['color_scheme_dark'] || {};
666
+ const colorScheme = userPreferences['color_scheme'] || {};
667
+ const colorSchemeDark = colorScheme['dark'] || {};
667
668
  const darkThemeColorString = colorSchemeDark['theme_color'];
668
669
  const hasDarkThemeColor = typeof darkThemeColorString === 'string';
669
670
  this.darkThemeColorField.parentElement?.classList.toggle('hidden', !hasDarkThemeColor);
@@ -53,6 +53,13 @@ const UIStrings = {
53
53
  */
54
54
  sS: '{PH1}: {PH2}',
55
55
  /**
56
+ *@description Text with three placeholders separated by a colon and a comma
57
+ *@example {Node removed} PH1
58
+ *@example {div#id1} PH2
59
+ *@example {checked} PH3
60
+ */
61
+ sSS: '{PH1}: {PH2}, {PH3}',
62
+ /**
56
63
  *@description Text exposed to screen readers on checked items.
57
64
  */
58
65
  checked: 'checked',
@@ -196,19 +203,26 @@ export class DOMBreakpointsSidebarPane extends UI.Widget.VBox implements
196
203
  description.textContent = breakpointTypeLabel ? breakpointTypeLabel() : null;
197
204
  const breakpointTypeText = breakpointTypeLabel ? breakpointTypeLabel() : '';
198
205
  UI.ARIAUtils.setAccessibleName(checkboxElement, breakpointTypeText);
206
+ const checkedStateText = item.enabled ? i18nString(UIStrings.checked) : i18nString(UIStrings.unchecked);
199
207
  const linkifiedNode = document.createElement('monospace');
200
208
  linkifiedNode.style.display = 'block';
201
209
  labelElement.appendChild(linkifiedNode);
202
210
  void Common.Linkifier.Linkifier.linkify(item.node, {preventKeyboardFocus: true, tooltip: undefined})
203
211
  .then(linkified => {
204
212
  linkifiedNode.appendChild(linkified);
213
+ // Give the checkbox an aria-label as it is required for all form element
205
214
  UI.ARIAUtils.setAccessibleName(
206
215
  checkboxElement, i18nString(UIStrings.sS, {PH1: breakpointTypeText, PH2: linkified.deepTextContent()}));
216
+ // The parent list element is the one that actually gets focused.
217
+ // Assign it an aria-label with complete information for the screen reader to read out properly
218
+ UI.ARIAUtils.setAccessibleName(
219
+ element,
220
+ i18nString(
221
+ UIStrings.sSS, {PH1: breakpointTypeText, PH2: linkified.deepTextContent(), PH3: checkedStateText}));
207
222
  });
208
223
 
209
224
  labelElement.appendChild(description);
210
225
 
211
- const checkedStateText = item.enabled ? i18nString(UIStrings.checked) : i18nString(UIStrings.unchecked);
212
226
  if (item === this.#highlightedBreakpoint) {
213
227
  element.classList.add('breakpoint-hit');
214
228
  UI.ARIAUtils.setDescription(element, i18nString(UIStrings.sBreakpointHit, {PH1: checkedStateText}));
@@ -146,14 +146,22 @@ const UIStrings = {
146
146
  */
147
147
  desktop: 'Desktop',
148
148
  /**
149
- *@description Text for option to enable simulated throttling in Lighthouse Panel
150
- */
151
- simulatedThrottling: 'Simulated throttling',
149
+ * @description Text for an option to select a throttling method.
150
+ */
151
+ throttlingMethod: 'Throttling method',
152
152
  /**
153
- *@description Tooltip text that appears when hovering over the 'Simulated Throttling' checkbox in the settings pane opened by clicking the setting cog in the start view of the audits panel
154
- */
153
+ * @description Text for an option in a dropdown to use simulated throttling. This is the default setting.
154
+ */
155
+ simulatedThrottling: 'Simulated throttling (default)',
156
+ /**
157
+ * @description Text for an option in a dropdown to use DevTools throttling. This option should only be used by advanced users.
158
+ */
159
+ devtoolsThrottling: 'DevTools throttling (advanced)',
160
+ /**
161
+ * @description Tooltip text that appears when hovering over the 'Simulated Throttling' checkbox in the settings pane opened by clicking the setting cog in the start view of the audits panel
162
+ */
155
163
  simulateASlowerPageLoadBasedOn:
156
- 'Simulate a slower page load, based on data from an initial unthrottled load. If disabled, the page is actually slowed with applied throttling.',
164
+ 'Simulated throttling simulates a slower page load based on data from an initial unthrottled load. DevTools throttling actually slows down the page.',
157
165
  /**
158
166
  *@description Text of checkbox to reset storage features prior to running audits in Lighthouse
159
167
  */
@@ -515,17 +523,24 @@ export const RuntimeSettings: RuntimeSetting[] = [
515
523
  {
516
524
  // This setting is disabled, but we keep it around to show in the UI.
517
525
  setting: Common.Settings.Settings.instance().createSetting(
518
- 'lighthouse.throttling', true, Common.Settings.SettingStorageType.Synced),
519
- title: i18nLazyString(UIStrings.simulatedThrottling),
526
+ 'lighthouse.throttling', 'simulate', Common.Settings.SettingStorageType.Synced),
527
+ title: i18nLazyString(UIStrings.throttlingMethod),
520
528
  // We will disable this when we have a Lantern trace viewer within DevTools.
521
529
  learnMore:
522
530
  'https://github.com/GoogleChrome/lighthouse/blob/master/docs/throttling.md#devtools-lighthouse-panel-throttling' as
523
531
  Platform.DevToolsPath.UrlString,
524
532
  description: i18nLazyString(UIStrings.simulateASlowerPageLoadBasedOn),
525
533
  setFlags: (flags: Flags, value: string|boolean): void => {
526
- flags.throttlingMethod = value ? 'simulate' : 'devtools';
534
+ if (typeof value === 'string') {
535
+ flags.throttlingMethod = value;
536
+ } else {
537
+ flags.throttlingMethod = value ? 'simulate' : 'devtools';
538
+ }
527
539
  },
528
- options: undefined,
540
+ options: [
541
+ {label: i18nLazyString(UIStrings.simulatedThrottling), value: 'simulate'},
542
+ {label: i18nLazyString(UIStrings.devtoolsThrottling), value: 'devtools'},
543
+ ],
529
544
  },
530
545
  {
531
546
  setting: Common.Settings.Settings.instance().createSetting(
@@ -103,6 +103,30 @@ export class StartView extends UI.Widget.Widget {
103
103
  }
104
104
  }
105
105
 
106
+ protected populateRuntimeSettingAsToolbarDropdown(settingName: string, toolbar: UI.Toolbar.Toolbar): void {
107
+ const runtimeSetting = RuntimeSettings.find(item => item.setting.name === settingName);
108
+ if (!runtimeSetting || !runtimeSetting.title) {
109
+ throw new Error(`${settingName} is not a setting with a title`);
110
+ }
111
+
112
+ const options = runtimeSetting.options?.map(option => ({label: option.label(), value: option.value})) || [];
113
+
114
+ runtimeSetting.setting.setTitle(runtimeSetting.title());
115
+ const control = new UI.Toolbar.ToolbarSettingComboBox(
116
+ options,
117
+ runtimeSetting.setting as Common.Settings.Setting<string>,
118
+ runtimeSetting.title(),
119
+ );
120
+ control.setTitle(runtimeSetting.description());
121
+ toolbar.appendToolbarItem(control);
122
+ if (runtimeSetting.learnMore) {
123
+ const link =
124
+ UI.XLink.XLink.create(runtimeSetting.learnMore, i18nString(UIStrings.learnMore), 'lighthouse-learn-more');
125
+ link.style.padding = '5px';
126
+ control.element.appendChild(link);
127
+ }
128
+ }
129
+
106
130
  protected populateFormControls(fragment: UI.Fragment.Fragment, mode?: string): void {
107
131
  // Populate the device type
108
132
  const deviceTypeFormElements = fragment.$('device-type-form-elements');
@@ -134,7 +158,7 @@ export class StartView extends UI.Widget.Widget {
134
158
  protected render(): void {
135
159
  this.populateRuntimeSettingAsToolbarCheckbox('lighthouse.legacy_navigation', this.settingsToolbarInternal);
136
160
  this.populateRuntimeSettingAsToolbarCheckbox('lighthouse.clear_storage', this.settingsToolbarInternal);
137
- this.populateRuntimeSettingAsToolbarCheckbox('lighthouse.throttling', this.settingsToolbarInternal);
161
+ this.populateRuntimeSettingAsToolbarDropdown('lighthouse.throttling', this.settingsToolbarInternal);
138
162
 
139
163
  this.startButton = UI.UIUtils.createTextButton(
140
164
  i18nString(UIStrings.generateReport),
@@ -49,7 +49,7 @@ export class StartViewFR extends StartView {
49
49
  protected render(): void {
50
50
  this.populateRuntimeSettingAsToolbarCheckbox('lighthouse.legacy_navigation', this.settingsToolbarInternal);
51
51
  this.populateRuntimeSettingAsToolbarCheckbox('lighthouse.clear_storage', this.settingsToolbarInternal);
52
- this.populateRuntimeSettingAsToolbarCheckbox('lighthouse.throttling', this.settingsToolbarInternal);
52
+ this.populateRuntimeSettingAsToolbarDropdown('lighthouse.throttling', this.settingsToolbarInternal);
53
53
 
54
54
  const {mode} = this.controller.getFlags();
55
55
  this.populateStartButton(mode);
@@ -52,6 +52,10 @@ details summary input {
52
52
  user-select: text;
53
53
  }
54
54
 
55
+ div.raw-headers-row {
56
+ display: block;
57
+ }
58
+
55
59
  .row:first-of-type {
56
60
  margin-top: 2px;
57
61
  }
@@ -103,4 +107,5 @@ details summary input {
103
107
  font-family: var(--source-code-font-family);
104
108
  font-size: var(--source-code-font-size);
105
109
  white-space: pre-wrap;
110
+ word-break: break-all;
106
111
  }
@@ -6,12 +6,14 @@ import * as Common from '../../../core/common/common.js';
6
6
  import * as i18n from '../../../core/i18n/i18n.js';
7
7
  import {assertNotNullOrUndefined} from '../../../core/platform/platform.js';
8
8
  import * as SDK from '../../../core/sdk/sdk.js';
9
+ import * as Buttons from '../../../ui/components/buttons/buttons.js';
9
10
  import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
10
11
  import * as UI from '../../../ui/legacy/legacy.js';
11
12
  import * as LitHtml from '../../../ui/lit-html/lit-html.js';
12
13
 
13
14
  import requestHeadersViewStyles from './RequestHeadersView.css.js';
14
15
 
16
+ const RAW_HEADER_CUTOFF = 3000;
15
17
  const {render, html} = LitHtml;
16
18
 
17
19
  const UIStrings = {
@@ -64,6 +66,10 @@ const UIStrings = {
64
66
  */
65
67
  responseHeaders: 'Response Headers',
66
68
  /**
69
+ *@description Text to show more content
70
+ */
71
+ showMore: 'Show more',
72
+ /**
67
73
  *@description HTTP response code
68
74
  */
69
75
  statusCode: 'Status Code',
@@ -117,6 +123,8 @@ export class RequestHeadersComponent extends HTMLElement {
117
123
  #request?: Readonly<SDK.NetworkRequest.NetworkRequest>;
118
124
  #showResponseHeadersText = false;
119
125
  #showRequestHeadersText = false;
126
+ #showResponseHeadersTextFull = false;
127
+ #showRequestHeadersTextFull = false;
120
128
 
121
129
  set data(data: RequestHeadersComponentData) {
122
130
  this.#request = data.request;
@@ -161,9 +169,8 @@ export class RequestHeadersComponent extends HTMLElement {
161
169
  } as CategoryData}
162
170
  aria-label=${i18nString(UIStrings.responseHeaders)}
163
171
  >
164
- ${this.#showResponseHeadersText ? html`
165
- <div class="row raw-headers">${this.#request.responseHeadersText.trim()}</div>
166
- ` : html`
172
+ ${this.#showResponseHeadersText ?
173
+ this.#renderRawHeaders(this.#request.responseHeadersText, true) : html`
167
174
  ${this.#request.sortedResponseHeaders.map(header => html`
168
175
  <div class="row">
169
176
  <div class="header-name">${header.name}:</div>
@@ -198,9 +205,8 @@ export class RequestHeadersComponent extends HTMLElement {
198
205
  } as CategoryData}
199
206
  aria-label=${i18nString(UIStrings.requestHeaders)}
200
207
  >
201
- ${(this.#showRequestHeadersText && requestHeadersText) ? html`
202
- <div class="row raw-headers">${requestHeadersText.trim()}</div>
203
- ` : html`
208
+ ${(this.#showRequestHeadersText && requestHeadersText) ?
209
+ this.#renderRawHeaders(requestHeadersText, false) : html`
204
210
  ${this.#request.requestHeaders().map(header => html`
205
211
  <div class="row">
206
212
  <div class="header-name">${header.name}:</div>
@@ -212,6 +218,50 @@ export class RequestHeadersComponent extends HTMLElement {
212
218
  `;
213
219
  }
214
220
 
221
+ #renderRawHeaders(rawHeadersText: string, forResponseHeaders: boolean): LitHtml.TemplateResult {
222
+ const trimmed = rawHeadersText.trim();
223
+ const showFull = forResponseHeaders ? this.#showResponseHeadersTextFull : this.#showRequestHeadersTextFull;
224
+ const isShortened = !showFull && trimmed.length > RAW_HEADER_CUTOFF;
225
+
226
+ const showMore = ():void => {
227
+ if (forResponseHeaders) {
228
+ this.#showResponseHeadersTextFull = true;
229
+ } else {
230
+ this.#showRequestHeadersTextFull = true;
231
+ }
232
+ this.#render();
233
+ };
234
+
235
+ const onContextMenuOpen = (event: Event): void => {
236
+ const showFull = forResponseHeaders ? this.#showResponseHeadersTextFull : this.#showRequestHeadersTextFull;
237
+ if (!showFull) {
238
+ const contextMenu = new UI.ContextMenu.ContextMenu(event);
239
+ const section = contextMenu.newSection();
240
+ section.appendItem(i18nString(UIStrings.showMore), showMore);
241
+ void contextMenu.show();
242
+ }
243
+ };
244
+
245
+ const addContextMenuListener = (el: Element):void => {
246
+ if (isShortened) {
247
+ el.addEventListener('contextmenu', onContextMenuOpen);
248
+ }
249
+ };
250
+
251
+ return html`
252
+ <div class="row raw-headers-row" on-render=${ComponentHelpers.Directives.nodeRenderedCallback(addContextMenuListener)}>
253
+ <div class="raw-headers">${isShortened ? trimmed.substring(0, RAW_HEADER_CUTOFF) : trimmed}</div>
254
+ ${isShortened ? html`
255
+ <${Buttons.Button.Button.litTagName}
256
+ .size=${Buttons.Button.Size.SMALL}
257
+ .variant=${Buttons.Button.Variant.SECONDARY}
258
+ @click=${showMore}
259
+ >${i18nString(UIStrings.showMore)}</${Buttons.Button.Button.litTagName}>
260
+ ` : LitHtml.nothing}
261
+ </div>
262
+ `;
263
+ }
264
+
215
265
  #renderGeneralSection(): LitHtml.TemplateResult {
216
266
  assertNotNullOrUndefined(this.#request);
217
267
 
@@ -31,6 +31,7 @@ export interface TreeOutlineData<TreeNodeDataType> {
31
31
  */
32
32
  tree: readonly TreeNode<TreeNodeDataType>[];
33
33
  filter?: (node: TreeNodeDataType) => FilterOption;
34
+ compact?: boolean;
34
35
  }
35
36
 
36
37
  export function defaultRenderer(node: TreeNode<string>): LitHtml.TemplateResult {
@@ -111,6 +112,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
111
112
  return LitHtml.html`${String(node.treeNodeData)}`;
112
113
  };
113
114
  #nodeFilter?: ((node: TreeNodeDataType) => FilterOption);
115
+ #compact = false;
114
116
 
115
117
  /**
116
118
  * scheduledRender = render() has been called and scheduled a render.
@@ -155,6 +157,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
155
157
  this.#defaultRenderer = data.defaultRenderer;
156
158
  this.#treeData = data.tree;
157
159
  this.#nodeFilter = data.filter;
160
+ this.#compact = data.compact || false;
158
161
 
159
162
  if (!this.#hasRenderedAtLeastOnce) {
160
163
  this.#selectedTreeNode = this.#treeData[0];
@@ -454,6 +457,7 @@ export class TreeOutline<TreeNodeDataType> extends HTMLElement {
454
457
  parent: isExpandableNode(node),
455
458
  selected: this.#isSelectedNode(node),
456
459
  'is-top-level': depth === 0,
460
+ compact: this.#compact,
457
461
  });
458
462
  const ariaExpandedAttribute =
459
463
  LitHtml.Directives.ifDefined(isExpandableNode(node) ? String(nodeIsExpanded) : undefined);