VSyncWaiter on Fuchsia will defer firing until frame start time (#29287) (#30250)

* VSyncWaiter on Fuchsia will defer firing until frame start time

The common vsync waiter implementation expects this invariant to be
held.

* Add a test for vsync_waiter

Co-authored-by: Kaushik Iska <iska.kaushik@gmail.com>
diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter
index bec85aa..bf78589 100755
--- a/ci/licenses_golden/licenses_flutter
+++ b/ci/licenses_golden/licenses_flutter
@@ -1495,6 +1495,7 @@
 FILE: ../../../flutter/shell/platform/fuchsia/flutter/unique_fdio_ns.h
 FILE: ../../../flutter/shell/platform/fuchsia/flutter/vsync_waiter.cc
 FILE: ../../../flutter/shell/platform/fuchsia/flutter/vsync_waiter.h
+FILE: ../../../flutter/shell/platform/fuchsia/flutter/vsync_waiter_unittest.cc
 FILE: ../../../flutter/shell/platform/fuchsia/flutter/vulkan_surface.cc
 FILE: ../../../flutter/shell/platform/fuchsia/flutter/vulkan_surface.h
 FILE: ../../../flutter/shell/platform/fuchsia/flutter/vulkan_surface_pool.cc
diff --git a/shell/platform/fuchsia/flutter/BUILD.gn b/shell/platform/fuchsia/flutter/BUILD.gn
index eb8cf30..e35739f 100644
--- a/shell/platform/fuchsia/flutter/BUILD.gn
+++ b/shell/platform/fuchsia/flutter/BUILD.gn
@@ -482,6 +482,7 @@
     "tests/gfx_session_connection_unittests.cc",
     "tests/pointer_event_utility.cc",
     "tests/pointer_event_utility.h",
+    "vsync_waiter_unittest.cc",
   ]
 
   # This is needed for //third_party/googletest for linking zircon symbols.
diff --git a/shell/platform/fuchsia/flutter/vsync_waiter.cc b/shell/platform/fuchsia/flutter/vsync_waiter.cc
index c66e126..aa7d3c2 100644
--- a/shell/platform/fuchsia/flutter/vsync_waiter.cc
+++ b/shell/platform/fuchsia/flutter/vsync_waiter.cc
@@ -30,10 +30,17 @@
       weak_factory_(this) {
   fire_callback_callback_ = [this](fml::TimePoint frame_start,
                                    fml::TimePoint frame_end) {
-    // Note: It is VERY important to set |pause_secondary_tasks| to false, else
-    // Animator will almost immediately crash on Fuchsia.
-    // FML_LOG(INFO) << "CRASH:: VsyncWaiter about to FireCallback";
-    FireCallback(frame_start, frame_end, /*pause_secondary_tasks=*/false);
+    task_runners_.GetUITaskRunner()->PostTaskForTime(
+        [frame_start, frame_end, weak_this = weak_ui_]() {
+          if (weak_this) {
+            // Note: It is VERY important to set |pause_secondary_tasks| to
+            // false, else Animator will almost immediately crash on Fuchsia.
+            // FML_LOG(INFO) << "CRASH:: VsyncWaiter about to FireCallback";
+            weak_this->FireCallback(frame_start, frame_end,
+                                    /*pause_secondary_tasks*/ false);
+          }
+        },
+        frame_start);
   };
 
   // Generate a WeakPtrFactory for use with the UI thread. This does not need
@@ -41,8 +48,9 @@
   // thread so we have ordering guarantees (see ::AwaitVSync())
   fml::TaskRunner::RunNowOrPostTask(
       task_runners_.GetUITaskRunner(), fml::MakeCopyable([this]() mutable {
-        this->weak_factory_ui_ =
+        weak_factory_ui_ =
             std::make_unique<fml::WeakPtrFactory<VsyncWaiter>>(this);
+        weak_ui_ = weak_factory_ui_->GetWeakPtr();
       }));
 }
 
diff --git a/shell/platform/fuchsia/flutter/vsync_waiter.h b/shell/platform/fuchsia/flutter/vsync_waiter.h
index 1e92064..ce46b90 100644
--- a/shell/platform/fuchsia/flutter/vsync_waiter.h
+++ b/shell/platform/fuchsia/flutter/vsync_waiter.h
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_VSYNC_WAITER_H_
-#define FLUTTER_SHELL_PLATFORM_FUCHSIA_VSYNC_WAITER_H_
+#ifndef FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_VSYNC_WAITER_H_
+#define FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_VSYNC_WAITER_H_
 
 #include <lib/async/cpp/wait.h>
 
@@ -46,6 +46,7 @@
   AwaitVsyncForSecondaryCallbackCallback
       await_vsync_for_secondary_callback_callback_;
 
+  fml::WeakPtr<VsyncWaiter> weak_ui_;
   std::unique_ptr<fml::WeakPtrFactory<VsyncWaiter>> weak_factory_ui_;
   fml::WeakPtrFactory<VsyncWaiter> weak_factory_;
 
@@ -54,4 +55,4 @@
 
 }  // namespace flutter_runner
 
-#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_VSYNC_WAITER_H_
+#endif  // FLUTTER_SHELL_PLATFORM_FUCHSIA_FLUTTER_VSYNC_WAITER_H_
diff --git a/shell/platform/fuchsia/flutter/vsync_waiter_unittest.cc b/shell/platform/fuchsia/flutter/vsync_waiter_unittest.cc
new file mode 100644
index 0000000..f01a2a0
--- /dev/null
+++ b/shell/platform/fuchsia/flutter/vsync_waiter_unittest.cc
@@ -0,0 +1,63 @@
+// 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 <gtest/gtest.h>
+
+#include <string>
+
+#include "flutter/fml/task_runner.h"
+#include "flutter/shell/common/thread_host.h"
+#include "fml/make_copyable.h"
+#include "fml/message_loop.h"
+#include "fml/synchronization/waitable_event.h"
+#include "fml/time/time_delta.h"
+#include "fml/time/time_point.h"
+#include "vsync_waiter.h"
+
+namespace flutter_runner {
+
+TEST(VSyncWaiterFuchsia, FrameScheduledForStartTime) {
+  using flutter::ThreadHost;
+  std::string prefix = "vsync_waiter_test";
+
+  fml::MessageLoop::EnsureInitializedForCurrentThread();
+  auto platform_task_runner = fml::MessageLoop::GetCurrent().GetTaskRunner();
+
+  ThreadHost thread_host =
+      ThreadHost(prefix, flutter::ThreadHost::Type::RASTER |
+                             flutter::ThreadHost::Type::UI |
+                             flutter::ThreadHost::Type::IO);
+  const flutter::TaskRunners task_runners(
+      prefix,                                      // Dart thread labels
+      platform_task_runner,                        // platform
+      thread_host.raster_thread->GetTaskRunner(),  // raster
+      thread_host.ui_thread->GetTaskRunner(),      // ui
+      thread_host.io_thread->GetTaskRunner()       // io
+  );
+
+  // await vsync will invoke the callback right away, but vsync waiter will post
+  // the task for frame_start time.
+  VsyncWaiter vsync_waiter(
+      [](FireCallbackCallback callback) {
+        const auto now = fml::TimePoint::Now();
+        const auto frame_start = now + fml::TimeDelta::FromMilliseconds(20);
+        const auto frame_end = now + fml::TimeDelta::FromMilliseconds(36);
+        callback(frame_start, frame_end);
+      },
+      /*secondary callback*/ nullptr, task_runners);
+
+  fml::AutoResetWaitableEvent latch;
+  task_runners.GetUITaskRunner()->PostTask([&]() {
+    vsync_waiter.AsyncWaitForVsync(
+        [&](std::unique_ptr<flutter::FrameTimingsRecorder> recorder) {
+          const auto now = fml::TimePoint::Now();
+          EXPECT_GT(now, recorder->GetVsyncStartTime());
+          latch.Signal();
+        });
+  });
+
+  latch.Wait();
+}
+
+}  // namespace flutter_runner