Skip to content

Use service extension manager to manage checkboxes in device.dart #72

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 14 additions & 169 deletions lib/device/device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,27 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:html' as html;

import 'package:devtools/vm_service_wrapper.dart';
import 'package:vm_service_lib/vm_service_lib.dart';

import 'package:devtools/service_extensions.dart' as extensions;
import '../framework/framework.dart';
import '../globals.dart';
import '../ui/elements.dart';

// TODO(devoncarew): set toggle values on a full restart (when we see a new isolate)
import '../ui/ui_utils.dart';

class DeviceScreen extends Screen {
DeviceScreen()
: super(
name: 'Device', id: 'device', iconClass: 'octicon-device-mobile') {
serviceManager.onConnectionAvailable.listen(_handleConnectionStart);
serviceManager.onConnectionClosed.listen(_handleConnectionStop);

deviceStatus = new StatusItem();
addStatusItem(deviceStatus);
}

StatusItem deviceStatus;

SetStateMixin framesChartStateMixin = new SetStateMixin();
ExtensionTracker extensionTracker;

CoreElement togglesDiv;
Map<String, bool> boolValues = <String, bool>{};

// All the service extensions for which we are showing checkboxes.
Map<String, CoreElement> serviceExtensionCheckboxes = {};

@override
void createContent(Framework framework, CoreElement mainDiv) {
Expand All @@ -51,172 +42,26 @@ class DeviceScreen extends Screen {
])
]);

_rebuildTogglesDiv();
}

void _handleConnectionStart(VmServiceWrapper service) {
extensionTracker = new ExtensionTracker(service);
extensionTracker.start();

extensionTracker.onChange.listen((_) {
framesChartStateMixin.setState(() {
if (extensionTracker.hasIsolateTargets && !visible) {
visible = true;
}

_rebuildTogglesDiv();
});
});

deviceStatus.element.text =
'${serviceManager.vm.targetCPU} ${serviceManager.vm.architectureBits}-bit';
_buildTogglesDiv();
}

void _handleConnectionStop(dynamic event) {
extensionTracker?.stop();

deviceStatus.element.text = '';
}

@override
HelpInfo get helpInfo => null;

void _rebuildTogglesDiv() {
if (togglesDiv == null || extensionTracker == null) {
return;
}

togglesDiv.clear();

_createBoolToggle('ext.flutter.debugPaint');
_createBoolToggle('ext.flutter.debugPaintBaselinesEnabled');
_createBoolToggle('ext.flutter.repaintRainbow');
_createBoolToggle('ext.flutter.showPerformanceOverlay');
_createBoolToggle('ext.flutter.debugAllowBanner');
_createBoolToggle('ext.flutter.profileWidgetBuilds');
}

void _createBoolToggle(String rpc) {
if (!extensionTracker.extensionToIsolatesMap.containsKey(rpc)) {
void _buildTogglesDiv() {
if (togglesDiv == null) {
return;
}

CoreElement input;

togglesDiv.add(div(c: 'form-checkbox')
..add(new CoreElement('label')
..add(<CoreElement>[
input = new CoreElement('input')..setAttribute('type', 'checkbox'),
span(text: rpc),
])));

if (boolValues.containsKey(rpc)) {
input.toggleAttribute('checked', boolValues[rpc]);
} else {
extensionTracker.callBoolExtensionMethod(rpc).then((bool value) {
input.toggleAttribute('checked', value);
boolValues[rpc] = value;
});
}

input.element.onChange.listen((_) {
final html.InputElement e = input.element;
boolValues[rpc] = e.checked;
extensionTracker.setBoolExtensionMethod(rpc, e.checked);
});
}
}

class ExtensionTracker {
ExtensionTracker(this.service) {
service.onIsolateEvent.listen((Event e) {
if (e.kind == 'ServiceExtensionAdded') {
_registerRpcForIsolate(e.extensionRPC, e.isolate);
}
});

serviceManager.isolateManager.isolates.forEach(_register);
serviceManager.isolateManager.onIsolateCreated.listen(_register);
serviceManager.isolateManager.onIsolateExited.listen(_removeIsolate);
}

final StreamController<Null> _changeController =
new StreamController<Null>.broadcast();

VmServiceWrapper service;

Map<String, Set<IsolateRef>> extensionToIsolatesMap =
<String, Set<IsolateRef>>{};

bool get hasConnection => service != null;

Stream<Null> get onChange => _changeController.stream;

bool get hasIsolateTargets {
for (Set<IsolateRef> set in extensionToIsolatesMap.values) {
if (set.isNotEmpty) {
return true;
}
}

return false;
}

void start() {}

void stop() {}

void _register(IsolateRef isolateRef) {
service.getIsolate(isolateRef.id).then((dynamic result) {
if (result is Isolate) {
final Isolate isolate = result;

if (isolate.extensionRPCs != null) {
for (String rpc in isolate.extensionRPCs) {
if (!extensionToIsolatesMap.containsKey(rpc)) {
extensionToIsolatesMap[rpc] = new Set<IsolateRef>();
}
extensionToIsolatesMap[rpc].add(isolateRef);
}
}
}

_changeController.add(null);
});
}

void _registerRpcForIsolate(String rpc, IsolateRef isolateRef) {
if (!extensionToIsolatesMap.containsKey(rpc)) {
extensionToIsolatesMap[rpc] = new Set<IsolateRef>();
}
extensionToIsolatesMap[rpc].add(isolateRef);
}

void _removeIsolate(IsolateRef isolateRef) {
for (Set<IsolateRef> set in extensionToIsolatesMap.values) {
set.remove(isolateRef);
}
}

Future<bool> callBoolExtensionMethod(String rpc) {
final IsolateRef isolateRef = extensionToIsolatesMap[rpc].first;
return service
.callServiceExtension(rpc, isolateId: isolateRef.id)
.then((Response response) {
return _convertToBool(response.json['enabled']);
});
}

Future<Response> setBoolExtensionMethod(String rpc, bool checked) {
final IsolateRef isolateRef = extensionToIsolatesMap[rpc].first;
return service.callServiceExtension(rpc,
isolateId: isolateRef.id, args: <String, bool>{'enabled': checked});
}

static bool _convertToBool(dynamic val) {
if (val is bool) {
return val;
}
return val.toString() == 'true';
togglesDiv.add(createExtensionCheckBox(extensions.debugPaint));
togglesDiv.add(createExtensionCheckBox(extensions.debugPaintBaselines));
togglesDiv.add(createExtensionCheckBox(extensions.repaintRainbow));
togglesDiv.add(createExtensionCheckBox(extensions.performanceOverlay));
togglesDiv.add(createExtensionCheckBox(extensions.debugAllowBanner));
togglesDiv.add(createExtensionCheckBox(extensions.profileWidgetBuilds));
}
}
4 changes: 4 additions & 0 deletions lib/ui/elements.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ Element $(String id) => querySelector('#$id');
CoreElement button({String text, String c, String a}) =>
new CoreElement('button', text: text, classes: c, attributes: a);

CoreElement checkbox({String text, String c, String a}) =>
new CoreElement('input', text: text, classes: c, attributes: a)
..setAttribute('type', 'checkbox');

CoreElement div({String text, String c, String a}) =>
new CoreElement('div', text: text, classes: c, attributes: a);

Expand Down
30 changes: 30 additions & 0 deletions lib/ui/ui_utils.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
// 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.

import 'dart:html' as html;

import '../globals.dart';
import 'elements.dart';
import 'primer.dart';

// TODO(kenzie): perhaps add same icons we use in IntelliJ to these buttons.
Expand All @@ -12,3 +19,26 @@ PButton createExtensionButton(String text, String extensionName) {
});
return button;
}

CoreElement createExtensionCheckBox(String extensionName) {
final CoreElement input = checkbox();

serviceManager.serviceExtensionManager.hasServiceExtension(
extensionName, (available) => input.disabled = !available);

serviceManager.serviceExtensionManager.getServiceExtensionState(extensionName,
(state) => input.toggleAttribute('checked', state.value ?? false));

input.element.onChange.listen((_) {
final html.InputElement e = input.element;
serviceManager.serviceExtensionManager
.setServiceExtensionState(extensionName, e.checked, e.checked);
});

return div(c: 'form-checkbox')
..add(new CoreElement('label')
..add(<CoreElement>[
input,
span(text: extensionName),
]));
}