Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions example/lib/device_frame_probe.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
// for JANK_MS per frame around each switch. With FIXED_DPR=true switches change
// only the size, which never wedged.
//
// cef_host logs `resize: no paint at WxH ... kick N` when a resize waits a
// second without a paint at its size. A healthy run has none; if they're back,
// cef_host has stopped asking for a fresh capture while a resize waits.
//
// Run: FLUTTER_CEF_HOST=<.../cef_host.app/Contents/MacOS/cef_host> \
// flutter run -d macos -t lib/device_frame_probe.dart
// [--dart-define=ROUNDS=30 --dart-define=LIMIT_MS=4000
Expand Down
7 changes: 7 additions & 0 deletions packages/flutter_cef_macos/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
## Unreleased

* Fix: on macOS, a resize could wait a second or more for its first paint at
the new size (up to about 4 s), most often mid-way through an animated device
switch. CEF paints by capturing the view, and after a big resize nothing asked
it to capture again once the renderer's new-size frame was in. `cef_host` now
asks for a fresh capture every 100 ms while a resize waits for its paint.
`device_frame` no longer needs the stall fallback (it did in 1 to 12 of 30
switches). The prebuilt must be republished.
* Fix: a view could stop painting for good after a resize that changed the
render scale, typically when switching the device frame in a preview of a page
slow to relayout. Hide/show and reload didn't bring it back. `cef_host` now
Expand Down
25 changes: 21 additions & 4 deletions packages/flutter_cef_macos/native/cef_host/browser_ops.mm
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,16 @@ void DoSetVisible(const std::shared_ptr<Slot>& slot, bool visible) {
// id first. It only gets to do that when no hold is on, so CEF sees geometry
// through cef_width/cef_height/cef_dpr, and those change only when no resize is
// in flight. Changes the plugin asks for meanwhile collapse into the latest one.
//
// The paint that ends a resize needs a capture after the resize lands. On macOS
// CEF paints by capturing the root frame at the size of the renderer's last
// activated frame. When the renderer's frame at the new size activates, the
// capture size changes, but a page with nothing else changing gives the
// capturer no damage, and it drops refresh requests while it still sees the
// content as animating. The one Invalidate at the start of a resize comes before
// all of that, so ask again every kResizeRefreshMs until the paint arrives.
namespace {
constexpr int kResizeRefreshMs = 100;
constexpr int kResizePaintWaitMs = 1000;
constexpr int kResizePaintMaxWaitMs = 4000;
constexpr int kResizeMaxKicks = 6;
Expand Down Expand Up @@ -437,6 +446,8 @@ void ApplyGeometry(const std::shared_ptr<Slot>& slot) {
host->SendExternalBeginFrame();
slot->resize_in_flight = true;
slot->resize_wait_since = std::chrono::steady_clock::now();
slot->resize_refresh_at =
slot->resize_wait_since + std::chrono::milliseconds(kResizeRefreshMs);
slot->resize_wait_ms = kResizePaintWaitMs;
slot->resize_kicks = 0;
}
Expand All @@ -455,13 +466,19 @@ void NoteViewPaint(const std::shared_ptr<Slot>& slot, int pixel_w, int pixel_h)
CefPostTask(TID_UI, base::BindOnce(&ApplyGeometry, slot));
}

// The fallback if a resize still doesn't paint. With the hold on and the
// geometry CEF sees unchanged, NotifyScreenInfoChanged only swaps in a fresh
// surface id, which gets the renderer to paint again. Waits back off (1, 2,
// 4 s) so a page that is merely slow to lay out isn't kicked over and over.
// Runs every begin frame while a resize is in flight: asks for a fresh capture
// every kResizeRefreshMs (see above), and kicks if the paint still doesn't come.
// With the hold on and the geometry CEF sees unchanged, NotifyScreenInfoChanged
// only swaps in a fresh surface id, which gets the renderer to paint again.
// Waits back off (1, 2, 4 s) so a page that is merely slow to lay out isn't
// kicked over and over.
void CheckResizeStall(const std::shared_ptr<Slot>& slot) {
if (!slot->resize_in_flight || !slot->visible || !slot->browser) return;
const auto now = std::chrono::steady_clock::now();
if (now >= slot->resize_refresh_at) {
slot->browser->GetHost()->Invalidate(PET_VIEW);
slot->resize_refresh_at = now + std::chrono::milliseconds(kResizeRefreshMs);
}
if (now - slot->resize_wait_since <
std::chrono::milliseconds(slot->resize_wait_ms))
return;
Expand Down
6 changes: 4 additions & 2 deletions packages/flutter_cef_macos/native/cef_host/host_state.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,12 @@ struct Slot {
// gating. UI-thread only, like `visible`.
int pump_interval_ms = 16;
// A resize handed to CEF and not yet painted (see ApplyGeometry in
// browser_ops.mm): since when, the pixel size CEF will paint, how long the
// current wait may run before a kick, and the kicks so far. UI-thread only.
// browser_ops.mm): since when, when to next ask for a fresh capture, the
// pixel size CEF will paint, how long the current wait may run before a kick,
// and the kicks so far. UI-thread only.
bool resize_in_flight = false;
std::chrono::steady_clock::time_point resize_wait_since{};
std::chrono::steady_clock::time_point resize_refresh_at{};
int resize_want_w = 0;
int resize_want_h = 0;
int resize_wait_ms = 0;
Expand Down
Loading