chrome-devtools-frontend 1.0.999791 → 1.0.1000713

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 (27) hide show
  1. package/front_end/.eslintrc.js +4 -4
  2. package/front_end/core/common/ParsedURL.ts +3 -3
  3. package/front_end/core/i18n/locales/en-US.json +6 -0
  4. package/front_end/core/i18n/locales/en-XL.json +6 -0
  5. package/front_end/models/extensions/ExtensionAPI.ts +4 -4
  6. package/front_end/models/extensions/ExtensionTraceProvider.ts +2 -1
  7. package/front_end/models/issues_manager/DeprecationIssue.ts +7 -2
  8. package/front_end/models/workspace/WorkspaceImpl.ts +1 -1
  9. package/front_end/panels/lighthouse/LighthouseController.ts +15 -0
  10. package/front_end/panels/network/NetworkItemView.ts +1 -1
  11. package/front_end/panels/sources/AddSourceMapURLDialog.ts +5 -4
  12. package/front_end/panels/sources/DebuggerPausedMessage.ts +2 -1
  13. package/front_end/panels/sources/DebuggerPlugin.ts +2 -2
  14. package/front_end/panels/sources/EditingLocationHistoryManager.ts +2 -4
  15. package/front_end/panels/sources/NavigatorView.ts +4 -5
  16. package/front_end/panels/sources/ScriptFormatterEditorAction.ts +2 -1
  17. package/front_end/panels/sources/TabbedEditorContainer.ts +14 -13
  18. package/front_end/panels/timeline/ExtensionTracingSession.ts +2 -1
  19. package/front_end/panels/timeline/TimelineFlameChartView.ts +1 -1
  20. package/front_end/panels/timeline/TimelineLoader.ts +2 -1
  21. package/front_end/panels/timeline/TimelinePanel.ts +3 -3
  22. package/front_end/panels/timeline/TimelineTreeView.ts +4 -4
  23. package/front_end/panels/timeline/TimelineUIUtils.ts +3 -3
  24. package/front_end/panels/webauthn/WebauthnPane.ts +49 -15
  25. package/package.json +1 -1
  26. package/scripts/eslint_rules/lib/enforce_bound_render_for_schedule_render.js +110 -0
  27. package/scripts/eslint_rules/tests/enforce_bound_render_for_schedule_render_test.js +74 -0
@@ -2,6 +2,7 @@
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
+ // clang-format off
5
6
  const path = require('path');
6
7
  const rulesDirPlugin = require('eslint-plugin-rulesdir');
7
8
  rulesDirPlugin.RULES_DIR = path.join(__dirname, '..', 'scripts', 'eslint_rules', 'lib');
@@ -28,6 +29,7 @@ module.exports = {
28
29
  'rules': {
29
30
  '@typescript-eslint/explicit-function-return-type': 2,
30
31
  'rulesdir/no_importing_images_from_src': 2,
32
+ 'rulesdir/enforce_bound_render_for_schedule_render': 2,
31
33
  'rulesdir/enforce_custom_event_names': 2,
32
34
  'rulesdir/set_data_type_reference': 2,
33
35
  'rulesdir/no_bound_component_methods': 2,
@@ -120,10 +122,7 @@ module.exports = {
120
122
  },
121
123
  {
122
124
  // Ignore type properties that require quotes
123
- 'selector': [
124
- 'typeProperty',
125
- 'enumMember'
126
- ],
125
+ 'selector': ['typeProperty', 'enumMember'],
127
126
  'format': null,
128
127
  'modifiers': ['requiresQuotes']
129
128
  }
@@ -150,3 +149,4 @@ module.exports = {
150
149
  }
151
150
  ]
152
151
  };
152
+ // clang-format on
@@ -153,10 +153,10 @@ export class ParsedURL {
153
153
  return null;
154
154
  }
155
155
 
156
- private static preEncodeSpecialCharactersInPath(path: string): string {
156
+ static preEncodeSpecialCharactersInPath(path: string): string {
157
157
  // Based on net::FilePathToFileURL. Ideally we would handle
158
158
  // '\\' as well on non-Windows file systems.
159
- for (const specialChar of ['%', ';', '#', '?']) {
159
+ for (const specialChar of ['%', ';', '#', '?', ' ']) {
160
160
  (path as string) = path.replaceAll(specialChar, encodeURIComponent(specialChar));
161
161
  }
162
162
  return path;
@@ -185,7 +185,7 @@ export class ParsedURL {
185
185
  */
186
186
  static urlFromParentUrlAndName(parentUrl: Platform.DevToolsPath.UrlString, name: string):
187
187
  Platform.DevToolsPath.UrlString {
188
- return ParsedURL.concatenate(parentUrl, '/', encodeURIComponent(name));
188
+ return ParsedURL.concatenate(parentUrl, '/', ParsedURL.preEncodeSpecialCharactersInPath(name));
189
189
  }
190
190
 
191
191
  static encodedPathToRawPathString(encPath: Platform.DevToolsPath.EncodedPathString):
@@ -5822,6 +5822,9 @@
5822
5822
  "panels/lighthouse/LighthouseController.ts | isThisPageUsableByPeopleWith": {
5823
5823
  "message": "Is this page usable by people with disabilities or impairments"
5824
5824
  },
5825
+ "panels/lighthouse/LighthouseController.ts | javaScriptDisabled": {
5826
+ "message": "JavaScript is disabled. You need to enable JavaScript to audit this page. Open the Command Menu and run the Enable JavaScript command to enable JavaScript."
5827
+ },
5825
5828
  "panels/lighthouse/LighthouseController.ts | legacyNavigation": {
5826
5829
  "message": "Legacy navigation"
5827
5830
  },
@@ -11528,6 +11531,9 @@
11528
11531
  "panels/webauthn/WebauthnPane.ts | signCount": {
11529
11532
  "message": "Signature Count"
11530
11533
  },
11534
+ "panels/webauthn/WebauthnPane.ts | supportsLargeBlob": {
11535
+ "message": "Supports large blob"
11536
+ },
11531
11537
  "panels/webauthn/WebauthnPane.ts | supportsResidentKeys": {
11532
11538
  "message": "Supports resident keys"
11533
11539
  },
@@ -5822,6 +5822,9 @@
5822
5822
  "panels/lighthouse/LighthouseController.ts | isThisPageUsableByPeopleWith": {
5823
5823
  "message": "Îś t̂h́îś p̂áĝé ûśâb́l̂é b̂ý p̂éôṕl̂é ŵít̂h́ d̂íŝáb̂íl̂ít̂íêś ôŕ îḿp̂áîŕm̂én̂t́ŝ"
5824
5824
  },
5825
+ "panels/lighthouse/LighthouseController.ts | javaScriptDisabled": {
5826
+ "message": "Ĵáv̂áŜćr̂íp̂t́ îś d̂íŝáb̂ĺêd́. Ŷóû ńêéd̂ t́ô én̂áb̂ĺê J́âv́âŚĉŕîṕt̂ t́ô áûd́ît́ t̂h́îś p̂áĝé. Ôṕêń t̂h́ê Ćôḿm̂án̂d́ M̂én̂ú âńd̂ ŕûń t̂h́ê Én̂áb̂ĺê J́âv́âŚĉŕîṕt̂ ćôḿm̂án̂d́ t̂ó êńâb́l̂é Ĵáv̂áŜćr̂íp̂t́."
5827
+ },
5825
5828
  "panels/lighthouse/LighthouseController.ts | legacyNavigation": {
5826
5829
  "message": "L̂éĝáĉý n̂áv̂íĝát̂íôń"
5827
5830
  },
@@ -11528,6 +11531,9 @@
11528
11531
  "panels/webauthn/WebauthnPane.ts | signCount": {
11529
11532
  "message": "Ŝíĝńât́ûŕê Ćôún̂t́"
11530
11533
  },
11534
+ "panels/webauthn/WebauthnPane.ts | supportsLargeBlob": {
11535
+ "message": "Ŝúp̂ṕôŕt̂ś l̂ár̂ǵê b́l̂ób̂"
11536
+ },
11531
11537
  "panels/webauthn/WebauthnPane.ts | supportsResidentKeys": {
11532
11538
  "message": "Ŝúp̂ṕôŕt̂ś r̂éŝíd̂én̂t́ k̂éŷś"
11533
11539
  },
@@ -28,7 +28,7 @@
28
28
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
29
  */
30
30
 
31
- import type * as Platform from '../../core/platform/platform.js';
31
+ import * as Platform from '../../core/platform/platform.js';
32
32
  import type * as PublicAPI from '../../../extension-api/ExtensionAPI'; // eslint-disable-line rulesdir/es_modules_import
33
33
  import type * as HAR from '../har/har.js';
34
34
 
@@ -141,7 +141,7 @@ export namespace PrivateAPI {
141
141
  type UpdateButtonRequest =
142
142
  {command: Commands.UpdateButton, id: string, icon?: string, tooltip?: string, disabled?: boolean};
143
143
  type CompleteTraceSessionRequest =
144
- {command: Commands.CompleteTraceSession, id: string, url: string, timeOffset: number};
144
+ {command: Commands.CompleteTraceSession, id: string, url: Platform.DevToolsPath.UrlString, timeOffset: number};
145
145
  type CreateSidebarPaneRequest = {command: Commands.CreateSidebarPane, id: string, panel: string, title: string};
146
146
  type SetSidebarHeightRequest = {command: Commands.SetSidebarHeight, id: string, height: string};
147
147
  type SetSidebarContentRequest = {
@@ -930,11 +930,11 @@ self.injectedExtensionAPI = function(
930
930
  }
931
931
 
932
932
  (TraceSessionImpl.prototype as Pick<APIImpl.TraceSession, 'complete'>) = {
933
- complete: function(this: APIImpl.TraceSession, url?: string, timeOffset?: number): void {
933
+ complete: function(this: APIImpl.TraceSession, url?: Platform.DevToolsPath.UrlString, timeOffset?: number): void {
934
934
  extensionServer.sendRequest({
935
935
  command: PrivateAPI.Commands.CompleteTraceSession,
936
936
  id: this._id,
937
- url: url || '',
937
+ url: url || Platform.DevToolsPath.EmptyUrlString,
938
938
  timeOffset: timeOffset || 0,
939
939
  });
940
940
  },
@@ -3,6 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import {ExtensionServer} from './ExtensionServer.js';
6
+ import type * as Platform from '../../core/platform/platform.js';
6
7
 
7
8
  export class ExtensionTraceProvider {
8
9
  private readonly extensionOrigin: string;
@@ -43,5 +44,5 @@ export class ExtensionTraceProvider {
43
44
  let _lastSessionId = 0;
44
45
 
45
46
  export interface TracingSession {
46
- complete(url: string, timeOffsetMicroseconds: number): void;
47
+ complete(url: Platform.DevToolsPath.UrlString, timeOffsetMicroseconds: number): void;
47
48
  }
@@ -327,8 +327,13 @@ const
327
327
  xmlHttpRequestSynchronousInNonWorkerOutsideBeforeUnload:
328
328
  'Synchronous `XMLHttpRequest` on the main thread is deprecated because of its detrimental effects to the end user\u2019s experience. For more help, check https://xhr.spec.whatwg.org/.',
329
329
  /**
330
- *@description TODO(crbug.com/1320365): Description needed for translation
331
- */
330
+ *@description Warning displayed to developers that instead of using
331
+ * `supportsSession()`, which returns a promise that resolves if
332
+ * the XR session can be supported and rejects if not, they should
333
+ * use `isSessionSupported()` which will return a promise which
334
+ * resolves to a boolean indicating if the XR session can be
335
+ * supported or not, but may reject to throw an exception.
336
+ */
332
337
  xrSupportsSession:
333
338
  '`supportsSession()` is deprecated. Please use `isSessionSupported()` and check the resolved boolean value instead.',
334
339
  };
@@ -183,7 +183,7 @@ export abstract class ProjectStore implements Project {
183
183
  const oldPath = uiSourceCode.url();
184
184
  const newPath = uiSourceCode.parentURL() ?
185
185
  Common.ParsedURL.ParsedURL.urlFromParentUrlAndName(uiSourceCode.parentURL(), newName) :
186
- encodeURIComponent(newName) as Platform.DevToolsPath.UrlString;
186
+ Common.ParsedURL.ParsedURL.preEncodeSpecialCharactersInPath(newName) as Platform.DevToolsPath.UrlString;
187
187
  const value = this.uiSourceCodesMap.get(oldPath) as {
188
188
  uiSourceCode: UISourceCode,
189
189
  index: number,
@@ -171,6 +171,11 @@ const UIStrings = {
171
171
  */
172
172
  resetStorageLocalstorage:
173
173
  'Reset storage (`cache`, `service workers`, etc) before auditing. (Good for performance & `PWA` testing)',
174
+ /**
175
+ *@description Explanation for user that Ligthhouse can only audit when JavaScript is enabled
176
+ */
177
+ javaScriptDisabled:
178
+ 'JavaScript is disabled. You need to enable JavaScript to audit this page. Open the Command Menu and run the Enable JavaScript command to enable JavaScript.',
174
179
  };
175
180
  const str_ = i18n.i18n.registerUIStrings('panels/lighthouse/LighthouseController.ts', UIStrings);
176
181
  const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -196,6 +201,9 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
196
201
  runtimeSetting.setting.addChangeListener(this.recomputePageAuditability.bind(this));
197
202
  }
198
203
 
204
+ const javaScriptDisabledSetting = Common.Settings.Settings.instance().moduleSetting('javaScriptDisabled');
205
+ javaScriptDisabledSetting.addChangeListener(this.recomputePageAuditability.bind(this));
206
+
199
207
  SDK.TargetManager.TargetManager.instance().observeModels(SDK.ServiceWorkerManager.ServiceWorkerManager, this);
200
208
  SDK.TargetManager.TargetManager.instance().addEventListener(
201
209
  SDK.TargetManager.Events.InspectedURLChanged, this.recomputePageAuditability, this);
@@ -273,6 +281,10 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
273
281
  return null;
274
282
  }
275
283
 
284
+ private javaScriptDisabled(): boolean {
285
+ return Common.Settings.Settings.instance().moduleSetting('javaScriptDisabled').get();
286
+ }
287
+
276
288
  private async hasImportantResourcesNotCleared(): Promise<string> {
277
289
  const clearStorageSetting =
278
290
  RuntimeSettings.find(runtimeSetting => runtimeSetting.setting.name === 'lighthouse.clear_storage');
@@ -363,6 +375,7 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
363
375
  const hasActiveServiceWorker = this.hasActiveServiceWorker();
364
376
  const hasAtLeastOneCategory = this.hasAtLeastOneCategory();
365
377
  const unauditablePageMessage = this.unauditablePageMessage();
378
+ const javaScriptDisabled = this.javaScriptDisabled();
366
379
 
367
380
  let helpText = '';
368
381
  if (hasActiveServiceWorker) {
@@ -371,6 +384,8 @@ export class LighthouseController extends Common.ObjectWrapper.ObjectWrapper<Eve
371
384
  helpText = i18nString(UIStrings.atLeastOneCategoryMustBeSelected);
372
385
  } else if (unauditablePageMessage) {
373
386
  helpText = unauditablePageMessage;
387
+ } else if (javaScriptDisabled) {
388
+ helpText = i18nString(UIStrings.javaScriptDisabled);
374
389
  }
375
390
 
376
391
  this.dispatchEventToListeners(Events.PageAuditabilityChanged, {helpText});
@@ -143,7 +143,7 @@ export class NetworkItemView extends UI.TabbedPane.TabbedPane {
143
143
  this.element.classList.add('network-item-view');
144
144
 
145
145
  this.resourceViewTabSetting = Common.Settings.Settings.instance().createSetting(
146
- 'resourceViewTab', NetworkForward.UIRequestLocation.UIRequestTabs.Preview);
146
+ 'resourceViewTab', NetworkForward.UIRequestLocation.UIRequestTabs.Headers);
147
147
 
148
148
  this.headersView = new RequestHeadersView(request);
149
149
  this.appendTab(
@@ -3,6 +3,7 @@
3
3
  // found in the LICENSE file.
4
4
 
5
5
  import * as i18n from '../../core/i18n/i18n.js';
6
+ import type * as Platform from '../../core/platform/platform.js';
6
7
  import * as UI from '../../ui/legacy/legacy.js';
7
8
 
8
9
  import dialogStyles from './dialog.css.js';
@@ -22,8 +23,8 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
22
23
  export class AddSourceMapURLDialog extends UI.Widget.HBox {
23
24
  private readonly input: HTMLInputElement;
24
25
  private readonly dialog: UI.Dialog.Dialog;
25
- private readonly callback: (arg0: string) => void;
26
- constructor(callback: (arg0: string) => void) {
26
+ private readonly callback: (arg0: Platform.DevToolsPath.UrlString) => void;
27
+ constructor(callback: (arg0: Platform.DevToolsPath.UrlString) => void) {
27
28
  super(/* isWebComponent */ true);
28
29
 
29
30
  this.contentElement.createChild('label').textContent = i18nString(UIStrings.sourceMapUrl);
@@ -51,13 +52,13 @@ export class AddSourceMapURLDialog extends UI.Widget.HBox {
51
52
  this.dialog.show();
52
53
  }
53
54
 
54
- private done(value: string): void {
55
+ private done(value: Platform.DevToolsPath.UrlString): void {
55
56
  this.dialog.hide();
56
57
  this.callback(value);
57
58
  }
58
59
 
59
60
  private apply(): void {
60
- this.done(this.input.value);
61
+ this.done(this.input.value as Platform.DevToolsPath.UrlString);
61
62
  }
62
63
 
63
64
  private onKeyDown(event: KeyboardEvent): void {
@@ -4,6 +4,7 @@
4
4
 
5
5
  import * as Common from '../../core/common/common.js';
6
6
  import * as i18n from '../../core/i18n/i18n.js';
7
+ import type * as Platform from '../../core/platform/platform.js';
7
8
  import * as SDK from '../../core/sdk/sdk.js';
8
9
 
9
10
  import debuggerPausedMessageStyles from './debuggerPausedMessage.css.js';
@@ -276,6 +277,6 @@ export const BreakpointTypeNouns = new Map([
276
277
  ]);
277
278
  interface PausedDetailsAuxData {
278
279
  description?: string;
279
- url?: string;
280
+ url?: Platform.DevToolsPath.UrlString;
280
281
  value?: string;
281
282
  }
@@ -472,11 +472,11 @@ export class DebuggerPlugin extends Plugin {
472
472
  }
473
473
 
474
474
  function addSourceMapURLDialogCallback(
475
- scriptFile: Bindings.ResourceScriptMapping.ResourceScriptFile, url: string): void {
475
+ scriptFile: Bindings.ResourceScriptMapping.ResourceScriptFile, url: Platform.DevToolsPath.UrlString): void {
476
476
  if (!url) {
477
477
  return;
478
478
  }
479
- scriptFile.addSourceMapURL(url as Platform.DevToolsPath.UrlString);
479
+ scriptFile.addSourceMapURL(url);
480
480
  }
481
481
 
482
482
  if (this.uiSourceCode.project().type() === Workspace.Workspace.projectTypes.Network &&
@@ -93,9 +93,7 @@ export class EditingLocationHistoryManager {
93
93
  }
94
94
 
95
95
  private reveal(entry: EditingLocationHistoryEntry): void {
96
- // TODO(crbug.com/1253323): Cast to UrlString will be removed when migration to branded types is complete.
97
- const uiSourceCode = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCode(
98
- entry.projectId, entry.url as Platform.DevToolsPath.UrlString);
96
+ const uiSourceCode = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCode(entry.projectId, entry.url);
99
97
  if (uiSourceCode) {
100
98
  this.revealing = true;
101
99
  this.sourcesView.showSourceLocation(uiSourceCode, entry.position, false, true);
@@ -131,7 +129,7 @@ export class EditingLocationHistoryManager {
131
129
 
132
130
  class EditingLocationHistoryEntry {
133
131
  readonly projectId: string;
134
- readonly url: string;
132
+ readonly url: Platform.DevToolsPath.UrlString;
135
133
  position: number;
136
134
 
137
135
  constructor(uiSourceCode: Workspace.UISourceCode.UISourceCode, position: number) {
@@ -290,7 +290,7 @@ export class NavigatorView extends UI.Widget.VBox implements SDK.TargetManager.O
290
290
  // Update folder titles.
291
291
  const pathTokens =
292
292
  Persistence.FileSystemWorkspaceBinding.FileSystemWorkspaceBinding.relativePath(binding.fileSystem);
293
- let folderPath = '' as Platform.DevToolsPath.EncodedPathString;
293
+ let folderPath = Platform.DevToolsPath.EmptyEncodedPathString;
294
294
  for (let i = 0; i < pathTokens.length - 1; ++i) {
295
295
  folderPath = Common.ParsedURL.ParsedURL.concatenate(folderPath, pathTokens[i]);
296
296
  const folderId =
@@ -744,7 +744,7 @@ export class NavigatorView extends UI.Widget.VBox implements SDK.TargetManager.O
744
744
  const folderId = this.folderNodeId(
745
745
  project, target, frame, uiSourceCode.origin(),
746
746
  currentNode instanceof NavigatorFolderTreeNode && currentNode.folderPath ||
747
- '' as Platform.DevToolsPath.EncodedPathString);
747
+ Platform.DevToolsPath.EmptyEncodedPathString);
748
748
  this.subfolderNodes.delete(folderId);
749
749
  parentNode.removeChild(currentNode);
750
750
  currentNode = (parentNode as NavigatorUISourceCodeTreeNode | null);
@@ -822,8 +822,7 @@ export class NavigatorView extends UI.Widget.VBox implements SDK.TargetManager.O
822
822
  contextMenu.editSection().appendItem(i18nString(UIStrings.rename), this.handleContextMenuRename.bind(this, node));
823
823
  contextMenu.editSection().appendItem(
824
824
  i18nString(UIStrings.makeACopy),
825
- this.handleContextMenuCreate.bind(
826
- this, project, '' as Platform.DevToolsPath.EncodedPathString, uiSourceCode));
825
+ this.handleContextMenuCreate.bind(this, project, Platform.DevToolsPath.EmptyEncodedPathString, uiSourceCode));
827
826
  contextMenu.editSection().appendItem(
828
827
  i18nString(UIStrings.delete), this.handleContextMenuDelete.bind(this, uiSourceCode));
829
828
  }
@@ -1472,7 +1471,7 @@ export class NavigatorFolderTreeNode extends NavigatorTreeNode {
1472
1471
  folderPath: Platform.DevToolsPath.EncodedPathString, title: string) {
1473
1472
  super(navigatorView, id, type);
1474
1473
  this.project = project;
1475
- this.folderPath = folderPath as Platform.DevToolsPath.EncodedPathString;
1474
+ this.folderPath = folderPath;
1476
1475
  this.title = title;
1477
1476
  }
1478
1477
 
@@ -4,6 +4,7 @@
4
4
 
5
5
  import type * as Common from '../../core/common/common.js';
6
6
  import * as i18n from '../../core/i18n/i18n.js';
7
+ import type * as Platform from '../../core/platform/platform.js';
7
8
  import * as FormatterModule from '../../models/formatter/formatter.js';
8
9
  import * as Persistence from '../../models/persistence/persistence.js';
9
10
  import * as Workspace from '../../models/workspace/workspace.js';
@@ -30,7 +31,7 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
30
31
  let scriptFormatterEditorActionInstance: ScriptFormatterEditorAction;
31
32
 
32
33
  export class ScriptFormatterEditorAction implements EditorAction {
33
- private readonly pathsToFormatOnLoad: Set<string>;
34
+ private readonly pathsToFormatOnLoad: Set<Platform.DevToolsPath.UrlString>;
34
35
  private sourcesView!: SourcesView;
35
36
  private button!: UI.Toolbar.ToolbarButton;
36
37
  private constructor() {
@@ -436,10 +436,10 @@ export class TabbedEditorContainer extends Common.ObjectWrapper.ObjectWrapper<Ev
436
436
  private updateHistory(): void {
437
437
  const tabIds = this.tabbedPane.lastOpenedTabIds(maximalPreviouslyViewedFilesCount);
438
438
 
439
- function tabIdToURI(this: TabbedEditorContainer, tabId: string): string {
439
+ function tabIdToURI(this: TabbedEditorContainer, tabId: string): Platform.DevToolsPath.UrlString {
440
440
  const tab = this.files.get(tabId);
441
441
  if (!tab) {
442
- return '';
442
+ return Platform.DevToolsPath.EmptyUrlString;
443
443
  }
444
444
  return tab.url();
445
445
  }
@@ -639,18 +639,19 @@ export let tabId = 0;
639
639
  export const maximalPreviouslyViewedFilesCount = 30;
640
640
 
641
641
  interface SerializedHistoryItem {
642
- url: string;
642
+ url: Platform.DevToolsPath.UrlString;
643
643
  selectionRange?: TextUtils.TextRange.SerializedTextRange;
644
644
  scrollLineNumber?: number;
645
645
  }
646
646
 
647
647
  export class HistoryItem {
648
- url: string;
648
+ url: Platform.DevToolsPath.UrlString;
649
649
  private isSerializable: boolean;
650
650
  selectionRange: TextUtils.TextRange.TextRange|undefined;
651
651
  scrollLineNumber: number|undefined;
652
652
 
653
- constructor(url: string, selectionRange?: TextUtils.TextRange.TextRange, scrollLineNumber?: number) {
653
+ constructor(
654
+ url: Platform.DevToolsPath.UrlString, selectionRange?: TextUtils.TextRange.TextRange, scrollLineNumber?: number) {
654
655
  this.url = url;
655
656
  this.isSerializable = url.length < HistoryItem.serializableUrlLengthLimit;
656
657
  this.selectionRange = selectionRange;
@@ -700,7 +701,7 @@ export class History {
700
701
  return new History(items);
701
702
  }
702
703
 
703
- index(url: string): number {
704
+ index(url: Platform.DevToolsPath.UrlString): number {
704
705
  const index = this.itemsIndex.get(url);
705
706
  if (index !== undefined) {
706
707
  return index;
@@ -716,12 +717,12 @@ export class History {
716
717
  }
717
718
  }
718
719
 
719
- selectionRange(url: string): TextUtils.TextRange.TextRange|undefined {
720
+ selectionRange(url: Platform.DevToolsPath.UrlString): TextUtils.TextRange.TextRange|undefined {
720
721
  const index = this.index(url);
721
722
  return index !== -1 ? this.items[index].selectionRange : undefined;
722
723
  }
723
724
 
724
- updateSelectionRange(url: string, selectionRange?: TextUtils.TextRange.TextRange): void {
725
+ updateSelectionRange(url: Platform.DevToolsPath.UrlString, selectionRange?: TextUtils.TextRange.TextRange): void {
725
726
  if (!selectionRange) {
726
727
  return;
727
728
  }
@@ -732,12 +733,12 @@ export class History {
732
733
  this.items[index].selectionRange = selectionRange;
733
734
  }
734
735
 
735
- scrollLineNumber(url: string): number|undefined {
736
+ scrollLineNumber(url: Platform.DevToolsPath.UrlString): number|undefined {
736
737
  const index = this.index(url);
737
738
  return index !== -1 ? this.items[index].scrollLineNumber : undefined;
738
739
  }
739
740
 
740
- updateScrollLineNumber(url: string, scrollLineNumber: number): void {
741
+ updateScrollLineNumber(url: Platform.DevToolsPath.UrlString, scrollLineNumber: number): void {
741
742
  const index = this.index(url);
742
743
  if (index === -1) {
743
744
  return;
@@ -745,7 +746,7 @@ export class History {
745
746
  this.items[index].scrollLineNumber = scrollLineNumber;
746
747
  }
747
748
 
748
- update(urls: string[]): void {
749
+ update(urls: Platform.DevToolsPath.UrlString[]): void {
749
750
  for (let i = urls.length - 1; i >= 0; --i) {
750
751
  const index = this.index(urls[i]);
751
752
  let item;
@@ -760,7 +761,7 @@ export class History {
760
761
  }
761
762
  }
762
763
 
763
- remove(url: string): void {
764
+ remove(url: Platform.DevToolsPath.UrlString): void {
764
765
  const index = this.index(url);
765
766
  if (index !== -1) {
766
767
  this.items.splice(index, 1);
@@ -786,7 +787,7 @@ export class History {
786
787
  return serializedHistory;
787
788
  }
788
789
 
789
- urls(): string[] {
790
+ urls(): Platform.DevToolsPath.UrlString[] {
790
791
  const result = [];
791
792
  for (let i = 0; i < this.items.length; ++i) {
792
793
  result.push(this.items[i].url);
@@ -2,6 +2,7 @@
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 type * as Platform from '../../core/platform/platform.js';
5
6
  import type * as SDK from '../../core/sdk/sdk.js';
6
7
  import type * as Extensions from '../../models/extensions/extensions.js';
7
8
 
@@ -42,7 +43,7 @@ export class ExtensionTracingSession implements Extensions.ExtensionTraceProvide
42
43
  this.completionCallback();
43
44
  }
44
45
 
45
- complete(url: string, timeOffsetMicroseconds: number): void {
46
+ complete(url: Platform.DevToolsPath.UrlString, timeOffsetMicroseconds: number): void {
46
47
  if (!url) {
47
48
  this.completionCallback();
48
49
  return;
@@ -161,7 +161,7 @@ export class TimelineFlameChartView extends UI.Widget.VBox implements PerfUI.Fla
161
161
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
162
162
  private readonly groupBySetting: Common.Settings.Setting<any>;
163
163
  private searchableView!: UI.SearchableView.SearchableView;
164
- private urlToColorCache?: Map<string, string>;
164
+ private urlToColorCache?: Map<Platform.DevToolsPath.UrlString, string>;
165
165
  private needsResizeToPreferredHeights?: boolean;
166
166
  private selectedSearchResult?: number;
167
167
  private searchRegex?: RegExp;
@@ -5,6 +5,7 @@
5
5
  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
+ import type * as Platform from '../../core/platform/platform.js';
8
9
  import * as SDK from '../../core/sdk/sdk.js';
9
10
  import * as Bindings from '../../models/bindings/bindings.js';
10
11
  import * as TextUtils from '../../models/text_utils/text_utils.js';
@@ -95,7 +96,7 @@ export class TimelineLoader implements Common.StringOutputStream.OutputStream {
95
96
  return loader;
96
97
  }
97
98
 
98
- static loadFromURL(url: string, client: Client): TimelineLoader {
99
+ static loadFromURL(url: Platform.DevToolsPath.UrlString, client: Client): TimelineLoader {
99
100
  const loader = new TimelineLoader(client);
100
101
  Host.ResourceLoader.loadAsStream(url, null, loader);
101
102
  return loader;
@@ -695,7 +695,7 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
695
695
  this.createFileSelector();
696
696
  }
697
697
 
698
- loadFromURL(url: string): void {
698
+ loadFromURL(url: Platform.DevToolsPath.UrlString): void {
699
699
  if (this.state !== State.Idle) {
700
700
  return;
701
701
  }
@@ -1249,7 +1249,7 @@ export class TimelinePanel extends UI.Panel.Panel implements Client, TimelineMod
1249
1249
  const item = items[0];
1250
1250
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.PerfPanelTraceImported);
1251
1251
  if (item.kind === 'string') {
1252
- const url = dataTransfer.getData('text/uri-list');
1252
+ const url = dataTransfer.getData('text/uri-list') as Platform.DevToolsPath.UrlString;
1253
1253
  if (new Common.ParsedURL.ParsedURL(url).isValid) {
1254
1254
  this.loadFromURL(url);
1255
1255
  }
@@ -1492,7 +1492,7 @@ export class LoadTimelineHandler implements Common.QueryParamHandler.QueryParamH
1492
1492
 
1493
1493
  handleQueryParam(value: string): void {
1494
1494
  void UI.ViewManager.ViewManager.instance().showView('timeline').then(() => {
1495
- TimelinePanel.instance().loadFromURL(window.decodeURIComponent(value));
1495
+ TimelinePanel.instance().loadFromURL(window.decodeURIComponent(value) as Platform.DevToolsPath.UrlString);
1496
1496
  });
1497
1497
  }
1498
1498
  }
@@ -756,9 +756,9 @@ export class AggregatedTimelineTreeView extends TimelineTreeView {
756
756
  }
757
757
 
758
758
  private beautifyDomainName(this: AggregatedTimelineTreeView, name: string): string {
759
- if (AggregatedTimelineTreeView.isExtensionInternalURL(name)) {
759
+ if (AggregatedTimelineTreeView.isExtensionInternalURL(name as Platform.DevToolsPath.UrlString)) {
760
760
  name = i18nString(UIStrings.chromeExtensionsOverhead);
761
- } else if (AggregatedTimelineTreeView.isV8NativeURL(name)) {
761
+ } else if (AggregatedTimelineTreeView.isV8NativeURL(name as Platform.DevToolsPath.UrlString)) {
762
762
  name = i18nString(UIStrings.vRuntime);
763
763
  } else if (name.startsWith('chrome-extension')) {
764
764
  name = this.executionContextNamesByOrigin.get(name) || name;
@@ -949,11 +949,11 @@ export class AggregatedTimelineTreeView extends TimelineTreeView {
949
949
  contextMenu.appendApplicableItems(frame.ownerNode);
950
950
  }
951
951
 
952
- private static isExtensionInternalURL(url: string): boolean {
952
+ private static isExtensionInternalURL(url: Platform.DevToolsPath.UrlString): boolean {
953
953
  return url.startsWith(AggregatedTimelineTreeView.extensionInternalPrefix);
954
954
  }
955
955
 
956
- private static isV8NativeURL(url: string): boolean {
956
+ private static isV8NativeURL(url: Platform.DevToolsPath.UrlString): boolean {
957
957
  return url.startsWith(AggregatedTimelineTreeView.v8NativePrefix);
958
958
  }
959
959
 
@@ -1473,7 +1473,7 @@ export class TimelineUIUtils {
1473
1473
  }
1474
1474
  }
1475
1475
 
1476
- static eventURL(event: SDK.TracingModel.Event): string|null {
1476
+ static eventURL(event: SDK.TracingModel.Event): Platform.DevToolsPath.UrlString|null {
1477
1477
  const data = event.args['data'] || event.args['beginData'];
1478
1478
  const url = data && data.url;
1479
1479
  if (url) {
@@ -1482,7 +1482,7 @@ export class TimelineUIUtils {
1482
1482
  const stackTrace = data && data['stackTrace'];
1483
1483
  const frame = stackTrace && stackTrace.length && stackTrace[0] ||
1484
1484
  TimelineModel.TimelineModel.TimelineData.forEvent(event).topFrame();
1485
- return frame && frame.url || null;
1485
+ return frame && frame.url as Platform.DevToolsPath.UrlString || null;
1486
1486
  }
1487
1487
 
1488
1488
  static eventStyle(event: SDK.TracingModel.Event): TimelineRecordStyle {
@@ -1533,7 +1533,7 @@ export class TimelineUIUtils {
1533
1533
  static eventColorByProduct(
1534
1534
  model: TimelineModel.TimelineModel.TimelineModelImpl, urlToColorCache: Map<string, string>,
1535
1535
  event: SDK.TracingModel.Event): string {
1536
- const url = TimelineUIUtils.eventURL(event) || '';
1536
+ const url = TimelineUIUtils.eventURL(event) || Platform.DevToolsPath.EmptyUrlString;
1537
1537
  let color = urlToColorCache.get(url);
1538
1538
  if (color) {
1539
1539
  return color;
@@ -84,6 +84,12 @@ const UIStrings = {
84
84
  */
85
85
  supportsResidentKeys: 'Supports resident keys',
86
86
  /**
87
+ *@description Label for checkbox that toggles large blob support on virtual authenticators. Large blobs are opaque data associated
88
+ * with a WebAuthn credential that a website can store, like an SSH certificate or a symmetric encryption key.
89
+ * See https://w3c.github.io/webauthn/#sctn-large-blob-extension
90
+ */
91
+ supportsLargeBlob: 'Supports large blob',
92
+ /**
87
93
  *@description Text to add something
88
94
  */
89
95
  add: 'Add',
@@ -244,10 +250,12 @@ export class WebauthnPaneImpl extends UI.Widget.VBox implements
244
250
  #protocolSelect: HTMLSelectElement|undefined;
245
251
  #transportSelect: HTMLSelectElement|undefined;
246
252
  #residentKeyCheckboxLabel: UI.UIUtils.CheckboxLabel|undefined;
247
- #residentKeyCheckbox: HTMLInputElement|undefined;
253
+ residentKeyCheckbox: HTMLInputElement|undefined;
248
254
  #userVerificationCheckboxLabel: UI.UIUtils.CheckboxLabel|undefined;
249
255
  #userVerificationCheckbox: HTMLInputElement|undefined;
250
- #addAuthenticatorButton: HTMLButtonElement|undefined;
256
+ #largeBlobCheckboxLabel: UI.UIUtils.CheckboxLabel|undefined;
257
+ largeBlobCheckbox: HTMLInputElement|undefined;
258
+ addAuthenticatorButton: HTMLButtonElement|undefined;
251
259
  #isEnabling?: Promise<void>;
252
260
 
253
261
  constructor() {
@@ -475,13 +483,18 @@ export class WebauthnPaneImpl extends UI.Widget.VBox implements
475
483
  }
476
484
 
477
485
  #updateNewAuthenticatorSectionOptions(): void {
478
- if (!this.#protocolSelect || !this.#residentKeyCheckbox || !this.#userVerificationCheckbox) {
486
+ if (!this.#protocolSelect || !this.residentKeyCheckbox || !this.#userVerificationCheckbox ||
487
+ !this.largeBlobCheckbox) {
479
488
  return;
480
489
  }
481
490
 
482
491
  if (this.#protocolSelect.value === Protocol.WebAuthn.AuthenticatorProtocol.Ctap2) {
483
- this.#residentKeyCheckbox.disabled = false;
492
+ this.residentKeyCheckbox.disabled = false;
484
493
  this.#userVerificationCheckbox.disabled = false;
494
+ this.largeBlobCheckbox.disabled = !this.residentKeyCheckbox.checked;
495
+ if (this.largeBlobCheckbox.disabled) {
496
+ this.largeBlobCheckbox.checked = false;
497
+ }
485
498
  this.#updateEnabledTransportOptions([
486
499
  Protocol.WebAuthn.AuthenticatorTransport.Usb,
487
500
  Protocol.WebAuthn.AuthenticatorTransport.Ble,
@@ -491,10 +504,12 @@ export class WebauthnPaneImpl extends UI.Widget.VBox implements
491
504
  Protocol.WebAuthn.AuthenticatorTransport.Internal,
492
505
  ]);
493
506
  } else {
494
- this.#residentKeyCheckbox.checked = false;
495
- this.#residentKeyCheckbox.disabled = true;
507
+ this.residentKeyCheckbox.checked = false;
508
+ this.residentKeyCheckbox.disabled = true;
496
509
  this.#userVerificationCheckbox.checked = false;
497
510
  this.#userVerificationCheckbox.disabled = true;
511
+ this.largeBlobCheckbox.checked = false;
512
+ this.largeBlobCheckbox.disabled = true;
498
513
  this.#updateEnabledTransportOptions([
499
514
  Protocol.WebAuthn.AuthenticatorTransport.Usb,
500
515
  Protocol.WebAuthn.AuthenticatorTransport.Ble,
@@ -524,6 +539,7 @@ export class WebauthnPaneImpl extends UI.Widget.VBox implements
524
539
  const transportGroup = this.#newAuthenticatorForm.createChild('div', 'authenticator-option');
525
540
  const residentKeyGroup = this.#newAuthenticatorForm.createChild('div', 'authenticator-option');
526
541
  const userVerificationGroup = this.#newAuthenticatorForm.createChild('div', 'authenticator-option');
542
+ const largeBlobGroup = this.#newAuthenticatorForm.createChild('div', 'authenticator-option');
527
543
  const addButtonGroup = this.#newAuthenticatorForm.createChild('div', 'authenticator-option');
528
544
 
529
545
  const protocolSelectTitle = UI.UIUtils.createLabel(i18nString(UIStrings.protocol), 'authenticator-option-label');
@@ -551,9 +567,9 @@ export class WebauthnPaneImpl extends UI.Widget.VBox implements
551
567
  this.#residentKeyCheckboxLabel = UI.UIUtils.CheckboxLabel.create(i18nString(UIStrings.supportsResidentKeys), false);
552
568
  this.#residentKeyCheckboxLabel.textElement.classList.add('authenticator-option-label');
553
569
  residentKeyGroup.appendChild(this.#residentKeyCheckboxLabel.textElement);
554
- this.#residentKeyCheckbox = this.#residentKeyCheckboxLabel.checkboxElement;
555
- this.#residentKeyCheckbox.checked = false;
556
- this.#residentKeyCheckbox.classList.add('authenticator-option-checkbox');
570
+ this.residentKeyCheckbox = this.#residentKeyCheckboxLabel.checkboxElement;
571
+ this.residentKeyCheckbox.checked = false;
572
+ this.residentKeyCheckbox.classList.add('authenticator-option-checkbox');
557
573
  residentKeyGroup.appendChild(this.#residentKeyCheckboxLabel);
558
574
 
559
575
  this.#userVerificationCheckboxLabel = UI.UIUtils.CheckboxLabel.create('Supports user verification', false);
@@ -564,17 +580,29 @@ export class WebauthnPaneImpl extends UI.Widget.VBox implements
564
580
  this.#userVerificationCheckbox.classList.add('authenticator-option-checkbox');
565
581
  userVerificationGroup.appendChild(this.#userVerificationCheckboxLabel);
566
582
 
567
- this.#addAuthenticatorButton =
583
+ this.#largeBlobCheckboxLabel = UI.UIUtils.CheckboxLabel.create(i18nString(UIStrings.supportsLargeBlob), false);
584
+ this.#largeBlobCheckboxLabel.textElement.classList.add('authenticator-option-label');
585
+ largeBlobGroup.appendChild(this.#largeBlobCheckboxLabel.textElement);
586
+ this.largeBlobCheckbox = this.#largeBlobCheckboxLabel.checkboxElement;
587
+ this.largeBlobCheckbox.checked = false;
588
+ this.largeBlobCheckbox.classList.add('authenticator-option-checkbox');
589
+ this.largeBlobCheckbox.name = 'large-blob-checkbox';
590
+ largeBlobGroup.appendChild(this.#largeBlobCheckboxLabel);
591
+
592
+ this.addAuthenticatorButton =
568
593
  UI.UIUtils.createTextButton(i18nString(UIStrings.add), this.#handleAddAuthenticatorButton.bind(this), '');
569
594
  addButtonGroup.createChild('div', 'authenticator-option-label');
570
- addButtonGroup.appendChild(this.#addAuthenticatorButton);
595
+ addButtonGroup.appendChild(this.addAuthenticatorButton);
571
596
  const addAuthenticatorTitle = UI.UIUtils.createLabel(i18nString(UIStrings.addAuthenticator), '');
572
- UI.ARIAUtils.bindLabelToControl(addAuthenticatorTitle, this.#addAuthenticatorButton);
597
+ UI.ARIAUtils.bindLabelToControl(addAuthenticatorTitle, this.addAuthenticatorButton);
573
598
 
574
599
  this.#updateNewAuthenticatorSectionOptions();
575
600
  if (this.#protocolSelect) {
576
601
  this.#protocolSelect.addEventListener('change', this.#updateNewAuthenticatorSectionOptions.bind(this));
577
602
  }
603
+ if (this.residentKeyCheckbox) {
604
+ this.residentKeyCheckbox.addEventListener('change', this.#updateNewAuthenticatorSectionOptions.bind(this));
605
+ }
578
606
  }
579
607
 
580
608
  async #handleAddAuthenticatorButton(): Promise<void> {
@@ -702,6 +730,7 @@ export class WebauthnPaneImpl extends UI.Widget.VBox implements
702
730
  const protocolField = sectionFields.createChild('div', 'authenticator-field');
703
731
  const transportField = sectionFields.createChild('div', 'authenticator-field');
704
732
  const srkField = sectionFields.createChild('div', 'authenticator-field');
733
+ const slbField = sectionFields.createChild('div', 'authenticator-field');
705
734
  const suvField = sectionFields.createChild('div', 'authenticator-field');
706
735
 
707
736
  uuidField.appendChild(UI.UIUtils.createLabel(i18nString(UIStrings.uuid), 'authenticator-option-label'));
@@ -709,6 +738,7 @@ export class WebauthnPaneImpl extends UI.Widget.VBox implements
709
738
  transportField.appendChild(UI.UIUtils.createLabel(i18nString(UIStrings.transport), 'authenticator-option-label'));
710
739
  srkField.appendChild(
711
740
  UI.UIUtils.createLabel(i18nString(UIStrings.supportsResidentKeys), 'authenticator-option-label'));
741
+ slbField.appendChild(UI.UIUtils.createLabel(i18nString(UIStrings.supportsLargeBlob), 'authenticator-option-label'));
712
742
  suvField.appendChild(
713
743
  UI.UIUtils.createLabel(i18nString(UIStrings.supportsUserVerification), 'authenticator-option-label'));
714
744
 
@@ -717,6 +747,8 @@ export class WebauthnPaneImpl extends UI.Widget.VBox implements
717
747
  transportField.createChild('div', 'authenticator-field-value').textContent = options.transport;
718
748
  srkField.createChild('div', 'authenticator-field-value').textContent =
719
749
  options.hasResidentKey ? i18nString(UIStrings.yes) : i18nString(UIStrings.no);
750
+ slbField.createChild('div', 'authenticator-field-value').textContent =
751
+ options.hasLargeBlob ? i18nString(UIStrings.yes) : i18nString(UIStrings.no);
720
752
  suvField.createChild('div', 'authenticator-field-value').textContent =
721
753
  options.hasUserVerification ? i18nString(UIStrings.yes) : i18nString(UIStrings.no);
722
754
  }
@@ -779,18 +811,20 @@ export class WebauthnPaneImpl extends UI.Widget.VBox implements
779
811
 
780
812
  #createOptionsFromCurrentInputs(): Protocol.WebAuthn.VirtualAuthenticatorOptions {
781
813
  // TODO(crbug.com/1034663): Add optionality for isUserVerified param.
782
- if (!this.#protocolSelect || !this.#transportSelect || !this.#residentKeyCheckbox ||
783
- !this.#userVerificationCheckbox) {
814
+ if (!this.#protocolSelect || !this.#transportSelect || !this.residentKeyCheckbox ||
815
+ !this.#userVerificationCheckbox || !this.largeBlobCheckbox) {
784
816
  throw new Error('Unable to create options from current inputs');
785
817
  }
786
818
 
787
819
  return {
788
820
  protocol: this.#protocolSelect.options[this.#protocolSelect.selectedIndex].value as
789
821
  Protocol.WebAuthn.AuthenticatorProtocol,
822
+ ctap2Version: Protocol.WebAuthn.Ctap2Version.Ctap2_1,
790
823
  transport: this.#transportSelect.options[this.#transportSelect.selectedIndex].value as
791
824
  Protocol.WebAuthn.AuthenticatorTransport,
792
- hasResidentKey: this.#residentKeyCheckbox.checked,
825
+ hasResidentKey: this.residentKeyCheckbox.checked,
793
826
  hasUserVerification: this.#userVerificationCheckbox.checked,
827
+ hasLargeBlob: this.largeBlobCheckbox.checked,
794
828
  automaticPresenceSimulation: true,
795
829
  isUserVerified: true,
796
830
  };
package/package.json CHANGED
@@ -55,5 +55,5 @@
55
55
  "unittest": "scripts/test/run_unittests.py --no-text-coverage",
56
56
  "watch": "vpython third_party/node/node.py --output scripts/watch_build.js"
57
57
  },
58
- "version": "1.0.999791"
58
+ "version": "1.0.1000713"
59
59
  }
@@ -0,0 +1,110 @@
1
+ // Copyright 2020 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
+ 'use strict';
5
+
6
+ function goToClassDeclaration(node) {
7
+ if (!node) {
8
+ return null;
9
+ }
10
+
11
+ if (node.type === 'ClassDeclaration') {
12
+ return node;
13
+ }
14
+
15
+ return goToClassDeclaration(node.parent);
16
+ }
17
+
18
+ function isMemberExpressionOnThis(memberExpression) {
19
+ if (!memberExpression) {
20
+ return false;
21
+ }
22
+
23
+ if (memberExpression.object.type === 'ThisExpression') {
24
+ // Take into `a.this.bind()` case into account
25
+ // `this` must be the last object in the `MemberExpression` chain
26
+ return !memberExpression.object.object;
27
+ }
28
+
29
+ return isMemberExpressionOnThis(memberExpression.object);
30
+ }
31
+
32
+ // Whether the right hand side of property definition is `this.xxx.yyy.bind(this);`
33
+ function isPropertyDefinitionViaBindCallToThis(propertyDefinition) {
34
+ if (propertyDefinition.value.type !== 'CallExpression' ||
35
+ propertyDefinition.value.callee.type !== 'MemberExpression') {
36
+ return false;
37
+ }
38
+
39
+ const isCalleeObjectThis = isMemberExpressionOnThis(propertyDefinition.value.callee);
40
+ // Whether the CallExpression is on a property of `this` (this.xxx.yyy.bind)
41
+ if (!isCalleeObjectThis) {
42
+ return false;
43
+ }
44
+
45
+ const isItBindCall = propertyDefinition.value.callee.property.name === 'bind';
46
+ // Whether the CallExpression is a `bind` call on a property of `this`
47
+ if (!isItBindCall) {
48
+ return false;
49
+ }
50
+
51
+ const callArgument = propertyDefinition.value.arguments[0];
52
+ // Call argument to `bind` is not `this`
53
+ if (!callArgument || callArgument.type !== 'ThisExpression') {
54
+ return false;
55
+ }
56
+
57
+ return true;
58
+ }
59
+
60
+ // Whether the property definition is arrow function like `#render = () => {}`
61
+ function isPropertyDefinitionViaArrowFunction(propertyDefinition) {
62
+ return propertyDefinition.value.type === 'ArrowFunctionExpression';
63
+ }
64
+
65
+ module.exports = {
66
+ meta: {
67
+ type: 'problem',
68
+ docs: {
69
+ description: 'Enforce render method to be bound while calling scheduleRender',
70
+ category: 'Possible Errors',
71
+ },
72
+ fixable: 'code',
73
+ schema: [] // no options
74
+ },
75
+ create: function(context) {
76
+ return {
77
+ CallExpression(node) {
78
+ // Calls in the form of `ScheduledRender.scheduleRender`
79
+ const isScheduleRenderCall = node.callee.type === 'MemberExpression' &&
80
+ node.callee.object?.property?.name === 'ScheduledRender' && node.callee.property?.name === 'scheduleRender';
81
+ if (!isScheduleRenderCall) {
82
+ return;
83
+ }
84
+
85
+ const callbackArgument = node.arguments[1];
86
+ // Whether the second argument points to a property of `this`
87
+ // like `ScheduledRender.scheduleRender(<any>, this.<any>)
88
+ if (callbackArgument.type !== 'MemberExpression' || callbackArgument.object.type !== 'ThisExpression') {
89
+ return;
90
+ }
91
+
92
+ const containingClassForTheCall = goToClassDeclaration(node);
93
+ // Only care about the calls in custom components
94
+ if (!containingClassForTheCall.superClass || containingClassForTheCall.superClass.name !== 'HTMLElement') {
95
+ return;
96
+ }
97
+
98
+ const calledMethod = callbackArgument.property;
99
+ // Check whether the called method is bound (it should be 'PropertyDefinition')
100
+ const propertyDefinition = containingClassForTheCall.body.body.find(
101
+ bodyNode => bodyNode.type === 'PropertyDefinition' && bodyNode.key.name === calledMethod.name);
102
+ if (!propertyDefinition ||
103
+ (!isPropertyDefinitionViaArrowFunction(propertyDefinition) &&
104
+ !isPropertyDefinitionViaBindCallToThis(propertyDefinition))) {
105
+ context.report({node, message: 'Bind `render` method of `scheduleRender` to `this` in components'});
106
+ }
107
+ }
108
+ };
109
+ }
110
+ };
@@ -0,0 +1,74 @@
1
+ // Copyright 2020 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
+ 'use strict';
5
+
6
+ const rule = require('../lib/enforce_bound_render_for_schedule_render.js');
7
+ const ruleTester = new (require('eslint').RuleTester)({
8
+ parserOptions: {ecmaVersion: 9, sourceType: 'module'},
9
+ parser: require.resolve('@typescript-eslint/parser'),
10
+ });
11
+
12
+ ruleTester.run('enforce_bound_render_for_schedule_render', rule, {
13
+ valid: [
14
+ {
15
+ code: `
16
+ class Component extends HTMLElement {
17
+ #boundRender = this.#render.bind(this);
18
+ get data(data) {
19
+ this.data = data;
20
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
21
+ }
22
+
23
+ #render() {}
24
+ }
25
+ `,
26
+ filename: 'front_end/ui/components/foo/Foo.ts',
27
+ },
28
+ {
29
+ code: `
30
+ class Component extends HTMLElement {
31
+ get data(data) {
32
+ this.data = data;
33
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
34
+ }
35
+
36
+ #render = () => {};
37
+ }
38
+ `,
39
+ filename: 'front_end/ui/components/foo/Foo.ts',
40
+ },
41
+ {
42
+ code: `
43
+ class Renderer {
44
+ render() {
45
+ }
46
+ }
47
+
48
+ class Component extends HTMLElement {
49
+ #renderer = new Renderer();
50
+ #boundRender = this.#renderer.render.bind(this);
51
+ get data(data) {
52
+ this.data = data;
53
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#boundRender);
54
+ }
55
+ }
56
+ `,
57
+ filename: 'front_end/ui/components/foo/Foo.ts',
58
+ },
59
+ ],
60
+ invalid: [
61
+ {
62
+ code: `class Component extends HTMLElement {
63
+ get data(data) {
64
+ this.data = data;
65
+ void ComponentHelpers.ScheduledRender.scheduleRender(this, this.#render);
66
+ }
67
+
68
+ #render() {}
69
+ }`,
70
+ filename: 'front_end/components/test.ts',
71
+ errors: [{message: 'Bind `render` method of `scheduleRender` to `this` in components'}],
72
+ },
73
+ ]
74
+ });