Fix shutdown race when running Dart on the message loop

- Shutdown the DartController when the content handler exits not when the content handler's message loop hits an error (which occurs when the final application closes its shell handle.)

Other fixes:

- DartMessageHandler considers the isolate as exited if it hit an unhandled exception error.
- DartMessageHandler can be configured to not quit the message loop when the isolate exits.

R=zra@google.com

Review URL: https://codereview.chromium.org/1688793002 .
diff --git a/services/dart/content_handler_main.cc b/services/dart/content_handler_main.cc
index ad599cc..219f2be 100644
--- a/services/dart/content_handler_main.cc
+++ b/services/dart/content_handler_main.cc
@@ -140,7 +140,11 @@
         default_strict_(false),
         run_on_message_loop_(false) {}
 
-  ~DartContentHandlerApp() override {}
+  ~DartContentHandlerApp() override {
+    // Shutdown the controller.
+    mojo::dart::DartController::Shutdown();
+    delete service_connector_;
+  }
 
   void ExtractApplication(base::FilePath* application_dir,
                           mojo::URLResponsePtr response,
@@ -242,13 +246,6 @@
     return true;
   }
 
-  // Overridden from ApplicationDelegate:
-  void Quit() override {
-    // Shutdown the controller.
-    mojo::dart::DartController::Shutdown();
-    delete service_connector_;
-  }
-
   mojo::TracingImpl tracing_;
   DartContentHandler content_handler_;
   DartContentHandler strict_content_handler_;
diff --git a/tonic/dart_message_handler.cc b/tonic/dart_message_handler.cc
index 60cd502..85565e6 100644
--- a/tonic/dart_message_handler.cc
+++ b/tonic/dart_message_handler.cc
@@ -15,6 +15,9 @@
 
 DartMessageHandler::DartMessageHandler()
     : handled_first_message_(false),
+      quit_message_loop_when_isolate_exits_(true),
+      isolate_exited_(false),
+      isolate_had_uncaught_exception_error_(false),
       task_runner_(nullptr) {
 }
 
@@ -43,6 +46,8 @@
   DartIsolateScope scope(dart_state->isolate());
   DartApiScope dart_api_scope;
 
+  bool error = false;
+
   // On the first message, check if we should pause on isolate start.
   if (!handled_first_message()) {
     set_handled_first_message(true);
@@ -62,7 +67,7 @@
       }
       Dart_SetPausedOnStart(false);
       // We've resumed, handle *all* normal messages that are in the queue.
-      LogIfError(Dart_HandleMessages());
+      error = LogIfError(Dart_HandleMessages());
     }
   } else if (Dart_IsPausedOnExit()) {
     // We are paused on isolate exit. Only handle service messages until we are
@@ -76,17 +81,25 @@
     }
   } else {
     // We are processing messages normally.
-    LogIfError(Dart_HandleMessage());
+    error = LogIfError(Dart_HandleMessage());
   }
 
-  if (!Dart_HasLivePorts()) {
+  if (error) {
+    // Remember that we had an uncaught exception error.
+    isolate_had_uncaught_exception_error_ = true;
+  }
+
+  if (error || !Dart_HasLivePorts()) {
     // The isolate has no live ports and would like to exit.
     if (Dart_ShouldPauseOnExit()) {
       // Mark that we are paused on exit.
       Dart_SetPausedOnExit(true);
     } else {
-      // Quit.
-      base::MessageLoop::current()->QuitWhenIdle();
+      isolate_exited_ = true;
+      if (quit_message_loop_when_isolate_exits()) {
+        // Quit.
+        base::MessageLoop::current()->QuitWhenIdle();
+      }
     }
   }
 }
diff --git a/tonic/dart_message_handler.h b/tonic/dart_message_handler.h
index 5cb2f9a..a6ab3d3 100644
--- a/tonic/dart_message_handler.h
+++ b/tonic/dart_message_handler.h
@@ -20,6 +20,27 @@
   // Messages for the current isolate will be scheduled on |runner|.
   void Initialize(const scoped_refptr<base::SingleThreadTaskRunner>& runner);
 
+  // Request the message loop to quit when isolate exits? Default is true.
+  void set_quit_message_loop_when_isolate_exits(
+      bool quit_message_loop_when_isolate_exits) {
+    quit_message_loop_when_isolate_exits_ =
+        quit_message_loop_when_isolate_exits;
+  }
+
+  bool quit_message_loop_when_isolate_exits() const {
+    return quit_message_loop_when_isolate_exits_;
+  }
+
+  // Did the isolate exit?
+  bool isolate_exited() const {
+    return isolate_exited_;
+  }
+
+  // Did the isolate have an uncaught exception error?
+  bool isolate_had_uncaught_exception_error() const {
+    return isolate_had_uncaught_exception_error_;
+  }
+
  protected:
   // Called from an unknown thread for each message.
   void OnMessage(DartState* dart_state);
@@ -39,6 +60,9 @@
   }
 
   bool handled_first_message_;
+  bool quit_message_loop_when_isolate_exits_;
+  bool isolate_exited_;
+  bool isolate_had_uncaught_exception_error_;
   scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
 
  private: