blob: a3a63974131957b10f112c8e6d1b3d194e459a66 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/// An application model used to programmatically query and drive the UI.
///
/// This is largely intended to aid in testing.
library model;
import 'dart:async';
import 'dart:convert';
import 'dart:js' as js;
import 'package:vm_service/vm_service.dart';
import '../debugger/debugger.dart';
import '../framework/framework.dart';
import '../globals.dart';
import '../logging/logging.dart';
import '../logging/logging_controller.dart';
import '../main.dart';
class App {
App(this.framework) {
_register<void>('devToolsReady', devToolsReady);
_register<void>('echo', echo);
_register<void>('switchPage', switchPage);
_register<String>('currentPageId', currentPageId);
// ConnectDialog
_register<void>('connectDialog.isVisible', connectDialogIsVisible);
_register<void>('connectDialog.connectTo', connectDialogConnectTo);
// LoggingScreen
_register<void>('logging.clearLogs', logsClearLogs);
_register<int>('logging.logCount', logsLogCount);
// DebuggerScreen
_register<String>('debugger.getState', debuggerGetState);
_register<String>('debugger.getLocation', debuggerGetLocation);
_register<void>('debugger.resume', debuggerResume);
_register<void>('debugger.pause', debuggerPause);
_register<void>('debugger.step', debuggerStep);
_register<void>('debugger.clearBreakpoints', debuggerClearBreakpoints);
_register<void>('debugger.addBreakpoint', debuggerAddBreakpoint);
_register<void>(
'debugger.setExceptionPauseMode', debuggerSetExceptionPauseMode);
_register<List<String>>('debugger.getBreakpoints', debuggerGetBreakpoints);
_register<bool>('debugger.supportsScripts', debuggerSupportsScripts);
_register<List<String>>('debugger.getScripts', debuggerGetScripts);
_register<List<String>>(
'debugger.getCallStackFrames', debuggerGetCallStackFrames);
_register<List<String>>('debugger.getVariables', debuggerGetVariables);
_register<String>(
'debugger.getConsoleContents', debuggerGetConsoleContents);
}
static App register(PerfToolFramework framework) {
return App(framework).._bind();
}
final PerfToolFramework framework;
void _bind() {
final js.JsObject binding = js.JsObject.jsify(<dynamic, dynamic>{});
binding['send'] = (String method, int id, dynamic arg) {
try {
final dynamic result = _dispatch(method, id, arg);
Future<dynamic>.value(result).then((dynamic result) {
_sendResponseResult(id, result);
}).catchError((dynamic error, StackTrace stackTrace) {
_sendReponseError(id, error, stackTrace);
});
} catch (error, stackTrace) {
_sendReponseError(id, error, stackTrace);
}
};
js.context['devtools'] = binding;
}
Future<void> devToolsReady(dynamic message) async {
_sendNotification('app.devToolsReady', message);
}
Future<void> echo(dynamic message) async {
_sendNotification('app.echo', message);
}
Future<void> switchPage(dynamic pageId) async {
final Screen screen = framework.getScreen(pageId);
if (screen == null) {
throw 'page $pageId not found';
}
framework.load(screen);
}
Future<String> currentPageId([dynamic _]) async {
return framework.current?.id;
}
Future<bool> connectDialogIsVisible([dynamic _]) async {
return framework.connectDialog.isVisible();
}
Future<void> connectDialogConnectTo([dynamic uri]) async {
// uri comes as a String (from JSON) so needs changing back to a URI.
return framework.connectDialog.connectTo(Uri.parse(uri));
}
Future<void> logsClearLogs([dynamic _]) async {
final LoggingScreen screen = framework.getScreen('logging');
screen.controller.loggingTableModel.setRows(<LogData>[]);
}
Future<int> logsLogCount([dynamic _]) async {
final LoggingScreen screen = framework.getScreen('logging');
return screen.controller.loggingTableModel.rowCount;
}
Future<String> debuggerGetState([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
return screen.debuggerState.isPaused.value ? 'paused' : 'running';
}
Future<String> debuggerGetConsoleContents([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
return screen.consoleArea.getContents();
}
Future<String> debuggerGetLocation([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
final scriptAndPos = screen.sourceEditor.executionPoint;
if (scriptAndPos == null) {
return null;
}
return '${scriptAndPos.uri}:${scriptAndPos.position.line - 1}';
}
Future<void> debuggerResume([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
await screen.debuggerState.resume();
}
Future<void> debuggerPause([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
await screen.debuggerState.pause();
}
Future<void> debuggerStep([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
await screen.debuggerState.stepOver();
}
Future<void> debuggerClearBreakpoints([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
await screen.debuggerState.clearBreakpoints();
}
Future<List<String>> debuggerGetBreakpoints([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
return screen.debuggerState.breakpoints.value
.map((breakpoint) => breakpoint.id)
.toList();
}
Future<bool> debuggerSupportsScripts([dynamic _]) async {
return (await serviceManager.serviceCapabilities).supportsGetScripts;
}
Future<List<String>> debuggerGetScripts([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
return screen.scriptsView.items.map((ScriptRef script) {
return script.uri;
}).toList();
}
Future<List<String>> debuggerGetCallStackFrames([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
return screen.callStackView.items.map((Frame frame) {
String name = frame.code?.name ?? '<none>';
if (name.startsWith('[Unoptimized] ')) {
name = name.substring('[Unoptimized] '.length);
}
String desc = '';
if (frame.kind == FrameKind.kAsyncSuspensionMarker) {
name = '<async break>';
} else {
desc = '${frame.location.script.uri}';
if (desc.contains('/')) {
desc = desc.substring(desc.lastIndexOf('/') + 1);
}
desc = ':$desc';
}
return '$name$desc';
}).toList();
}
Future<List<String>> debuggerGetVariables([dynamic _]) async {
final DebuggerScreen screen = framework.getScreen('debugger');
return screen.variablesView.items.map((BoundVariable variable) {
final dynamic value = variable.value;
String valueStr;
if (value is InstanceRef) {
if (value.valueAsString == null) {
valueStr = value.classRef.name;
} else {
valueStr = value.valueAsString;
}
} else if (value is Sentinel) {
valueStr = value.valueAsString;
} else {
valueStr = value.toString();
}
return '${variable.name}:$valueStr';
}).toList();
}
Future<void> debuggerAddBreakpoint([dynamic params]) async {
final String path = params[0];
final int line = params[1] + 1;
final DebuggerScreen screen = framework.getScreen('debugger');
await screen.debuggerState.addBreakpointByPathFragment(path, line);
}
Future<void> debuggerSetExceptionPauseMode([dynamic params]) async {
final String mode = params;
final DebuggerScreen screen = framework.getScreen('debugger');
await screen.debuggerState.setExceptionPauseMode(mode);
}
void _sendNotification(String event, [dynamic params]) {
final Map<String, dynamic> map = <String, dynamic>{
'event': event,
};
if (params != null) {
map['params'] = params;
}
// TODO(terry): Shouldn't print to console by default.
print('[${jsonEncode(map)}]');
}
void _sendResponseResult(int id, [dynamic result]) {
final Map<String, dynamic> map = <String, dynamic>{
'id': id,
};
if (result != null) {
map['result'] = result;
}
// TODO(terry): Shouldn't print to console by default.
print('[${jsonEncode(map)}]');
}
void _sendReponseError(int id, dynamic error, StackTrace stackTrace) {
final Map<String, dynamic> map = <String, dynamic>{
'id': id,
'error': <String, String>{
'message': error.toString(),
'stackTrace': stackTrace.toString(),
},
};
// TODO(terry): Better error message to user and log to GA too?
print('[${jsonEncode(map)}]');
}
dynamic _dispatch(String method, int id, dynamic arg) {
final Handler<dynamic> handler = _handlers[method];
if (handler != null) {
return handler(arg);
} else {
print('handler not found for $method()');
throw 'no handler found for $method()';
}
}
final Map<String, Handler<dynamic>> _handlers = <String, Handler<dynamic>>{};
void _register<T>(String idMethod, Handler<T> fn) {
_handlers[idMethod] = fn;
}
}
typedef Handler<T> = Future<T> Function(dynamic arg);