lighthouse 10.2.0-dev.20230612 → 10.3.0-dev.20230613

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 (64) hide show
  1. package/core/audits/deprecations.js +1 -1
  2. package/core/audits/server-response-time.d.ts +2 -1
  3. package/core/audits/server-response-time.js +11 -2
  4. package/core/audits/uses-rel-preconnect.js +20 -4
  5. package/core/lib/bf-cache-strings.d.ts +3 -0
  6. package/core/lib/bf-cache-strings.js +248 -232
  7. package/core/lib/cdt/generated/SourceMap.d.ts +18 -31
  8. package/core/lib/cdt/generated/SourceMap.js +51 -101
  9. package/core/lib/dependency-graph/simulator/network-analyzer.js +13 -0
  10. package/core/lib/deprecation-description.d.ts +17 -0
  11. package/core/lib/deprecation-description.js +71 -0
  12. package/core/lib/deprecations-strings.d.ts +282 -63
  13. package/core/lib/deprecations-strings.js +282 -551
  14. package/core/lib/network-request.d.ts +6 -6
  15. package/core/lib/network-request.js +6 -9
  16. package/package.json +6 -6
  17. package/shared/localization/locales/ar-XB.json +20 -170
  18. package/shared/localization/locales/ar.json +20 -170
  19. package/shared/localization/locales/bg.json +19 -169
  20. package/shared/localization/locales/ca.json +19 -169
  21. package/shared/localization/locales/cs.json +19 -169
  22. package/shared/localization/locales/da.json +19 -169
  23. package/shared/localization/locales/de.json +19 -169
  24. package/shared/localization/locales/el.json +19 -169
  25. package/shared/localization/locales/en-GB.json +9 -159
  26. package/shared/localization/locales/en-US.json +115 -70
  27. package/shared/localization/locales/en-XA.json +20 -170
  28. package/shared/localization/locales/en-XL.json +115 -70
  29. package/shared/localization/locales/es-419.json +19 -169
  30. package/shared/localization/locales/es.json +19 -169
  31. package/shared/localization/locales/fi.json +19 -169
  32. package/shared/localization/locales/fil.json +17 -167
  33. package/shared/localization/locales/fr.json +18 -168
  34. package/shared/localization/locales/he.json +19 -169
  35. package/shared/localization/locales/hi.json +20 -170
  36. package/shared/localization/locales/hr.json +19 -169
  37. package/shared/localization/locales/hu.json +19 -169
  38. package/shared/localization/locales/id.json +19 -169
  39. package/shared/localization/locales/it.json +19 -169
  40. package/shared/localization/locales/ja.json +19 -169
  41. package/shared/localization/locales/ko.json +19 -169
  42. package/shared/localization/locales/lt.json +20 -170
  43. package/shared/localization/locales/lv.json +20 -170
  44. package/shared/localization/locales/nl.json +18 -168
  45. package/shared/localization/locales/no.json +19 -169
  46. package/shared/localization/locales/pl.json +19 -169
  47. package/shared/localization/locales/pt-PT.json +19 -169
  48. package/shared/localization/locales/pt.json +19 -169
  49. package/shared/localization/locales/ro.json +19 -169
  50. package/shared/localization/locales/ru.json +19 -169
  51. package/shared/localization/locales/sk.json +20 -170
  52. package/shared/localization/locales/sl.json +19 -169
  53. package/shared/localization/locales/sr-Latn.json +19 -169
  54. package/shared/localization/locales/sr.json +19 -169
  55. package/shared/localization/locales/sv.json +19 -169
  56. package/shared/localization/locales/ta.json +19 -169
  57. package/shared/localization/locales/te.json +19 -169
  58. package/shared/localization/locales/th.json +19 -169
  59. package/shared/localization/locales/tr.json +19 -169
  60. package/shared/localization/locales/uk.json +19 -169
  61. package/shared/localization/locales/vi.json +20 -170
  62. package/shared/localization/locales/zh-HK.json +19 -169
  63. package/shared/localization/locales/zh-TW.json +19 -169
  64. package/shared/localization/locales/zh.json +19 -169
@@ -1,10 +1,10 @@
1
- export = TextSourceMap;
2
- declare class TextSourceMap {
1
+ export = SourceMap;
2
+ declare class SourceMap {
3
3
  /**
4
4
  * Implements Source Map V3 model. See https://github.com/google/closure-compiler/wiki/Source-Maps
5
5
  * for format description.
6
6
  */
7
- constructor(compiledURL: any, sourceMappingURL: any, payload: any, initiator: any);
7
+ constructor(compiledURL: any, sourceMappingURL: any, payload: any);
8
8
  compiledURL(): any;
9
9
  url(): any;
10
10
  sourceURLs(): any[];
@@ -36,22 +36,21 @@ declare class TextSourceMap {
36
36
  findReverseEntries(sourceURL: any, lineNumber: any, columnNumber: any): any;
37
37
  findReverseRanges(sourceURL: any, lineNumber: any, columnNumber: any): any[];
38
38
  /** @return {Array<{lineNumber: number, columnNumber: number, sourceURL?: string, sourceLineNumber: number, sourceColumnNumber: number, name?: string, lastColumnNumber?: number}>} */
39
- mappings(): Array<{
39
+ mappings(): {
40
40
  lineNumber: number;
41
41
  columnNumber: number;
42
- sourceURL?: string;
42
+ sourceURL?: string | undefined;
43
43
  sourceLineNumber: number;
44
44
  sourceColumnNumber: number;
45
- name?: string;
46
- lastColumnNumber?: number;
47
- }>;
45
+ name?: string | undefined;
46
+ lastColumnNumber?: number | undefined;
47
+ }[];
48
48
  reversedMappings(sourceURL: any): any;
49
49
  eachSection(callback: any): void;
50
50
  parseSources(sourceMap: any): void;
51
51
  parseMap(map: any, lineNumber: any, columnNumber: any): void;
52
52
  isSeparator(char: any): boolean;
53
53
  decodeVLQ(stringCharIterator: any): number;
54
- reverseMapTextRange(url: any, textRange: any): any;
55
54
  mapsOrigin(): boolean;
56
55
  hasIgnoreListHint(sourceURL: any): any;
57
56
  /**
@@ -63,30 +62,18 @@ declare class TextSourceMap {
63
62
  findRanges(predicate: any, options: any): any[];
64
63
  #private;
65
64
  }
66
- declare namespace TextSourceMap {
67
- export { __esModule, SourceMapV3, Section, Offset, SourceMapEntry, TextSourceMap };
65
+ declare namespace SourceMap {
66
+ export { parseSourceMap, __esModule, SourceMapEntry, SourceMap };
68
67
  }
68
+ /**
69
+ * Parses the {@link content} as JSON, ignoring BOM markers in the beginning, and
70
+ * also handling the CORB bypass prefix correctly.
71
+ *
72
+ * @param content the string representation of a sourcemap.
73
+ * @returns the {@link SourceMapV3} representation of the {@link content}.
74
+ */
75
+ declare function parseSourceMap(content: any): any;
69
76
  declare const __esModule: boolean;
70
- declare class SourceMapV3 {
71
- version: any;
72
- file: any;
73
- sources: any;
74
- sections: any;
75
- mappings: any;
76
- sourceRoot: any;
77
- names: any;
78
- sourcesContent: any;
79
- x_google_ignoreList: any;
80
- }
81
- declare class Section {
82
- map: any;
83
- offset: any;
84
- url: any;
85
- }
86
- declare class Offset {
87
- line: any;
88
- column: any;
89
- }
90
77
  declare class SourceMapEntry {
91
78
  static compare(entry1: any, entry2: any): number;
92
79
  constructor(lineNumber: any, columnNumber: any, sourceURL: any, sourceLineNumber: any, sourceColumnNumber: any, name: any);
@@ -8,7 +8,7 @@ const Platform = require('../Platform.js');
8
8
  // Use of this source code is governed by a BSD-style license that can be
9
9
  // found in the LICENSE file.
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.TextSourceMap = exports.SourceMapEntry = exports.Offset = exports.Section = exports.SourceMapV3 = void 0;
11
+ exports.SourceMap = exports.SourceMapEntry = exports.parseSourceMap = void 0;
12
12
  /*
13
13
  * Copyright (C) 2012 Google Inc. All rights reserved.
14
14
  *
@@ -41,42 +41,24 @@ exports.TextSourceMap = exports.SourceMapEntry = exports.Offset = exports.Sectio
41
41
  ;
42
42
  ;
43
43
  ;
44
- ;
45
- ;
46
- ;
47
- ;
48
- ;
49
- ;
50
- class SourceMapV3 {
51
- version;
52
- file;
53
- sources;
54
- sections;
55
- mappings;
56
- sourceRoot;
57
- names;
58
- sourcesContent;
59
- // eslint-disable-next-line @typescript-eslint/naming-convention
60
- x_google_ignoreList;
61
- constructor() {
44
+ /**
45
+ * Parses the {@link content} as JSON, ignoring BOM markers in the beginning, and
46
+ * also handling the CORB bypass prefix correctly.
47
+ *
48
+ * @param content the string representation of a sourcemap.
49
+ * @returns the {@link SourceMapV3} representation of the {@link content}.
50
+ */
51
+ function parseSourceMap(content) {
52
+ if (content.startsWith(')]}')) {
53
+ content = content.substring(content.indexOf('\n'));
62
54
  }
63
- }
64
- exports.SourceMapV3 = SourceMapV3;
65
- class Section {
66
- map;
67
- offset;
68
- url;
69
- constructor() {
55
+ if (content.charCodeAt(0) === 0xFEFF) {
56
+ // Strip BOM at the beginning before parsing the JSON.
57
+ content = content.slice(1);
70
58
  }
59
+ return JSON.parse(content);
71
60
  }
72
- exports.Section = Section;
73
- class Offset {
74
- line;
75
- column;
76
- constructor() {
77
- }
78
- }
79
- exports.Offset = Offset;
61
+ exports.parseSourceMap = parseSourceMap;
80
62
  class SourceMapEntry {
81
63
  lineNumber;
82
64
  columnNumber;
@@ -106,8 +88,7 @@ for (let i = 0; i < base64Digits.length; ++i) {
106
88
  base64Map.set(base64Digits.charAt(i), i);
107
89
  }
108
90
  const sourceMapToSourceList = new WeakMap();
109
- class TextSourceMap {
110
- #initiator;
91
+ class SourceMap {
111
92
  #json;
112
93
  #compiledURLInternal;
113
94
  #sourceMappingURL;
@@ -118,17 +99,15 @@ class TextSourceMap {
118
99
  * Implements Source Map V3 model. See https://github.com/google/closure-compiler/wiki/Source-Maps
119
100
  * for format description.
120
101
  */
121
- constructor(compiledURL, sourceMappingURL, payload, initiator) {
122
- this.#initiator = initiator;
102
+ constructor(compiledURL, sourceMappingURL, payload) {
123
103
  this.#json = payload;
124
104
  this.#compiledURLInternal = compiledURL;
125
105
  this.#sourceMappingURL = sourceMappingURL;
126
106
  this.#baseURL = (sourceMappingURL.startsWith('data:') ? compiledURL : sourceMappingURL);
127
107
  this.#mappingsInternal = null;
128
108
  this.#sourceInfos = new Map();
129
- if (this.#json.sections) {
130
- const sectionWithURL = Boolean(this.#json.sections.find(section => Boolean(section.url)));
131
- if (sectionWithURL) {
109
+ if ('sections' in this.#json) {
110
+ if (this.#json.sections.find(section => 'url' in section)) {
132
111
  console.warn(`SourceMap "${sourceMappingURL}" contains unsupported "URL" field in one of its sections.`);
133
112
  }
134
113
  }
@@ -266,6 +245,8 @@ class TextSourceMap {
266
245
  if (this.#mappingsInternal === null) {
267
246
  this.#mappingsInternal = [];
268
247
  this.eachSection(this.parseMap.bind(this));
248
+ // As per spec, mappings are not necessarily sorted.
249
+ this.mappings().sort(SourceMapEntry.compare);
269
250
  this.#computeReverseMappings(this.#mappingsInternal);
270
251
  this.#json = null;
271
252
  }
@@ -303,42 +284,43 @@ class TextSourceMap {
303
284
  if (!this.#json) {
304
285
  return;
305
286
  }
306
- if (!this.#json.sections) {
307
- callback(this.#json, 0, 0);
308
- return;
287
+ if ('sections' in this.#json) {
288
+ for (const section of this.#json.sections) {
289
+ if ('map' in section) {
290
+ callback(section.map, section.offset.line, section.offset.column);
291
+ }
292
+ }
309
293
  }
310
- for (const section of this.#json.sections) {
311
- callback(section.map, section.offset.line, section.offset.column);
294
+ else {
295
+ callback(this.#json, 0, 0);
312
296
  }
313
297
  }
314
298
  parseSources(sourceMap) {
315
299
  const sourcesList = [];
316
- const sourceRoot = sourceMap.sourceRoot || Platform.DevToolsPath.EmptyUrlString;
300
+ const sourceRoot = sourceMap.sourceRoot ?? '';
317
301
  const ignoreList = new Set(sourceMap.x_google_ignoreList);
318
302
  for (let i = 0; i < sourceMap.sources.length; ++i) {
319
303
  let href = sourceMap.sources[i];
320
304
  // The source map v3 proposal says to prepend the sourceRoot to the source URL
321
305
  // and if the resulting URL is not absolute, then resolve the source URL against
322
- // the source map URL. Appending the sourceRoot (if one exists) is not likely to
306
+ // the source map URL. Prepending the sourceRoot (if one exists) is not likely to
323
307
  // be meaningful or useful if the source URL is already absolute though. In this
324
308
  // case, use the source URL as is without prepending the sourceRoot.
325
309
  if (Common.ParsedURL.ParsedURL.isRelativeURL(href)) {
326
310
  if (sourceRoot && !sourceRoot.endsWith('/') && href && !href.startsWith('/')) {
327
- href = Common.ParsedURL.ParsedURL.concatenate(sourceRoot, '/', href);
311
+ href = sourceRoot.concat('/', href);
328
312
  }
329
313
  else {
330
- href = Common.ParsedURL.ParsedURL.concatenate(sourceRoot, href);
314
+ href = sourceRoot.concat(href);
331
315
  }
332
316
  }
333
- let url = '' || href;
317
+ const url = '' || href;
334
318
  const source = sourceMap.sourcesContent && sourceMap.sourcesContent[i];
335
- if (url === this.#compiledURLInternal && source) {
336
- }
337
319
  sourcesList.push(url);
338
320
  if (!this.#sourceInfos.has(url)) {
339
321
  const content = source ?? null;
340
322
  const ignoreListHint = ignoreList.has(i);
341
- this.#sourceInfos.set(url, new TextSourceMap.SourceInfo(content, ignoreListHint));
323
+ this.#sourceInfos.set(url, { content, ignoreListHint, reverseMappings: null });
342
324
  }
343
325
  }
344
326
  sourceMapToSourceList.set(sourceMap, sourcesList);
@@ -355,8 +337,8 @@ class TextSourceMap {
355
337
  // only reach this point when we are certain
356
338
  // we have the list available.
357
339
  const sources = sourceMapToSourceList.get(map);
358
- const names = map.names || [];
359
- const stringCharIterator = new TextSourceMap.StringCharIterator(map.mappings);
340
+ const names = map.names ?? [];
341
+ const stringCharIterator = new SourceMap.StringCharIterator(map.mappings);
360
342
  let sourceURL = sources && sources[sourceIndex];
361
343
  while (true) {
362
344
  if (stringCharIterator.peek() === ',') {
@@ -393,8 +375,6 @@ class TextSourceMap {
393
375
  nameIndex += this.decodeVLQ(stringCharIterator);
394
376
  this.mappings().push(new SourceMapEntry(lineNumber, columnNumber, sourceURL, sourceLineNumber, sourceColumnNumber, names[nameIndex]));
395
377
  }
396
- // As per spec, mappings are not necessarily sorted.
397
- this.mappings().sort(SourceMapEntry.compare);
398
378
  }
399
379
  isSeparator(char) {
400
380
  return char === ',' || char === ';';
@@ -403,38 +383,17 @@ class TextSourceMap {
403
383
  // Read unsigned value.
404
384
  let result = 0;
405
385
  let shift = 0;
406
- let digit = TextSourceMap._VLQ_CONTINUATION_MASK;
407
- while (digit & TextSourceMap._VLQ_CONTINUATION_MASK) {
386
+ let digit = SourceMap._VLQ_CONTINUATION_MASK;
387
+ while (digit & SourceMap._VLQ_CONTINUATION_MASK) {
408
388
  digit = base64Map.get(stringCharIterator.next()) || 0;
409
- result += (digit & TextSourceMap._VLQ_BASE_MASK) << shift;
410
- shift += TextSourceMap._VLQ_BASE_SHIFT;
389
+ result += (digit & SourceMap._VLQ_BASE_MASK) << shift;
390
+ shift += SourceMap._VLQ_BASE_SHIFT;
411
391
  }
412
392
  // Fix the sign.
413
393
  const negative = result & 1;
414
394
  result >>= 1;
415
395
  return negative ? -result : result;
416
396
  }
417
- reverseMapTextRange(url, textRange) {
418
- function comparator(position, mappingIndex) {
419
- if (position.lineNumber !== mappings[mappingIndex].sourceLineNumber) {
420
- return position.lineNumber - mappings[mappingIndex].sourceLineNumber;
421
- }
422
- return position.columnNumber - mappings[mappingIndex].sourceColumnNumber;
423
- }
424
- const reverseMappings = this.reversedMappings(url);
425
- const mappings = this.mappings();
426
- if (!reverseMappings.length) {
427
- return null;
428
- }
429
- const startIndex = Platform.ArrayUtilities.lowerBound(reverseMappings, { lineNumber: textRange.startLine, columnNumber: textRange.startColumn }, comparator);
430
- const endIndex = Platform.ArrayUtilities.upperBound(reverseMappings, { lineNumber: textRange.endLine, columnNumber: textRange.endColumn }, comparator);
431
- if (endIndex >= reverseMappings.length) {
432
- return null;
433
- }
434
- const startMapping = mappings[reverseMappings[startIndex]];
435
- const endMapping = mappings[reverseMappings[endIndex]];
436
- return new TextUtils.TextRange.TextRange(startMapping.lineNumber, startMapping.columnNumber, endMapping.lineNumber, endMapping.columnNumber);
437
- }
438
397
  mapsOrigin() {
439
398
  const mappings = this.mappings();
440
399
  if (mappings.length > 0) {
@@ -482,17 +441,17 @@ class TextSourceMap {
482
441
  return ranges;
483
442
  }
484
443
  }
485
- exports.TextSourceMap = TextSourceMap;
486
- (function (TextSourceMap) {
444
+ exports.SourceMap = SourceMap;
445
+ (function (SourceMap) {
487
446
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
488
447
  // eslint-disable-next-line @typescript-eslint/naming-convention
489
- TextSourceMap._VLQ_BASE_SHIFT = 5;
448
+ SourceMap._VLQ_BASE_SHIFT = 5;
490
449
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
491
450
  // eslint-disable-next-line @typescript-eslint/naming-convention
492
- TextSourceMap._VLQ_BASE_MASK = (1 << 5) - 1;
451
+ SourceMap._VLQ_BASE_MASK = (1 << 5) - 1;
493
452
  // TODO(crbug.com/1172300) Ignored during the jsdoc to ts migration
494
453
  // eslint-disable-next-line @typescript-eslint/naming-convention
495
- TextSourceMap._VLQ_CONTINUATION_MASK = 1 << 5;
454
+ SourceMap._VLQ_CONTINUATION_MASK = 1 << 5;
496
455
  class StringCharIterator {
497
456
  string;
498
457
  position;
@@ -510,18 +469,9 @@ exports.TextSourceMap = TextSourceMap;
510
469
  return this.position < this.string.length;
511
470
  }
512
471
  }
513
- TextSourceMap.StringCharIterator = StringCharIterator;
514
- class SourceInfo {
515
- content;
516
- ignoreListHint;
517
- reverseMappings = null;
518
- constructor(content, ignoreListHint) {
519
- this.content = content;
520
- this.ignoreListHint = ignoreListHint;
521
- }
522
- }
523
- TextSourceMap.SourceInfo = SourceInfo;
524
- })(TextSourceMap = exports.TextSourceMap || (exports.TextSourceMap = {}));
472
+ SourceMap.StringCharIterator = StringCharIterator;
473
+ })(SourceMap = exports.SourceMap || (exports.SourceMap = {}));
525
474
 
526
475
 
527
- module.exports = TextSourceMap;
476
+ module.exports = SourceMap;
477
+ SourceMap.parseSourceMap = parseSourceMap;
@@ -139,6 +139,15 @@ class NetworkAnalyzer {
139
139
  return NetworkAnalyzer._estimateValueByOrigin(records, ({timing, connectionReused, record}) => {
140
140
  if (connectionReused) return;
141
141
 
142
+ // In LR, network records are missing connection timing, but we've smuggled it in via headers.
143
+ if (global.isLightrider && record.lrStatistics) {
144
+ if (record.protocol.startsWith('h3')) {
145
+ return record.lrStatistics.TCPMs;
146
+ } else {
147
+ return [record.lrStatistics.TCPMs / 2, record.lrStatistics.TCPMs / 2];
148
+ }
149
+ }
150
+
142
151
  const {connectStart, sslStart, sslEnd, connectEnd} = timing;
143
152
  if (connectEnd >= 0 && connectStart >= 0 && record.protocol.startsWith('h3')) {
144
153
  // These values are equal to sslStart and sslEnd for h3.
@@ -247,6 +256,10 @@ class NetworkAnalyzer {
247
256
  */
248
257
  static _estimateResponseTimeByOrigin(records, rttByOrigin) {
249
258
  return NetworkAnalyzer._estimateValueByOrigin(records, ({record, timing}) => {
259
+ // Lightrider does not have timings for sendEnd, but we do have this timing which should be
260
+ // close to the response time.
261
+ if (global.isLightrider && record.lrStatistics) return record.lrStatistics.requestMs;
262
+
250
263
  if (!Number.isFinite(timing.receiveHeadersEnd) || timing.receiveHeadersEnd < 0) return;
251
264
  if (!Number.isFinite(timing.sendEnd) || timing.sendEnd < 0) return;
252
265
 
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @param {LH.Crdp.Audits.DeprecationIssueDetails} issueDetails
3
+ */
4
+ export function getIssueDetailDescription(issueDetails: LH.Crdp.Audits.DeprecationIssueDetails): {
5
+ substitutions: Map<string, import("../index.js").IcuMessage | undefined>;
6
+ links: {
7
+ link: string;
8
+ linkTitle: import("../index.js").IcuMessage;
9
+ }[];
10
+ message: import("../index.js").IcuMessage | undefined;
11
+ };
12
+ export namespace UIStrings {
13
+ const feature: string;
14
+ const milestone: string;
15
+ const title: string;
16
+ }
17
+ //# sourceMappingURL=deprecation-description.d.ts.map
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @license Copyright 2023 The Lighthouse Authors. All Rights Reserved.
3
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
4
+ * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
5
+ */
6
+
7
+ import * as i18n from '../lib/i18n/i18n.js';
8
+ import {DEPRECATIONS_METADATA, UIStrings as DeprecationUIStrings} from './deprecations-strings.js';
9
+
10
+ const UIStrings = {
11
+ // Store strings used across messages in this block.
12
+ /**
13
+ * @description This links to the chrome feature status page when one exists.
14
+ */
15
+ feature: 'Check the feature status page for more details.',
16
+ /**
17
+ * @description This links to the chromium dash schedule when a milestone is set.
18
+ * @example {100} milestone
19
+ */
20
+ milestone: 'This change will go into effect with milestone {milestone}.',
21
+ /**
22
+ * @description Title of issue raised when a deprecated feature is used
23
+ */
24
+ title: 'Deprecated Feature Used',
25
+ };
26
+
27
+ const str_ = i18n.createIcuMessageFn(import.meta.url, UIStrings);
28
+ const deprecationsStr_ = i18n.createIcuMessageFn(import.meta.url, DeprecationUIStrings);
29
+
30
+ /**
31
+ * @param {LH.Crdp.Audits.DeprecationIssueDetails} issueDetails
32
+ */
33
+ function getIssueDetailDescription(issueDetails) {
34
+ let message;
35
+ const type = /** @type {keyof DEPRECATIONS_METADATA} */ (issueDetails.type);
36
+ const maybeEnglishMessage = DeprecationUIStrings[type];
37
+ if (maybeEnglishMessage) {
38
+ message = deprecationsStr_(maybeEnglishMessage);
39
+ }
40
+
41
+ const links = [];
42
+ /** @type {{chromeStatusFeature?: number, milestone?: number}|undefined} */
43
+ const deprecationMeta = DEPRECATIONS_METADATA[type];
44
+ const feature = deprecationMeta?.chromeStatusFeature ?? 0;
45
+ if (feature !== 0) {
46
+ links.push({
47
+ link: `https://chromestatus.com/feature/${feature}`,
48
+ linkTitle: str_(UIStrings.feature),
49
+ });
50
+ }
51
+ const milestone = deprecationMeta?.milestone ?? 0;
52
+ if (milestone !== 0) {
53
+ links.push({
54
+ link: 'https://chromiumdash.appspot.com/schedule',
55
+ linkTitle: str_(UIStrings.milestone, {milestone}),
56
+ });
57
+ }
58
+ return ({
59
+ substitutions: new Map([
60
+ ['PLACEHOLDER_title', str_(UIStrings.title)],
61
+ ['PLACEHOLDER_message', message],
62
+ ]),
63
+ links,
64
+ message,
65
+ });
66
+ }
67
+
68
+ export {
69
+ getIssueDetailDescription,
70
+ UIStrings,
71
+ };