blob: 16a1ac0af0380e4d78fd5215b463b0bb15772c25 [file] [log] [blame]
// Copyright 2015 Google. All rights reserved. Use of this source code is
// governed by a BSD-style license that can be found in the LICENSE file.
library crmux.forwarder;
import 'dart:async'
show Future, Stream, StreamController, StreamSink, StreamSubscription;
import 'dart:convert' show jsonDecode, jsonEncode;
import 'package:logging/logging.dart' show Logger;
import 'dom_model.dart' show flattenAttributesMap;
import 'webkit_inspection_protocol.dart'
show WipConnection, WipDom, WipError, WipEvent, WipResponse;
/// Forwards a [Stream] to a [WipConnection] and events
/// from a [WipConnection] to a [StreamSink].
class WipForwarder {
static final _log = Logger('ChromeForwarder');
final Stream<String> _in;
final StreamSink _out;
final WipConnection _debugger;
final WipDom? domModel;
/// If false, no Debugger.paused events will be forwarded back to the client.
/// This gets automatically set to true if a breakpoint is set by the client.
bool forwardPausedEvents = false;
final List<StreamSubscription> _subscriptions = <StreamSubscription>[];
final StreamController<void> _closedController = StreamController.broadcast();
factory WipForwarder(WipConnection debugger, Stream<String> stream,
{StreamSink? sink, WipDom? domModel}) {
sink ??= stream as StreamSink;
return WipForwarder._(debugger, stream, sink, domModel);
}
WipForwarder._(this._debugger, this._in, this._out, this.domModel) {
_subscriptions.add(_in.listen(_onClientDataHandler,
onError: _onClientErrorHandler, onDone: _onClientDoneHandler));
_subscriptions.add(_debugger.onNotification.listen(_onDebuggerDataHandler,
onError: _onDebuggerErrorHandler, onDone: _onDebuggerDoneHandler));
}
Future _onClientDataHandler(String data) async {
var json = jsonDecode(data) as Map<String, dynamic>;
var response = {'id': json['id']};
_log.info('Forwarding to debugger: $data');
try {
var method = json['method'] as String;
var params = json['params'] as Map<String, dynamic>;
bool processed = false;
if (method.contains('reakpoint')) {
forwardPausedEvents = true;
}
if (domModel != null) {
switch (method) {
case 'DOM.getDocument':
response['result'] = {'root': (await domModel!.getDocument())};
processed = true;
break;
case 'DOM.getAttributes':
var attributes = flattenAttributesMap(
await domModel!.getAttributes(params['nodeId'] as int));
response['result'] = {'attributes': attributes};
processed = true;
break;
}
}
if (!processed) {
WipResponse resp = await _debugger.sendCommand(method, params);
if (resp.result != null) {
response['result'] = resp.result;
}
}
} on WipError catch (e) {
response['error'] = e.error;
} catch (e, s) {
_log.severe(json['id'], e.toString(), s);
response['error'] = e.toString();
}
_log.info('forwarding response: $response');
_out.add(jsonEncode(response));
}
void _onClientErrorHandler(Object error, StackTrace stackTrace) {
_log.severe('error from forwarded client', error, stackTrace);
}
void _onClientDoneHandler() {
_log.info('forwarded client closed.');
stop();
}
void _onDebuggerDataHandler(WipEvent event) {
if (event.method == 'Debugger.paused' && !forwardPausedEvents) {
_log.info('not forwarding event: $event');
return;
}
_log.info('forwarding event: $event');
var json = <String, dynamic>{'method': event.method};
if (event.params != null) {
json['params'] = event.params;
}
_out.add(jsonEncode(json));
}
void _onDebuggerErrorHandler(Object error, StackTrace stackTrace) {
_log.severe('error from debugger', error, stackTrace);
}
void _onDebuggerDoneHandler() {
_log.info('debugger closed');
stop();
}
void pause() {
assert(_subscriptions.isNotEmpty);
_log.info('Pausing forwarding');
for (var s in _subscriptions) {
s.pause();
}
_subscriptions.clear();
}
void resume() {
assert(_subscriptions.isNotEmpty);
_log.info('Resuming forwarding');
for (var s in _subscriptions) {
s.resume();
}
_subscriptions.clear();
}
Future stop() {
assert(_subscriptions.isNotEmpty);
_log.info('Stopping forwarding');
for (var s in _subscriptions) {
s.cancel();
}
_subscriptions.clear();
_closedController.add(null);
return Future.wait([_closedController.close(), _out.close()]);
}
Stream get onClosed => _closedController.stream;
}