chrome-devtools-frontend 1.0.1015723 → 1.0.1016605

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.
@@ -314,7 +314,7 @@ export class DebuggerModel extends SDKModel<EventTypes> {
314
314
  await this.agent.invoke_disable();
315
315
  this.#isPausingInternal = false;
316
316
  this.globalObjectCleared();
317
- this.dispatchEventToListeners(Events.DebuggerWasDisabled);
317
+ this.dispatchEventToListeners(Events.DebuggerWasDisabled, this);
318
318
  if (typeof this.#debuggerId === 'string') {
319
319
  _debuggerIdToModel.delete(this.#debuggerId);
320
320
  }
@@ -973,7 +973,7 @@ export enum Events {
973
973
 
974
974
  export type EventTypes = {
975
975
  [Events.DebuggerWasEnabled]: DebuggerModel,
976
- [Events.DebuggerWasDisabled]: void,
976
+ [Events.DebuggerWasDisabled]: DebuggerModel,
977
977
  [Events.DebuggerPaused]: DebuggerModel,
978
978
  [Events.DebuggerResumed]: DebuggerModel,
979
979
  [Events.ParsedScriptSource]: Script,
@@ -34,6 +34,7 @@
34
34
 
35
35
  import type * as ProtocolProxyApi from '../../generated/protocol-proxy-api.js';
36
36
  import * as Protocol from '../../generated/protocol.js';
37
+ import type {DOMPinnedWebIDLProp, DOMPinnedWebIDLType} from '../common/JavaScriptMetaData.js';
37
38
 
38
39
  import type {DebuggerModel, FunctionDetails} from './DebuggerModel.js';
39
40
  import type {RuntimeModel} from './RuntimeModel.js';
@@ -309,6 +310,8 @@ export class RemoteObject {
309
310
  isNode(): boolean {
310
311
  return false;
311
312
  }
313
+
314
+ webIdl?: RemoteObjectWebIdlTypeMetadata;
312
315
  }
313
316
 
314
317
  export class RemoteObjectImpl extends RemoteObject {
@@ -728,6 +731,8 @@ export class RemoteObjectProperty {
728
731
  getter: RemoteObject|undefined;
729
732
  setter: RemoteObject|undefined;
730
733
 
734
+ webIdl?: RemoteObjectWebIdlPropertyMetadata;
735
+
731
736
  constructor(
732
737
  name: string, value: RemoteObject|null, enumerable?: boolean, writable?: boolean, isOwn?: boolean,
733
738
  wasThrown?: boolean, symbol?: RemoteObject|null, synthetic?: boolean,
@@ -1188,3 +1193,13 @@ export interface GetPropertiesResult {
1188
1193
  properties: RemoteObjectProperty[]|null;
1189
1194
  internalProperties: RemoteObjectProperty[]|null;
1190
1195
  }
1196
+
1197
+ export interface RemoteObjectWebIdlTypeMetadata {
1198
+ info: DOMPinnedWebIDLType;
1199
+ state: Map<string, string>;
1200
+ }
1201
+
1202
+ export interface RemoteObjectWebIdlPropertyMetadata {
1203
+ info: DOMPinnedWebIDLProp;
1204
+ applicable?: boolean;
1205
+ }
@@ -257,28 +257,29 @@ export class Script implements TextUtils.ContentProvider.ContentProvider, FrameA
257
257
  return source + '\n //# sourceURL=' + this.sourceURL;
258
258
  }
259
259
 
260
- async editSource(newSource: string):
261
- Promise<{error: string | null, exceptionDetails?: Protocol.Runtime.ExceptionDetails}> {
260
+ async editSource(newSource: string): Promise<
261
+ {status: Protocol.Debugger.SetScriptSourceResponseStatus, exceptionDetails?: Protocol.Runtime.ExceptionDetails}> {
262
262
  newSource = Script.trimSourceURLComment(newSource);
263
263
  // We append correct #sourceURL to script for consistency only. It's not actually needed for things to work correctly.
264
264
  newSource = this.appendSourceURLCommentIfNeeded(newSource);
265
265
 
266
- if (!this.scriptId) {
267
- return {error: 'Script failed to parse'};
268
- }
269
-
270
266
  const {content: oldSource} = await this.requestContent();
271
267
  if (oldSource === newSource) {
272
- return {error: null};
268
+ return {status: Protocol.Debugger.SetScriptSourceResponseStatus.Ok};
273
269
  }
274
270
  const response = await this.debuggerModel.target().debuggerAgent().invoke_setScriptSource(
275
271
  {scriptId: this.scriptId, scriptSource: newSource});
272
+ if (response.getError()) {
273
+ // Something went seriously wrong, like the V8 inspector no longer knowing about this script without
274
+ // shutting down the Debugger agent etc.
275
+ throw new Error(`Script#editSource failed for script with id ${this.scriptId}: ${response.getError()}`);
276
+ }
276
277
 
277
- if (!response.getError() && !response.exceptionDetails) {
278
+ if (!response.getError() && response.status === Protocol.Debugger.SetScriptSourceResponseStatus.Ok) {
278
279
  this.#contentPromise = Promise.resolve({content: newSource, isEncoded: false});
279
280
  }
280
281
 
281
- return {error: response.getError() || null, exceptionDetails: response.exceptionDetails};
282
+ return {status: response.status, exceptionDetails: response.exceptionDetails};
282
283
  }
283
284
 
284
285
  rawLocation(lineNumber: number, columnNumber: number): Location|null {
@@ -409,6 +409,18 @@ export type EventTypes = {
409
409
  [Events.BreakpointRemoved]: BreakpointLocation,
410
410
  };
411
411
 
412
+ const enum DebuggerUpdateResult {
413
+ OK = 'OK',
414
+ ERROR = 'ERROR',
415
+ // PENDING implies that the current update requires another re-run.
416
+ PENDING = 'PENDING',
417
+ }
418
+
419
+ const enum ResolveLocationResult {
420
+ OK = 'OK',
421
+ ERROR = 'ERROR',
422
+ }
423
+
412
424
  export class Breakpoint implements SDK.TargetManager.SDKModelObserver<SDK.DebuggerModel.DebuggerModel> {
413
425
  readonly breakpointManager: BreakpointManager;
414
426
  urlInternal: Platform.DevToolsPath.UrlString;
@@ -444,25 +456,49 @@ export class Breakpoint implements SDK.TargetManager.SDKModelObserver<SDK.Debugg
444
456
 
445
457
  async refreshInDebugger(): Promise<void> {
446
458
  if (!this.isRemoved) {
447
- const breakpoints = Array.from(this.#modelBreakpoints.values());
448
- await Promise.all(breakpoints.map(breakpoint => breakpoint.refreshBreakpoint()));
459
+ const modelBreakpoints = Array.from(this.#modelBreakpoints.values());
460
+ await Promise.all(modelBreakpoints.map(async modelBreakpoint => {
461
+ await modelBreakpoint.resetBreakpoint();
462
+ return this.#updateModel(modelBreakpoint);
463
+ }));
449
464
  }
450
465
  }
451
466
 
452
467
  modelAdded(debuggerModel: SDK.DebuggerModel.DebuggerModel): void {
453
468
  const debuggerWorkspaceBinding = this.breakpointManager.debuggerWorkspaceBinding;
454
- this.#modelBreakpoints.set(debuggerModel, new ModelBreakpoint(debuggerModel, this, debuggerWorkspaceBinding));
469
+ const modelBreakpoint = new ModelBreakpoint(debuggerModel, this, debuggerWorkspaceBinding);
470
+ this.#modelBreakpoints.set(debuggerModel, modelBreakpoint);
471
+ void this.#updateModel(modelBreakpoint);
472
+
473
+ debuggerModel.addEventListener(SDK.DebuggerModel.Events.DebuggerWasEnabled, this.#onDebuggerEnabled, this);
474
+ debuggerModel.addEventListener(SDK.DebuggerModel.Events.DebuggerWasDisabled, this.#onDebuggerDisabled, this);
455
475
  }
456
476
 
457
477
  modelRemoved(debuggerModel: SDK.DebuggerModel.DebuggerModel): void {
458
478
  const modelBreakpoint = this.#modelBreakpoints.get(debuggerModel);
479
+ modelBreakpoint?.cleanUpAfterDebuggerIsGone();
459
480
  this.#modelBreakpoints.delete(debuggerModel);
460
481
 
461
- if (!modelBreakpoint) {
462
- return;
482
+ this.#removeDebuggerModelListeners(debuggerModel);
483
+ }
484
+
485
+ #removeDebuggerModelListeners(debuggerModel: SDK.DebuggerModel.DebuggerModel): void {
486
+ debuggerModel.removeEventListener(SDK.DebuggerModel.Events.DebuggerWasEnabled, this.#onDebuggerEnabled, this);
487
+ debuggerModel.removeEventListener(SDK.DebuggerModel.Events.DebuggerWasDisabled, this.#onDebuggerDisabled, this);
488
+ }
489
+
490
+ #onDebuggerEnabled(event: Common.EventTarget.EventTargetEvent<SDK.DebuggerModel.DebuggerModel>): void {
491
+ const debuggerModel = event.data;
492
+ const model = this.#modelBreakpoints.get(debuggerModel);
493
+ if (model) {
494
+ void this.#updateModel(model);
463
495
  }
464
- modelBreakpoint.cleanUpAfterDebuggerIsGone();
465
- modelBreakpoint.removeEventListeners();
496
+ }
497
+
498
+ #onDebuggerDisabled(event: Common.EventTarget.EventTargetEvent<SDK.DebuggerModel.DebuggerModel>): void {
499
+ const debuggerModel = event.data;
500
+ const model = this.#modelBreakpoints.get(debuggerModel);
501
+ model?.cleanUpAfterDebuggerIsGone();
466
502
  }
467
503
 
468
504
  modelBreakpoint(debuggerModel: SDK.DebuggerModel.DebuggerModel): ModelBreakpoint|undefined {
@@ -589,21 +625,17 @@ export class Breakpoint implements SDK.TargetManager.SDKModelObserver<SDK.Debugg
589
625
  this.addAllUnboundLocations();
590
626
  }
591
627
  }
592
- await Promise.all(
593
- Array.from(this.#modelBreakpoints.values()).map(modelBreakpoint => modelBreakpoint.scheduleUpdateInDebugger()));
628
+ return this.#updateModels();
594
629
  }
595
630
 
596
631
  async remove(keepInStorage: boolean): Promise<void> {
597
632
  this.isRemoved = true;
598
633
  const removeFromStorage = !keepInStorage;
599
634
 
600
- // Await removing for all targets.
601
- const updatePromises: Promise<void>[] = [];
602
- for (const modelBreakpoint of this.#modelBreakpoints.values()) {
603
- modelBreakpoint.removeEventListeners();
604
- updatePromises.push(modelBreakpoint.scheduleUpdateInDebugger());
635
+ for (const debuggerModel of this.#modelBreakpoints.keys()) {
636
+ this.#removeDebuggerModelListeners(debuggerModel);
605
637
  }
606
- await Promise.all(updatePromises);
638
+ await this.#updateModels();
607
639
 
608
640
  this.breakpointManager.removeBreakpoint(this, removeFromStorage);
609
641
  this.breakpointManager.targetManager.unobserveModels(SDK.DebuggerModel.DebuggerModel, this);
@@ -638,6 +670,17 @@ export class Breakpoint implements SDK.TargetManager.SDKModelObserver<SDK.Debugg
638
670
  getIsRemoved(): boolean {
639
671
  return this.isRemoved;
640
672
  }
673
+
674
+ async #updateModels(): Promise<void> {
675
+ await Promise.all(Array.from(this.#modelBreakpoints.values()).map(model => this.#updateModel(model)));
676
+ }
677
+
678
+ async #updateModel(model: ModelBreakpoint): Promise<void> {
679
+ const success = await model.scheduleUpdateInDebugger();
680
+ if (!success) {
681
+ await this.remove(false);
682
+ }
683
+ }
641
684
  }
642
685
 
643
686
  export class ModelBreakpoint {
@@ -646,8 +689,7 @@ export class ModelBreakpoint {
646
689
  readonly #debuggerWorkspaceBinding: DebuggerWorkspaceBinding;
647
690
  readonly #liveLocations: LiveLocationPool;
648
691
  readonly #uiLocations: Map<LiveLocation, Workspace.UISourceCode.UILocation>;
649
- #hasPendingUpdate: boolean;
650
- #updatePromise: Promise<void>|null;
692
+ #updateMutex = new Common.Mutex.Mutex();
651
693
  #cancelCallback: boolean;
652
694
  #currentState: Breakpoint.State|null;
653
695
  #breakpointIds: Protocol.Debugger.BreakpointId[];
@@ -662,18 +704,9 @@ export class ModelBreakpoint {
662
704
  this.#liveLocations = new LiveLocationPool();
663
705
 
664
706
  this.#uiLocations = new Map();
665
- this.#debuggerModel.addEventListener(
666
- SDK.DebuggerModel.Events.DebuggerWasDisabled, this.cleanUpAfterDebuggerIsGone, this);
667
- this.#debuggerModel.addEventListener(
668
- SDK.DebuggerModel.Events.DebuggerWasEnabled, this.scheduleUpdateInDebugger, this);
669
- this.#hasPendingUpdate = false;
670
- this.#updatePromise = null;
671
707
  this.#cancelCallback = false;
672
708
  this.#currentState = null;
673
709
  this.#breakpointIds = [];
674
- if (this.#debuggerModel.debuggerEnabled()) {
675
- void this.scheduleUpdateInDebugger();
676
- }
677
710
  }
678
711
 
679
712
  get currentState(): Breakpoint.State|null {
@@ -689,18 +722,20 @@ export class ModelBreakpoint {
689
722
  this.#liveLocations.disposeAll();
690
723
  }
691
724
 
692
- scheduleUpdateInDebugger(): Promise<void> {
693
- this.#hasPendingUpdate = true;
694
- if (!this.#updatePromise) {
695
- this.#updatePromise = (async(): Promise<void> => {
696
- while (this.#hasPendingUpdate) {
697
- this.#hasPendingUpdate = false;
698
- await this.updateInDebugger();
699
- }
700
- this.#updatePromise = null;
701
- })();
725
+ // Returns true, if scheduling ran as expected.
726
+ // Returns false, if an error occurred.
727
+ async scheduleUpdateInDebugger(): Promise<boolean> {
728
+ if (!this.#debuggerModel.debuggerEnabled()) {
729
+ return true;
730
+ }
731
+
732
+ const release = await this.#updateMutex.acquire();
733
+ let result = DebuggerUpdateResult.PENDING;
734
+ while (result === DebuggerUpdateResult.PENDING) {
735
+ result = await this.#updateInDebugger();
702
736
  }
703
- return this.#updatePromise;
737
+ release();
738
+ return result === DebuggerUpdateResult.OK;
704
739
  }
705
740
 
706
741
  private scriptDiverged(): boolean {
@@ -713,16 +748,16 @@ export class ModelBreakpoint {
713
748
  return false;
714
749
  }
715
750
 
716
- private async updateInDebugger(): Promise<void> {
751
+ async #updateInDebugger(): Promise<DebuggerUpdateResult> {
717
752
  if (this.#debuggerModel.target().isDisposed()) {
718
753
  this.cleanUpAfterDebuggerIsGone();
719
- return;
754
+ return DebuggerUpdateResult.OK;
720
755
  }
721
-
722
756
  const lineNumber = this.#breakpoint.lineNumber();
723
757
  const columnNumber = this.#breakpoint.columnNumber();
724
758
  const condition = this.#breakpoint.condition();
725
759
 
760
+ // Calculate the new state.
726
761
  let newState: Breakpoint.State|null = null;
727
762
  if (!this.#breakpoint.getIsRemoved() && this.#breakpoint.enabled() && !this.scriptDiverged()) {
728
763
  let debuggerLocations: SDK.DebuggerModel.Location[] = [];
@@ -766,71 +801,97 @@ export class ModelBreakpoint {
766
801
  }
767
802
  }
768
803
  }
804
+ const hasBackendState = this.#breakpointIds.length;
769
805
 
770
- if (this.#breakpointIds.length && Breakpoint.State.equals(newState, this.#currentState)) {
771
- return;
806
+ // Case 1: State hasn't changed, and back-end is up to date and has information
807
+ // on some breakpoints.
808
+ if (hasBackendState && Breakpoint.State.equals(newState, this.#currentState)) {
809
+ return DebuggerUpdateResult.OK;
772
810
  }
811
+
773
812
  this.#breakpoint.currentState = newState;
774
813
 
775
- if (this.#breakpointIds.length) {
776
- await this.refreshBreakpoint();
777
- return;
814
+ // Case 2: State has changed, and the back-end has outdated information on old
815
+ // breakpoints.
816
+ if (hasBackendState) {
817
+ // Reset the current state.
818
+ await this.resetBreakpoint();
819
+ // Schedule another run of updates, to finally update to the new state.
820
+ return DebuggerUpdateResult.PENDING;
778
821
  }
779
822
 
823
+ // Case 3: State is null (no breakpoints to set), and back-end is up to date
824
+ // (no info on breakpoints).
780
825
  if (!newState) {
781
- return;
826
+ return DebuggerUpdateResult.OK;
782
827
  }
783
828
 
784
- const results = await Promise.all(newState.positions.map(pos => {
785
- if (pos.url) {
786
- return this.#debuggerModel.setBreakpointByURL(pos.url, pos.lineNumber, pos.columnNumber, condition);
787
- }
788
- return this.#debuggerModel.setBreakpointInAnonymousScript(
789
- pos.scriptId, pos.scriptHash as string, pos.lineNumber, pos.columnNumber, condition);
790
- }));
791
- const breakpointIds: Protocol.Debugger.BreakpointId[] = [];
792
- let locations: SDK.DebuggerModel.Location[] = [];
793
- let maybeRescheduleUpdate = false;
794
- for (const result of results) {
795
- if (result.breakpointId) {
796
- breakpointIds.push(result.breakpointId);
797
- locations = locations.concat(result.locations);
798
- } else if (this.#debuggerModel.debuggerEnabled() && !this.#debuggerModel.isReadyToPause()) {
799
- maybeRescheduleUpdate = true;
800
- }
801
- }
829
+ // Case 4: State is not null, so we have breakpoints to set and the back-end
830
+ // has no information on breakpoints yet. Set the breakpoints.
831
+ const {breakpointIds, locations, serverError} = await this.#setBreakpointOnBackend(newState);
802
832
 
833
+ const maybeRescheduleUpdate =
834
+ serverError && this.#debuggerModel.debuggerEnabled() && !this.#debuggerModel.isReadyToPause();
803
835
  if (!breakpointIds.length && maybeRescheduleUpdate) {
804
836
  // TODO(crbug.com/1229541): This is a quickfix to prevent #breakpoints from
805
837
  // disappearing if the Debugger is actually not enabled
806
838
  // yet. This quickfix should be removed as soon as we have a solution
807
839
  // to correctly synchronize the front-end with the inspector back-end.
808
- void this.scheduleUpdateInDebugger();
809
- return;
840
+ return DebuggerUpdateResult.PENDING;
810
841
  }
811
842
 
812
843
  this.#currentState = newState;
813
844
  if (this.#cancelCallback) {
814
845
  this.#cancelCallback = false;
815
- return;
846
+ return DebuggerUpdateResult.OK;
816
847
  }
817
848
 
849
+ // Something went wrong: we expect to have a non-null state, but have not received any
850
+ // breakpointIds from the back-end.
818
851
  if (!breakpointIds.length) {
819
- // Do not await the remove, as we otherwise will create a circular
820
- // dependency. Removing breakpoints will call `scheduleUpdateInDebugger` again.
821
- // Calling it again would cause it to await this current run of `scheduleInDebugger`, which
822
- // will then deadlock.
823
- void this.#breakpoint.remove(true);
824
- return;
852
+ return DebuggerUpdateResult.ERROR;
825
853
  }
826
854
 
827
855
  this.#breakpointIds = breakpointIds;
828
856
  this.#breakpointIds.forEach(
829
857
  breakpointId => this.#debuggerModel.addBreakpointListener(breakpointId, this.breakpointResolved, this));
830
- await Promise.all(locations.map(location => this.addResolvedLocation(location)));
858
+ const resolvedResults = await Promise.all(locations.map(location => this.addResolvedLocation(location)));
859
+
860
+ // Breakpoint clash: the resolved location resolves to a different breakpoint, report an error.
861
+ if (resolvedResults.includes(ResolveLocationResult.ERROR)) {
862
+ return DebuggerUpdateResult.ERROR;
863
+ }
864
+ return DebuggerUpdateResult.OK;
865
+ }
866
+
867
+ async #setBreakpointOnBackend(newState: Breakpoint.State): Promise<{
868
+ breakpointIds: Protocol.Debugger.BreakpointId[],
869
+ locations: SDK.DebuggerModel.Location[],
870
+ serverError: boolean,
871
+ }> {
872
+ const condition = this.#breakpoint.condition();
873
+ const results = await Promise.all(newState.positions.map(pos => {
874
+ if (pos.url) {
875
+ return this.#debuggerModel.setBreakpointByURL(pos.url, pos.lineNumber, pos.columnNumber, condition);
876
+ }
877
+ return this.#debuggerModel.setBreakpointInAnonymousScript(
878
+ pos.scriptId, pos.scriptHash as string, pos.lineNumber, pos.columnNumber, condition);
879
+ }));
880
+ const breakpointIds: Protocol.Debugger.BreakpointId[] = [];
881
+ let locations: SDK.DebuggerModel.Location[] = [];
882
+ let serverError = false;
883
+ for (const result of results) {
884
+ if (result.breakpointId) {
885
+ breakpointIds.push(result.breakpointId);
886
+ locations = locations.concat(result.locations);
887
+ } else {
888
+ serverError = true;
889
+ }
890
+ }
891
+ return {breakpointIds, locations, serverError};
831
892
  }
832
893
 
833
- async refreshBreakpoint(): Promise<void> {
894
+ async resetBreakpoint(): Promise<void> {
834
895
  if (!this.#breakpointIds.length) {
835
896
  return;
836
897
  }
@@ -838,7 +899,6 @@ export class ModelBreakpoint {
838
899
  await Promise.all(this.#breakpointIds.map(id => this.#debuggerModel.removeBreakpoint(id)));
839
900
  this.didRemoveFromDebugger();
840
901
  this.#currentState = null;
841
- void this.scheduleUpdateInDebugger();
842
902
  }
843
903
 
844
904
  private didRemoveFromDebugger(): void {
@@ -855,7 +915,10 @@ export class ModelBreakpoint {
855
915
 
856
916
  private async breakpointResolved({data: location}: Common.EventTarget.EventTargetEvent<SDK.DebuggerModel.Location>):
857
917
  Promise<void> {
858
- await this.addResolvedLocation(location);
918
+ const result = await this.addResolvedLocation(location);
919
+ if (result === ResolveLocationResult.ERROR) {
920
+ await this.#breakpoint.remove(false);
921
+ }
859
922
  }
860
923
 
861
924
  private async locationUpdated(liveLocation: LiveLocation): Promise<void> {
@@ -874,39 +937,29 @@ export class ModelBreakpoint {
874
937
  }
875
938
  }
876
939
 
877
- private async addResolvedLocation(location: SDK.DebuggerModel.Location): Promise<void> {
940
+ private async addResolvedLocation(location: SDK.DebuggerModel.Location): Promise<ResolveLocationResult> {
878
941
  const uiLocation = await this.#debuggerWorkspaceBinding.rawLocationToUILocation(location);
879
942
  if (!uiLocation) {
880
- return;
943
+ return ResolveLocationResult.OK;
881
944
  }
882
945
  const breakpointLocation = this.#breakpoint.breakpointManager.findBreakpoint(uiLocation);
883
946
  if (breakpointLocation && breakpointLocation.breakpoint !== this.#breakpoint) {
884
947
  // location clash
885
- await this.#breakpoint.remove(false /* keepInStorage */);
886
- return;
948
+ return ResolveLocationResult.ERROR;
887
949
  }
888
950
  await this.#debuggerWorkspaceBinding.createLiveLocation(
889
951
  location, this.locationUpdated.bind(this), this.#liveLocations);
952
+ return ResolveLocationResult.OK;
890
953
  }
891
954
 
892
955
  cleanUpAfterDebuggerIsGone(): void {
893
- if (this.#updatePromise) {
894
- this.#cancelCallback = true;
895
- }
896
- this.#hasPendingUpdate = false;
956
+ this.#cancelCallback = true;
897
957
  this.resetLocations();
898
958
  this.#currentState = null;
899
959
  if (this.#breakpointIds.length) {
900
960
  this.didRemoveFromDebugger();
901
961
  }
902
962
  }
903
-
904
- removeEventListeners(): void {
905
- this.#debuggerModel.removeEventListener(
906
- SDK.DebuggerModel.Events.DebuggerWasDisabled, this.cleanUpAfterDebuggerIsGone, this);
907
- this.#debuggerModel.removeEventListener(
908
- SDK.DebuggerModel.Events.DebuggerWasEnabled, this.scheduleUpdateInDebugger, this);
909
- }
910
963
  }
911
964
 
912
965
  interface Position {
@@ -33,7 +33,7 @@ import * as i18n from '../../core/i18n/i18n.js';
33
33
  import type * as Platform from '../../core/platform/platform.js';
34
34
  import * as SDK from '../../core/sdk/sdk.js';
35
35
  import * as Workspace from '../workspace/workspace.js';
36
- import type * as Protocol from '../../generated/protocol.js';
36
+ import * as Protocol from '../../generated/protocol.js';
37
37
 
38
38
  import type {Breakpoint} from './BreakpointManager.js';
39
39
  import {BreakpointManager} from './BreakpointManager.js';
@@ -320,34 +320,48 @@ export class ResourceScriptFile extends Common.ObjectWrapper.ObjectWrapper<Resou
320
320
  .breakpointLocationsForUISourceCode(this.#uiSourceCodeInternal)
321
321
  .map(breakpointLocation => breakpointLocation.breakpoint);
322
322
  const source = this.#uiSourceCodeInternal.workingCopy();
323
- void this.scriptInternal.editSource(source).then(({error, exceptionDetails}) => {
324
- void this.scriptSourceWasSet(source, breakpoints, error, exceptionDetails);
323
+ void this.scriptInternal.editSource(source).then(({status, exceptionDetails}) => {
324
+ void this.scriptSourceWasSet(source, breakpoints, status, exceptionDetails);
325
325
  });
326
326
  }
327
327
 
328
328
  async scriptSourceWasSet(
329
- source: string, breakpoints: Breakpoint[], error: string|null,
329
+ source: string, breakpoints: Breakpoint[], status: Protocol.Debugger.SetScriptSourceResponseStatus,
330
330
  exceptionDetails?: Protocol.Runtime.ExceptionDetails): Promise<void> {
331
- if (!error && !exceptionDetails) {
331
+ if (status === Protocol.Debugger.SetScriptSourceResponseStatus.Ok) {
332
332
  this.#scriptSource = source;
333
333
  }
334
334
  await this.update();
335
335
 
336
- if (!error && !exceptionDetails) {
336
+ if (status === Protocol.Debugger.SetScriptSourceResponseStatus.Ok) {
337
337
  // Live edit can cause #breakpoints to be in the wrong position, or to be lost altogether.
338
338
  // If any #breakpoints were in the pre-live edit script, they need to be re-added.
339
339
  await Promise.all(breakpoints.map(breakpoint => breakpoint.refreshInDebugger()));
340
340
  return;
341
341
  }
342
342
  if (!exceptionDetails) {
343
+ // TODO(crbug.com/1334484): Instead of to the console, report these errors in an "info bar" at the bottom
344
+ // of the text editor, similar to e.g. source mapping errors.
343
345
  Common.Console.Console.instance().addMessage(
344
- i18nString(UIStrings.liveEditFailed, {PH1: String(error)}), Common.Console.MessageLevel.Warning);
346
+ i18nString(UIStrings.liveEditFailed, {PH1: getErrorText(status)}), Common.Console.MessageLevel.Warning);
345
347
  return;
346
348
  }
347
349
  const messageText = i18nString(UIStrings.liveEditCompileFailed, {PH1: exceptionDetails.text});
348
350
  this.#uiSourceCodeInternal.addLineMessage(
349
351
  Workspace.UISourceCode.Message.Level.Error, messageText, exceptionDetails.lineNumber,
350
352
  exceptionDetails.columnNumber);
353
+
354
+ function getErrorText(status: Protocol.Debugger.SetScriptSourceResponseStatus): string {
355
+ switch (status) {
356
+ case Protocol.Debugger.SetScriptSourceResponseStatus.BlockedByActiveFunction:
357
+ return 'Functions that are on the stack (currently being executed) can not be edited';
358
+ case Protocol.Debugger.SetScriptSourceResponseStatus.BlockedByActiveGenerator:
359
+ return 'Async functions/generators that are active can not be edited';
360
+ case Protocol.Debugger.SetScriptSourceResponseStatus.CompileError:
361
+ case Protocol.Debugger.SetScriptSourceResponseStatus.Ok:
362
+ throw new Error('Compile errors and Ok status must not be reported on the console');
363
+ }
364
+ }
351
365
  }
352
366
 
353
367
  private async update(): Promise<void> {
@@ -181,13 +181,14 @@ export const scopeIdentifiers = async function(
181
181
  }
182
182
  };
183
183
 
184
- const identifierAndPunctuationRegExp = /^\s*([A-Za-z_$][A-Za-z_$0-9]*)\s*([.;,]?)\s*$/;
184
+ const identifierAndPunctuationRegExp = /^\s*([A-Za-z_$][A-Za-z_$0-9]*)\s*([.;,=]?)\s*$/;
185
185
 
186
186
  const enum Punctuation {
187
- None = 0,
188
- Comma = 1,
189
- Dot = 2,
190
- Semicolon = 3,
187
+ None = 'none',
188
+ Comma = 'comma',
189
+ Dot = 'dot',
190
+ Semicolon = 'semicolon',
191
+ Equals = 'equals',
191
192
  }
192
193
 
193
194
  const resolveScope =
@@ -341,6 +342,9 @@ const resolveScope =
341
342
  case ';':
342
343
  punctuation = Punctuation.Semicolon;
343
344
  break;
345
+ case '=':
346
+ punctuation = Punctuation.Equals;
347
+ break;
344
348
  case '':
345
349
  punctuation = Punctuation.None;
346
350
  break;
@@ -81,7 +81,15 @@ export class LighthousePanel extends UI.Panel.Panel {
81
81
  private rightToolbar!: UI.Toolbar.Toolbar;
82
82
  private showSettingsPaneSetting!: Common.Settings.Setting<boolean>;
83
83
  private stateBefore?: {
84
- emulation: {enabled: boolean, outlineEnabled: boolean, toolbarControlsEnabled: boolean},
84
+ emulation: {
85
+ type: EmulationModel.DeviceModeModel.Type,
86
+ enabled: boolean,
87
+ outlineEnabled: boolean,
88
+ toolbarControlsEnabled: boolean,
89
+ scale: number,
90
+ device: EmulationModel.EmulatedDevices.EmulatedDevice|null,
91
+ mode: EmulationModel.EmulatedDevices.Mode|null,
92
+ },
85
93
  network: {conditions: SDK.NetworkManager.Conditions},
86
94
  };
87
95
  private isLHAttached?: boolean;
@@ -385,7 +393,7 @@ export class LighthousePanel extends UI.Panel.Panel {
385
393
  }
386
394
 
387
395
  } catch (err) {
388
- await this.resetEmulationAndProtocolConnection();
396
+ await this.restoreEmulationAndProtocolConnection();
389
397
  if (err instanceof Error) {
390
398
  this.statusView.renderBugReport(err);
391
399
  }
@@ -413,12 +421,12 @@ export class LighthousePanel extends UI.Panel.Panel {
413
421
 
414
422
  Host.userMetrics.actionTaken(Host.UserMetrics.Action.LighthouseFinished);
415
423
 
416
- await this.resetEmulationAndProtocolConnection();
424
+ await this.restoreEmulationAndProtocolConnection();
417
425
  this.buildReportUI(lighthouseResponse.lhr, lighthouseResponse.artifacts);
418
426
  // Give focus to the new audit button when completed
419
427
  this.newButton.element.focus();
420
428
  } catch (err) {
421
- await this.resetEmulationAndProtocolConnection();
429
+ await this.restoreEmulationAndProtocolConnection();
422
430
  if (err instanceof Error) {
423
431
  this.statusView.renderBugReport(err);
424
432
  }
@@ -430,7 +438,7 @@ export class LighthousePanel extends UI.Panel.Panel {
430
438
  private async cancelLighthouse(): Promise<void> {
431
439
  this.currentLighthouseRun = undefined;
432
440
  this.statusView.updateStatus(i18nString(UIStrings.cancelling));
433
- await this.resetEmulationAndProtocolConnection();
441
+ await this.restoreEmulationAndProtocolConnection();
434
442
  this.renderStartView();
435
443
  }
436
444
 
@@ -447,9 +455,13 @@ export class LighthousePanel extends UI.Panel.Panel {
447
455
  const emulationModel = EmulationModel.DeviceModeModel.DeviceModeModel.instance();
448
456
  this.stateBefore = {
449
457
  emulation: {
458
+ type: emulationModel.type(),
450
459
  enabled: emulationModel.enabledSetting().get(),
451
460
  outlineEnabled: emulationModel.deviceOutlineSetting().get(),
452
461
  toolbarControlsEnabled: emulationModel.toolbarControlsEnabledSetting().get(),
462
+ scale: emulationModel.scaleSetting().get(),
463
+ device: emulationModel.device(),
464
+ mode: emulationModel.mode(),
453
465
  },
454
466
  network: {conditions: SDK.NetworkManager.MultitargetNetworkManager.instance().networkConditions()},
455
467
  };
@@ -473,7 +485,7 @@ export class LighthousePanel extends UI.Panel.Panel {
473
485
  this.isLHAttached = true;
474
486
  }
475
487
 
476
- private async resetEmulationAndProtocolConnection(): Promise<void> {
488
+ private async restoreEmulationAndProtocolConnection(): Promise<void> {
477
489
  if (!this.isLHAttached) {
478
490
  return;
479
491
  }
@@ -483,9 +495,25 @@ export class LighthousePanel extends UI.Panel.Panel {
483
495
 
484
496
  if (this.stateBefore) {
485
497
  const emulationModel = EmulationModel.DeviceModeModel.DeviceModeModel.instance();
486
- emulationModel.enabledSetting().set(this.stateBefore.emulation.enabled);
487
- emulationModel.deviceOutlineSetting().set(this.stateBefore.emulation.outlineEnabled);
488
- emulationModel.toolbarControlsEnabledSetting().set(this.stateBefore.emulation.toolbarControlsEnabled);
498
+
499
+ // Detaching a session after overriding device metrics will prevent other sessions from overriding device metrics in the future.
500
+ // A workaround is to call "Emulation.clearDeviceMetricOverride" which is the result of the next line.
501
+ // https://bugs.chromium.org/p/chromium/issues/detail?id=1337089
502
+ emulationModel.emulate(EmulationModel.DeviceModeModel.Type.None, null, null);
503
+
504
+ const {type, enabled, outlineEnabled, toolbarControlsEnabled, scale, device, mode} = this.stateBefore.emulation;
505
+ emulationModel.enabledSetting().set(enabled);
506
+ emulationModel.deviceOutlineSetting().set(outlineEnabled);
507
+ emulationModel.toolbarControlsEnabledSetting().set(toolbarControlsEnabled);
508
+
509
+ // `emulate` will ignore the `scale` parameter for responsive emulation.
510
+ // In this case we can just set it here.
511
+ if (type === EmulationModel.DeviceModeModel.Type.Responsive) {
512
+ emulationModel.scaleSetting().set(scale);
513
+ }
514
+
515
+ emulationModel.emulate(type, device, mode, scale);
516
+
489
517
  SDK.NetworkManager.MultitargetNetworkManager.instance().setNetworkConditions(this.stateBefore.network.conditions);
490
518
  delete this.stateBefore;
491
519
  }
@@ -36,6 +36,7 @@ import * as LinearMemoryInspector from '../../../components/linear_memory_inspec
36
36
  import * as Platform from '../../../../core/platform/platform.js';
37
37
  import * as SDK from '../../../../core/sdk/sdk.js';
38
38
  import * as TextUtils from '../../../../models/text_utils/text_utils.js';
39
+ import * as JavaScriptMetaData from '../../../../models/javascript_metadata/javascript_metadata.js';
39
40
  import * as IconButton from '../../../components/icon_button/icon_button.js';
40
41
  import * as TextEditor from '../../../components/text_editor/text_editor.js';
41
42
  import * as UI from '../../legacy.js';
@@ -126,8 +127,9 @@ const EXPANDABLE_MAX_LENGTH = 50;
126
127
  const EXPANDABLE_MAX_DEPTH = 100;
127
128
 
128
129
  const parentMap = new WeakMap<SDK.RemoteObject.RemoteObjectProperty, SDK.RemoteObject.RemoteObject|null>();
129
-
130
130
  const objectPropertiesSectionMap = new WeakMap<Element, ObjectPropertiesSection>();
131
+ const domPinnedProperties =
132
+ JavaScriptMetaData.JavaScriptMetadata.JavaScriptMetadataImpl.domPinnedProperties.DOMPinnedProperties;
131
133
 
132
134
  export const getObjectPropertiesSectionFrom = (element: Element): ObjectPropertiesSection|undefined => {
133
135
  return objectPropertiesSectionMap.get(element);
@@ -203,6 +205,37 @@ export class ObjectPropertiesSection extends UI.TreeOutline.TreeOutlineInShadow
203
205
  return objectPropertiesSection;
204
206
  }
205
207
 
208
+ static assignWebIDLMetadata(
209
+ value: SDK.RemoteObject.RemoteObject|null, properties: SDK.RemoteObject.RemoteObjectProperty[]): void {
210
+ if (!value) {
211
+ return;
212
+ }
213
+
214
+ const isInstance = value.type === 'object' && value.className !== null;
215
+ const webIdlType = isInstance ? domPinnedProperties[value.className] : undefined;
216
+ if (webIdlType) {
217
+ value.webIdl = {info: webIdlType, state: new Map()};
218
+ } else {
219
+ return;
220
+ }
221
+
222
+ for (const property of properties) {
223
+ const webIdlProperty = webIdlType?.props?.[property.name];
224
+ if (webIdlProperty) {
225
+ property.webIdl = {info: webIdlProperty};
226
+ }
227
+ }
228
+ }
229
+
230
+ static getPropertyValuesByNames(properties: SDK.RemoteObject.RemoteObjectProperty[]):
231
+ Map<string, SDK.RemoteObject.RemoteObject|undefined> {
232
+ const map = new Map();
233
+ for (const property of properties) {
234
+ map.set(property.name, property.value);
235
+ }
236
+ return map;
237
+ }
238
+
206
239
  static compareProperties(
207
240
  propertyA: SDK.RemoteObject.RemoteObjectProperty, propertyB: SDK.RemoteObject.RemoteObjectProperty): number {
208
241
  if (!propertyA.synthetic && propertyB.synthetic) {
@@ -744,6 +777,33 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
744
777
  internalProperties: SDK.RemoteObject.RemoteObjectProperty[]|null, skipProto: boolean,
745
778
  skipGettersAndSetters: boolean, value: SDK.RemoteObject.RemoteObject|null,
746
779
  linkifier?: Components.Linkifier.Linkifier, emptyPlaceholder?: string|null): void {
780
+ ObjectPropertiesSection.assignWebIDLMetadata(value, properties);
781
+ const names = ObjectPropertiesSection.getPropertyValuesByNames(properties);
782
+
783
+ if (value?.webIdl) {
784
+ const parentRules = value.webIdl.info.rules;
785
+ if (parentRules) {
786
+ for (const {when: name, is: expected} of parentRules) {
787
+ if (names.get(name)?.value === expected) {
788
+ value.webIdl.state.set(name, expected);
789
+ }
790
+ }
791
+ }
792
+
793
+ for (const property of properties) {
794
+ if (property.webIdl) {
795
+ const parentState = value.webIdl.state;
796
+ const propertyRules = property.webIdl.info.rules;
797
+ if (!parentRules && !propertyRules) {
798
+ property.webIdl.applicable = true;
799
+ } else {
800
+ property.webIdl.applicable =
801
+ !propertyRules || propertyRules?.some(rule => parentState.get(rule.when) === rule.is);
802
+ }
803
+ }
804
+ }
805
+ }
806
+
747
807
  properties.sort(ObjectPropertiesSection.compareProperties);
748
808
  internalProperties = internalProperties || [];
749
809
 
@@ -1053,15 +1113,36 @@ export class ObjectPropertyTreeElement extends UI.TreeOutline.TreeElement {
1053
1113
  this.expandedValueElement = this.createExpandedValueElement(this.property.value);
1054
1114
  }
1055
1115
 
1056
- this.listItemElement.removeChildren();
1116
+ let adorner: Element|string = '';
1057
1117
  let container: Element;
1118
+
1119
+ if (this.property.webIdl?.applicable) {
1120
+ const icon = new IconButton.Icon.Icon();
1121
+ icon.data = {
1122
+ iconName: 'elements_panel_icon',
1123
+ color: 'var(--color-text-secondary)',
1124
+ width: '16px',
1125
+ height: '16px',
1126
+ };
1127
+ adorner = UI.Fragment.html`
1128
+ <span class='adorner'>${icon}</span>
1129
+ `;
1130
+ }
1131
+
1058
1132
  if (isInternalEntries) {
1059
- container = UI.Fragment.html`<span class='name-and-value'>${this.nameElement}</span>`;
1133
+ container = UI.Fragment.html`
1134
+ <span class='name-and-value'>${adorner}${this.nameElement}</span>
1135
+ `;
1060
1136
  } else {
1061
- container = UI.Fragment.html`<span class='name-and-value'>${this.nameElement}: ${this.valueElement}</span>`;
1137
+ container = UI.Fragment.html`
1138
+ <span class='name-and-value'>${adorner}${this.nameElement}: ${this.valueElement}</span>
1139
+ `;
1062
1140
  }
1141
+
1142
+ this.listItemElement.removeChildren();
1063
1143
  this.rowContainer = (container as HTMLElement);
1064
1144
  this.listItemElement.appendChild(this.rowContainer);
1145
+ this.listItemElement.dataset.webidl = this.property.webIdl?.applicable ? 'true' : 'false';
1065
1146
  }
1066
1147
 
1067
1148
  private updatePropertyPath(): void {
@@ -59,9 +59,22 @@
59
59
  overflow: hidden;
60
60
  }
61
61
 
62
+ .object-properties-section [data-webidl="true"] > .name-and-value > .adorner {
63
+ flex-shrink: 0;
64
+ width: 16px;
65
+ height: 16px;
66
+ margin-right: 2px;
67
+ }
68
+
69
+ .object-properties-section [data-webidl="true"] > .name-and-value > .name {
70
+ font-weight: bold;
71
+ }
72
+
62
73
  .name-and-value {
63
74
  overflow: hidden;
64
75
  line-height: 16px;
76
+ display: flex;
77
+ white-space: break-spaces;
65
78
  }
66
79
 
67
80
  .editing-sub-part .name-and-value {
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.1015723"
58
+ "version": "1.0.1016605"
59
59
  }