chrome-devtools-frontend 1.0.1534251 → 1.0.1534717

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.
@@ -68,6 +68,27 @@ https://chromium.googlesource.com/chromium/tools/build/+/refs/heads/main/recipes
68
68
  and upload a CL for
69
69
  [`chromium/tools/build`](https://chromium.googlesource.com/chromium/tools/build/+/refs/heads/main).
70
70
 
71
+ It's good practice to also manually test your recipes. You can test your recipe
72
+ changes against a build that was performed with the same recipes. To do so
73
+ follow these steps:
74
+ - authenticate with `led auth-login`
75
+ - select a particular build that ran with your target recipe ([example](https://ci.chromium.org/ui/p/devtools-frontend/builders/try/dtf_linux_dbg/14161))
76
+ - got to Infra tab and collect the Buildbucket id (8700153319150087425 for the example above)
77
+ - run `led get-build --real-build 8700153319150087425 | led edit-recipe-bundle | led edit-system -p 25 | led launch` while in your local recipe checkout
78
+ - collect the output link to your test build and verify that you got the expected result
79
+
80
+ ## Pin older version of DevTools recipes to a branch
81
+
82
+ Updating a recipe that depends on DevTools repo recent changes can break the
83
+ build in beta/stable/extended branches. If those changes cannot be back-merged
84
+ into the branches you can pin the older version of the recipe to the branches in
85
+ infra/config.
86
+
87
+ To do so update the `legacy_recipe` config in `definitions.star` with the number
88
+ of the last branch you need the old version of the recipe to run on and with the
89
+ revision hash of that version. Branches with number higher than the one you
90
+ configured will run ToT recipe version. [Example](https://crrev.com/c/7003685)
91
+
71
92
  ## Updating test commands in the infrastructure
72
93
 
73
94
  The DevTools recipes are defined in
@@ -228,6 +249,17 @@ Below is a detailed description of what happens in such a build:
228
249
  - fail the builder if tests failed in the deflaking phase
229
250
  - otherwise report build as passing
230
251
 
252
+ ## Skip flake detection
253
+
254
+ Test that are already flaky can end up being updated in CLs that do not deal
255
+ with the original flakiness nor can be blamed for introducing or increasing the
256
+ flakiness of the test. However CQ builder will fail in Flake Detection steps
257
+ because they picked up the test as it was being touched.
258
+
259
+ You can skip Flake Detection for your tests by adding the `Skip-Flake-Detection`
260
+ CL footer (see example [CL](https://crrev.com/c/6994031)). Make sure you use the
261
+ full path to your test.
262
+
231
263
  ### Common build failures
232
264
 
233
265
  The first place where a build usually fails is on `bot_update` and this usually
@@ -600,13 +600,9 @@ class InspectorFrontendAPIImpl {
600
600
  resourceLoaderStreamWrite(id, chunk);
601
601
  }
602
602
  }
603
- /**
604
- * Used in `front_end/devtools_compatibility.js` to verify that calls from there
605
- * are valid.
606
- */
607
- export type InspectorFrontendAPIImplMethods = keyof InspectorFrontendAPIImpl;
608
603
 
609
604
  (function(): void {
605
+
610
606
  function initializeInspectorFrontendHost(): void {
611
607
  if (!InspectorFrontendHostInstance) {
612
608
  // Instantiate stub for web-hosted mode if necessary.
@@ -550,5 +550,6 @@ export const enum EnumeratedHistogram {
550
550
  LighthouseCategoryUsed = 'DevTools.LighthouseCategoryUsed',
551
551
  SwatchActivated = 'DevTools.SwatchActivated',
552
552
  AnimationPlaybackRateChanged = 'DevTools.AnimationPlaybackRateChanged',
553
+ BuiltInAiAvailability = 'DevTools.BuiltInAiAvailability',
553
554
  // LINT.ThenChange(/front_end/devtools_compatibility.js:EnumeratedHistogram)
554
555
  }
@@ -307,6 +307,16 @@ export class UserMetrics {
307
307
  InspectorFrontendHostInstance.recordCountHistogram(
308
308
  'DevTools.PerformanceAI.MainThreadActivityResponseSize', bytes, 0, 100_000, 100);
309
309
  }
310
+
311
+ builtInAiAvailability(availability: BuiltInAiAvailability): void {
312
+ InspectorFrontendHostInstance.recordEnumeratedHistogram(
313
+ EnumeratedHistogram.BuiltInAiAvailability, availability, BuiltInAiAvailability.MAX_VALUE);
314
+ }
315
+
316
+ consoleInsightTeaserGenerated(timeInMilliseconds: number): void {
317
+ InspectorFrontendHostInstance.recordPerformanceHistogram(
318
+ 'DevTools.Insights.TeaserGenerationTime', timeInMilliseconds);
319
+ }
310
320
  }
311
321
 
312
322
  /**
@@ -1229,3 +1239,17 @@ export const enum TimelineNavigationSetting {
1229
1239
  SWITCHED_TO_MODERN = 3,
1230
1240
  MAX_VALUE = 4,
1231
1241
  }
1242
+
1243
+ export const enum BuiltInAiAvailability {
1244
+ UNAVAILABLE_HAS_GPU = 0,
1245
+ DOWNLOADABLE_HAS_GPU = 1,
1246
+ DOWNLOADING_HAS_GPU = 2,
1247
+ AVAILABLE_HAS_GPU = 3,
1248
+ DISABLED_HAS_GPU = 4,
1249
+ UNAVAILABLE_NO_GPU = 5,
1250
+ DOWNLOADABLE_NO_GPU = 6,
1251
+ DOWNLOADING_NO_GPU = 7,
1252
+ AVAILABLE_NO_GPU = 8,
1253
+ DISABLED_NO_GPU = 9,
1254
+ MAX_VALUE = 10,
1255
+ }
@@ -855,13 +855,11 @@ export class TargetBase {
855
855
  * of the invoke_enable, etc. methods that the front-end uses.
856
856
  */
857
857
  class AgentPrototype {
858
- replyArgs: Record<string, string[]>;
859
858
  description = '';
860
859
  metadata: Record<string, {parameters: CommandParameter[], description: string, replyArgs: string[]}>;
861
860
  readonly domain: string;
862
861
  target!: TargetBase;
863
862
  constructor(domain: string) {
864
- this.replyArgs = {};
865
863
  this.domain = domain;
866
864
  this.metadata = {};
867
865
  }
@@ -869,11 +867,6 @@ class AgentPrototype {
869
867
  registerCommand(
870
868
  methodName: UnqualifiedName, parameters: CommandParameter[], replyArgs: string[], description: string): void {
871
869
  const domainAndMethod = qualifyName(this.domain, methodName);
872
- function sendMessagePromise(this: AgentPrototype, ...args: unknown[]): Promise<unknown> {
873
- return AgentPrototype.prototype.sendMessageToBackendPromise.call(this, domainAndMethod, parameters, args);
874
- }
875
- // @ts-expect-error Method code generation
876
- this[methodName] = sendMessagePromise;
877
870
  this.metadata[domainAndMethod] = {parameters, description, replyArgs};
878
871
 
879
872
  function invoke(this: AgentPrototype, request: Object|undefined = {}): Promise<Protocol.ProtocolResponseWithError> {
@@ -882,88 +875,6 @@ class AgentPrototype {
882
875
 
883
876
  // @ts-expect-error Method code generation
884
877
  this['invoke_' + methodName] = invoke;
885
- this.replyArgs[domainAndMethod] = replyArgs;
886
- }
887
-
888
- private prepareParameters(
889
- method: string, parameters: CommandParameter[], args: unknown[], errorCallback: (arg0: string) => void): Object
890
- |null {
891
- const params: Record<string, unknown> = {};
892
- let hasParams = false;
893
-
894
- for (const param of parameters) {
895
- const paramName = param.name;
896
- const typeName = param.type;
897
- const optionalFlag = param.optional;
898
-
899
- if (!args.length && !optionalFlag) {
900
- errorCallback(
901
- `Protocol Error: Invalid number of arguments for method '${method}' call. ` +
902
- `It must have the following arguments ${JSON.stringify(parameters)}'.`);
903
- return null;
904
- }
905
-
906
- const value = args.shift();
907
- if (optionalFlag && typeof value === 'undefined') {
908
- continue;
909
- }
910
- const expectedJSType = typeName === 'array' ? 'object' : typeName;
911
- if (typeof value !== expectedJSType) {
912
- errorCallback(
913
- `Protocol Error: Invalid type of argument '${paramName}' for method '${method}' call. ` +
914
- `It must be '${typeName}' but it is '${typeof value}'.`);
915
- return null;
916
- }
917
-
918
- params[paramName] = value;
919
- hasParams = true;
920
- }
921
-
922
- if (args.length) {
923
- errorCallback(`Protocol Error: Extra ${args.length} arguments in a call to method '${method}'.`);
924
- return null;
925
- }
926
-
927
- return hasParams ? params : null;
928
- }
929
-
930
- private sendMessageToBackendPromise(method: QualifiedName, parameters: CommandParameter[], args: unknown[]):
931
- Promise<unknown> {
932
- let errorMessage;
933
- function onError(message: string): void {
934
- console.error(message);
935
- errorMessage = message;
936
- }
937
- const params = this.prepareParameters(method, parameters, args, onError);
938
- if (errorMessage) {
939
- return Promise.resolve(null);
940
- }
941
-
942
- return new Promise(resolve => {
943
- // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
944
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
945
- const callback: Callback = (error: MessageError|null, result: any|null): void => {
946
- if (error) {
947
- if (!test.suppressRequestErrors && error.code !== DevToolsStubErrorCode && error.code !== GenericErrorCode &&
948
- error.code !== ConnectionClosedErrorCode) {
949
- console.error('Request ' + method + ' failed. ' + JSON.stringify(error));
950
- }
951
-
952
- resolve(null);
953
- return;
954
- }
955
-
956
- const args = this.replyArgs[method];
957
- resolve(result && args.length ? result[args[0]] : undefined);
958
- };
959
-
960
- const router = this.target.router();
961
- if (!router) {
962
- SessionRouter.dispatchConnectionError(callback, method);
963
- } else {
964
- router.sendMessage(this.target.sessionId, this.domain, method, params, callback);
965
- }
966
- });
967
878
  }
968
879
 
969
880
  private invoke(method: QualifiedName, request: Object|null): Promise<Protocol.ProtocolResponseWithError> {
@@ -521,6 +521,7 @@ interface DevToolsStartingStyleDebugging {
521
521
 
522
522
  interface AiPromptApi {
523
523
  enabled: boolean;
524
+ allowWithoutGpu: boolean;
524
525
  }
525
526
 
526
527
  interface DevToolsIndividualRequestThrottling {
@@ -109,7 +109,7 @@ export class RehydratingConnection implements ProtocolClient.ConnectionTransport
109
109
  if (this.#rehydratingWindow.opener) {
110
110
  this.#rehydratingWindow.opener.postMessage({type: 'REHYDRATING_WINDOW_READY'});
111
111
  } else if (this.#rehydratingWindow !== window.top) {
112
- this.#rehydratingWindow.parent.postMessage({type: 'REHYDRATING_IFRAME_READY'});
112
+ this.#rehydratingWindow.parent.postMessage({type: 'REHYDRATING_IFRAME_READY'}, '*');
113
113
  } else {
114
114
  this.#onConnectionLost(i18nString(UIStrings.noHostWindow));
115
115
  }
@@ -7,10 +7,6 @@
7
7
  // https://crsrc.org/c/third_party/blink/renderer/controller/dev_tools_frontend_impl.cc;l=107
8
8
  (window => {
9
9
  /**
10
- * A function that tries to check the remotely connected instance
11
- * major version. You should check against this to provide
12
- * forward and backwards compatibility.
13
- *
14
10
  * @returns {number|null}
15
11
  */
16
12
  function getRemoteMajorVersion() {
@@ -24,6 +20,8 @@
24
20
  return null;
25
21
  }
26
22
  }
23
+ // eslint-disable-next-line no-unused-vars
24
+ const majorVersion = getRemoteMajorVersion();
27
25
 
28
26
  // DevToolsAPI ----------------------------------------------------------------
29
27
  /**
@@ -55,9 +53,19 @@
55
53
  _addExtensionCallback = null;
56
54
 
57
55
  /**
58
- * @type {ReturnType<typeof Promise.withResolvers<string>>}
56
+ * @type {Promise<string>}
59
57
  */
60
- _initialTargetIdPromiseWithResolver = Promise.withResolvers();
58
+ _initialTargetIdPromise;
59
+ /**
60
+ * @type {(param:string) => void}
61
+ */
62
+ _setInitialTargetId;
63
+
64
+ constructor() {
65
+ this._initialTargetIdPromise = new Promise(resolve => {
66
+ this._setInitialTargetId = resolve;
67
+ });
68
+ }
61
69
 
62
70
  /**
63
71
  * @param id
@@ -89,9 +97,7 @@
89
97
  }
90
98
 
91
99
  /**
92
- * @typedef {import('./core/host/InspectorFrontendHostAPI.js').Events} Events
93
- * @typedef {import('./core/host/InspectorFrontendHost.js').InspectorFrontendAPIImplMethods} Methods
94
- * @param method {`${Events|Methods}`}
100
+ * @param method
95
101
  * @param args
96
102
  */
97
103
  _dispatchOnInspectorFrontendAPI(method, args) {
@@ -161,7 +167,7 @@
161
167
  }
162
168
 
163
169
  /**
164
- * @param count {number}
170
+ * @param count
165
171
  */
166
172
  deviceCountUpdated(count) {
167
173
  this._dispatchOnInspectorFrontendAPI('deviceCountUpdated', [count]);
@@ -341,7 +347,7 @@
341
347
  * @param targetId {string}
342
348
  */
343
349
  setInitialTargetId(targetId) {
344
- this._initialTargetIdPromiseWithResolver.resolve(targetId);
350
+ this._setInitialTargetId(targetId);
345
351
  }
346
352
 
347
353
  /**
@@ -437,7 +443,8 @@
437
443
  TimelineNavigationSettingState: 'DevTools.TimelineNavigationSettingState',
438
444
  SyncSetting: 'DevTools.SyncSetting',
439
445
  SwatchActivated: 'DevTools.SwatchActivated',
440
- AnimationPlaybackRateChanged: 'DevTools.AnimationPlaybackRateChanged'
446
+ AnimationPlaybackRateChanged: 'DevTools.AnimationPlaybackRateChanged',
447
+ BuiltInAiAvailability: 'DevTools.BuiltInAiAvailability'
441
448
  // LINT.ThenChange(/front_end/core/host/InspectorFrontendHostAPI.ts:EnumeratedHistogram)
442
449
  };
443
450
 
@@ -452,6 +459,34 @@
452
459
  */
453
460
  events;
454
461
 
462
+ /**
463
+ * @returns
464
+ */
465
+ getSelectionBackgroundColor() {
466
+ return '#6e86ff';
467
+ }
468
+
469
+ /**
470
+ * @returns
471
+ */
472
+ getSelectionForegroundColor() {
473
+ return '#ffffff';
474
+ }
475
+
476
+ /**
477
+ * @returns
478
+ */
479
+ getInactiveSelectionBackgroundColor() {
480
+ return '#c9c8c8';
481
+ }
482
+
483
+ /**
484
+ * @returns
485
+ */
486
+ getInactiveSelectionForegroundColor() {
487
+ return '#323232';
488
+ }
489
+
455
490
  /**
456
491
  * @returns
457
492
  */
@@ -986,12 +1021,21 @@
986
1021
  }
987
1022
 
988
1023
  // Backward-compatible methods below this line --------------------------------------------
1024
+ /**
1025
+ * Support for legacy front-ends (<M65).
1026
+ * @returns
1027
+ */
1028
+ isUnderTest() {
1029
+ return false;
1030
+ }
1031
+
1032
+ // Backward-compatible methods end before line --------------------------------------------
989
1033
 
990
1034
  /**
991
1035
  * @returns
992
1036
  */
993
1037
  initialTargetId() {
994
- return DevToolsAPI._initialTargetIdPromiseWithResolver.promise;
1038
+ return DevToolsAPI._initialTargetIdPromise;
995
1039
  }
996
1040
 
997
1041
  /**
@@ -1110,9 +1154,155 @@
1110
1154
  }
1111
1155
 
1112
1156
  function installBackwardsCompatibility() {
1113
- // Any polyfill that we need for backwards compatibility should be
1114
- // Added in this function
1115
- // Use getRemoteMajorVersion to get the correct Major Chrome version
1157
+ const majorVersion = getRemoteMajorVersion();
1158
+ if (!majorVersion) {
1159
+ return;
1160
+ }
1161
+
1162
+ /** @type {!Array<string>} */
1163
+ const styleRules = [];
1164
+ // Shadow DOM V0 polyfill
1165
+ if (majorVersion <= 73 && !Element.prototype.createShadowRoot) {
1166
+ Element.prototype.createShadowRoot = function() {
1167
+ try {
1168
+ return this.attachShadow({mode: 'open'});
1169
+ } catch {
1170
+ // some elements we use to add shadow roots can no
1171
+ // longer have shadow roots.
1172
+ const fakeShadowHost = document.createElement('span');
1173
+ this.appendChild(fakeShadowHost);
1174
+ fakeShadowHost.className = 'fake-shadow-host';
1175
+ return fakeShadowHost.createShadowRoot();
1176
+ }
1177
+ };
1178
+
1179
+ const origAdd = DOMTokenList.prototype.add;
1180
+ DOMTokenList.prototype.add = function(...tokens) {
1181
+ if (tokens[0].startsWith('insertion-point') || tokens[0].startsWith('tabbed-pane-header')) {
1182
+ this._myElement.slot = '.' + tokens[0];
1183
+ }
1184
+ return origAdd.apply(this, tokens);
1185
+ };
1186
+
1187
+ const origCreateElement = Document.prototype.createElement;
1188
+ Document.prototype.createElement = function(tagName, ...rest) {
1189
+ if (tagName === 'content') {
1190
+ tagName = 'slot';
1191
+ }
1192
+ const element = origCreateElement.call(this, tagName, ...rest);
1193
+ element.classList._myElement = element;
1194
+ return element;
1195
+ };
1196
+
1197
+ Object.defineProperty(HTMLSlotElement.prototype, 'select', {
1198
+ set(selector) {
1199
+ this.name = selector;
1200
+ }
1201
+ });
1202
+ }
1203
+
1204
+ // Custom Elements V0 polyfill
1205
+ if (majorVersion <= 73 && !Document.prototype.hasOwnProperty('registerElement')) {
1206
+ const fakeRegistry = new Map();
1207
+ Document.prototype.registerElement = function(typeExtension, options) {
1208
+ const {prototype, extends: localName} = options;
1209
+ const document = this;
1210
+ const callback = function() {
1211
+ const element = document.createElement(localName || typeExtension);
1212
+ const skip = new Set(['constructor', '__proto__']);
1213
+ for (const key of Object.keys(Object.getOwnPropertyDescriptors(prototype.__proto__ || {}))) {
1214
+ if (skip.has(key)) {
1215
+ continue;
1216
+ }
1217
+ element[key] = prototype[key];
1218
+ }
1219
+ element.setAttribute('is', typeExtension);
1220
+ if (element['createdCallback']) {
1221
+ element['createdCallback']();
1222
+ }
1223
+ return element;
1224
+ };
1225
+ fakeRegistry.set(typeExtension, callback);
1226
+ return callback;
1227
+ };
1228
+
1229
+ const origCreateElement = Document.prototype.createElement;
1230
+ Document.prototype.createElement = function(tagName, fakeCustomElementType) {
1231
+ const fakeConstructor = fakeRegistry.get(fakeCustomElementType);
1232
+ if (fakeConstructor) {
1233
+ return fakeConstructor();
1234
+ }
1235
+ return origCreateElement.call(this, tagName, fakeCustomElementType);
1236
+ };
1237
+
1238
+ // DevTools front-ends mistakenly assume that
1239
+ // classList.toggle('a', undefined) works as
1240
+ // classList.toggle('a', false) rather than as
1241
+ // classList.toggle('a');
1242
+ const originalDOMTokenListToggle = DOMTokenList.prototype.toggle;
1243
+ DOMTokenList.prototype.toggle = function(token, force) {
1244
+ if (arguments.length === 1) {
1245
+ force = !this.contains(token);
1246
+ }
1247
+ return originalDOMTokenListToggle.call(this, token, Boolean(force));
1248
+ };
1249
+ }
1250
+
1251
+ if (majorVersion <= 66) {
1252
+ /** @type {(!function(number, number):Element|undefined)} */
1253
+ ShadowRoot.prototype.__originalShadowRootElementFromPoint;
1254
+
1255
+ if (!ShadowRoot.prototype.__originalShadowRootElementFromPoint) {
1256
+ ShadowRoot.prototype.__originalShadowRootElementFromPoint = ShadowRoot.prototype.elementFromPoint;
1257
+ /**
1258
+ * @param x
1259
+ * @param y
1260
+ * @returns
1261
+ */
1262
+ ShadowRoot.prototype.elementFromPoint = function(x, y) {
1263
+ const originalResult = ShadowRoot.prototype.__originalShadowRootElementFromPoint.apply(this, arguments);
1264
+ if (this.host && originalResult === this.host) {
1265
+ return null;
1266
+ }
1267
+ return originalResult;
1268
+ };
1269
+ }
1270
+ }
1271
+
1272
+ if (majorVersion <= 71) {
1273
+ styleRules.push(
1274
+ '.coverage-toolbar-container, .animation-timeline-toolbar-container, .computed-properties { flex-basis: auto; }');
1275
+ }
1276
+
1277
+ installExtraStyleRules(styleRules);
1278
+ }
1279
+
1280
+ /**
1281
+ * @param styleRules
1282
+ */
1283
+ function installExtraStyleRules(styleRules) {
1284
+ if (!styleRules.length) {
1285
+ return;
1286
+ }
1287
+ const styleText = styleRules.join('\n');
1288
+ document.head.appendChild(createStyleElement(styleText));
1289
+
1290
+ const origCreateShadowRoot = HTMLElement.prototype.createShadowRoot;
1291
+ HTMLElement.prototype.createShadowRoot = function(...args) {
1292
+ const shadowRoot = origCreateShadowRoot.call(this, ...args);
1293
+ shadowRoot.appendChild(createStyleElement(styleText));
1294
+ return shadowRoot;
1295
+ };
1296
+ }
1297
+
1298
+ /**
1299
+ * @param styleText
1300
+ * @returns
1301
+ */
1302
+ function createStyleElement(styleText) {
1303
+ const style = document.createElement('style');
1304
+ style.textContent = styleText;
1305
+ return style;
1116
1306
  }
1117
1307
 
1118
1308
  installBackwardsCompatibility();
@@ -2,10 +2,13 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
+ import * as Host from '../../core/host/host.js';
5
6
  import * as Root from '../../core/root/root.js';
6
7
 
7
8
  let builtInAiInstance: BuiltInAi|undefined;
8
- let availability = '';
9
+ let availability: LanguageModelAvailability|undefined;
10
+ let hasGpu: boolean|undefined;
11
+ let isFirstRun = true;
9
12
 
10
13
  export interface LanguageModel {
11
14
  promptStreaming: (arg0: string, opts?: {
@@ -15,20 +18,62 @@ export interface LanguageModel {
15
18
  destroy: () => void;
16
19
  }
17
20
 
21
+ export const enum LanguageModelAvailability {
22
+ UNAVAILABLE = 'unavailable',
23
+ DOWNLOADABLE = 'downloadable',
24
+ DOWNLOADING = 'downloading',
25
+ AVAILABLE = 'available',
26
+ DISABLED = 'disabled',
27
+ }
28
+
18
29
  export class BuiltInAi {
19
30
  #consoleInsightsSession: LanguageModel;
20
31
 
21
- static async isAvailable(): Promise<boolean> {
32
+ static async getLanguageModelAvailability(): Promise<LanguageModelAvailability> {
22
33
  if (!Root.Runtime.hostConfig.devToolsAiPromptApi?.enabled) {
23
- return false;
34
+ return LanguageModelAvailability.DISABLED;
35
+ }
36
+ try {
37
+ // @ts-expect-error
38
+ availability = await window.LanguageModel.availability({expectedOutputs: [{type: 'text', languages: ['en']}]}) as
39
+ LanguageModelAvailability;
40
+ return availability;
41
+ } catch {
42
+ return LanguageModelAvailability.UNAVAILABLE;
24
43
  }
25
- // @ts-expect-error
26
- availability = await window.LanguageModel.availability({expectedOutputs: [{type: 'text', languages: ['en']}]});
27
- return availability === 'available';
28
44
  }
29
45
 
30
46
  static cachedIsAvailable(): boolean {
31
- return availability === 'available';
47
+ return availability === LanguageModelAvailability.AVAILABLE;
48
+ }
49
+
50
+ static isGpuAvailable(): boolean {
51
+ const hasGpuHelper = (): boolean => {
52
+ const canvas = document.createElement('canvas');
53
+ try {
54
+ const webgl = canvas.getContext('webgl');
55
+ if (!webgl) {
56
+ return false;
57
+ }
58
+ const debugInfo = webgl.getExtension('WEBGL_debug_renderer_info');
59
+ if (!debugInfo) {
60
+ return false;
61
+ }
62
+ const renderer = webgl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL);
63
+ if (renderer.includes('SwiftShader')) {
64
+ return false;
65
+ }
66
+ } catch {
67
+ return false;
68
+ }
69
+ return true;
70
+ };
71
+
72
+ if (hasGpu !== undefined) {
73
+ return hasGpu;
74
+ }
75
+ hasGpu = hasGpuHelper();
76
+ return hasGpu;
32
77
  }
33
78
 
34
79
  private constructor(consoleInsightsSession: LanguageModel) {
@@ -37,40 +82,97 @@ export class BuiltInAi {
37
82
 
38
83
  static async instance(): Promise<BuiltInAi|undefined> {
39
84
  if (builtInAiInstance === undefined) {
40
- if (!(await BuiltInAi.isAvailable())) {
41
- return undefined;
85
+ if (isFirstRun) {
86
+ const languageModelAvailability = await BuiltInAi.getLanguageModelAvailability();
87
+ const hasGpu = BuiltInAi.isGpuAvailable();
88
+ if (hasGpu) {
89
+ switch (languageModelAvailability) {
90
+ case LanguageModelAvailability.UNAVAILABLE:
91
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.UNAVAILABLE_HAS_GPU);
92
+ break;
93
+ case LanguageModelAvailability.DOWNLOADABLE:
94
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DOWNLOADABLE_HAS_GPU);
95
+ break;
96
+ case LanguageModelAvailability.DOWNLOADING:
97
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DOWNLOADING_HAS_GPU);
98
+ break;
99
+ case LanguageModelAvailability.AVAILABLE:
100
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.AVAILABLE_HAS_GPU);
101
+ break;
102
+ case LanguageModelAvailability.DISABLED:
103
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DISABLED_HAS_GPU);
104
+ break;
105
+ }
106
+ } else {
107
+ switch (languageModelAvailability) {
108
+ case LanguageModelAvailability.UNAVAILABLE:
109
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.UNAVAILABLE_NO_GPU);
110
+ break;
111
+ case LanguageModelAvailability.DOWNLOADABLE:
112
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DOWNLOADABLE_NO_GPU);
113
+ break;
114
+ case LanguageModelAvailability.DOWNLOADING:
115
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DOWNLOADING_NO_GPU);
116
+ break;
117
+ case LanguageModelAvailability.AVAILABLE:
118
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.AVAILABLE_NO_GPU);
119
+ break;
120
+ case LanguageModelAvailability.DISABLED:
121
+ Host.userMetrics.builtInAiAvailability(Host.UserMetrics.BuiltInAiAvailability.DISABLED_NO_GPU);
122
+ break;
123
+ }
124
+ }
125
+ isFirstRun = false;
126
+ if (!Root.Runtime.hostConfig.devToolsAiPromptApi?.allowWithoutGpu && !hasGpu) {
127
+ return undefined;
128
+ }
129
+ if (languageModelAvailability !== LanguageModelAvailability.AVAILABLE) {
130
+ return undefined;
131
+ }
132
+ } else {
133
+ if (!Root.Runtime.hostConfig.devToolsAiPromptApi?.allowWithoutGpu && !BuiltInAi.isGpuAvailable()) {
134
+ return undefined;
135
+ }
136
+ if ((await BuiltInAi.getLanguageModelAvailability()) !== LanguageModelAvailability.AVAILABLE) {
137
+ return undefined;
138
+ }
42
139
  }
43
- // @ts-expect-error
44
- const consoleInsightsSession = await window.LanguageModel.create({
45
- initialPrompts: [{
46
- role: 'system',
47
- content: `
48
- You are an expert web developer. Your goal is to help a human web developer who
49
- is using Chrome DevTools to debug a web site or web app. The Chrome DevTools
50
- console is showing a message which is either an error or a warning. Please help
51
- the user understand the problematic console message.
52
140
 
53
- Your instructions are as follows:
54
- - Explain the reason why the error or warning is showing up.
55
- - The explanation has a maximum length of 200 characters. Anything beyond this
56
- length will be cut off. Make sure that your explanation is at most 200 characters long.
57
- - Your explanation should not end in the middle of a sentence.
58
- - Your explanation should consist of a single paragraph only. Do not include any
59
- headings or code blocks. Only write a single paragraph of text.
60
- - Your response should be concise and to the point. Avoid lengthy explanations
61
- or unnecessary details.
62
- `
63
- }],
64
- expectedInputs: [{
65
- type: 'text',
66
- languages: ['en'],
67
- }],
68
- expectedOutputs: [{
69
- type: 'text',
70
- languages: ['en'],
71
- }],
72
- }) as LanguageModel;
73
- builtInAiInstance = new BuiltInAi(consoleInsightsSession);
141
+ try {
142
+ // @ts-expect-error
143
+ const consoleInsightsSession = await window.LanguageModel.create({
144
+ initialPrompts: [{
145
+ role: 'system',
146
+ content: `
147
+ You are an expert web developer. Your goal is to help a human web developer who
148
+ is using Chrome DevTools to debug a web site or web app. The Chrome DevTools
149
+ console is showing a message which is either an error or a warning. Please help
150
+ the user understand the problematic console message.
151
+
152
+ Your instructions are as follows:
153
+ - Explain the reason why the error or warning is showing up.
154
+ - The explanation has a maximum length of 200 characters. Anything beyond this
155
+ length will be cut off. Make sure that your explanation is at most 200 characters long.
156
+ - Your explanation should not end in the middle of a sentence.
157
+ - Your explanation should consist of a single paragraph only. Do not include any
158
+ headings or code blocks. Only write a single paragraph of text.
159
+ - Your response should be concise and to the point. Avoid lengthy explanations
160
+ or unnecessary details.
161
+ `
162
+ }],
163
+ expectedInputs: [{
164
+ type: 'text',
165
+ languages: ['en'],
166
+ }],
167
+ expectedOutputs: [{
168
+ type: 'text',
169
+ languages: ['en'],
170
+ }],
171
+ }) as LanguageModel;
172
+ builtInAiInstance = new BuiltInAi(consoleInsightsSession);
173
+ } catch {
174
+ return undefined;
175
+ }
74
176
  }
75
177
  return builtInAiInstance;
76
178
  }
@@ -6,7 +6,6 @@ import * as Common from '../../core/common/common.js';
6
6
  import * as Host from '../../core/host/host.js';
7
7
  import * as i18n from '../../core/i18n/i18n.js';
8
8
  import * as Badges from '../../models/badges/badges.js';
9
- import * as WindowBoundsService from '../../services/window_bounds/window_bounds.js';
10
9
  import * as Buttons from '../../ui/components/buttons/buttons.js';
11
10
  import * as UI from '../../ui/legacy/legacy.js';
12
11
  import * as Lit from '../../ui/lit/lit.js';
@@ -166,8 +165,7 @@ export class BadgeNotification extends UI.Widget.Widget {
166
165
 
167
166
  #positionNotification(): void {
168
167
  const boundingRect = this.contentElement.getBoundingClientRect();
169
- const container =
170
- WindowBoundsService.WindowBoundsService.WindowBoundsServiceImpl.instance().getDevToolsBoundingElement();
168
+ const container = UI.UIUtils.getDevToolsBoundingElement();
171
169
  this.contentElement.positionAt(
172
170
  LEFT_OFFSET, container.clientHeight - boundingRect.height - BOTTOM_OFFSET, container);
173
171
  }
@@ -346,6 +346,7 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
346
346
  this.#headerText = this.#consoleViewMessage.toMessageTextString().substring(0, 70);
347
347
  this.#isGenerating = true;
348
348
  this.#timeoutId = setTimeout(this.#setSlow.bind(this), SLOW_GENERATION_CUTOFF_MILLISECONDS);
349
+ const startTime = performance.now();
349
350
  let teaserText = '';
350
351
  try {
351
352
  for await (const chunk of this.#getOnDeviceInsight()) {
@@ -366,6 +367,7 @@ export class ConsoleInsightTeaser extends UI.Widget.Widget {
366
367
  }
367
368
 
368
369
  clearTimeout(this.#timeoutId);
370
+ Host.userMetrics.consoleInsightTeaserGenerated(performance.now() - startTime);
369
371
  this.#isGenerating = false;
370
372
  this.#mainText = teaserText;
371
373
  this.requestUpdate();
@@ -29,7 +29,6 @@ import {
29
29
  type HeaderEditedEvent,
30
30
  type HeaderEditorDescriptor,
31
31
  type HeaderRemovedEvent,
32
- type HeaderSectionRow,
33
32
  type HeaderSectionRowData,
34
33
  isValidHeaderName,
35
34
  } from './HeaderSectionRow.js';
@@ -515,7 +514,7 @@ export class ResponseHeaderSection extends ResponseHeaderSectionBase {
515
514
  this.#updateOverrides(this.#headerEditors[index].name, this.#headerEditors[index].value || '', index);
516
515
  this.#render();
517
516
 
518
- const rows = this.shadow.querySelectorAll<HeaderSectionRow>('devtools-header-section-row');
517
+ const rows = this.shadow.querySelectorAll('devtools-header-section-row');
519
518
  const [lastRow] = Array.from(rows).slice(-1);
520
519
  lastRow?.focus();
521
520
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.HeaderOverrideHeaderAdded);
@@ -5,11 +5,11 @@
5
5
 
6
6
  import * as i18n from '../../../core/i18n/i18n.js';
7
7
  import * as Platform from '../../../core/platform/platform.js';
8
- import * as WindowBoundsService from '../../../services/window_bounds/window_bounds.js';
9
8
  import * as ComponentHelpers from '../../../ui/components/helpers/helpers.js';
10
9
  import * as RenderCoordinator from '../../../ui/components/render_coordinator/render_coordinator.js';
11
10
  import * as Lit from '../../../ui/lit/lit.js';
12
11
  import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
12
+ import * as UI from '../../legacy/legacy.js';
13
13
  import * as Buttons from '../buttons/buttons.js';
14
14
 
15
15
  import dialogStyles from './dialog.css.js';
@@ -77,10 +77,6 @@ interface DialogData {
77
77
  */
78
78
  dialogShownCallback: (() => unknown)|null;
79
79
 
80
- /**
81
- * Optional. Service that provides the window dimensions used for positioning the Dialog.
82
- */
83
- windowBoundsService: WindowBoundsService.WindowBoundsService.WindowBoundsService;
84
80
  /**
85
81
  * Whether the dialog is closed when the 'Escape' key is pressed. When true, the event is
86
82
  * propagation is stopped.
@@ -132,7 +128,6 @@ export class Dialog extends HTMLElement {
132
128
  horizontalAlignment: DialogHorizontalAlignment.CENTER,
133
129
  getConnectorCustomXPosition: null,
134
130
  dialogShownCallback: null,
135
- windowBoundsService: WindowBoundsService.WindowBoundsService.WindowBoundsServiceImpl.instance(),
136
131
  closeOnESC: true,
137
132
  closeOnScroll: true,
138
133
  closeButton: false,
@@ -161,7 +156,7 @@ export class Dialog extends HTMLElement {
161
156
  this.#forceDialogCloseInDevToolsBound();
162
157
  });
163
158
  readonly #dialogResizeObserver = new ResizeObserver(this.#updateDialogBounds.bind(this));
164
- #devToolsBoundingElement = this.windowBoundsService.getDevToolsBoundingElement();
159
+ #devToolsBoundingElement = UI.UIUtils.getDevToolsBoundingElement();
165
160
 
166
161
  // We bind here because we have to listen to keydowns on the entire window,
167
162
  // not on the Dialog element itself. This is because if the user has the
@@ -204,16 +199,6 @@ export class Dialog extends HTMLElement {
204
199
  this.#onStateChange();
205
200
  }
206
201
 
207
- get windowBoundsService(): WindowBoundsService.WindowBoundsService.WindowBoundsService {
208
- return this.#props.windowBoundsService;
209
- }
210
-
211
- set windowBoundsService(windowBoundsService: WindowBoundsService.WindowBoundsService.WindowBoundsService) {
212
- this.#props.windowBoundsService = windowBoundsService;
213
- this.#devToolsBoundingElement = this.windowBoundsService.getDevToolsBoundingElement();
214
- this.#onStateChange();
215
- }
216
-
217
202
  get bestVerticalPosition(): DialogVerticalPosition|null {
218
203
  return this.#bestVerticalPosition;
219
204
  }
@@ -733,6 +718,11 @@ export class Dialog extends HTMLElement {
733
718
  VisualLogging.setMappedParent(this.#getDialog(), this.parentElementOrShadowHost() as HTMLElement);
734
719
  // clang-format on
735
720
  }
721
+
722
+ setBoundingElementForTesting(element: HTMLElement): void {
723
+ this.#devToolsBoundingElement = element;
724
+ this.#onStateChange();
725
+ }
736
726
  }
737
727
 
738
728
  customElements.define('devtools-dialog', Dialog);
@@ -3,8 +3,8 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as Common from '../../../core/common/common.js';
6
- import * as WindowBoundsService from '../../../services/window_bounds/window_bounds.js';
7
6
  import * as CodeMirror from '../../../third_party/codemirror.next/codemirror.next.js';
7
+ import * as UI from '../../legacy/legacy.js';
8
8
  import * as ThemeSupport from '../../legacy/theme_support/theme_support.js';
9
9
  import * as CodeHighlighter from '../code_highlighter/code_highlighter.js';
10
10
 
@@ -166,8 +166,7 @@ export class TextEditor extends HTMLElement {
166
166
  }
167
167
 
168
168
  #startObservingResize(): void {
169
- const devtoolsElement =
170
- WindowBoundsService.WindowBoundsService.WindowBoundsServiceImpl.instance().getDevToolsBoundingElement();
169
+ const devtoolsElement = UI.UIUtils.getDevToolsBoundingElement();
171
170
  if (devtoolsElement) {
172
171
  this.#devtoolsResizeObserver.observe(devtoolsElement);
173
172
  }
@@ -7,7 +7,6 @@ import * as Common from '../../../core/common/common.js';
7
7
  import type * as Host from '../../../core/host/host.js';
8
8
  import * as i18n from '../../../core/i18n/i18n.js';
9
9
  import * as TextUtils from '../../../models/text_utils/text_utils.js';
10
- import * as WindowBoundsService from '../../../services/window_bounds/window_bounds.js';
11
10
  import * as CM from '../../../third_party/codemirror.next/codemirror.next.js';
12
11
  import * as UI from '../../legacy/legacy.js';
13
12
  import * as VisualLogging from '../../visual_logging/visual_logging.js';
@@ -343,8 +342,7 @@ let sideBarElement: HTMLElement|null = null;
343
342
 
344
343
  function getTooltipSpace(): DOMRect {
345
344
  if (!sideBarElement) {
346
- sideBarElement =
347
- WindowBoundsService.WindowBoundsService.WindowBoundsServiceImpl.instance().getDevToolsBoundingElement();
345
+ sideBarElement = UI.UIUtils.getDevToolsBoundingElement();
348
346
  }
349
347
  return sideBarElement.getBoundingClientRect();
350
348
  }
@@ -56,6 +56,7 @@ import confirmDialogStyles from './confirmDialog.css.js';
56
56
  import {Dialog} from './Dialog.js';
57
57
  import {GlassPane, PointerEventsBehavior, SizeBehavior} from './GlassPane.js';
58
58
  import inspectorCommonStyles from './inspectorCommon.css.js';
59
+ import {InspectorView} from './InspectorView.js';
59
60
  import {KeyboardShortcut, Keys} from './KeyboardShortcut.js';
60
61
  import smallBubbleStyles from './smallBubble.css.js';
61
62
  import {Tooltip} from './Tooltip.js';
@@ -2348,3 +2349,7 @@ export function copyTextToClipboard(text: string, alert?: string): void {
2348
2349
  ARIAUtils.LiveAnnouncer.alert(alert);
2349
2350
  }
2350
2351
  }
2352
+
2353
+ export function getDevToolsBoundingElement(): HTMLElement {
2354
+ return InspectorView.maybeGetInspectorViewInstance()?.element || document.body;
2355
+ }
@@ -99,7 +99,7 @@ export class CSSAngle extends HTMLElement {
99
99
  this.angleElement = this.querySelector<HTMLElement>('.css-angle');
100
100
  }
101
101
  if (!this.swatchElement) {
102
- this.swatchElement = this.querySelector<HTMLElement>('devtools-css-angle-swatch');
102
+ this.swatchElement = this.querySelector('devtools-css-angle-swatch');
103
103
  }
104
104
  if (!this.angleElement || !this.swatchElement) {
105
105
  return;
@@ -4,7 +4,7 @@
4
4
  /* eslint-disable @devtools/no-imperative-dom-api */
5
5
 
6
6
  import * as i18n from '../../../../core/i18n/i18n.js';
7
- import * as WindowBounds from '../../../../services/window_bounds/window_bounds.js';
7
+ import * as UI from '../../legacy.js';
8
8
  import * as ThemeSupport from '../../theme_support/theme_support.js';
9
9
 
10
10
  import type {FlameChart} from './FlameChart.js';
@@ -124,7 +124,7 @@ export class BrickBreaker extends HTMLElement {
124
124
  #keyPressHandlerBound = this.#keyPressHandler.bind(this);
125
125
  #closeGameBound = this.#closeGame.bind(this);
126
126
  #mouseMoveHandlerBound = this.#mouseMoveHandler.bind(this);
127
- #boundingElement = WindowBounds.WindowBoundsService.WindowBoundsServiceImpl.instance().getDevToolsBoundingElement();
127
+ #boundingElement = UI.UIUtils.getDevToolsBoundingElement();
128
128
  // Value by which we moved the game up relative to the viewport
129
129
  #gameViewportOffset = 0;
130
130
  #running = false;
@@ -3,8 +3,6 @@
3
3
  // found in the LICENSE file.
4
4
  import * as Host from '../../../../core/host/host.js';
5
5
 
6
- let fontFamily: string|null = null;
7
-
8
6
  /**
9
7
  * Because we run our UI in a couple of contexts (actual app & test
10
8
  * environments) and on multiple platforms, the font is not consistent, so this
@@ -19,18 +17,7 @@ let fontFamily: string|null = null;
19
17
  * to ensure that the screenshot tests are consistent.
20
18
  **/
21
19
  export function getFontFamilyForCanvas(): string {
22
- if (fontFamily) {
23
- return fontFamily;
24
- }
25
-
26
- const bodyStyles = getComputedStyle(document.body);
27
- if (bodyStyles.fontFamily) {
28
- fontFamily = bodyStyles.fontFamily;
29
- } else {
30
- fontFamily = Host.Platform.fontFamily();
31
- }
32
-
33
- return fontFamily;
20
+ return Host.Platform.fontFamily();
34
21
  }
35
22
 
36
23
  export const DEFAULT_FONT_SIZE = '11px';
@@ -217,11 +217,7 @@ export function drawGridLineNamesAndAssertLabels(
217
217
 
218
218
  for (const expected of expectedLabels) {
219
219
  const foundLabel = foundLabels.find(({textContent}) => textContent === expected.textContent);
220
-
221
- if (!foundLabel) {
222
- assert.fail(`Expected line name label with text content ${expected.textContent} not found`);
223
- return;
224
- }
220
+ assert.exists(foundLabel, `Expected line name label with text content ${expected.textContent} not found`);
225
221
 
226
222
  if (expected.type === 'column' && typeof expected.x !== 'undefined') {
227
223
  assert.closeTo(
@@ -256,11 +252,7 @@ export function drawGridAreaNamesAndAssertLabels(
256
252
  });
257
253
  for (const expected of expectedLabels) {
258
254
  const foundLabel = foundLabels.find(({textContent}) => textContent === expected.textContent);
259
-
260
- if (!foundLabel) {
261
- assert.fail(`Expected area label with text content ${expected.textContent} not found`);
262
- return;
263
- }
255
+ assert.exists(foundLabel, `Expected area label with text content ${expected.textContent} not found`);
264
256
 
265
257
  if (typeof expected.left !== 'undefined') {
266
258
  assert.strictEqual(
package/package.json CHANGED
@@ -102,5 +102,5 @@
102
102
  "flat-cache": "6.1.12"
103
103
  }
104
104
  },
105
- "version": "1.0.1534251"
105
+ "version": "1.0.1534717"
106
106
  }
@@ -1,27 +0,0 @@
1
- // Copyright 2021 The Chromium Authors
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 * as Legacy from '../../ui/legacy/legacy.js';
6
-
7
- export interface WindowBoundsService {
8
- getDevToolsBoundingElement(): HTMLElement;
9
- }
10
-
11
- let windowBoundsServiceImplInstance: WindowBoundsServiceImpl;
12
- export class WindowBoundsServiceImpl implements WindowBoundsService {
13
- static instance(opts: {
14
- forceNew: boolean|null,
15
- } = {forceNew: null}): WindowBoundsServiceImpl {
16
- const {forceNew} = opts;
17
- if (!windowBoundsServiceImplInstance || forceNew) {
18
- windowBoundsServiceImplInstance = new WindowBoundsServiceImpl();
19
- }
20
-
21
- return windowBoundsServiceImplInstance;
22
- }
23
-
24
- getDevToolsBoundingElement(): HTMLElement {
25
- return Legacy.InspectorView.InspectorView.maybeGetInspectorViewInstance()?.element || document.body;
26
- }
27
- }
@@ -1,9 +0,0 @@
1
- // Copyright 2021 The Chromium Authors
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 * as WindowBoundsService from './WindowBoundsService.js';
6
-
7
- export {
8
- WindowBoundsService,
9
- };