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