blob: 95a39287503e365fce66ff9bb46fc21444185664 [file] [log] [blame]
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// @dart = 2.6
part of engine;
html.Element _logElement;
html.Element _logContainer;
List<_LogMessage> _logBuffer = <_LogMessage>[];
class _LogMessage {
_LogMessage(this.message);
int duplicateCount = 1;
final String message;
@override
String toString() {
if (duplicateCount == 1) {
return message;
}
return '${duplicateCount}x $message';
}
}
/// A drop-in replacement for [print] that prints on the screen into a
/// fixed-positioned element.
///
/// This is useful, for example, for print-debugging on iOS when debugging over
/// USB is not available.
void printOnScreen(Object object) {
if (_logElement == null) {
_initialize();
}
final String message = '$object';
if (_logBuffer.isNotEmpty && _logBuffer.last.message == message) {
_logBuffer.last.duplicateCount += 1;
} else {
_logBuffer.add(_LogMessage(message));
}
if (_logBuffer.length > 80) {
_logBuffer = _logBuffer.sublist(_logBuffer.length - 50);
}
_logContainer.text = _logBuffer.join('\n');
// Also log to console for browsers that give you access to it.
print(message);
}
void _initialize() {
_logElement = html.Element.tag('flt-onscreen-log');
_logElement.setAttribute('aria-hidden', 'true');
_logElement.style
..position = 'fixed'
..left = '0'
..right = '0'
..bottom = '0'
..height = '25%'
..backgroundColor = 'rgba(0, 0, 0, 0.85)'
..color = 'white'
..fontSize = '8px'
..whiteSpace = 'pre-wrap'
..overflow = 'hidden'
..zIndex = '1000';
_logContainer = html.Element.tag('flt-log-container');
_logContainer.setAttribute('aria-hidden', 'true');
_logContainer.style
..position = 'absolute'
..bottom = '0';
_logElement.append(_logContainer);
html.document.body.append(_logElement);
}
/// Dump the current stack to the console using [print] and
/// [defaultStackFilter].
///
/// The current stack is obtained using [StackTrace.current].
///
/// The `maxFrames` argument can be given to limit the stack to the given number
/// of lines. By default, all non-filtered stack lines are shown.
///
/// The `label` argument, if present, will be printed before the stack.
void debugPrintStack({String label, int maxFrames}) {
if (label != null) {
print(label);
}
Iterable<String> lines =
StackTrace.current.toString().trimRight().split('\n');
if (maxFrames != null) {
lines = lines.take(maxFrames);
}
print(defaultStackFilter(lines).join('\n'));
}
/// Converts a stack to a string that is more readable by omitting stack
/// frames that correspond to Dart internals.
///
/// This function expects its input to be in the format used by
/// [StackTrace.toString()]. The output of this function is similar to that
/// format but the frame numbers will not be consecutive (frames are elided)
/// and the final line may be prose rather than a stack frame.
Iterable<String> defaultStackFilter(Iterable<String> frames) {
const List<String> filteredPackages = <String>[
'dart:async-patch',
'dart:async',
'dart:_runtime',
];
final RegExp stackParser =
RegExp(r'^#[0-9]+ +([^.]+).* \(([^/\\]*)[/\\].+:[0-9]+(?::[0-9]+)?\)$');
final RegExp packageParser = RegExp(r'^([^:]+):(.+)$');
final List<String> result = <String>[];
final List<String> skipped = <String>[];
for (String line in frames) {
final Match match = stackParser.firstMatch(line);
if (match != null) {
assert(match.groupCount == 2);
if (filteredPackages.contains(match.group(2))) {
final Match packageMatch = packageParser.firstMatch(match.group(2));
if (packageMatch != null && packageMatch.group(1) == 'package') {
skipped.add(
'package ${packageMatch.group(2)}'); // avoid "package package:foo"
} else {
skipped.add('package ${match.group(2)}');
}
continue;
}
}
result.add(line);
}
if (skipped.length == 1) {
result.add('(elided one frame from ${skipped.single})');
} else if (skipped.length > 1) {
final List<String> where = Set<String>.from(skipped).toList()..sort();
if (where.length > 1) {
where[where.length - 1] = 'and ${where.last}';
}
if (where.length > 2) {
result.add('(elided ${skipped.length} frames from ${where.join(", ")})');
} else {
result.add('(elided ${skipped.length} frames from ${where.join(" ")})');
}
}
return result;
}
String debugIdentify(Object object) {
return '${object.runtimeType}(@${object.hashCode})';
}