[engine] reland: more consistently flush message loops tasks (#56815)

Changes the following shell callbacks to flush the dart event loop:

* OnPlatformViewSetViewportMetrics
* OnPlatformViewDispatchPointerDataPacket
* OnPlatformViewDispatchPlatformMessage
* OnPlatformViewSetSemanticsEnabled
* OnPlatformViewSetAccessibilityFeatures
Using a new TaskRunner API RunNowAndFlushMessages. If the task runner can run tasks on the current thread, this will immediately invoke a callback and then post an empty task to the event loop to ensure dart listeners fire.

Unlike https://github.com/flutter/engine/pull/56738, does not touch the vsync API - which looks like it depends on scheduling behavior today, at least for iOS.
diff --git a/fml/task_runner.cc b/fml/task_runner.cc
index 5f9dd01..8920a8f 100644
--- a/fml/task_runner.cc
+++ b/fml/task_runner.cc
@@ -52,6 +52,7 @@
                                                 loop_queue_id);
 }
 
+// static
 void TaskRunner::RunNowOrPostTask(const fml::RefPtr<fml::TaskRunner>& runner,
                                   const fml::closure& task) {
   FML_DCHECK(runner);
@@ -62,4 +63,20 @@
   }
 }
 
+// static
+void TaskRunner::RunNowAndFlushMessages(
+    const fml::RefPtr<fml::TaskRunner>& runner,
+    const fml::closure& task) {
+  FML_DCHECK(runner);
+  if (runner->RunsTasksOnCurrentThread()) {
+    task();
+    // Post an empty task to make the UI message loop run its task observers.
+    // The observers will execute any Dart microtasks queued by the platform
+    // message handler.
+    runner->PostTask([] {});
+  } else {
+    runner->PostTask(task);
+  }
+}
+
 }  // namespace fml
diff --git a/fml/task_runner.h b/fml/task_runner.h
index 885c1b3..6bc1c1a 100644
--- a/fml/task_runner.h
+++ b/fml/task_runner.h
@@ -62,6 +62,14 @@
   static void RunNowOrPostTask(const fml::RefPtr<fml::TaskRunner>& runner,
                                const fml::closure& task);
 
+  /// Like RunNowOrPostTask, except that if the task can be immediately
+  /// executed, an empty task will still be posted to the runner afterwards.
+  ///
+  /// This is used to ensure that messages posted to Dart from the platform
+  /// thread always flush the Dart event loop.
+  static void RunNowAndFlushMessages(const fml::RefPtr<fml::TaskRunner>& runner,
+                                     const fml::closure& task);
+
  protected:
   explicit TaskRunner(fml::RefPtr<MessageLoopImpl> loop);
 
diff --git a/shell/common/fixtures/shell_test.dart b/shell/common/fixtures/shell_test.dart
index 6072756..24dc227 100644
--- a/shell/common/fixtures/shell_test.dart
+++ b/shell/common/fixtures/shell_test.dart
@@ -640,3 +640,13 @@
     });
   };
 }
+
+@pragma('vm:entry-point')
+void testPointerActions() {
+  PlatformDispatcher.instance.onPointerDataPacket = (PointerDataPacket pointer) async {
+    await null;
+    Future<void>.value().then((_) {
+      notifyNative();
+    });
+  };
+}
diff --git a/shell/common/shell.cc b/shell/common/shell.cc
index c6078f7..ba28d8e 100644
--- a/shell/common/shell.cc
+++ b/shell/common/shell.cc
@@ -2,6 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "fml/task_runner.h"
 #define RAPIDJSON_HAS_STDSTRING 1
 #include "flutter/shell/common/shell.h"
 
@@ -1036,7 +1037,7 @@
         }
       });
 
-  fml::TaskRunner::RunNowOrPostTask(
+  fml::TaskRunner::RunNowAndFlushMessages(
       task_runners_.GetUITaskRunner(),
       [engine = engine_->GetWeakPtr(), view_id, metrics]() {
         if (engine) {
@@ -1075,24 +1076,16 @@
   }
 #endif  // FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
 
-  if (task_runners_.GetUITaskRunner()->RunsTasksOnCurrentThread()) {
-    engine_->DispatchPlatformMessage(std::move(message));
-
-    // Post an empty task to make the UI message loop run its task observers.
-    // The observers will execute any Dart microtasks queued by the platform
-    // message handler.
-    task_runners_.GetUITaskRunner()->PostTask([] {});
-  } else {
-    // The static leak checker gets confused by the use of fml::MakeCopyable.
-    // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
-    task_runners_.GetUITaskRunner()->PostTask(
-        fml::MakeCopyable([engine = engine_->GetWeakPtr(),
-                           message = std::move(message)]() mutable {
-          if (engine) {
-            engine->DispatchPlatformMessage(std::move(message));
-          }
-        }));
-  }
+  // The static leak checker gets confused by the use of fml::MakeCopyable.
+  // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
+  fml::TaskRunner::RunNowAndFlushMessages(
+      task_runners_.GetUITaskRunner(),
+      fml::MakeCopyable([engine = engine_->GetWeakPtr(),
+                         message = std::move(message)]() mutable {
+        if (engine) {
+          engine->DispatchPlatformMessage(std::move(message));
+        }
+      }));
 }
 
 // |PlatformView::Delegate|
@@ -1104,7 +1097,7 @@
   TRACE_FLOW_BEGIN("flutter", "PointerEvent", next_pointer_flow_id_);
   FML_DCHECK(is_set_up_);
   FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
-  fml::TaskRunner::RunNowOrPostTask(
+  fml::TaskRunner::RunNowAndFlushMessages(
       task_runners_.GetUITaskRunner(),
       fml::MakeCopyable([engine = weak_engine_, packet = std::move(packet),
                          flow_id = next_pointer_flow_id_]() mutable {
@@ -1122,7 +1115,8 @@
   FML_DCHECK(is_set_up_);
   FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
 
-  task_runners_.GetUITaskRunner()->PostTask(
+  fml::TaskRunner::RunNowAndFlushMessages(
+      task_runners_.GetUITaskRunner(),
       fml::MakeCopyable([engine = engine_->GetWeakPtr(), node_id, action,
                          args = std::move(args)]() mutable {
         if (engine) {
@@ -1136,12 +1130,13 @@
   FML_DCHECK(is_set_up_);
   FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
 
-  fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(),
-                                    [engine = engine_->GetWeakPtr(), enabled] {
-                                      if (engine) {
-                                        engine->SetSemanticsEnabled(enabled);
-                                      }
-                                    });
+  fml::TaskRunner::RunNowAndFlushMessages(
+      task_runners_.GetUITaskRunner(),
+      [engine = engine_->GetWeakPtr(), enabled] {
+        if (engine) {
+          engine->SetSemanticsEnabled(enabled);
+        }
+      });
 }
 
 // |PlatformView::Delegate|
@@ -1149,12 +1144,12 @@
   FML_DCHECK(is_set_up_);
   FML_DCHECK(task_runners_.GetPlatformTaskRunner()->RunsTasksOnCurrentThread());
 
-  fml::TaskRunner::RunNowOrPostTask(task_runners_.GetUITaskRunner(),
-                                    [engine = engine_->GetWeakPtr(), flags] {
-                                      if (engine) {
-                                        engine->SetAccessibilityFeatures(flags);
-                                      }
-                                    });
+  fml::TaskRunner::RunNowAndFlushMessages(
+      task_runners_.GetUITaskRunner(), [engine = engine_->GetWeakPtr(), flags] {
+        if (engine) {
+          engine->SetAccessibilityFeatures(flags);
+        }
+      });
 }
 
 // |PlatformView::Delegate|
diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc
index ba56e9f..8c6a0c2 100644
--- a/shell/common/shell_unittests.cc
+++ b/shell/common/shell_unittests.cc
@@ -4312,7 +4312,8 @@
   ASSERT_FALSE(DartVMRef::IsInstanceRunning());
 }
 
-TEST_F(ShellTest, SemanticsActionsPostTask) {
+// Verifies a semantics Action will flush the dart event loop.
+TEST_F(ShellTest, SemanticsActionsFlushMessageLoop) {
   Settings settings = CreateSettingsForFixture();
   ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
                          ThreadHost::Type::kPlatform);
@@ -4327,13 +4328,6 @@
   configuration.SetEntrypoint("testSemanticsActions");
 
   RunEngine(shell.get(), std::move(configuration));
-
-  task_runners.GetPlatformTaskRunner()->PostTask([&] {
-    SendSemanticsAction(shell.get(), 0, SemanticsAction::kTap,
-                        fml::MallocMapping(nullptr, 0));
-  });
-
-  // Fulfill native function for the second Shell's entrypoint.
   fml::CountDownLatch latch(1);
   AddNativeCallback(
       // The Dart native function names aren't very consistent but this is
@@ -4341,6 +4335,42 @@
       // fixture.
       "NotifyNative",
       CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));
+
+  task_runners.GetPlatformTaskRunner()->PostTask([&] {
+    SendSemanticsAction(shell.get(), 0, SemanticsAction::kTap,
+                        fml::MallocMapping(nullptr, 0));
+  });
+  latch.Wait();
+
+  DestroyShell(std::move(shell), task_runners);
+  ASSERT_FALSE(DartVMRef::IsInstanceRunning());
+}
+
+// Verifies a pointer event will flush the dart event loop.
+TEST_F(ShellTest, PointerPacketFlushMessageLoop) {
+  Settings settings = CreateSettingsForFixture();
+  ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
+                         ThreadHost::Type::kPlatform);
+  auto task_runner = thread_host.platform_thread->GetTaskRunner();
+  TaskRunners task_runners("test", task_runner, task_runner, task_runner,
+                           task_runner);
+
+  EXPECT_EQ(task_runners.GetPlatformTaskRunner(),
+            task_runners.GetUITaskRunner());
+  auto shell = CreateShell(settings, task_runners);
+  auto configuration = RunConfiguration::InferFromSettings(settings);
+  configuration.SetEntrypoint("testPointerActions");
+
+  RunEngine(shell.get(), std::move(configuration));
+  fml::CountDownLatch latch(1);
+  AddNativeCallback(
+      // The Dart native function names aren't very consistent but this is
+      // just the native function name of the second vm entrypoint in the
+      // fixture.
+      "NotifyNative",
+      CREATE_NATIVE_ENTRY([&](auto args) { latch.CountDown(); }));
+
+  DispatchFakePointerData(shell.get(), 23);
   latch.Wait();
 
   DestroyShell(std::move(shell), task_runners);
diff --git a/shell/common/vsync_waiter.cc b/shell/common/vsync_waiter.cc
index 80074fa..fdc8c64 100644
--- a/shell/common/vsync_waiter.cc
+++ b/shell/common/vsync_waiter.cc
@@ -127,7 +127,6 @@
 
     fml::TaskQueueId ui_task_queue_id =
         task_runners_.GetUITaskRunner()->GetTaskQueueId();
-
     task_runners_.GetUITaskRunner()->PostTask(
         [ui_task_queue_id, callback, flow_identifier, frame_start_time,
          frame_target_time, pause_secondary_tasks]() {