• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2013 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5part of engine;
6
7html.Element _logElement;
8html.Element _logContainer;
9List<_LogMessage> _logBuffer = <_LogMessage>[];
10
11class _LogMessage {
12  _LogMessage(this.message);
13
14  int duplicateCount = 1;
15  final String message;
16
17  @override
18  String toString() {
19    if (duplicateCount == 1) {
20      return message;
21    }
22    return '${duplicateCount}x $message';
23  }
24}
25
26/// A drop-in replacement for [print] that prints on the screen into a
27/// fixed-positioned element.
28///
29/// This is useful, for example, for print-debugging on iOS when debugging over
30/// USB is not available.
31void printOnScreen(Object object) {
32  if (_logElement == null) {
33    _initialize();
34  }
35
36  final String message = '$object';
37  if (_logBuffer.isNotEmpty && _logBuffer.last.message == message) {
38    _logBuffer.last.duplicateCount += 1;
39  } else {
40    _logBuffer.add(_LogMessage(message));
41  }
42
43  if (_logBuffer.length > 80) {
44    _logBuffer = _logBuffer.sublist(_logBuffer.length - 50);
45  }
46
47  _logContainer.text = _logBuffer.join('\n');
48
49  // Also log to console for browsers that give you access to it.
50  print(message);
51}
52
53void _initialize() {
54  _logElement = html.Element.tag('flt-onscreen-log');
55  _logElement.setAttribute('aria-hidden', 'true');
56  _logElement.style
57    ..position = 'fixed'
58    ..left = '0'
59    ..right = '0'
60    ..bottom = '0'
61    ..height = '25%'
62    ..backgroundColor = 'rgba(0, 0, 0, 0.85)'
63    ..color = 'white'
64    ..fontSize = '8px'
65    ..whiteSpace = 'pre-wrap'
66    ..overflow = 'hidden'
67    ..zIndex = '1000';
68
69  _logContainer = html.Element.tag('flt-log-container');
70  _logContainer.setAttribute('aria-hidden', 'true');
71  _logContainer.style
72    ..position = 'absolute'
73    ..bottom = '0';
74  _logElement.append(_logContainer);
75
76  html.document.body.append(_logElement);
77}
78
79/// Dump the current stack to the console using [print] and
80/// [defaultStackFilter].
81///
82/// The current stack is obtained using [StackTrace.current].
83///
84/// The `maxFrames` argument can be given to limit the stack to the given number
85/// of lines. By default, all non-filtered stack lines are shown.
86///
87/// The `label` argument, if present, will be printed before the stack.
88void debugPrintStack({String label, int maxFrames}) {
89  if (label != null) print(label);
90  Iterable<String> lines =
91      StackTrace.current.toString().trimRight().split('\n');
92  if (maxFrames != null) lines = lines.take(maxFrames);
93  print(defaultStackFilter(lines).join('\n'));
94}
95
96/// Converts a stack to a string that is more readable by omitting stack
97/// frames that correspond to Dart internals.
98///
99/// This function expects its input to be in the format used by
100/// [StackTrace.toString()]. The output of this function is similar to that
101/// format but the frame numbers will not be consecutive (frames are elided)
102/// and the final line may be prose rather than a stack frame.
103Iterable<String> defaultStackFilter(Iterable<String> frames) {
104  const List<String> filteredPackages = <String>[
105    'dart:async-patch',
106    'dart:async',
107    'dart:_runtime',
108  ];
109  final RegExp stackParser =
110      RegExp(r'^#[0-9]+ +([^.]+).* \(([^/\\]*)[/\\].+:[0-9]+(?::[0-9]+)?\)$');
111  final RegExp packageParser = RegExp(r'^([^:]+):(.+)$');
112  final List<String> result = <String>[];
113  final List<String> skipped = <String>[];
114  for (String line in frames) {
115    final Match match = stackParser.firstMatch(line);
116    if (match != null) {
117      assert(match.groupCount == 2);
118      if (filteredPackages.contains(match.group(2))) {
119        final Match packageMatch = packageParser.firstMatch(match.group(2));
120        if (packageMatch != null && packageMatch.group(1) == 'package') {
121          skipped.add(
122              'package ${packageMatch.group(2)}'); // avoid "package package:foo"
123        } else {
124          skipped.add('package ${match.group(2)}');
125        }
126        continue;
127      }
128    }
129    result.add(line);
130  }
131  if (skipped.length == 1) {
132    result.add('(elided one frame from ${skipped.single})');
133  } else if (skipped.length > 1) {
134    final List<String> where = Set<String>.from(skipped).toList()..sort();
135    if (where.length > 1) where[where.length - 1] = 'and ${where.last}';
136    if (where.length > 2) {
137      result.add('(elided ${skipped.length} frames from ${where.join(", ")})');
138    } else {
139      result.add('(elided ${skipped.length} frames from ${where.join(" ")})');
140    }
141  }
142  return result;
143}
144
145String debugIdentify(Object object) {
146  return '${object.runtimeType}(@${object.hashCode})';
147}
148