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
@@ -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};
@@ -470,8 +470,16 @@ export class ParsedURL {
470
470
  return ParsedURL.substring(url as Platform.DevToolsPath.UrlString, 0, wasmFunctionIndex);
471
471
  }
472
472
 
473
+ private static beginsWithWindowsDriveLetter(url: string): boolean {
474
+ return /^[A-Za-z]:/.test(url);
475
+ }
476
+
477
+ private static beginsWithScheme(url: string): boolean {
478
+ return /^[A-Za-z][A-Za-z0-9+.-]*:/.test(url);
479
+ }
480
+
473
481
  static isRelativeURL(url: string): boolean {
474
- return !(/^[A-Za-z][A-Za-z0-9+.-]*:/.test(url));
482
+ return !this.beginsWithScheme(url) || this.beginsWithWindowsDriveLetter(url);
475
483
  }
476
484
 
477
485
  get displayName(): string {
@@ -1469,9 +1469,6 @@
1469
1469
  "models/issues_manager/DeprecationIssue.ts | rtcpMuxPolicyNegotiate": {
1470
1470
  "message": "The rtcpMuxPolicy option is deprecated and will be removed."
1471
1471
  },
1472
- "models/issues_manager/DeprecationIssue.ts | rtpDataChannel": {
1473
- "message": "RTP data channels are no longer supported. The RtpDataChannels constraint is currently ignored, and may cause an error at a later date."
1474
- },
1475
1472
  "models/issues_manager/DeprecationIssue.ts | sharedArrayBufferConstructedWithoutIsolation": {
1476
1473
  "message": "SharedArrayBuffer will require cross-origin isolation. See https://developer.chrome.com/blog/enabling-shared-array-buffer/ for more details."
1477
1474
  },
@@ -3803,6 +3800,9 @@
3803
3800
  "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | sS": {
3804
3801
  "message": "{PH1}: {PH2}"
3805
3802
  },
3803
+ "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | sSS": {
3804
+ "message": "{PH1}: {PH2}, {PH3}"
3805
+ },
3806
3806
  "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | subtreeModified": {
3807
3807
  "message": "Subtree modified"
3808
3808
  },
@@ -5951,6 +5951,9 @@
5951
5951
  "panels/lighthouse/LighthouseController.ts | desktop": {
5952
5952
  "message": "Desktop"
5953
5953
  },
5954
+ "panels/lighthouse/LighthouseController.ts | devtoolsThrottling": {
5955
+ "message": "DevTools throttling (advanced)"
5956
+ },
5954
5957
  "panels/lighthouse/LighthouseController.ts | doesThisPageFollowBestPractices": {
5955
5958
  "message": "Does this page follow best practices for modern web development"
5956
5959
  },
@@ -6015,10 +6018,10 @@
6015
6018
  "message": "SEO"
6016
6019
  },
6017
6020
  "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."
6021
+ "message": "Simulated throttling simulates a slower page load based on data from an initial unthrottled load. DevTools throttling actually slows down the page."
6019
6022
  },
6020
6023
  "panels/lighthouse/LighthouseController.ts | simulatedThrottling": {
6021
- "message": "Simulated throttling"
6024
+ "message": "Simulated throttling (default)"
6022
6025
  },
6023
6026
  "panels/lighthouse/LighthouseController.ts | snapshot": {
6024
6027
  "message": "Snapshot"
@@ -6032,6 +6035,9 @@
6032
6035
  "panels/lighthouse/LighthouseController.ts | thereMayBeStoredDataAffectingSingular": {
6033
6036
  "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
6037
  },
6038
+ "panels/lighthouse/LighthouseController.ts | throttlingMethod": {
6039
+ "message": "Throttling method"
6040
+ },
6035
6041
  "panels/lighthouse/LighthouseController.ts | timespan": {
6036
6042
  "message": "Timespan"
6037
6043
  },
@@ -6653,6 +6659,9 @@
6653
6659
  "panels/network/components/RequestHeadersView.ts | responseHeaders": {
6654
6660
  "message": "Response Headers"
6655
6661
  },
6662
+ "panels/network/components/RequestHeadersView.ts | showMore": {
6663
+ "message": "Show more"
6664
+ },
6656
6665
  "panels/network/components/RequestHeadersView.ts | statusCode": {
6657
6666
  "message": "Status Code"
6658
6667
  },
@@ -1469,9 +1469,6 @@
1469
1469
  "models/issues_manager/DeprecationIssue.ts | rtcpMuxPolicyNegotiate": {
1470
1470
  "message": "T̂h́ê rtcpMuxPolicy óp̂t́îón̂ íŝ d́êṕr̂éĉát̂éd̂ án̂d́ ŵíl̂ĺ b̂é r̂ém̂óv̂éd̂."
1471
1471
  },
1472
- "models/issues_manager/DeprecationIssue.ts | rtpDataChannel": {
1473
- "message": "RTP data channels âŕê ńô ĺôńĝér̂ śûṕp̂ór̂t́êd́. T̂h́ê RtpDataChannels ćôńŝt́r̂áîńt̂ íŝ ćûŕr̂én̂t́l̂ý îǵn̂ór̂éd̂, án̂d́ m̂áŷ ćâúŝé âń êŕr̂ór̂ át̂ á l̂át̂ér̂ d́ât́ê."
1474
- },
1475
1472
  "models/issues_manager/DeprecationIssue.ts | sharedArrayBufferConstructedWithoutIsolation": {
1476
1473
  "message": "SharedArrayBuffer ŵíl̂ĺ r̂éq̂úîŕê ćr̂óŝś-ôŕîǵîń îśôĺât́îón̂. Śêé ĥt́t̂ṕŝ://d́êv́êĺôṕêŕ.ĉh́r̂óm̂é.ĉóm̂/b́l̂óĝ/én̂áb̂ĺîńĝ-śĥár̂éd̂-ár̂ŕâý-b̂úf̂f́êŕ/ f̂ór̂ ḿôŕê d́êt́âíl̂ś."
1477
1474
  },
@@ -3803,6 +3800,9 @@
3803
3800
  "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | sS": {
3804
3801
  "message": "{PH1}: {PH2}"
3805
3802
  },
3803
+ "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | sSS": {
3804
+ "message": "{PH1}: {PH2}, {PH3}"
3805
+ },
3806
3806
  "panels/browser_debugger/DOMBreakpointsSidebarPane.ts | subtreeModified": {
3807
3807
  "message": "Ŝúb̂t́r̂éê ḿôd́îf́îéd̂"
3808
3808
  },
@@ -5951,6 +5951,9 @@
5951
5951
  "panels/lighthouse/LighthouseController.ts | desktop": {
5952
5952
  "message": "D̂éŝḱt̂óp̂"
5953
5953
  },
5954
+ "panels/lighthouse/LighthouseController.ts | devtoolsThrottling": {
5955
+ "message": "D̂év̂T́ôól̂ś t̂h́r̂ót̂t́l̂ín̂ǵ (âd́v̂án̂ćêd́)"
5956
+ },
5954
5957
  "panels/lighthouse/LighthouseController.ts | doesThisPageFollowBestPractices": {
5955
5958
  "message": "D̂óêś t̂h́îś p̂áĝé f̂ól̂ĺôẃ b̂éŝt́ p̂ŕâćt̂íĉéŝ f́ôŕ m̂ód̂ér̂ń ŵéb̂ d́êv́êĺôṕm̂én̂t́"
5956
5959
  },
@@ -6015,10 +6018,10 @@
6015
6018
  "message": "ŜÉÔ"
6016
6019
  },
6017
6020
  "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̂ǵ."
6021
+ "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
6022
  },
6020
6023
  "panels/lighthouse/LighthouseController.ts | simulatedThrottling": {
6021
- "message": "Ŝím̂úl̂át̂éd̂ t́ĥŕôt́t̂ĺîńĝ"
6024
+ "message": "Ŝím̂úl̂át̂éd̂ t́ĥŕôt́t̂ĺîńĝ (d́êf́âúl̂t́)"
6022
6025
  },
6023
6026
  "panels/lighthouse/LighthouseController.ts | snapshot": {
6024
6027
  "message": "Ŝńâṕŝh́ôt́"
@@ -6032,6 +6035,9 @@
6032
6035
  "panels/lighthouse/LighthouseController.ts | thereMayBeStoredDataAffectingSingular": {
6033
6036
  "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
6037
  },
6038
+ "panels/lighthouse/LighthouseController.ts | throttlingMethod": {
6039
+ "message": "T̂h́r̂ót̂t́l̂ín̂ǵ m̂ét̂h́ôd́"
6040
+ },
6035
6041
  "panels/lighthouse/LighthouseController.ts | timespan": {
6036
6042
  "message": "T̂ím̂éŝṕâń"
6037
6043
  },
@@ -6653,6 +6659,9 @@
6653
6659
  "panels/network/components/RequestHeadersView.ts | responseHeaders": {
6654
6660
  "message": "R̂éŝṕôńŝé Ĥéâd́êŕŝ"
6655
6661
  },
6662
+ "panels/network/components/RequestHeadersView.ts | showMore": {
6663
+ "message": "Ŝh́ôẃ m̂ór̂é"
6664
+ },
6656
6665
  "panels/network/components/RequestHeadersView.ts | statusCode": {
6657
6666
  "message": "Ŝt́ât́ûś Ĉód̂é"
6658
6667
  },
@@ -3,15 +3,19 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import type * as Protocol from '../../generated/protocol.js';
6
+ import type * as Platform from '../platform/platform.js';
6
7
 
7
8
  export class CSSFontFace {
8
9
  readonly #fontFamily: string;
9
10
  readonly #fontVariationAxes: Protocol.CSS.FontVariationAxis[];
10
11
  readonly #fontVariationAxesByTag: Map<string, Protocol.CSS.FontVariationAxis>;
12
+ readonly #src: Platform.DevToolsPath.UrlString;
13
+
11
14
  constructor(payload: Protocol.CSS.FontFace) {
12
15
  this.#fontFamily = payload.fontFamily;
13
16
  this.#fontVariationAxes = payload.fontVariationAxes || [];
14
17
  this.#fontVariationAxesByTag = new Map();
18
+ this.#src = payload.src as Platform.DevToolsPath.UrlString;
15
19
  for (const axis of this.#fontVariationAxes) {
16
20
  this.#fontVariationAxesByTag.set(axis.tag, axis);
17
21
  }
@@ -21,6 +25,10 @@ export class CSSFontFace {
21
25
  return this.#fontFamily;
22
26
  }
23
27
 
28
+ getSrc(): Platform.DevToolsPath.UrlString {
29
+ return this.#src;
30
+ }
31
+
24
32
  getVariationAxisByTag(tag: string): Protocol.CSS.FontVariationAxis|undefined {
25
33
  return this.#fontVariationAxesByTag.get(tag);
26
34
  }
@@ -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') {
@@ -315,7 +315,6 @@ export function registerCommands(inspectorBackend) {
315
315
  'RTCPeerConnectionComplexPlanBSdpUsingDefaultSdpSemantics',
316
316
  RTCPeerConnectionSdpSemanticsPlanB: 'RTCPeerConnectionSdpSemanticsPlanB',
317
317
  RtcpMuxPolicyNegotiate: 'RtcpMuxPolicyNegotiate',
318
- RTPDataChannel: 'RTPDataChannel',
319
318
  SharedArrayBufferConstructedWithoutIsolation: 'SharedArrayBufferConstructedWithoutIsolation',
320
319
  TextToSpeech_DisallowedByAutoplay: 'TextToSpeech_DisallowedByAutoplay',
321
320
  V8SharedArrayBufferConstructedInExtensionWithoutIsolation:
@@ -2380,7 +2379,8 @@ export function registerCommands(inspectorBackend) {
2380
2379
  inspectorBackend.registerEnum(
2381
2380
  'Page.FileChooserOpenedEventMode', {SelectSingle: 'selectSingle', SelectMultiple: 'selectMultiple'});
2382
2381
  inspectorBackend.registerEvent('Page.fileChooserOpened', ['frameId', 'backendNodeId', 'mode']);
2383
- inspectorBackend.registerEvent('Page.frameAttached', ['frameId', 'parentFrameId', 'stack']);
2382
+ inspectorBackend.registerEvent(
2383
+ 'Page.frameAttached', ['frameId', 'parentFrameId', 'stack', 'adScriptId', 'debuggerId']);
2384
2384
  inspectorBackend.registerEvent('Page.frameClearedScheduledNavigation', ['frameId']);
2385
2385
  inspectorBackend.registerEnum('Page.FrameDetachedEventReason', {Remove: 'remove', Swap: 'swap'});
2386
2386
  inspectorBackend.registerEvent('Page.frameDetached', ['frameId', 'reason']);
@@ -1062,7 +1062,6 @@ export namespace Audits {
1062
1062
  'RTCPeerConnectionComplexPlanBSdpUsingDefaultSdpSemantics',
1063
1063
  RTCPeerConnectionSdpSemanticsPlanB = 'RTCPeerConnectionSdpSemanticsPlanB',
1064
1064
  RtcpMuxPolicyNegotiate = 'RtcpMuxPolicyNegotiate',
1065
- RTPDataChannel = 'RTPDataChannel',
1066
1065
  SharedArrayBufferConstructedWithoutIsolation = 'SharedArrayBufferConstructedWithoutIsolation',
1067
1066
  TextToSpeech_DisallowedByAutoplay = 'TextToSpeech_DisallowedByAutoplay',
1068
1067
  V8SharedArrayBufferConstructedInExtensionWithoutIsolation =
@@ -11765,6 +11764,8 @@ export namespace Page {
11765
11764
  * JavaScript stack trace of when frame was attached, only set if frame initiated from script.
11766
11765
  */
11767
11766
  stack?: Runtime.StackTrace;
11767
+ adScriptId?: Runtime.ScriptId;
11768
+ debuggerId?: Runtime.UniqueDebuggerId;
11768
11769
  }
11769
11770
 
11770
11771
  /**
@@ -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