From 4e23ecd668ab7cc2d4fbff50676862d170c1284a Mon Sep 17 00:00:00 2001 From: Hixie Date: Tue, 15 Dec 2015 15:18:52 -0800 Subject: [PATCH] Catch exceptions in pointer handling If we don't catch these exceptions, we get confused about what's going on with the pointers, and the app basically stops working. --- packages/flutter/lib/services.dart | 1 + .../flutter/lib/src/gestures/binding.dart | 29 +++++++++++- .../lib/src/gestures/pointer_router.dart | 36 +++++++++++++-- packages/flutter/lib/src/rendering/debug.dart | 44 +----------------- packages/flutter/lib/src/services/print.dart | 46 +++++++++++++++++++ 5 files changed, 108 insertions(+), 48 deletions(-) create mode 100644 packages/flutter/lib/src/services/print.dart diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart index c3e2962a31..1d21784b6e 100644 --- a/packages/flutter/lib/services.dart +++ b/packages/flutter/lib/services.dart @@ -19,5 +19,6 @@ export 'src/services/image_cache.dart'; export 'src/services/image_decoder.dart'; export 'src/services/image_resource.dart'; export 'src/services/keyboard.dart'; +export 'src/services/print.dart'; export 'src/services/service_registry.dart'; export 'src/services/shell.dart'; diff --git a/packages/flutter/lib/src/gestures/binding.dart b/packages/flutter/lib/src/gestures/binding.dart index 3b8294cdd4..608cb54c1c 100644 --- a/packages/flutter/lib/src/gestures/binding.dart +++ b/packages/flutter/lib/src/gestures/binding.dart @@ -16,6 +16,8 @@ import 'events.dart'; import 'hit_test.dart'; import 'pointer_router.dart'; +typedef void GesturerExceptionHandler(PointerEvent event, HitTestTarget target, dynamic exception, StackTrace stack); + abstract class Gesturer extends BindingBase implements HitTestTarget, HitTestable { void initInstances() { @@ -76,11 +78,34 @@ abstract class Gesturer extends BindingBase implements HitTestTarget, HitTestabl result.add(new HitTestEntry(this)); } + /// This callback is invoked whenever an exception is caught by the Gesturer + /// binding. The 'event' argument is the pointer event that was being routed. + /// The 'target' argument is the class whose handleEvent function threw the + /// exception. The 'exception' argument contains the object that was thrown, + /// and the 'stack' argument contains the stack trace. The callback is invoked + /// after the information is printed to the console. + GesturerExceptionHandler debugGesturerExceptionHandler; + /// Dispatch the given event to the path of the given hit test result void dispatchEvent(PointerEvent event, HitTestResult result) { assert(result != null); - for (HitTestEntry entry in result.path) - entry.target.handleEvent(event, entry); + for (HitTestEntry entry in result.path) { + try { + entry.target.handleEvent(event, entry); + } catch (exception, stack) { + debugPrint('-- EXCEPTION --'); + debugPrint('The following exception was raised while dispatching a pointer event:'); + debugPrint('$exception'); + debugPrint('Stack trace:'); + debugPrint('$stack'); + debugPrint('Event:'); + debugPrint('$event'); + debugPrint('Target:'); + debugPrint('${entry.target}'); + if (debugGesturerExceptionHandler != null) + debugGesturerExceptionHandler(event, entry.target, exception, stack); + } + } } void handleEvent(PointerEvent event, HitTestEntry entry) { diff --git a/packages/flutter/lib/src/gestures/pointer_router.dart b/packages/flutter/lib/src/gestures/pointer_router.dart index 6ef9f9e56b..bea6eb65e4 100644 --- a/packages/flutter/lib/src/gestures/pointer_router.dart +++ b/packages/flutter/lib/src/gestures/pointer_router.dart @@ -2,11 +2,15 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/services.dart'; + import 'events.dart'; /// A callback that receives a [PointerEvent] typedef void PointerRoute(PointerEvent event); +typedef void PointerExceptionHandler(PointerRouter source, PointerEvent event, PointerRoute route, dynamic exception, StackTrace stack); + /// A routing table for [PointerEvent] events. class PointerRouter { final Map> _routeMap = new Map>(); @@ -34,14 +38,38 @@ class PointerRouter { _routeMap.remove(pointer); } - /// Calls the routes registed for this pointer event. + /// This callback is invoked whenever an exception is caught by the pointer + /// router. The 'source' argument is the [PointerRouter] object that caught + /// the exception. The 'event' argument is the pointer event that was being + /// routed. The 'route' argument is the callback that threw the exception. The + /// 'exception' argument contains the object that was thrown, and the 'stack' + /// argument contains the stack trace. The callback is invoked after the + /// information (exception, stack trace, and event; not the route callback + /// itself) is printed to the console. + PointerExceptionHandler debugPointerExceptionHandler; + + /// Calls the routes registered for this pointer event. /// - /// Calls the routes in the order in which they were added to the route. + /// Routes are called in the order in which they were added to the + /// PointerRouter object. void route(PointerEvent event) { List routes = _routeMap[event.pointer]; if (routes == null) return; - for (PointerRoute route in new List.from(routes)) - route(event); + for (PointerRoute route in new List.from(routes)) { + try { + route(event); + } catch (exception, stack) { + debugPrint('-- EXCEPTION --'); + debugPrint('The following exception was raised while routing a pointer event:'); + debugPrint('$exception'); + debugPrint('Stack trace:'); + debugPrint('$stack'); + debugPrint('Event:'); + debugPrint('$event'); + if (debugPointerExceptionHandler != null) + debugPointerExceptionHandler(this, event, route, exception, stack); + } + } } } diff --git a/packages/flutter/lib/src/rendering/debug.dart b/packages/flutter/lib/src/rendering/debug.dart index 31a94ee163..2662962f2c 100644 --- a/packages/flutter/lib/src/rendering/debug.dart +++ b/packages/flutter/lib/src/rendering/debug.dart @@ -3,7 +3,6 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:collection'; import 'dart:convert' show JSON; import 'dart:developer' as developer; import 'dart:ui' as ui; @@ -11,7 +10,8 @@ import 'dart:ui' as ui; import 'package:flutter/painting.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; -import 'package:vector_math/vector_math_64.dart'; + +export 'package:flutter/services.dart' show debugPrint; /// Causes each RenderBox to paint a box around its bounds. bool debugPaintSizeEnabled = false; @@ -127,43 +127,3 @@ Future _timeDilation(String method, Map _debugPrintBuffer = new Queue(); -Stopwatch _debugPrintStopwatch = new Stopwatch(); -bool _debugPrintScheduled = false; -void _debugPrintTask() { - _debugPrintScheduled = false; - if (_debugPrintStopwatch.elapsed > _kDebugPrintPauseTime) { - _debugPrintStopwatch.stop(); - _debugPrintStopwatch.reset(); - _debugPrintedCharacters = 0; - } - while (_debugPrintedCharacters < _kDebugPrintCapacity && _debugPrintBuffer.length > 0) { - String line = _debugPrintBuffer.removeFirst(); - _debugPrintedCharacters += line.length; // TODO(ianh): Use the UTF-8 byte length instead - print(line); - } - if (_debugPrintBuffer.length > 0) { - _debugPrintScheduled = true; - _debugPrintedCharacters = 0; - new Timer(_kDebugPrintPauseTime, _debugPrintTask); - } else { - _debugPrintStopwatch.start(); - } -} diff --git a/packages/flutter/lib/src/services/print.dart b/packages/flutter/lib/src/services/print.dart new file mode 100644 index 0000000000..a676eec72a --- /dev/null +++ b/packages/flutter/lib/src/services/print.dart @@ -0,0 +1,46 @@ +// Copyright 2015 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:async'; +import 'dart:collection'; + +/// Prints a message to the console, which you can access using the "flutter" +/// tool's "logs" command ("flutter logs"). +/// +/// This function very crudely attempts to throttle the rate at which messages +/// are sent to avoid data loss on Android. This means that interleaving calls +/// to this function (directly or indirectly via [debugDumpRenderTree] or +/// [debugDumpApp]) and to the Dart [print] method can result in out-of-order +/// messages in the logs. +void debugPrint(String message) { + _debugPrintBuffer.addAll(message.split('\n')); + if (!_debugPrintScheduled) + _debugPrintTask(); +} +int _debugPrintedCharacters = 0; +int _kDebugPrintCapacity = 16 * 1024; +Duration _kDebugPrintPauseTime = const Duration(seconds: 1); +Queue _debugPrintBuffer = new Queue(); +Stopwatch _debugPrintStopwatch = new Stopwatch(); +bool _debugPrintScheduled = false; +void _debugPrintTask() { + _debugPrintScheduled = false; + if (_debugPrintStopwatch.elapsed > _kDebugPrintPauseTime) { + _debugPrintStopwatch.stop(); + _debugPrintStopwatch.reset(); + _debugPrintedCharacters = 0; + } + while (_debugPrintedCharacters < _kDebugPrintCapacity && _debugPrintBuffer.length > 0) { + String line = _debugPrintBuffer.removeFirst(); + _debugPrintedCharacters += line.length; // TODO(ianh): Use the UTF-8 byte length instead + print(line); + } + if (_debugPrintBuffer.length > 0) { + _debugPrintScheduled = true; + _debugPrintedCharacters = 0; + new Timer(_kDebugPrintPauseTime, _debugPrintTask); + } else { + _debugPrintStopwatch.start(); + } +}