22// for details. All rights reserved. Use of this source code is governed by a
33// BSD-style license that can be found in the LICENSE file.
44
5+ import 'dart:async' ;
6+
57import 'package:dwds/src/debugging/remote_debugger.dart' ;
68import 'package:webkit_inspection_protocol/webkit_inspection_protocol.dart' ;
79
10+ typedef _EventStreamState = ({
11+ StreamController controller,
12+ WipEventTransformer transformer,
13+ });
14+
815/// A remote debugger with a Webkit Inspection Protocol connection.
916class WebkitDebugger implements RemoteDebugger {
10- final WipDebugger _wipDebugger;
17+ WipDebugger _wipDebugger;
1118
1219 /// Null until [close] is called.
1320 ///
1421 /// All subsequent calls to [close] will return this future.
1522 Future <void >? _closed;
1623
17- WebkitDebugger (this ._wipDebugger);
24+ // We need to forward the events from the [WipDebugger] instance rather than
25+ // directly expose its streams as it may need to be recreated due to an
26+ // unexpected loss in connection to the debugger.
27+ final _onClosedController = StreamController <WipConnection >.broadcast ();
28+ final _onConsoleAPICalledController =
29+ StreamController <ConsoleAPIEvent >.broadcast ();
30+ final _onExceptionThrownController =
31+ StreamController <ExceptionThrownEvent >.broadcast ();
32+ final _onGlobalObjectClearedController =
33+ StreamController <GlobalObjectClearedEvent >.broadcast ();
34+ final _onPausedController = StreamController <DebuggerPausedEvent >.broadcast ();
35+ final _onResumedController =
36+ StreamController <DebuggerResumedEvent >.broadcast ();
37+ final _onScriptParsedController =
38+ StreamController <ScriptParsedEvent >.broadcast ();
39+ final _onTargetCrashedController =
40+ StreamController <TargetCrashedEvent >.broadcast ();
41+
42+ /// Tracks and manages all subscriptions to streams created with
43+ /// `eventStream` .
44+ final _eventStreams = < String , _EventStreamState > {};
45+
46+ late final _controllers = < StreamController > [
47+ _onClosedController,
48+ _onConsoleAPICalledController,
49+ _onExceptionThrownController,
50+ _onGlobalObjectClearedController,
51+ _onPausedController,
52+ _onResumedController,
53+ _onScriptParsedController,
54+ _onTargetCrashedController,
55+ ];
56+
57+ final _streamSubscriptions = < StreamSubscription > [];
58+
59+ @override
60+ Future <void > Function ()? onReconnect;
61+
62+ WebkitDebugger (this ._wipDebugger) {
63+ _initialize ();
64+ }
65+
66+ void _initialize () {
67+ late StreamSubscription sub;
68+ sub = _wipDebugger.connection.onClose.listen ((connection) async {
69+ await sub.cancel ();
70+ if (_closed != null ) {
71+ // The connection closing is expected.
72+ _onClosedController.add (connection);
73+ await Future .wait ([
74+ for (final controller in _controllers) controller.close (),
75+ for (final MapEntry (value: (: controller, transformer: _))
76+ in _eventStreams.entries)
77+ controller.close (),
78+ ]);
79+ return ;
80+ }
81+ var retry = false ;
82+ var retryCount = 0 ;
83+ const maxAttempts = 5 ;
84+ do {
85+ retry = false ;
86+ try {
87+ _wipDebugger = WipDebugger (
88+ await WipConnection .connect (connection.url),
89+ );
90+ await onReconnect? .call ();
91+ } on Exception {
92+ await Future .delayed (Duration (milliseconds: 25 << retryCount));
93+ retry = true ;
94+ retryCount++ ;
95+ }
96+ } while (retry && retryCount <= maxAttempts);
97+ _initialize ();
98+ });
99+
100+ final runtime = _wipDebugger.connection.runtime;
101+ _streamSubscriptions
102+ ..forEach ((e) => e.cancel ())
103+ ..clear ()
104+ ..addAll ([
105+ runtime.onConsoleAPICalled.listen (_onConsoleAPICalledController.add),
106+ runtime.onExceptionThrown.listen (_onExceptionThrownController.add),
107+ _wipDebugger.onGlobalObjectCleared.listen (
108+ _onGlobalObjectClearedController.add,
109+ ),
110+ _wipDebugger.onPaused.listen (_onPausedController.add),
111+ _wipDebugger.onResumed.listen (_onResumedController.add),
112+ _wipDebugger.onScriptParsed.listen (_onScriptParsedController.add),
113+ _wipDebugger
114+ .eventStream (
115+ 'Inspector.targetCrashed' ,
116+ (WipEvent event) => TargetCrashedEvent (event.json),
117+ )
118+ .listen (_onTargetCrashedController.add),
119+
120+ for (final MapEntry (: key, value: (: controller, : transformer))
121+ in _eventStreams.entries)
122+ _wipDebugger
123+ .eventStream (key, transformer)
124+ .listen (controller.add, onError: controller.addError),
125+ ]);
126+ }
18127
19128 @override
20129 Stream <ConsoleAPIEvent > get onConsoleAPICalled =>
21- _wipDebugger.connection.runtime.onConsoleAPICalled ;
130+ _onConsoleAPICalledController.stream ;
22131
23132 @override
24133 Stream <ExceptionThrownEvent > get onExceptionThrown =>
25- _wipDebugger.connection.runtime.onExceptionThrown ;
134+ _onExceptionThrownController.stream ;
26135
27136 @override
28137 Future <WipResponse > sendCommand (
@@ -31,7 +140,10 @@ class WebkitDebugger implements RemoteDebugger {
31140 }) => _wipDebugger.sendCommand (command, params: params);
32141
33142 @override
34- Future <void > close () => _closed ?? = _wipDebugger.connection.close ();
143+ Future <void > close () => _closed ?? = () async {
144+ await _wipDebugger.connection.close ();
145+ await Future .wait ([for (final sub in _streamSubscriptions) sub.cancel ()]);
146+ }();
35147
36148 @override
37149 Future <void > disable () => _wipDebugger.disable ();
@@ -103,31 +215,40 @@ class WebkitDebugger implements RemoteDebugger {
103215 }
104216
105217 @override
106- Stream <T > eventStream <T >(String method, WipEventTransformer <T > transformer) =>
107- _wipDebugger.eventStream (method, transformer);
218+ Stream <T > eventStream <T >(String method, WipEventTransformer <T > transformer) {
219+ return _eventStreams
220+ .putIfAbsent (method, () {
221+ final controller = StreamController <T >();
222+ final stream = _wipDebugger.eventStream (method, transformer);
223+ stream.listen (controller.add, onError: controller.addError);
224+ return (controller: controller, transformer: transformer);
225+ })
226+ .controller
227+ .stream
228+ .cast <T >();
229+ }
108230
109231 @override
110232 Stream <GlobalObjectClearedEvent > get onGlobalObjectCleared =>
111- _wipDebugger.onGlobalObjectCleared ;
233+ _onGlobalObjectClearedController.stream ;
112234
113235 @override
114- Stream <DebuggerPausedEvent > get onPaused => _wipDebugger.onPaused ;
236+ Stream <DebuggerPausedEvent > get onPaused => _onPausedController.stream ;
115237
116238 @override
117- Stream <DebuggerResumedEvent > get onResumed => _wipDebugger.onResumed ;
239+ Stream <DebuggerResumedEvent > get onResumed => _onResumedController.stream ;
118240
119241 @override
120- Stream <ScriptParsedEvent > get onScriptParsed => _wipDebugger.onScriptParsed;
242+ Stream <ScriptParsedEvent > get onScriptParsed =>
243+ _onScriptParsedController.stream;
121244
122245 @override
123- Stream <TargetCrashedEvent > get onTargetCrashed => _wipDebugger.eventStream (
124- 'Inspector.targetCrashed' ,
125- (WipEvent event) => TargetCrashedEvent (event.json),
126- );
246+ Stream <TargetCrashedEvent > get onTargetCrashed =>
247+ _onTargetCrashedController.stream;
127248
128249 @override
129250 Map <String , WipScript > get scripts => _wipDebugger.scripts;
130251
131252 @override
132- Stream <WipConnection > get onClose => _wipDebugger.connection.onClose ;
253+ Stream <WipConnection > get onClose => _onClosedController.stream ;
133254}
0 commit comments