send ext.flutter.debugDumpApp; ext.flutter.reassemble (#4810)

* send ext.flutter.debugDumpApp; ext.flutter.reassemble

* expose debugDumpRenderTree
diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart
index 7b9838d..aae4567 100644
--- a/packages/flutter/lib/src/rendering/binding.dart
+++ b/packages/flutter/lib/src/rendering/binding.dart
@@ -57,6 +57,14 @@
     });
 
     assert(() {
+      registerSignalServiceExtension(
+        name: 'debugDumpRenderTree',
+        callback: debugDumpRenderTree
+      );
+      return true;
+    });
+
+    assert(() {
       // this service extension only works in checked mode
       registerBoolServiceExtension(
         name: 'repaintRainbow',
diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart
index e2c98c1..d4a134a 100644
--- a/packages/flutter/lib/src/widgets/binding.dart
+++ b/packages/flutter/lib/src/widgets/binding.dart
@@ -72,6 +72,11 @@
   void initServiceExtensions() {
     super.initServiceExtensions();
 
+    registerSignalServiceExtension(
+      name: 'debugDumpApp',
+      callback: debugDumpApp
+    );
+
     registerBoolServiceExtension(
       name: 'showPerformanceOverlay',
       getter: () => WidgetsApp.showPerformanceOverlayOverride,
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart
index e23e27e..6786712 100644
--- a/packages/flutter_tools/lib/src/device.dart
+++ b/packages/flutter_tools/lib/src/device.dart
@@ -191,6 +191,8 @@
 
   bool get supportsRestart => false;
 
+  bool get restartSendsFrameworkInitEvent => true;
+
   /// Restart the given app; the application will already have been launched with
   /// [startApp].
   Future<bool> restartApp(
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 7f71142..6cf8a4e 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -579,6 +579,9 @@
   bool get supportsRestart => run.useReloadSources;
 
   @override
+  bool get restartSendsFrameworkInitEvent => false;
+
+  @override
   Future<bool> restartApp(
     ApplicationPackage package,
     LaunchResult result, {
@@ -587,7 +590,15 @@
   }) async {
     if (observatory.firstIsolateId == null)
       throw 'Application isolate not found';
-    return observatory.reloadSources(observatory.firstIsolateId);
+    Event result = await observatory.reloadSources(observatory.firstIsolateId);
+    dynamic error = result.response['reloadError'];
+    if (error != null) {
+      printError('Error reloading application sources: $error');
+      return false;
+    } else {
+      await observatory.flutterReassemble(observatory.firstIsolateId);
+      return true;
+    }
   }
 
   @override
diff --git a/packages/flutter_tools/lib/src/observatory.dart b/packages/flutter_tools/lib/src/observatory.dart
index 37f8de4..5ec6750 100644
--- a/packages/flutter_tools/lib/src/observatory.dart
+++ b/packages/flutter_tools/lib/src/observatory.dart
@@ -9,8 +9,6 @@
 import 'package:json_rpc_2/json_rpc_2.dart' as rpc;
 import 'package:web_socket_channel/io.dart';
 
-import 'globals.dart';
-
 class Observatory {
   Observatory._(this.peer, this.port) {
     peer.registerMethod('streamNotify', (rpc.Parameters event) {
@@ -115,7 +113,7 @@
     });
   }
 
-  Future<bool> reloadSources(String isolateId) async {
+  Future<Response> reloadSources(String isolateId) async {
     Completer<Event> whenIsolateReloads = new Completer<Event>();
     StreamSubscription<Event> sub = onIsolateEvent
       .where((Event event) => event.kind == 'IsolateReload')
@@ -123,11 +121,7 @@
 
     try {
       await sendRequest('_reloadSources', <String, dynamic>{ 'isolateId': isolateId });
-      Event event = await whenIsolateReloads.future.timeout(new Duration(seconds: 20));
-      dynamic error = event.response['reloadError'];
-      if (error != null)
-        printError('Error reloading application sources: $error');
-      return error == null;
+      return await whenIsolateReloads.future.timeout(new Duration(seconds: 20));
     } finally {
       await sub.cancel();
     }
@@ -215,6 +209,25 @@
 
   // Flutter extension methods.
 
+  Future<Response> flutterDebugDumpApp(String isolateId) {
+    return peer.sendRequest('ext.flutter.debugDumpApp', <String, dynamic>{
+      'isolateId': isolateId
+    }).then((dynamic result) => new Response(result));
+  }
+
+  Future<Response> flutterDebugDumpRenderTree(String isolateId) {
+    return peer.sendRequest('ext.flutter.debugDumpRenderTree', <String, dynamic>{
+      'isolateId': isolateId
+    }).then((dynamic result) => new Response(result));
+  }
+
+  /// Causes the application to pick up any changed code.
+  Future<Response> flutterReassemble(String isolateId) {
+    return peer.sendRequest('ext.flutter.reassemble', <String, dynamic>{
+      'isolateId': isolateId
+    }).then((dynamic result) => new Response(result));
+  }
+
   Future<Response> flutterExit(String isolateId) {
     return peer.sendRequest('ext.flutter.exit', <String, dynamic>{
       'isolateId': isolateId
diff --git a/packages/flutter_tools/lib/src/run.dart b/packages/flutter_tools/lib/src/run.dart
index 495d7eb..efa7ce2 100644
--- a/packages/flutter_tools/lib/src/run.dart
+++ b/packages/flutter_tools/lib/src/run.dart
@@ -82,9 +82,13 @@
     } else {
       Status status = logger.startProgress('Re-starting application...');
 
-      Future<Event> extensionAddedEvent = observatory.onExtensionEvent
-        .where((Event event) => event.extensionKind == 'Flutter.FrameworkInitialization')
-        .first;
+      Future<Event> extensionAddedEvent;
+
+      if (device.restartSendsFrameworkInitEvent) {
+        extensionAddedEvent = observatory.onExtensionEvent
+          .where((Event event) => event.extensionKind == 'Flutter.FrameworkInitialization')
+          .first;
+      }
 
       bool restartResult = await device.restartApp(
         _package,
@@ -95,7 +99,7 @@
 
       status.stop(showElapsedTime: true);
 
-      if (restartResult) {
+      if (restartResult && extensionAddedEvent != null) {
         // TODO(devoncarew): We should restore the route here.
         await extensionAddedEvent;
       }
@@ -255,6 +259,10 @@
             _stopApp();
           } else if (useDevFS && lower == 'd') {
             _updateDevFS();
+          } else if (lower == 'w') {
+            _debugDumpApp();
+          } else if (lower == 't') {
+            _debugDumpRenderTree();
           }
         });
       }
@@ -293,6 +301,14 @@
     });
   }
 
+  void _debugDumpApp() {
+    observatory.flutterDebugDumpApp(observatory.firstIsolateId);
+  }
+
+  void _debugDumpRenderTree() {
+    observatory.flutterDebugDumpRenderTree(observatory.firstIsolateId);
+  }
+
   DevFS devFS;
 
   Future<Null> _updateDevFS() async {
@@ -361,6 +377,7 @@
   void _printHelp() {
     String restartText = device.supportsRestart ? ', "r" or F5 to restart the app,' : '';
     printStatus('Type "h" or F1 for help$restartText and "q", F10, or ctrl-c to quit.');
+    printStatus('Type "w" to print the widget hierarchy of the app, and "t" for the render tree.');
 
     if (useDevFS)
       printStatus('Type "d" to send modified project files to the the client\'s DevFS.');