Revert "Revert "Listen to `WM_CLOSE` message on Windows to allow framework to cancel exit (#40493)" (#40739)"

This reverts commit 22f27ad8be9ad946464ffc366da18681dd190426.
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index d15d5c9..490211e 100644
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -3118,6 +3118,8 @@
 ORIGIN: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/windows/window_state.h + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.cc + ../../../flutter/LICENSE
+ORIGIN: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/windows/windows_proc_table.cc + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/windows/windows_proc_table.h + ../../../flutter/LICENSE
 ORIGIN: ../../../flutter/shell/platform/windows/windows_registry.cc + ../../../flutter/LICENSE
@@ -5708,6 +5710,8 @@
 FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.cc
 FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager.h
 FILE: ../../../flutter/shell/platform/windows/window_state.h
+FILE: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.cc
+FILE: ../../../flutter/shell/platform/windows/windows_lifecycle_manager.h
 FILE: ../../../flutter/shell/platform/windows/windows_proc_table.cc
 FILE: ../../../flutter/shell/platform/windows/windows_proc_table.h
 FILE: ../../../flutter/shell/platform/windows/windows_registry.cc
diff --git a/shell/platform/windows/BUILD.gn b/shell/platform/windows/BUILD.gn
index 1f7a850..db9053a 100644
--- a/shell/platform/windows/BUILD.gn
+++ b/shell/platform/windows/BUILD.gn
@@ -103,6 +103,8 @@
     "window_proc_delegate_manager.cc",
     "window_proc_delegate_manager.h",
     "window_state.h",
+    "windows_lifecycle_manager.cc",
+    "windows_lifecycle_manager.h",
     "windows_proc_table.cc",
     "windows_proc_table.h",
     "windows_registry.cc",
diff --git a/shell/platform/windows/fixtures/main.dart b/shell/platform/windows/fixtures/main.dart
index 1e8d5a9..c9794f6 100644
--- a/shell/platform/windows/fixtures/main.dart
+++ b/shell/platform/windows/fixtures/main.dart
@@ -6,6 +6,7 @@
 import 'dart:io' as io;
 import 'dart:typed_data' show ByteData, Uint8List;
 import 'dart:ui' as ui;
+import 'dart:convert';
 
 // Signals a waiting latch in the native test.
 @pragma('vm:external-name', 'Signal')
@@ -87,6 +88,66 @@
 }
 
 @pragma('vm:entry-point')
+void exitTestExit() async {
+  final Completer<ByteData?> closed = Completer<ByteData?>();
+  ui.channelBuffers.setListener('flutter/platform', (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
+    final String jsonString = json.encode(<Map<String, String>>[{'response': 'exit'}]);
+    final ByteData responseData = ByteData.sublistView(Uint8List.fromList(utf8.encode(jsonString)));
+    callback(responseData);
+    closed.complete(data);
+  });
+  await closed.future;
+
+  // From here down, nothing should be called, because the application will have already been closed.
+  final Completer<ByteData?> exited = Completer<ByteData?>();
+  final String jsonString = json.encode(<String, dynamic>{
+    'method': 'System.exitApplication',
+    'args': <String, dynamic>{
+      'type': 'required', 'exitCode': 0
+      }
+    });
+  ui.PlatformDispatcher.instance.sendPlatformMessage(
+    'flutter/platform',
+    ByteData.sublistView(
+      Uint8List.fromList(utf8.encode(jsonString))
+    ),
+    (ByteData? reply) {
+      exited.complete(reply);
+    });
+  await exited.future;
+}
+
+@pragma('vm:entry-point')
+void exitTestCancel() async {
+  final Completer<ByteData?> closed = Completer<ByteData?>();
+  ui.channelBuffers.setListener('flutter/platform', (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
+    final String jsonString = json.encode(<Map<String, String>>[{'response': 'cancel'}]);
+    final ByteData responseData = ByteData.sublistView(Uint8List.fromList(utf8.encode(jsonString)));
+    callback(responseData);
+    closed.complete(data);
+  });
+  await closed.future;
+
+  // Because the request was canceled, the below shall execute.
+  final Completer<ByteData?> exited = Completer<ByteData?>();
+  final String jsonString = json.encode(<String, dynamic>{
+    'method': 'System.exitApplication',
+    'args': <String, dynamic>{
+      'type': 'required', 'exitCode': 0
+      }
+    });
+  ui.PlatformDispatcher.instance.sendPlatformMessage(
+    'flutter/platform',
+    ByteData.sublistView(
+      Uint8List.fromList(utf8.encode(jsonString))
+    ),
+    (ByteData? reply) {
+      exited.complete(reply);
+    });
+  await exited.future;
+}
+
+@pragma('vm:entry-point')
 void customEntrypoint() {}
 
 @pragma('vm:entry-point')
diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc
index d3c9332..7a652c2 100644
--- a/shell/platform/windows/flutter_windows_engine.cc
+++ b/shell/platform/windows/flutter_windows_engine.cc
@@ -161,7 +161,8 @@
     std::unique_ptr<WindowsRegistry> registry)
     : project_(std::make_unique<FlutterProjectBundle>(project)),
       aot_data_(nullptr, nullptr),
-      windows_registry_(std::move(registry)) {
+      windows_registry_(std::move(registry)),
+      lifecycle_manager_(std::make_unique<WindowsLifecycleManager>(this)) {
   embedder_api_.struct_size = sizeof(FlutterEngineProcTable);
   FlutterEngineGetProcAddresses(&embedder_api_);
 
@@ -203,6 +204,17 @@
       std::make_unique<FlutterWindowsTextureRegistrar>(this, gl_procs_);
   surface_manager_ = AngleSurfaceManager::Create();
   window_proc_delegate_manager_ = std::make_unique<WindowProcDelegateManager>();
+  window_proc_delegate_manager_->RegisterTopLevelWindowProcDelegate(
+      [](HWND hwnd, UINT msg, WPARAM wpar, LPARAM lpar, void* user_data,
+         LRESULT* result) {
+        BASE_DCHECK(user_data);
+        FlutterWindowsEngine* that =
+            static_cast<FlutterWindowsEngine*>(user_data);
+        BASE_DCHECK(that->lifecycle_manager_);
+        return that->lifecycle_manager_->WindowProc(hwnd, msg, wpar, lpar,
+                                                    result);
+      },
+      static_cast<void*>(this));
 
   // Set up internal channels.
   // TODO: Replace this with an embedder.h API. See
@@ -750,4 +762,13 @@
                               reinterpret_cast<const uint8_t*>(""), 0);
 }
 
+void FlutterWindowsEngine::RequestApplicationQuit(ExitType exit_type,
+                                                  UINT exit_code) {
+  platform_handler_->RequestAppExit(exit_type, exit_code);
+}
+
+void FlutterWindowsEngine::OnQuit(UINT exit_code) {
+  lifecycle_manager_->Quit(exit_code);
+}
+
 }  // namespace flutter
diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h
index 01de0d3..e496404 100644
--- a/shell/platform/windows/flutter_windows_engine.h
+++ b/shell/platform/windows/flutter_windows_engine.h
@@ -35,6 +35,7 @@
 #include "flutter/shell/platform/windows/text_input_plugin.h"
 #include "flutter/shell/platform/windows/window_proc_delegate_manager.h"
 #include "flutter/shell/platform/windows/window_state.h"
+#include "flutter/shell/platform/windows/windows_lifecycle_manager.h"
 #include "flutter/shell/platform/windows/windows_registry.h"
 #include "third_party/rapidjson/include/rapidjson/document.h"
 
@@ -254,6 +255,12 @@
   // Updates accessibility, e.g. switch to high contrast mode
   void UpdateAccessibilityFeatures(FlutterAccessibilityFeature flags);
 
+  // Called when the application quits in response to a quit request.
+  void OnQuit(UINT exit_code);
+
+  // Called when a WM_CLOSE message is received.
+  void RequestApplicationQuit(ExitType exit_type, UINT exit_code);
+
  protected:
   // Creates the keyboard key handler.
   //
@@ -395,6 +402,9 @@
   // Wrapper providing Windows registry access.
   std::unique_ptr<WindowsRegistry> windows_registry_;
 
+  // Handler for top level window messages.
+  std::unique_ptr<WindowsLifecycleManager> lifecycle_manager_;
+
   FML_DISALLOW_COPY_AND_ASSIGN(FlutterWindowsEngine);
 };
 
diff --git a/shell/platform/windows/flutter_windows_engine_unittests.cc b/shell/platform/windows/flutter_windows_engine_unittests.cc
index f0d9492..f71de73 100644
--- a/shell/platform/windows/flutter_windows_engine_unittests.cc
+++ b/shell/platform/windows/flutter_windows_engine_unittests.cc
@@ -619,5 +619,92 @@
   }
 }
 
+class MockWindowsLifecycleManager : public WindowsLifecycleManager {
+ public:
+  MockWindowsLifecycleManager(FlutterWindowsEngine* engine)
+      : WindowsLifecycleManager(engine) {}
+  virtual ~MockWindowsLifecycleManager() {}
+
+  MOCK_CONST_METHOD1(Quit, void(UINT));
+};
+
+TEST_F(FlutterWindowsEngineTest, TestExit) {
+  FlutterWindowsEngineBuilder builder{GetContext()};
+  builder.SetDartEntrypoint("exitTestExit");
+  bool finished = false;
+  bool did_call = false;
+
+  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
+
+  EngineModifier modifier(engine.get());
+  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
+  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
+  ON_CALL(*handler, Quit).WillByDefault([&finished](UINT exit_code) {
+    finished = exit_code == 0;
+  });
+  EXPECT_CALL(*handler, Quit).Times(1);
+  modifier.SetLifecycleManager(std::move(handler));
+
+  auto binary_messenger =
+      std::make_unique<BinaryMessengerImpl>(engine->messenger());
+  binary_messenger->SetMessageHandler(
+      "flutter/platform", [&did_call](const uint8_t* message,
+                                      size_t message_size, BinaryReply reply) {
+        did_call = true;
+        char response[] = "";
+        reply(reinterpret_cast<uint8_t*>(response), 0);
+      });
+
+  engine->Run();
+
+  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
+                                                               0);
+
+  while (!finished) {
+    engine->task_runner()->ProcessTasks();
+  }
+
+  EXPECT_FALSE(did_call);
+}
+
+TEST_F(FlutterWindowsEngineTest, TestExitCancel) {
+  FlutterWindowsEngineBuilder builder{GetContext()};
+  builder.SetDartEntrypoint("exitTestCancel");
+  bool finished = false;
+  bool did_call = false;
+
+  std::unique_ptr<FlutterWindowsEngine> engine = builder.Build();
+
+  EngineModifier modifier(engine.get());
+  modifier.embedder_api().RunsAOTCompiledDartCode = []() { return false; };
+  auto handler = std::make_unique<MockWindowsLifecycleManager>(engine.get());
+  ON_CALL(*handler, Quit).WillByDefault([&finished](int64_t exit_code) {
+    finished = true;
+  });
+  EXPECT_CALL(*handler, Quit).Times(0);
+  modifier.SetLifecycleManager(std::move(handler));
+
+  auto binary_messenger =
+      std::make_unique<BinaryMessengerImpl>(engine->messenger());
+  binary_messenger->SetMessageHandler(
+      "flutter/platform", [&did_call](const uint8_t* message,
+                                      size_t message_size, BinaryReply reply) {
+        did_call = true;
+        char response[] = "";
+        reply(reinterpret_cast<uint8_t*>(response), 0);
+      });
+
+  engine->Run();
+
+  engine->window_proc_delegate_manager()->OnTopLevelWindowProc(0, WM_CLOSE, 0,
+                                                               0);
+
+  while (!did_call) {
+    engine->task_runner()->ProcessTasks();
+  }
+
+  EXPECT_FALSE(finished);
+}
+
 }  // namespace testing
 }  // namespace flutter
diff --git a/shell/platform/windows/platform_handler.cc b/shell/platform/windows/platform_handler.cc
index a33710b..cec1daf 100644
--- a/shell/platform/windows/platform_handler.cc
+++ b/shell/platform/windows/platform_handler.cc
@@ -28,8 +28,6 @@
 static constexpr char kExitCodeKey[] = "exitCode";
 
 static constexpr char kExitTypeKey[] = "type";
-static constexpr char kExitTypeCancelable[] = "cancelable";
-static constexpr char kExitTypeRequired[] = "required";
 
 static constexpr char kExitResponseKey[] = "response";
 static constexpr char kExitResponseCancel[] = "cancel";
@@ -44,6 +42,10 @@
 static constexpr int kAccessDeniedErrorCode = 5;
 static constexpr int kErrorSuccess = 0;
 
+static constexpr char kExitRequestError[] = "ExitApplication error";
+static constexpr char kInvalidExitRequestMessage[] =
+    "Invalid application exit request";
+
 namespace flutter {
 
 namespace {
@@ -205,6 +207,16 @@
 
 }  // namespace
 
+static ExitType StringToExitType(const std::string& string) {
+  if (string.compare(PlatformHandler::kExitTypeRequired) == 0) {
+    return ExitType::required;
+  } else if (string.compare(PlatformHandler::kExitTypeCancelable) == 0) {
+    return ExitType::cancelable;
+  }
+  FML_LOG(ERROR) << string << " is not recognized as a valid exit type.";
+  return ExitType::required;
+}
+
 PlatformHandler::PlatformHandler(
     BinaryMessenger* messenger,
     FlutterWindowsEngine* engine,
@@ -354,12 +366,12 @@
 }
 
 void PlatformHandler::SystemExitApplication(
-    const std::string& exit_type,
-    int64_t exit_code,
+    ExitType exit_type,
+    UINT exit_code,
     std::unique_ptr<MethodResult<rapidjson::Document>> result) {
   rapidjson::Document result_doc;
   result_doc.SetObject();
-  if (exit_type.compare(kExitTypeRequired) == 0) {
+  if (exit_type == ExitType::required) {
     QuitApplication(exit_code);
     result_doc.GetObjectW().AddMember(kExitResponseKey, kExitResponseExit,
                                       result_doc.GetAllocator());
@@ -372,8 +384,12 @@
   }
 }
 
-void PlatformHandler::RequestAppExit(const std::string& exit_type,
-                                     int64_t exit_code) {
+// Indicates whether an exit request may be canceled by the framework.
+// These values must be kept in sync with ExitType in platform_handler.h
+static constexpr const char* kExitTypeNames[] = {
+    PlatformHandler::kExitTypeRequired, PlatformHandler::kExitTypeCancelable};
+
+void PlatformHandler::RequestAppExit(ExitType exit_type, UINT exit_code) {
   auto callback = std::make_unique<MethodResultFunctions<rapidjson::Document>>(
       [this, exit_code](const rapidjson::Document* response) {
         RequestAppExitSuccess(response, exit_code);
@@ -381,21 +397,31 @@
       nullptr, nullptr);
   auto args = std::make_unique<rapidjson::Document>();
   args->SetObject();
-  args->GetObjectW().AddMember(kExitTypeKey, exit_type, args->GetAllocator());
+  args->GetObjectW().AddMember(
+      kExitTypeKey, std::string(kExitTypeNames[static_cast<int>(exit_type)]),
+      args->GetAllocator());
   channel_->InvokeMethod(kRequestAppExitMethod, std::move(args),
                          std::move(callback));
 }
 
 void PlatformHandler::RequestAppExitSuccess(const rapidjson::Document* result,
-                                            int64_t exit_code) {
-  const std::string& exit_type = result[0][kExitResponseKey].GetString();
+                                            UINT exit_code) {
+  rapidjson::Value::ConstMemberIterator itr =
+      result->FindMember(kExitResponseKey);
+  if (itr == result->MemberEnd() || !itr->value.IsString()) {
+    FML_LOG(ERROR) << "Application request response did not contain a valid "
+                      "response value";
+    return;
+  }
+  const std::string& exit_type = itr->value.GetString();
+
   if (exit_type.compare(kExitResponseExit) == 0) {
     QuitApplication(exit_code);
   }
 }
 
-void PlatformHandler::QuitApplication(int64_t exit_code) {
-  PostQuitMessage(exit_code);
+void PlatformHandler::QuitApplication(UINT exit_code) {
+  engine_->OnQuit(exit_code);
 }
 
 void PlatformHandler::HandleMethodCall(
@@ -404,9 +430,24 @@
   const std::string& method = method_call.method_name();
   if (method.compare(kExitApplicationMethod) == 0) {
     const rapidjson::Value& arguments = method_call.arguments()[0];
-    const std::string& exit_type = arguments[kExitTypeKey].GetString();
-    int64_t exit_code = arguments[kExitCodeKey].GetInt64();
-    SystemExitApplication(exit_type, exit_code, std::move(result));
+
+    rapidjson::Value::ConstMemberIterator itr =
+        arguments.FindMember(kExitTypeKey);
+    if (itr == arguments.MemberEnd() || !itr->value.IsString()) {
+      result->Error(kExitRequestError, kInvalidExitRequestMessage);
+      return;
+    }
+    const std::string& exit_type = itr->value.GetString();
+
+    itr = arguments.FindMember(kExitCodeKey);
+    if (itr == arguments.MemberEnd() || !itr->value.IsInt()) {
+      result->Error(kExitRequestError, kInvalidExitRequestMessage);
+      return;
+    }
+    UINT exit_code = arguments[kExitCodeKey].GetInt();
+
+    SystemExitApplication(StringToExitType(exit_type), exit_code,
+                          std::move(result));
   } else if (method.compare(kGetClipboardDataMethod) == 0) {
     // Only one string argument is expected.
     const rapidjson::Value& format = method_call.arguments()[0];
diff --git a/shell/platform/windows/platform_handler.h b/shell/platform/windows/platform_handler.h
index 096378d..f5eb9f2 100644
--- a/shell/platform/windows/platform_handler.h
+++ b/shell/platform/windows/platform_handler.h
@@ -22,6 +22,13 @@
 class FlutterWindowsEngine;
 class ScopedClipboardInterface;
 
+// Indicates whether an exit request may be canceled by the framework.
+// These values must be kept in sync with kExitTypeNames in platform_handler.cc
+enum class ExitType {
+  required,
+  cancelable,
+};
+
 // Handler for internal system channels.
 class PlatformHandler {
  public:
@@ -33,6 +40,14 @@
 
   virtual ~PlatformHandler();
 
+  // String values used for encoding/decoding exit requests.
+  static constexpr char kExitTypeCancelable[] = "cancelable";
+  static constexpr char kExitTypeRequired[] = "required";
+
+  // Send a request to the framework to test if a cancelable exit request
+  // should be canceled or honored.
+  virtual void RequestAppExit(ExitType exit_type, UINT exit_code);
+
  protected:
   // Gets plain text from the clipboard and provides it to |result| as the
   // value in a dictionary with the given |key|.
@@ -57,21 +72,17 @@
 
   // Handle a request from the framework to exit the application.
   virtual void SystemExitApplication(
-      const std::string& exit_type,
-      int64_t exit_code,
+      ExitType exit_type,
+      UINT exit_code,
       std::unique_ptr<MethodResult<rapidjson::Document>> result);
 
   // Actually quit the application with the provided exit code.
-  virtual void QuitApplication(int64_t exit_code);
-
-  // Send a request to the framework to test if a cancelable exit request
-  // should be canceled or honored.
-  virtual void RequestAppExit(const std::string& exit_type, int64_t exit_code);
+  virtual void QuitApplication(UINT exit_code);
 
   // Callback from when the cancelable exit request response request is
   // answered by the framework.
   virtual void RequestAppExitSuccess(const rapidjson::Document* result,
-                                     int64_t exit_code);
+                                     UINT exit_code);
 
   // A error type to use for error responses.
   static constexpr char kClipboardError[] = "Clipboard error";
diff --git a/shell/platform/windows/platform_handler_unittests.cc b/shell/platform/windows/platform_handler_unittests.cc
index 85d4f5a..eb87956 100644
--- a/shell/platform/windows/platform_handler_unittests.cc
+++ b/shell/platform/windows/platform_handler_unittests.cc
@@ -82,7 +82,7 @@
                void(const std::string&,
                     std::unique_ptr<MethodResult<rapidjson::Document>>));
 
-  MOCK_METHOD1(QuitApplication, void(int64_t exit_code));
+  MOCK_METHOD1(QuitApplication, void(UINT exit_code));
 
  private:
   FML_DISALLOW_COPY_AND_ASSIGN(MockPlatformHandler);
@@ -483,7 +483,7 @@
 
 TEST_F(PlatformHandlerTest, SystemExitApplicationRequired) {
   use_headless_engine();
-  int exit_code = -1;
+  UINT exit_code = 0;
 
   TestBinaryMessenger messenger([](const std::string& channel,
                                    const uint8_t* message, size_t size,
@@ -491,7 +491,7 @@
   MockPlatformHandler platform_handler(&messenger, engine());
 
   ON_CALL(platform_handler, QuitApplication)
-      .WillByDefault([&exit_code](int ec) { exit_code = ec; });
+      .WillByDefault([&exit_code](UINT ec) { exit_code = ec; });
   EXPECT_CALL(platform_handler, QuitApplication).Times(1);
 
   std::string result = SimulatePlatformMessage(
@@ -524,7 +524,7 @@
 TEST_F(PlatformHandlerTest, SystemExitApplicationCancelableExit) {
   use_headless_engine();
   bool called_cancel = false;
-  int exit_code = -1;
+  UINT exit_code = 0;
 
   TestBinaryMessenger messenger(
       [&called_cancel](const std::string& channel, const uint8_t* message,
@@ -536,7 +536,7 @@
   MockPlatformHandler platform_handler(&messenger, engine());
 
   ON_CALL(platform_handler, QuitApplication)
-      .WillByDefault([&exit_code](int ec) { exit_code = ec; });
+      .WillByDefault([&exit_code](UINT ec) { exit_code = ec; });
   EXPECT_CALL(platform_handler, QuitApplication).Times(1);
 
   std::string result = SimulatePlatformMessage(
diff --git a/shell/platform/windows/testing/engine_modifier.h b/shell/platform/windows/testing/engine_modifier.h
index f49362b..ee355c0 100644
--- a/shell/platform/windows/testing/engine_modifier.h
+++ b/shell/platform/windows/testing/engine_modifier.h
@@ -66,6 +66,10 @@
   // restart. This resets the keyboard's state if it exists.
   void Restart() { engine_->OnPreEngineRestart(); }
 
+  void SetLifecycleManager(std::unique_ptr<WindowsLifecycleManager>&& handler) {
+    engine_->lifecycle_manager_ = std::move(handler);
+  }
+
  private:
   FlutterWindowsEngine* engine_;
 
diff --git a/shell/platform/windows/windows_lifecycle_manager.cc b/shell/platform/windows/windows_lifecycle_manager.cc
new file mode 100644
index 0000000..c8e8548
--- /dev/null
+++ b/shell/platform/windows/windows_lifecycle_manager.cc
@@ -0,0 +1,120 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "windows_lifecycle_manager.h"
+
+#include <TlHelp32.h>
+#include <WinUser.h>
+#include <Windows.h>
+#include <tchar.h>
+
+#include "flutter/shell/platform/windows/flutter_windows_engine.h"
+
+namespace flutter {
+
+WindowsLifecycleManager::WindowsLifecycleManager(FlutterWindowsEngine* engine)
+    : engine_(engine) {}
+
+WindowsLifecycleManager::~WindowsLifecycleManager() {}
+
+void WindowsLifecycleManager::Quit(UINT exit_code) const {
+  ::PostQuitMessage(exit_code);
+}
+
+bool WindowsLifecycleManager::WindowProc(HWND hwnd,
+                                         UINT msg,
+                                         WPARAM wpar,
+                                         LPARAM lpar,
+                                         LRESULT* result) {
+  switch (msg) {
+    case WM_CLOSE:
+      if (IsLastWindowOfProcess()) {
+        engine_->RequestApplicationQuit(ExitType::cancelable, 0);
+      }
+      return true;
+  }
+  return false;
+}
+
+class ThreadSnapshot {
+ public:
+  ThreadSnapshot() {
+    thread_snapshot_ = ::CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+  }
+  ~ThreadSnapshot() {
+    if (thread_snapshot_ != INVALID_HANDLE_VALUE) {
+      ::CloseHandle(thread_snapshot_);
+    }
+  }
+
+  std::optional<THREADENTRY32> GetFirstThread() {
+    if (thread_snapshot_ == INVALID_HANDLE_VALUE) {
+      FML_LOG(ERROR) << "Failed to get thread snapshot";
+      return std::nullopt;
+    }
+    THREADENTRY32 thread;
+    thread.dwSize = sizeof(thread);
+    if (!::Thread32First(thread_snapshot_, &thread)) {
+      DWORD error_num = ::GetLastError();
+      char msg[256];
+      ::FormatMessageA(
+          FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL,
+          error_num, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), msg, 256,
+          nullptr);
+      FML_LOG(ERROR) << "Failed to get thread(" << error_num << "): " << msg;
+      return std::nullopt;
+    }
+    return thread;
+  }
+
+  bool GetNextThread(THREADENTRY32& thread) {
+    if (thread_snapshot_ == INVALID_HANDLE_VALUE) {
+      return false;
+    }
+    return ::Thread32Next(thread_snapshot_, &thread);
+  }
+
+ private:
+  HANDLE thread_snapshot_;
+};
+
+static int64_t NumWindowsForThread(const THREADENTRY32& thread) {
+  int64_t num_windows = 0;
+  ::EnumThreadWindows(
+      thread.th32ThreadID,
+      [](HWND hwnd, LPARAM lparam) {
+        int64_t* windows_ptr = reinterpret_cast<int64_t*>(lparam);
+        if (::GetParent(hwnd) == nullptr) {
+          (*windows_ptr)++;
+        }
+        return *windows_ptr <= 1 ? TRUE : FALSE;
+      },
+      reinterpret_cast<LPARAM>(&num_windows));
+  return num_windows;
+}
+
+bool WindowsLifecycleManager::IsLastWindowOfProcess() {
+  DWORD pid = ::GetCurrentProcessId();
+  ThreadSnapshot thread_snapshot;
+  std::optional<THREADENTRY32> first_thread = thread_snapshot.GetFirstThread();
+  if (!first_thread.has_value()) {
+    FML_LOG(ERROR) << "No first thread found";
+    return true;
+  }
+
+  int num_windows = 0;
+  THREADENTRY32 thread = *first_thread;
+  do {
+    if (thread.th32OwnerProcessID == pid) {
+      num_windows += NumWindowsForThread(thread);
+      if (num_windows > 1) {
+        return false;
+      }
+    }
+  } while (thread_snapshot.GetNextThread(thread));
+
+  return num_windows <= 1;
+}
+
+}  // namespace flutter
diff --git a/shell/platform/windows/windows_lifecycle_manager.h b/shell/platform/windows/windows_lifecycle_manager.h
new file mode 100644
index 0000000..7f45277
--- /dev/null
+++ b/shell/platform/windows/windows_lifecycle_manager.h
@@ -0,0 +1,37 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_LIFECYCLE_MANAGER_H_
+#define FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_LIFECYCLE_MANAGER_H_
+
+#include <Windows.h>
+
+#include <cstdint>
+
+namespace flutter {
+
+class FlutterWindowsEngine;
+
+/// A manager for lifecycle events of the top-level window.
+///
+/// Currently handles the following events:
+/// WM_CLOSE
+class WindowsLifecycleManager {
+ public:
+  WindowsLifecycleManager(FlutterWindowsEngine* engine);
+  virtual ~WindowsLifecycleManager();
+
+  virtual void Quit(UINT exit_code) const;
+
+  bool WindowProc(HWND hwnd, UINT msg, WPARAM w, LPARAM l, LRESULT* result);
+
+ private:
+  bool IsLastWindowOfProcess();
+
+  FlutterWindowsEngine* engine_;
+};
+
+}  // namespace flutter
+
+#endif  // FLUTTER_SHELL_PLATFORM_WINDOWS_WINDOWS_LIFECYCLE_MANAGER_H_