diff --git a/shell/platform/darwin/desktop/main_mac.mm b/shell/platform/darwin/desktop/main_mac.mm index 8087d0e92cd6d08441f572fc69806d05cd238124..808a5f63f1d145ac2fb4d731f77c5c971deddee6 100644 --- a/shell/platform/darwin/desktop/main_mac.mm +++ b/shell/platform/darwin/desktop/main_mac.mm @@ -7,13 +7,69 @@ #include #include "flutter/fml/message_loop.h" +#include "flutter/shell/common/platform_view.h" #include "flutter/shell/common/shell.h" #include "flutter/shell/common/switches.h" #include "flutter/shell/platform/darwin/common/platform_mac.h" #include "flutter/shell/platform/darwin/desktop/flutter_application.h" +#include "flutter/shell/testing/test_runner.h" #include "flutter/shell/testing/testing.h" #include "lib/fxl/command_line.h" #include "lib/fxl/logging.h" +#include "lib/tonic/dart_microtask_queue.h" + +// Exit codes used by the Dart command line tool. +const int kApiErrorExitCode = 253; +const int kCompilationErrorExitCode = 254; +const int kErrorExitCode = 255; + +// Checks whether the engine's main Dart isolate has no pending work. If so, +// then exit the given message loop. +class ScriptCompletionTaskObserver : public fml::TaskObserver { + public: + ScriptCompletionTaskObserver(fxl::RefPtr task_runner) + : main_task_runner_(std::move(task_runner)), + prev_live_(false), + last_error_(tonic::kNoError) {} + + void DidProcessTask() override { + shell::TestRunner& test_runner = shell::TestRunner::Shared(); + shell::Engine& engine = test_runner.platform_view().engine(); + + if (engine.GetLoadScriptError() != tonic::kNoError) { + last_error_ = engine.GetLoadScriptError(); + main_task_runner_->PostTask([]() { fml::MessageLoop::GetCurrent().Terminate(); }); + return; + } + + bool live = engine.UIIsolateHasLivePorts(); + if (prev_live_ && !live) { + last_error_ = engine.GetUIIsolateLastError(); + main_task_runner_->PostTask([]() { fml::MessageLoop::GetCurrent().Terminate(); }); + } + prev_live_ = live; + } + + tonic::DartErrorHandleType last_error() { return last_error_; } + + private: + fxl::RefPtr main_task_runner_; + bool prev_live_; + tonic::DartErrorHandleType last_error_; +}; + +int ConvertErrorTypeToExitCode(tonic::DartErrorHandleType error) { + switch (error) { + case tonic::kCompilationErrorType: + return kCompilationErrorExitCode; + case tonic::kApiErrorType: + return kApiErrorExitCode; + case tonic::kUnknownErrorType: + return kErrorExitCode; + default: + return 0; + } +} static fxl::CommandLine InitializedCommandLine() { std::vector args_vector; @@ -47,8 +103,31 @@ int main(int argc, const char* argv[]) { if (command_line.HasOption(shell::FlagForSwitch(shell::Switch::NonInteractive))) { if (!shell::InitForTesting(std::move(command_line))) return 1; + + // Note that this task observer must be added after the observer that drains + // the microtask queue. + ScriptCompletionTaskObserver task_observer(fml::MessageLoop::GetCurrent().GetTaskRunner()); + blink::Threads::UI()->PostTask( + [&task_observer] { fml::MessageLoop::GetCurrent().AddTaskObserver(&task_observer); }); + fml::MessageLoop::GetCurrent().Run(); - return EXIT_SUCCESS; + + shell::TestRunner& test_runner = shell::TestRunner::Shared(); + tonic::DartErrorHandleType error = test_runner.platform_view().engine().GetLoadScriptError(); + if (error == tonic::kNoError) + error = task_observer.last_error(); + if (error == tonic::kNoError) { + fxl::AutoResetWaitableEvent latch; + blink::Threads::UI()->PostTask([&error, &latch] { + error = tonic::DartMicrotaskQueue::GetForCurrentThread()->GetLastError(); + latch.Signal(); + }); + latch.Wait(); + } + + // The script has completed and the engine may not be in a clean state, + // so just stop the process. + exit(ConvertErrorTypeToExitCode(error)); } else { return NSApplicationMain(argc, argv); }