chrome-devtools-frontend 1.0.1007307 → 1.0.1007778

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.
@@ -714,11 +714,14 @@ grd_files_debug_sources = [
714
714
  "front_end/models/emulation/DeviceModeModel.js",
715
715
  "front_end/models/emulation/EmulatedDevices.js",
716
716
  "front_end/models/extensions/ExtensionAPI.js",
717
+ "front_end/models/extensions/ExtensionEndpoint.js",
717
718
  "front_end/models/extensions/ExtensionPanel.js",
718
719
  "front_end/models/extensions/ExtensionServer.js",
719
720
  "front_end/models/extensions/ExtensionTraceProvider.js",
720
721
  "front_end/models/extensions/ExtensionView.js",
721
722
  "front_end/models/extensions/LanguageExtensionEndpoint.js",
723
+ "front_end/models/extensions/RecorderExtensionEndpoint.js",
724
+ "front_end/models/extensions/RecorderPluginManager.js",
722
725
  "front_end/models/formatter/FormatterWorkerPool.js",
723
726
  "front_end/models/formatter/ScriptFormatter.js",
724
727
  "front_end/models/formatter/SourceFormatter.js",
@@ -94,6 +94,7 @@ export namespace Chrome {
94
94
  panels: Panels;
95
95
  inspectedWindow: InspectedWindow;
96
96
  languageServices: LanguageExtensions;
97
+ recorder: RecorderExtensions;
97
98
  }
98
99
 
99
100
  export interface ExperimentalDevToolsAPI {
@@ -170,6 +171,10 @@ export namespace Chrome {
170
171
  payload: unknown;
171
172
  }
172
173
 
174
+ export interface RecorderExtensionPlugin {
175
+ stringify(obj: Record<string, any>): Promise<string>;
176
+ }
177
+
173
178
  export interface LanguageExtensionPlugin {
174
179
  /**
175
180
  * A new raw module has been loaded. If the raw wasm module references an external debug info module, its URL will be
@@ -272,6 +277,11 @@ export namespace Chrome {
272
277
  unregisterLanguageExtensionPlugin(plugin: LanguageExtensionPlugin): Promise<void>;
273
278
  }
274
279
 
280
+ export interface RecorderExtensions {
281
+ registerRecorderExtensionPlugin(plugin: RecorderExtensionPlugin, pluginName: string): Promise<void>;
282
+ unregisterRecorderExtensionPlugin(plugin: RecorderExtensionPlugin): Promise<void>;
283
+ }
284
+
275
285
  export interface Chrome {
276
286
  devtools: DevToolsAPI;
277
287
  experimental: {devtools: ExperimentalDevToolsAPI};
@@ -3803,6 +3803,9 @@
3803
3803
  "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | sS": {
3804
3804
  "message": "{PH1}: {PH2}"
3805
3805
  },
3806
+ "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | sSS": {
3807
+ "message": "{PH1}: {PH2}, {PH3}"
3808
+ },
3806
3809
  "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | subtreeModified": {
3807
3810
  "message": "Subtree modified"
3808
3811
  },
@@ -5951,6 +5954,9 @@
5951
5954
  "panels/lighthouse/LighthouseController.ts | desktop": {
5952
5955
  "message": "Desktop"
5953
5956
  },
5957
+ "panels/lighthouse/LighthouseController.ts | devtoolsThrottling": {
5958
+ "message": "DevTools throttling (advanced)"
5959
+ },
5954
5960
  "panels/lighthouse/LighthouseController.ts | doesThisPageFollowBestPractices": {
5955
5961
  "message": "Does this page follow best practices for modern web development"
5956
5962
  },
@@ -6015,10 +6021,10 @@
6015
6021
  "message": "SEO"
6016
6022
  },
6017
6023
  "panels/lighthouse/LighthouseController.ts | simulateASlowerPageLoadBasedOn": {
6018
- "message": "Simulate a slower page load, based on data from an initial unthrottled load. If disabled, the page is actually slowed with applied throttling."
6024
+ "message": "Simulated throttling simulates a slower page load based on data from an initial unthrottled load. DevTools throttling actually slows down the page."
6019
6025
  },
6020
6026
  "panels/lighthouse/LighthouseController.ts | simulatedThrottling": {
6021
- "message": "Simulated throttling"
6027
+ "message": "Simulated throttling (default)"
6022
6028
  },
6023
6029
  "panels/lighthouse/LighthouseController.ts | snapshot": {
6024
6030
  "message": "Snapshot"
@@ -6032,6 +6038,9 @@
6032
6038
  "panels/lighthouse/LighthouseController.ts | thereMayBeStoredDataAffectingSingular": {
6033
6039
  "message": "There may be stored data affecting loading performance in this location: {PH1}. Audit this page in an incognito window to prevent those resources from affecting your scores."
6034
6040
  },
6041
+ "panels/lighthouse/LighthouseController.ts | throttlingMethod": {
6042
+ "message": "Throttling method"
6043
+ },
6035
6044
  "panels/lighthouse/LighthouseController.ts | timespan": {
6036
6045
  "message": "Timespan"
6037
6046
  },
@@ -6653,6 +6662,9 @@
6653
6662
  "panels/network/components/RequestHeadersView.ts | responseHeaders": {
6654
6663
  "message": "Response Headers"
6655
6664
  },
6665
+ "panels/network/components/RequestHeadersView.ts | showMore": {
6666
+ "message": "Show more"
6667
+ },
6656
6668
  "panels/network/components/RequestHeadersView.ts | statusCode": {
6657
6669
  "message": "Status Code"
6658
6670
  },
@@ -3803,6 +3803,9 @@
3803
3803
  "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | sS": {
3804
3804
  "message": "{PH1}: {PH2}"
3805
3805
  },
3806
+ "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | sSS": {
3807
+ "message": "{PH1}: {PH2}, {PH3}"
3808
+ },
3806
3809
  "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | subtreeModified": {
3807
3810
  "message": "Ŝúb̂t́r̂éê ḿôd́îf́îéd̂"
3808
3811
  },
@@ -5951,6 +5954,9 @@
5951
5954
  "panels/lighthouse/LighthouseController.ts | desktop": {
5952
5955
  "message": "D̂éŝḱt̂óp̂"
5953
5956
  },
5957
+ "panels/lighthouse/LighthouseController.ts | devtoolsThrottling": {
5958
+ "message": "D̂év̂T́ôól̂ś t̂h́r̂ót̂t́l̂ín̂ǵ (âd́v̂án̂ćêd́)"
5959
+ },
5954
5960
  "panels/lighthouse/LighthouseController.ts | doesThisPageFollowBestPractices": {
5955
5961
  "message": "D̂óêś t̂h́îś p̂áĝé f̂ól̂ĺôẃ b̂éŝt́ p̂ŕâćt̂íĉéŝ f́ôŕ m̂ód̂ér̂ń ŵéb̂ d́êv́êĺôṕm̂én̂t́"
5956
5962
  },
@@ -6015,10 +6021,10 @@
6015
6021
  "message": "ŜÉÔ"
6016
6022
  },
6017
6023
  "panels/lighthouse/LighthouseController.ts | simulateASlowerPageLoadBasedOn": {
6018
- "message": "Ŝím̂úl̂át̂é â śl̂óŵér̂ ṕâǵê ĺôád̂, b́âśêd́ ôń d̂át̂á f̂ŕôḿ âń îńît́îál̂ ún̂t́ĥŕôt́t̂ĺêd́ l̂óâd́. Îf́ d̂íŝáb̂ĺêd́, t̂h́ê ṕâǵê ŝ áĉt́ûál̂ĺl̂óŵéd̂ ît́ĥ ṕl̂êd́ t̂h́r̂ót̂t́l̂ín̂ǵ."
6024
+ "message": "Ŝím̂úl̂át̂é t́ĥŕôt́t̂ĺîńĝ śîḿûĺt́êś śl̂óŵér̂ ṕâǵê ĺôád̂ b́âśêd́ ôń d̂át̂á f̂ŕôḿ âń îńît́îál̂ ún̂t́ĥŕôt́t̂ĺêd́ l̂óâd́. D̂év̂T́ôól̂ś t̂h́r̂ót̂t́l̂ín̂ âćt̂úâĺl̂ý ŝĺôẃŝ d́ôẃn̂ t́ĥáĝé."
6019
6025
  },
6020
6026
  "panels/lighthouse/LighthouseController.ts | simulatedThrottling": {
6021
- "message": "Ŝím̂úl̂át̂éd̂ t́ĥŕôt́t̂ĺîńĝ"
6027
+ "message": "Ŝím̂úl̂át̂éd̂ t́ĥŕôt́t̂ĺîńĝ (d́êf́âúl̂t́)"
6022
6028
  },
6023
6029
  "panels/lighthouse/LighthouseController.ts | snapshot": {
6024
6030
  "message": "Ŝńâṕŝh́ôt́"
@@ -6032,6 +6038,9 @@
6032
6038
  "panels/lighthouse/LighthouseController.ts | thereMayBeStoredDataAffectingSingular": {
6033
6039
  "message": "T̂h́êŕê ḿâý b̂é ŝt́ôŕêd́ d̂át̂á âf́f̂éĉt́îńĝ ĺôád̂ín̂ǵ p̂ér̂f́ôŕm̂án̂ćê ín̂ t́ĥíŝ ĺôćât́îón̂: {PH1}. Áûd́ît́ t̂h́îś p̂áĝé îń âń îńĉóĝńît́ô ẃîńd̂óŵ t́ô ṕr̂év̂én̂t́ t̂h́ôśê ŕêśôúr̂ćêś f̂ŕôḿ âf́f̂éĉt́îńĝ ýôúr̂ śĉór̂éŝ."
6034
6040
  },
6041
+ "panels/lighthouse/LighthouseController.ts | throttlingMethod": {
6042
+ "message": "T̂h́r̂ót̂t́l̂ín̂ǵ m̂ét̂h́ôd́"
6043
+ },
6035
6044
  "panels/lighthouse/LighthouseController.ts | timespan": {
6036
6045
  "message": "T̂ím̂éŝṕâń"
6037
6046
  },
@@ -6653,6 +6662,9 @@
6653
6662
  "panels/network/components/RequestHeadersView.ts | responseHeaders": {
6654
6663
  "message": "R̂éŝṕôńŝé Ĥéâd́êŕŝ"
6655
6664
  },
6665
+ "panels/network/components/RequestHeadersView.ts | showMore": {
6666
+ "message": "Ŝh́ôẃ m̂ór̂é"
6667
+ },
6656
6668
  "panels/network/components/RequestHeadersView.ts | statusCode": {
6657
6669
  "message": "Ŝt́ât́ûś Ĉód̂é"
6658
6670
  },
@@ -143,10 +143,7 @@ async function invokeLH(action: string, args: any): Promise<unknown> {
143
143
  const {page} = puppeteerConnection;
144
144
  const configContext = {
145
145
  logLevel: flags.logLevel,
146
- settingsOverrides: {
147
- channel: flags.channel,
148
- locale: flags.locale,
149
- },
146
+ settingsOverrides: flags,
150
147
  };
151
148
 
152
149
  if (action === 'snapshot') {
@@ -100,6 +100,18 @@ LighthouseTestRunner._checkboxStateLabel = function(checkboxContainer) {
100
100
  return `[${checkedLabel}] ${label}`;
101
101
  };
102
102
 
103
+ /**
104
+ * @param {?Element} combobox
105
+ * @return {string}
106
+ */
107
+ LighthouseTestRunner._comboboxStateLabel = function(combobox) {
108
+ if (!combobox) {
109
+ return 'missing';
110
+ }
111
+
112
+ return `${combobox.ariaLabel}: ${combobox.value}`;
113
+ };
114
+
103
115
  /**
104
116
  * @param {?Element} button
105
117
  * @return {string}
@@ -130,6 +142,10 @@ LighthouseTestRunner.dumpStartAuditState = function() {
130
142
  TestRunner.addResult(LighthouseTestRunner._checkboxStateLabel(element));
131
143
  });
132
144
 
145
+ for (const combobox of toolbarShadowRoot.querySelectorAll('select')) {
146
+ TestRunner.addResult(LighthouseTestRunner._comboboxStateLabel(combobox));
147
+ }
148
+
133
149
  const helpText = containerElement.querySelector('.lighthouse-help-text');
134
150
  if (!helpText.classList.contains('hidden')) {
135
151
  TestRunner.addResult(`Help text: ${helpText.textContent}`);
@@ -86,6 +86,7 @@ export namespace PrivateAPI {
86
86
  Unsubscribe = 'unsubscribe',
87
87
  UpdateButton = 'updateButton',
88
88
  RegisterLanguageExtensionPlugin = 'registerLanguageExtensionPlugin',
89
+ RegisterRecorderExtensionPlugin = 'registerRecorderExtensionPlugin',
89
90
  }
90
91
 
91
92
  export const enum LanguageExtensionPluginCommands {
@@ -108,6 +109,14 @@ export namespace PrivateAPI {
108
109
  UnregisteredLanguageExtensionPlugin = 'unregisteredLanguageExtensionPlugin',
109
110
  }
110
111
 
112
+ export const enum RecorderExtensionPluginCommands {
113
+ Stringify = 'stringify',
114
+ }
115
+
116
+ export const enum RecorderExtensionPluginEvents {
117
+ UnregisteredRecorderExtensionPlugin = 'unregisteredRecorderExtensionPlugin',
118
+ }
119
+
111
120
  export interface EvaluateOptions {
112
121
  frameURL?: string;
113
122
  useContentScriptContext?: boolean;
@@ -120,6 +129,11 @@ export namespace PrivateAPI {
120
129
  port: MessagePort,
121
130
  supportedScriptTypes: PublicAPI.Chrome.DevTools.SupportedScriptTypes,
122
131
  };
132
+ type RegisterRecorderExtensionPluginRequest = {
133
+ command: Commands.RegisterRecorderExtensionPlugin,
134
+ pluginName: string,
135
+ port: MessagePort,
136
+ };
123
137
  type SubscribeRequest = {command: Commands.Subscribe, type: string};
124
138
  type UnsubscribeRequest = {command: Commands.Unsubscribe, type: string};
125
139
  type AddRequestHeadersRequest = {
@@ -182,13 +196,13 @@ export namespace PrivateAPI {
182
196
  type GetHARRequest = {command: Commands.GetHAR};
183
197
  type GetPageResourcesRequest = {command: Commands.GetPageResources};
184
198
 
185
- export type ServerRequests = RegisterLanguageExtensionPluginRequest|SubscribeRequest|UnsubscribeRequest|
186
- AddRequestHeadersRequest|ApplyStyleSheetRequest|CreatePanelRequest|ShowPanelRequest|CreateToolbarButtonRequest|
187
- UpdateButtonRequest|CompleteTraceSessionRequest|CreateSidebarPaneRequest|SetSidebarHeightRequest|
188
- SetSidebarContentRequest|SetSidebarPageRequest|OpenResourceRequest|SetOpenResourceHandlerRequest|
189
- SetThemeChangeHandlerRequest|ReloadRequest|EvaluateOnInspectedPageRequest|GetRequestContentRequest|
190
- GetResourceContentRequest|SetResourceContentRequest|AddTraceProviderRequest|ForwardKeyboardEventRequest|
191
- GetHARRequest|GetPageResourcesRequest;
199
+ export type ServerRequests = RegisterRecorderExtensionPluginRequest|RegisterLanguageExtensionPluginRequest|
200
+ SubscribeRequest|UnsubscribeRequest|AddRequestHeadersRequest|ApplyStyleSheetRequest|CreatePanelRequest|
201
+ ShowPanelRequest|CreateToolbarButtonRequest|UpdateButtonRequest|CompleteTraceSessionRequest|
202
+ CreateSidebarPaneRequest|SetSidebarHeightRequest|SetSidebarContentRequest|SetSidebarPageRequest|
203
+ OpenResourceRequest|SetOpenResourceHandlerRequest|SetThemeChangeHandlerRequest|ReloadRequest|
204
+ EvaluateOnInspectedPageRequest|GetRequestContentRequest|GetResourceContentRequest|SetResourceContentRequest|
205
+ AddTraceProviderRequest|ForwardKeyboardEventRequest|GetHARRequest|GetPageResourcesRequest;
192
206
  export type ExtensionServerRequestMessage = PrivateAPI.ServerRequests&{requestId?: number};
193
207
 
194
208
  type AddRawModuleRequest = {
@@ -256,6 +270,13 @@ export namespace PrivateAPI {
256
270
  RawLocationToSourceLocationRequest|GetScopeInfoRequest|ListVariablesInScopeRequest|RemoveRawModuleRequest|
257
271
  GetTypeInfoRequest|GetFormatterRequest|GetInspectableAddressRequest|GetFunctionInfoRequest|
258
272
  GetInlinedFunctionRangesRequest|GetInlinedCalleesRangesRequest|GetMappedLinesRequest;
273
+
274
+ type StringifyRequest = {
275
+ method: RecorderExtensionPluginCommands.Stringify,
276
+ parameters: {recording: Record<string, unknown>},
277
+ };
278
+
279
+ export type RecorderExtensionRequests = StringifyRequest;
259
280
  }
260
281
 
261
282
  declare global {
@@ -264,7 +285,7 @@ declare global {
264
285
  (extensionInfo: ExtensionDescriptor, inspectedTabId: string, themeName: string, keysToForward: number[],
265
286
  testHook:
266
287
  (extensionServer: APIImpl.ExtensionServerClient, extensionAPI: APIImpl.InspectorExtensionAPI) => unknown,
267
- injectedScriptId: number) => void;
288
+ injectedScriptId: number, targetWindow?: Window) => void;
268
289
  buildExtensionAPIInjectedScript(
269
290
  extensionInfo: ExtensionDescriptor, inspectedTabId: string, themeName: string, keysToForward: number[],
270
291
  testHook: undefined|((extensionServer: unknown, extensionAPI: unknown) => unknown)): string;
@@ -283,6 +304,7 @@ export type ExtensionDescriptor = {
283
304
  namespace APIImpl {
284
305
  export interface InspectorExtensionAPI {
285
306
  languageServices: PublicAPI.Chrome.DevTools.LanguageExtensions;
307
+ recorder: PublicAPI.Chrome.DevTools.RecorderExtensions;
286
308
  timeline: Timeline;
287
309
  network: PublicAPI.Chrome.DevTools.Network;
288
310
  panels: PublicAPI.Chrome.DevTools.Panels;
@@ -357,6 +379,10 @@ namespace APIImpl {
357
379
  _plugins: Map<PublicAPI.Chrome.DevTools.LanguageExtensionPlugin, MessagePort>;
358
380
  }
359
381
 
382
+ export interface RecorderExtensions extends PublicAPI.Chrome.DevTools.RecorderExtensions {
383
+ _plugins: Map<PublicAPI.Chrome.DevTools.RecorderExtensionPlugin, MessagePort>;
384
+ }
385
+
360
386
  export interface ExtensionPanel extends ExtensionView, PublicAPI.Chrome.DevTools.ExtensionPanel {
361
387
  show(): void;
362
388
  }
@@ -392,7 +418,7 @@ namespace APIImpl {
392
418
  self.injectedExtensionAPI = function(
393
419
  extensionInfo: ExtensionDescriptor, inspectedTabId: string, themeName: string, keysToForward: number[],
394
420
  testHook: (extensionServer: APIImpl.ExtensionServerClient, extensionAPI: APIImpl.InspectorExtensionAPI) => unknown,
395
- injectedScriptId: number): void {
421
+ injectedScriptId: number, targetWindowForTest?: Window): void {
396
422
  const keysToForwardSet = new Set<number>(keysToForward);
397
423
  const chrome = window.chrome || {};
398
424
 
@@ -473,6 +499,7 @@ self.injectedExtensionAPI = function(
473
499
  this.network = new (Constructor(Network))();
474
500
  this.timeline = new (Constructor(Timeline))();
475
501
  this.languageServices = new (Constructor(LanguageServicesAPI))();
502
+ this.recorder = new (Constructor(RecorderServicesAPI))();
476
503
  defineDeprecatedProperty(this, 'webInspector', 'resources', 'network');
477
504
  }
478
505
 
@@ -671,6 +698,60 @@ self.injectedExtensionAPI = function(
671
698
  __proto__: ExtensionViewImpl.prototype,
672
699
  };
673
700
 
701
+ function RecorderServicesAPIImpl(this: APIImpl.RecorderExtensions): void {
702
+ this._plugins = new Map();
703
+ }
704
+
705
+ (RecorderServicesAPIImpl.prototype as
706
+ Pick<APIImpl.RecorderExtensions, 'registerRecorderExtensionPlugin'|'unregisterRecorderExtensionPlugin'>) = {
707
+ registerRecorderExtensionPlugin: async function(
708
+ this: APIImpl.RecorderExtensions, plugin: PublicAPI.Chrome.DevTools.RecorderExtensionPlugin,
709
+ pluginName: string): Promise<void> {
710
+ if (this._plugins.has(plugin)) {
711
+ throw new Error(`Tried to register plugin '${pluginName}' twice`);
712
+ }
713
+ const channel = new MessageChannel();
714
+ const port = channel.port1;
715
+ this._plugins.set(plugin, port);
716
+ port.onmessage = ({data}: MessageEvent<{requestId: number}&PrivateAPI.RecorderExtensionRequests>): void => {
717
+ const {requestId} = data;
718
+ dispatchMethodCall(data)
719
+ .then(result => port.postMessage({requestId, result}))
720
+ .catch(error => port.postMessage({requestId, error: {message: error.message}}));
721
+ };
722
+
723
+ function dispatchMethodCall(request: PrivateAPI.RecorderExtensionRequests): Promise<unknown> {
724
+ switch (request.method) {
725
+ case PrivateAPI.RecorderExtensionPluginCommands.Stringify:
726
+ return plugin.stringify(request.parameters.recording);
727
+ default:
728
+ throw new Error(`'${request.method}' is not recognized`);
729
+ }
730
+ }
731
+
732
+ await new Promise<void>(resolve => {
733
+ extensionServer.sendRequest(
734
+ {
735
+ command: PrivateAPI.Commands.RegisterRecorderExtensionPlugin,
736
+ pluginName,
737
+ port: channel.port2,
738
+ },
739
+ () => resolve(), [channel.port2]);
740
+ });
741
+ },
742
+
743
+ unregisterRecorderExtensionPlugin: async function(
744
+ this: APIImpl.RecorderExtensions, plugin: PublicAPI.Chrome.DevTools.RecorderExtensionPlugin): Promise<void> {
745
+ const port = this._plugins.get(plugin);
746
+ if (!port) {
747
+ throw new Error('Tried to unregister a plugin that was not previously registered');
748
+ }
749
+ this._plugins.delete(plugin);
750
+ port.postMessage({event: PrivateAPI.RecorderExtensionPluginEvents.UnregisteredRecorderExtensionPlugin});
751
+ port.close();
752
+ },
753
+ };
754
+
674
755
  function LanguageServicesAPIImpl(this: APIImpl.LanguageExtensions): void {
675
756
  this._plugins = new Map();
676
757
  }
@@ -787,6 +868,7 @@ self.injectedExtensionAPI = function(
787
868
  }
788
869
 
789
870
  const LanguageServicesAPI = declareInterfaceClass(LanguageServicesAPIImpl);
871
+ const RecorderServicesAPI = declareInterfaceClass(RecorderServicesAPIImpl);
790
872
  const Button = declareInterfaceClass(ButtonImpl);
791
873
  const EventSink = declareInterfaceClass(EventSinkImpl);
792
874
  const ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
@@ -1128,7 +1210,7 @@ self.injectedExtensionAPI = function(
1128
1210
 
1129
1211
  document.addEventListener('keydown', forwardKeyboardEvent, false);
1130
1212
 
1131
- function ExtensionServerClient(this: APIImpl.ExtensionServerClient): void {
1213
+ function ExtensionServerClient(this: APIImpl.ExtensionServerClient, targetWindow: Window): void {
1132
1214
  this._callbacks = {};
1133
1215
  this._handlers = {};
1134
1216
  this._lastRequestId = 0;
@@ -1141,7 +1223,7 @@ self.injectedExtensionAPI = function(
1141
1223
  this._port.addEventListener('message', this._onMessage.bind(this), false);
1142
1224
  this._port.start();
1143
1225
 
1144
- window.parent.postMessage('registerExtension', '*', [channel.port2]);
1226
+ targetWindow.postMessage('registerExtension', '*', [channel.port2]);
1145
1227
  }
1146
1228
 
1147
1229
  (ExtensionServerClient.prototype as Pick<
@@ -1225,7 +1307,7 @@ self.injectedExtensionAPI = function(
1225
1307
  }
1226
1308
  }
1227
1309
 
1228
- const extensionServer = new (Constructor(ExtensionServerClient))();
1310
+ const extensionServer = new (Constructor(ExtensionServerClient))(targetWindowForTest || window.parent);
1229
1311
 
1230
1312
  const coreAPI = new (Constructor(InspectorExtensionAPI))();
1231
1313
 
@@ -1241,6 +1323,7 @@ self.injectedExtensionAPI = function(
1241
1323
  chrome.devtools!.panels = coreAPI.panels;
1242
1324
  chrome.devtools!.panels.themeName = themeName;
1243
1325
  chrome.devtools!.languageServices = coreAPI.languageServices;
1326
+ chrome.devtools!.recorder = coreAPI.recorder;
1244
1327
 
1245
1328
  // default to expose experimental APIs for now.
1246
1329
  if (extensionInfo.exposeExperimentalAPIs !== false) {
@@ -0,0 +1,69 @@
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
+ type Response = {
6
+ requestId: number,
7
+ result: unknown,
8
+ error: Error|null,
9
+ };
10
+
11
+ type Event = {
12
+ event: string,
13
+ };
14
+
15
+ type Message = MessageEvent<Response|Event>;
16
+
17
+ export class ExtensionEndpoint {
18
+ private readonly port: MessagePort;
19
+ private nextRequestId: number = 0;
20
+ private pendingRequests: Map<number, {
21
+ resolve: (arg: unknown) => void,
22
+ reject: (error: Error) => void,
23
+ }>;
24
+
25
+ constructor(port: MessagePort) {
26
+ this.port = port;
27
+ this.port.onmessage = this.onResponse.bind(this);
28
+ this.pendingRequests = new Map();
29
+ }
30
+
31
+ sendRequest<ReturnType>(method: string, parameters: unknown): Promise<ReturnType> {
32
+ return new Promise((resolve, reject) => {
33
+ const requestId = this.nextRequestId++;
34
+ this.pendingRequests.set(requestId, {resolve: resolve as (arg: unknown) => void, reject});
35
+ this.port.postMessage({requestId, method, parameters});
36
+ });
37
+ }
38
+
39
+ protected disconnect(): void {
40
+ for (const {reject} of this.pendingRequests.values()) {
41
+ reject(new Error('Extension endpoint disconnected'));
42
+ }
43
+ this.pendingRequests.clear();
44
+ this.port.close();
45
+ }
46
+
47
+ private onResponse({data}: Message): void {
48
+ if ('event' in data) {
49
+ this.handleEvent(data);
50
+ return;
51
+ }
52
+ const {requestId, result, error} = data;
53
+ const pendingRequest = this.pendingRequests.get(requestId);
54
+ if (!pendingRequest) {
55
+ console.error(`No pending request ${requestId}`);
56
+ return;
57
+ }
58
+ this.pendingRequests.delete(requestId);
59
+ if (error) {
60
+ pendingRequest.reject(new Error(error.message));
61
+ } else {
62
+ pendingRequest.resolve(result);
63
+ }
64
+ }
65
+
66
+ protected handleEvent(_event: Event): void {
67
+ throw new Error('handleEvent is not implemented');
68
+ }
69
+ }
@@ -52,7 +52,9 @@ import {ExtensionButton, ExtensionPanel, ExtensionSidebarPane} from './Extension
52
52
  import type {TracingSession} from './ExtensionTraceProvider.js';
53
53
  import {ExtensionTraceProvider} from './ExtensionTraceProvider.js';
54
54
  import {LanguageExtensionEndpoint} from './LanguageExtensionEndpoint.js';
55
+ import {RecorderExtensionEndpoint} from './RecorderExtensionEndpoint.js';
55
56
  import {PrivateAPI} from './ExtensionAPI.js';
57
+ import {RecorderPluginManager} from './RecorderPluginManager.js';
56
58
 
57
59
  const extensionOrigins: WeakMap<MessagePort, Platform.DevToolsPath.UrlString> = new WeakMap();
58
60
 
@@ -138,6 +140,8 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
138
140
  this.registerHandler(PrivateAPI.Commands.UpdateButton, this.onUpdateButton.bind(this));
139
141
  this.registerHandler(
140
142
  PrivateAPI.Commands.RegisterLanguageExtensionPlugin, this.registerLanguageExtensionEndpoint.bind(this));
143
+ this.registerHandler(
144
+ PrivateAPI.Commands.RegisterRecorderExtensionPlugin, this.registerRecorderExtensionEndpoint.bind(this));
141
145
  window.addEventListener('message', this.onWindowMessage.bind(this), false); // Only for main window.
142
146
 
143
147
  const existingTabId =
@@ -215,6 +219,16 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
215
219
  return this.status.OK();
216
220
  }
217
221
 
222
+ private registerRecorderExtensionEndpoint(
223
+ message: PrivateAPI.ExtensionServerRequestMessage, _shared_port: MessagePort): Record {
224
+ if (message.command !== PrivateAPI.Commands.RegisterRecorderExtensionPlugin) {
225
+ return this.status.E_BADARG('command', `expected ${PrivateAPI.Commands.Subscribe}`);
226
+ }
227
+ const {pluginName, port} = message;
228
+ RecorderPluginManager.instance().addPlugin(new RecorderExtensionEndpoint(pluginName, port));
229
+ return this.status.OK();
230
+ }
231
+
218
232
  private inspectedURLChanged(event: Common.EventTarget.EventTargetEvent<SDK.Target.Target>): void {
219
233
  if (!this.canInspectURL(event.data.inspectedURL())) {
220
234
  this.disableExtensions();
@@ -856,6 +870,13 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
856
870
  }
857
871
  }
858
872
 
873
+ addExtensionForTest(extensionInfo: Host.InspectorFrontendHostAPI.ExtensionDescriptor, origin: string): boolean
874
+ |undefined {
875
+ const name = extensionInfo.name || `Extension ${origin}`;
876
+ this.registeredExtensions.set(origin, {name});
877
+ return true;
878
+ }
879
+
859
880
  private addExtension(extensionInfo: Host.InspectorFrontendHostAPI.ExtensionDescriptor): boolean|undefined {
860
881
  const startPage = extensionInfo.startPage;
861
882
 
@@ -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
  };
@@ -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', 'simulated', 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
 
package/package.json CHANGED
@@ -55,5 +55,5 @@
55
55
  "unittest": "scripts/test/run_unittests.py --no-text-coverage",
56
56
  "watch": "vpython third_party/node/node.py --output scripts/watch_build.js"
57
57
  },
58
- "version": "1.0.1007307"
58
+ "version": "1.0.1007778"
59
59
  }