[Windows] Per-resize-step Moved/Resized event construction + delivery on the window thread make frameless edge-drag visibly lag behind the frame (content "trails" the border)

Đang mở
#30 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

Đánh giá

Độ khó
4/5
Thời gian dự kiến
3-5 ngày
Mức phù hợp với người mới
38/100
Loại issue
Lỗi
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
dart, flutter
Lĩnh vực
desktop, performance

Hướng nghiên cứu

Start with window_manager_windows.cpp at WindowGeometryProc, OnWindowChanged, and OnWindowEvent; trace how WM_WINDOWPOSCHANGED builds and delivers moved and resized events during sizing. Then inspect window_message_dispatcher.cpp for the per-message dispatch cost. Use the described left-edge SetWindowPos probe to compare event counts and per-step time; done means reducing the resize lag while preserving correct geometry events.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

[Windows] 逐步构建/投递 Moved/Resized 事件拖慢窗口线程,导致无边框边缘拖拽"内容追边框" / Per-resize-step Moved/Resized event construction + delivery on the window thread make frameless edge-drag visibly lag behind the frame (content "trails" the border)

本 issue 提供中英双语:中文在前,英文原文在后(--- 分隔)。
Bilingual issue: Chinese first, then the original English text below the separator.


中文版

概述

在 Windows 上,注册了 nativeapi 窗口监听器时,交互式拖拽窗口边缘会出现明显的内容追边框(橡皮筋效应):边框跟着鼠标走,而 Flutter 内容晚几帧才到达。在 sizing 模态循环期间,矩形每变化一步都会产生一条 WM_WINDOWPOSCHANGED;每一步里,nativeapi 的几何子类(geometry subclass)对比快照、查询矩形和 DPI(GetSize())、构造一个 WindowMoved/Resized 事件,并通过 NativeCallable + delivery_is_active/delivery_complete 握手投递给 Dart——这一切都发生在窗口线程上,每秒执行数百次。Dart 侧的帧耗时指标看起来健康(我们的 Debug 采集中 rasterP50 ≈ 1.4 ms,而帧管线 span 的 P50 约为 13–16 ms——这里看的是形态,不是绝对值),说明瓶颈在平台线程的调度延迟,而不是渲染负载。

在 Release 保真度下做 A/B 开关(同一二进制、单变量探针,见下文)显示:回归的绝大部分可归因于逐步事件路径,而仅包一层 Window wrapper 时也有一小块可测量的残留。我们在源码和实测中都确认:minimumSize / titleBarStyle 这两个 setter 的处理函数会在应用窗口上安装通用的 WindowMessageDispatcher(整体替换 GWLP_WNDPROC),即使监听器数量为零——因此每条消息都要走一遍 mutex/vector/sort 路径,普通无边框应用也会被收税(见根因第 4 条和量化小节)。

机器相关但在我们的笔记本上保持一致:脚本化的左边缘负载(1500 步一像素的 SetWindowPos)产生了恰好 1500 个 WindowMovedEvent + 1500 个 WindowResizedEvent——每一步 WM_WINDOWPOSCHANGED 构造并投递两个事件,零合并,并使窗口线程每步的 wall time 在裸 Flutter 窗口约 10–13 ms 的基础上增加约 1.3–1.8 ms(约 10–15%)。方法与细节见下文"量化复现"。

环境

  • nativeapi / cnativeapi / nativeapi_flutter:0.5.10(pub.dev,截至 2026-11-10 为最新发布版)
  • nativeapi-core main HEAD 上的 window_manager_windows.cpp 与 0.5.10 pub 包逐字节一致(我们做过 diff)——下文所有内容也是当前上游行为;两个版本的行号通用。
  • Flutter 3.47.6 stable(Windows x64),Dart 3.13
  • Windows 11(Build 26300),单 GPU(RTX 4060 Laptop,直连面板)
  • 应用:无边框窗口(titleBarStyle = hidden),普通 Flutter UI

我们如何隔离问题(同一 Release 二进制上的 A/B)

我们构建了一个应用,并在运行时逐项开关 nativeapi 的使用:

配置 边缘拖拽手感
完整 nativeapi 使用(注册监听器、隐藏标题栏、设置最小尺寸) 明显追边框
跳过 hideTitleBar()(绕过 frame/命中测试链,监听器仍在) 仍然追边框
不设置 minimumSize 仍然追边框
包装了窗口,但从未调用 addListener() 接近正常,有轻微残留
完全不创建 nativeapi Window wrapper(纯 Win32 + Flutter) 流畅
旧技术栈(window_manager 0.5.x + bitsdojo,无 nativeapi) 流畅

因此,成本的大部分跟随"事件注册与投递";较小的残留跟随 wrapper 自身的逐消息链(几何子类 + 标题栏/命中测试处理函数)——凡是 wrapper 存在的行都有这部分。

量化复现(独立探针应用,同一二进制,环境变量切换档位)

由于无法可复现地脚本化真实鼠标拖拽,我们驱动拖拽所使用的同一条消息路径:一个独立进程对窗口左边缘发出 SetWindowPos 一像素振荡(x+1/w−1,然后 x−1/w+1)。每一步都会穿过 sizing 消息路径(WM_WINDOWPOSCHANGED),且每次调用都会阻塞到窗口线程处理完毕——因此每步 wall time 就是窗口线程的工作时间。探针应用是一个普通的 flutter create Windows 应用,通过 Window.createWithNativeWindow 绑定 runner 的 HWND;各档位由运行时环境变量切换:

档位 监听器 titleBarStyle=hidden minimumSize 含义
B — — — 裸 Flutter 窗口(基线)
A ✔ — — 仅几何事件链
C ✔ ✔ ✔ 我们修复前的应用形态
D — ✔ ✔ 我们修复后的应用形态(事件已退订)

结果(Release,RTX 4060 笔记本,每档每轮 1500 步,轮次交错;热漂移使后段轮次整体变慢,因此差值按约 ±0.5 ms 计):

  1. 每步事件数——零合并。 A/C 档记录到 1500 步左边缘拖拽产生 moved = 1500 和 resized = 1500:每一步 WM_WINDOWPOSCHANGED 恰好构造并投递两个事件到 Dart(左/右边缘拖拽同时改变位置和尺寸,因此 OnWindowChanged 的两个分支都会触发)。负载期间持续约 165 事件/秒;某一轮里 3000 个中有 8 个被跳过,说明投递既非无损也无合并。这与代码一致:moved(:834-835)和 resized(:837-839)是独立的 if,且 OnWindowEvent(:892-901)对每个事件重新查询注册表 + GetSize()/GetPosition()。
  2. 每步窗口线程税。 同轮配对差值:A−B ≈ +1.3 … +1.8 ms/步,基线 B ≈ 9.6 … 13.8 ms/步(约 10–15%);C−B ≈ +1.7 … +2.4。事件链本身占了大头,与主观阶梯表一致。
  3. 附带发现(行为类,可能是有意为之): 对一个已经可见的窗口设置 titleBarStyle = TitleBarStyle.hidden 会清除 WS_VISIBLE——窗口保留几何,但 IsWindowVisible 返回 false,直到我们再次调用 show()。我们的应用只是因为遵循"先隐藏 → 改样式 → 再 show"的顺序才躲过了这个问题。

探针应用 + 驱动脚本总共约 150 行;如有需要我们乐意附上或贴出。

根因指针(cnativeapi-0.5.10)

  1. 几何事件在每一步拖拽中都在窗口线程上构建并投递——边缘拖拽每步两个。
    window_manager_windows.cpp:203-217——WindowGeometryProc(由 WatchWindowGeometry 通过 SetWindowSubclass 安装,:222-224)拦截 WM_WINDOWPOSCHANGED,先调用原过程,然后 OnWindowChanged(:778-841)对比快照(:769-775,GetWindowRect+IsIconic+IsZoomed),并独立触发 moved(:834-835)和 resized(:837-839)——边缘拖拽同时改变 x 和宽度,所以每步两者都触发(实测:1500 步恰好产生 1500+1500 个事件,零合并)。
    每个事件还额外通过 C++ getter 重新查询几何(OnWindowEvent,window_manager_windows.cpp:862-906 → 注册表查找 + window->GetSize()/GetPosition() = 矩形 + DPI 计算),构造一个 WindowMoved/WindowResized,并通过带 native_event_delivery_is_active / native_event_delivery_complete 握手的 NativeCallable 投递(bindings/dart/nativeapi/lib/src/callbacks.dart,deliverEvent;DispatchWindowEvent :1199-1202)。在边缘拖拽期间,WM_WINDOWPOSCHANGED 以鼠标采样率到达——每秒数百次这样的构建+跨语言投递,而这条线程同时还必须让 sizing 模态循环和 WM_SIZE → MoveWindow(child) → engine present 链继续运行。
  2. 你们自己的代码就记录着这种失败模式。
    window_manager_windows.cpp:143-148 解释了为什么几何上报采用子类(subclass)而不是 EVENT_OBJECT_LOCATIONCHANGE WinEvent 钩子:跨上下文(out-of-context)的 WinEvent"会在线程下一次读取消息时被投递——正好在 Flutter 的 resize 中间,而 Flutter resize 在等待新尺寸的一帧时会泵消息……而从那里被调用的监听器(Dart 监听器是同步的)让每一个 resize 步骤都迟到了。"子类把投递点移到了一个确定的位置;它并没有减少每步的工作本身。
  3. 遮挡(occlusion)有合并,但 Moved/Resized 没有。
    window_manager_windows.cpp:511,546-547——遮挡路径会启动一个 100 ms 定时器,理由是"这些事件是一阵一阵来的(拖拽会让窗口一秒动几十次)"。WindowMoved/WindowResized 没有等价的节流:边缘拖拽实测每一步 WM_WINDOWPOSCHANGED 恰好两个事件。
  4. 已确认:通用 dispatcher 会被安装到普通应用窗口上——由标准 setter 安装,即使监听器数量为零。
    WindowMessageDispatcher::RegisterHandler(hwnd, …)(window_message_dispatcher.cpp:34-52)调用 InstallHook(hwnd)(:163-186),也就是 SetWindowLongPtr(GWLP_WNDPROC, DispatchWindowProc) 替换——而调用者是日常功能:最小/最大尺寸处理函数(window_windows.cpp:1560-1568,WM_GETMINMAXINFO)、always-on-bottom(:1659-1667)、标题栏/命中测试处理函数(:~1700)。因此仅 minimumSize / titleBarStyle 就让应用窗口的每条消息都走一遍逐消息的 mutex 快照 + 两次哈希查找 + std::vector 分配 + std::sort 路径(window_message_dispatcher.cpp:96-161),包括拖拽期间的高频非客户区消息(WM_NCHITTEST、WM_MOUSEMOVE、WM_SIZING)。我们的无监听器 D 档相比基线仍多花约 +1–2 ms/步,这是在 item 1 事件链税(A 档)之外的部分;item 1 占主导,但两者都需要修才能真正消除差距。

最小复现

一个 Flutter Windows 应用:

final window = na.WindowManager.instance.getCurrent()!; // 或包装 runner 的 HWND
final id = window.addListener((e) { /* no-op */ });     // 保持监听器注册
// 无边框:
window.titleBarStyle = na.TitleBarStyle.hidden;

然后以中等、稳定的速度水平拖拽窗口边缘,再与删掉 addListener 一行(或在拖拽前 removeListener)的情况对比。差异可以通过边框与内容的边界很容易看出来;当窗口内是静态高对比布局时最明显(任何内容滞后都会一目了然)。

如果你想要无需人工拖拽的量化采集:上文"量化复现"小节脚本化了同一条消息路径(从第二进程发出 SetWindowPos 左边缘振荡)并统计产生的 Dart 侧事件;我们还建议用 QueryPerformanceCounter 对 WindowGeometryProc 中 WM_WINDOWPOSCHANGED 的处理(入口到返回)以及 Dart 投递握手计时,在一个 10 s 负载上做直方图,对比注册监听器与不注册。

修复建议(任选其一或更好的想法)

  1. 合并/节流 Moved/Resized 投递——你们对遮挡事件已经这么做了:在突发事件上启动定时器,并始终在 WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE 会话边沿发出最新几何。大多数应用需要的是会话边沿 + 节流后的流,而不是每条 WM_WINDOWPOSCHANGED 一个事件。
  2. 省掉每个事件额外的 getter 往返——快照里已经持有新的 WINDOWPOS;尺寸/位置可以直接从它推导,而不是重新查询矩形 + DPI。
  3. Dispatcher 快路径(如果它被安装到应用窗口上——见开放问题 4):提供消息类别白名单或 anyHandlerCares 位图,让不关心的消息直接到达原过程,绕过逐消息的 mutex/vector/sort;考虑用 SetWindowSubclass 取代 GWLP_WNDPROC 替换。
  4. 记录几何订阅的逐窗口成本,让宿主应用能决定自己注册什么。

关联

  • nativeapi-core #78("Verify: events, input and icon reports from …")——这是一份 Windows 事件路径报告,带有复现结构,正好是该 issue 所要求的形态。
  • 同一用户的早期报告:libnativeapi/nativeapi #18(caption 拖拽区域在 SIZEMOVE 模态循环中冻结;已由 0.5.3 的"事件在拥有窗口的线程上投递"修复)——本报告针对的正是那次修复所产生的投递路径的每步成本。
  • 历史症状亲属:libnativeapi/nativeapi #17("从 0.2.5 起 resize 落后于指针")——当前 SetWindowSubclass 设计正源自那次修复(见根因第 2 条引用的代码注释);本 issue 报告的残留正是那笔交易的另一半。
  • 构建探针时还遇到(独立问题,你们的未发布树中已修复——0.5.10 仍没有):事件结构体变成 tree-shake 不可达时,AOT gen_snapshot 报"Class with illegal cid: native_window_event_t"——dart-lang/sdk#50540 workaround,CHANGELOG Unreleased / 你们的 #26,感谢。
  • 状态核查(2026-11-10):0.5.10 是最新发布版;nativeapi-core main HEAD 仍包含上述确切行为(文件逐字节一致),两个 CHANGELOG 的 Unreleased 中都没有相关条目。

English original

Summary

On Windows, with nativeapi window listeners registered, an interactive edge-resize shows
clear content trailing behind the window frame (rubber-banding): the frame follows the
mouse while the Flutter content arrives frames late. During the sizing modal loop, every
rectangle step produces a WM_WINDOWPOSCHANGED; per step, nativeapi's geometry subclass
diffs a snapshot, queries rect + DPI (GetSize()), constructs a WindowMoved/Resized
event and delivers it to Dart through the NativeCallable +
delivery_is_active/delivery_complete handshake — all on the window thread, hundreds of
times per second. Dart-side frame timings stay healthy (in our Debug captures
rasterP50 ≈ 1.4 ms while frame pipeline span P50 ≈ 13–16 ms — shape, not absolutes),
pointing at platform-thread scheduling latency rather than rendering load.

A/B toggles at Release fidelity (same binary, single-variable probes below) attribute the
bulk of the regression to the per-step event path, with a measurable residual while merely
wrapped. We confirmed in code and in measurement that minimumSize / titleBarStyle
setter handlers install the generic WindowMessageDispatcher (full GWLP_WNDPROC swap)
on the app window even with zero listeners — so the per-message mutex/vector/sort path
taxes every message of a plain frameless app too (see root-cause item 4 and the
quantified section).

Machine-specific but consistent on our laptop: a scripted left-edge load of 1500 one-pixel
SetWindowPos steps produced exactly 1500 WindowMovedEvents + 1500
WindowResizedEvents — two constructed-and-delivered events per WM_WINDOWPOSCHANGED,
zero coalescing
, and raised per-step window-thread wall time by ~1.3–1.8 ms on top of a
~10–13 ms bare-Flutter-window step (~10–15 %). Details and method in "Quantified
reproduction" below.

Environment

  • nativeapi / cnativeapi / nativeapi_flutter: 0.5.10 (pub.dev; latest published as of
    2026-11-10
    )
  • window_manager_windows.cpp on nativeapi-core main HEAD is byte-for-byte identical
    to the 0.5.10 pub package (we diffed it) — everything below is also current-upstream;
    line numbers are shared between the two.
  • Flutter 3.47.6 stable (Windows x64), Dart 3.13
  • Windows 11 (Build 26300), single GPU (RTX 4060 Laptop, direct panel)
  • App: frameless window (titleBarStyle = hidden), plain Flutter UI

How we isolated it (A/B on the same Release binary)

We built one app and toggled individual parts of our nativeapi usage at runtime:

Configuration Edge-drag feel
Full nativeapi usage (listener registered, hidden title bar, min size) clearly trails
hideTitleBar() skipped (frame/hit-test chain bypassed, listener still on) still trails
minimumSize not set still trails
Window wrapped, but addListener() never called nearly fine, faint residual
nativeapi Window wrapper not created at all (plain Win32 + Flutter) smooth
Previous stack (window_manager 0.5.x + bitsdojo, no nativeapi) smooth

So the bulk of the cost tracks event registration and delivery; a smaller residual tracks the
wrapper's own per-message chains (geometry subclass + title-bar/hit-test handlers), present in
every row where the wrapper existed.

Quantified reproduction (standalone probe app, same binary, env-toggled arms)

Because we cannot script a real mouse-drag reproducibly, we drive the same message path a
drag uses: a separate process issues SetWindowPos one-pixel oscillations on the window's
left edge (x+1/w−1 then x−1/w+1). Every step crosses the sizing message path
(WM_WINDOWPOSCHANGED) and each call blocks until the window thread has finished handling
it — so wall-time-per-step is window-thread work time. The probe app is a plain
flutter create Windows app that binds its runner HWND via
Window.createWithNativeWindow; arms are runtime env toggles:

Arm Listener titleBarStyle=hidden minimumSize meaning
B — — — bare Flutter window (baseline)
A ✔ — — geometry event chain only
C ✔ ✔ ✔ our pre-fix app shape
D — ✔ ✔ our post-fix app shape (events unsubscribed)

Results (Release, RTX 4060 laptop, 1500 steps/arm/round, rounds interleaved; thermal drift
made late rounds uniformly slower, so treat deltas as ~±0.5 ms):

  1. Event count per step — zero coalescing. Arm A/C runs recorded
    moved = 1500 and resized = 1500 for 1500 left-edge steps: exactly two
    constructed + Dart-delivered events per WM_WINDOWPOSCHANGED
    (a left/right-edge drag
    mutates position and size, so both branches of OnWindowChanged fire). Sustained
    ~165 events/s during the load; in one run 8 of 3000 were skipped, so delivery is neither
    lossless nor coalesced. This matches the code: moved (:834-835) and resized
    (:837-839) are independent ifs, and OnWindowEvent (:892-901) re-queries the registry
    • GetSize()/GetPosition() per event.
  2. Per-step window-thread tax. Paired same-round deltas: A−B ≈ +1.3 … +1.8 ms/step
    on top of a B ≈ 9.6 … 13.8 ms/step bare baseline (~10–15 %); C−B ≈ +1.7 … +2.4.
    The event chain alone accounts for most of it, consistent with the subjective ladder.
  3. Side finding (behavioral, maybe intended): applying
    titleBarStyle = TitleBarStyle.hidden to an already-visible window cleared
    WS_VISIBLE — the window kept its geometry but IsWindowVisible returned false until we
    called show() again. Our app dodged this only because it follows the
    hide → set-style → show order.

Probe app + driver are tiny (~150 lines total); happy to attach or post them if useful.

Root-cause pointers (cnativeapi-0.5.10)

  1. Geometry events are built and delivered per drag step, on the window thread —
    two per step on an edge drag.

    window_manager_windows.cpp:203-217 — WindowGeometryProc (installed via
    SetWindowSubclass by WatchWindowGeometry, :222-224) intercepts WM_WINDOWPOSCHANGED,
    calls the original procedure first, then OnWindowChanged (:778-841) diffs a snapshot
    (:769-775, GetWindowRect+IsIconic+IsZoomed) and fires moved (:834-835) and
    resized (:837-839) independently — an edge drag changes both x and width, so both
    fire per step (measured: exactly 1500+1500 events for 1500 steps, zero coalescing).
    Each event additionally re-queries geometry through the C++ getters
    (OnWindowEvent, window_manager_windows.cpp:862-906 → registry lookup +
    window->GetSize()/GetPosition() = rect + DPI math), constructs a
    WindowMoved/WindowResized, and delivers it via NativeCallable with the
    native_event_delivery_is_active / native_event_delivery_complete handshake
    (bindings/dart/nativeapi/lib/src/callbacks.dart, deliverEvent;
    DispatchWindowEvent :1199-1202). During an edge drag, WM_WINDOWPOSCHANGED arrives at
    mouse-sampling rate — hundreds of these builds+crossings per second on the thread that
    must also keep the sizing modal loop and the WM_SIZE → MoveWindow(child) → engine present chain moving.
  2. Your own code documents this failure mode.
    window_manager_windows.cpp:143-148 explains why geometry reporting is a subclass rather
    than an EVENT_OBJECT_LOCATIONCHANGE WinEvent hook: out-of-context WinEvents "are
    delivered whenever [the thread] next reads a message — in the middle of Flutter's resize,
    which pumps messages while it waits for a frame of the new size … and the listeners called
    from there (Dart ones synchronously) made every resize step late." The subclass moves
    the delivery point to a well-defined place; it does not reduce the per-step work itself.
  3. Coalescing exists for occlusion, but not for Moved/Resized.
    window_manager_windows.cpp:511,546-547 — the occlusion path arms a 100 ms timer
    because "the events come in bursts (a drag moves a window dozens of times a second)".
    WindowMoved/WindowResized have no equivalent throttle: measured exactly two events
    per WM_WINDOWPOSCHANGED on an edge drag.
  4. Confirmed: the generic dispatcher is installed on plain app windows — by the
    standard setters, even with zero listeners.

    WindowMessageDispatcher::RegisterHandler(hwnd, …)
    (window_message_dispatcher.cpp:34-52) calls InstallHook(hwnd) (:163-186), a
    SetWindowLongPtr(GWLP_WNDPROC, DispatchWindowProc) swap — and the callers are everyday
    features: the min/max-size handler (window_windows.cpp:1560-1568, WM_GETMINMAXINFO),
    always-on-bottom (:1659-1667), and the title-bar/hit-test handler (:~1700). So
    minimumSize / titleBarStyle alone put every message of the app window through the
    per-message mutex snapshot + two hash lookups + std::vector alloc + std::sort path
    (window_message_dispatcher.cpp:96-161), including the high-rate non-client messages of
    a drag (WM_NCHITTEST, WM_MOUSEMOVE, WM_SIZING). Our no-listener arm D still cost
    ~+1–2 ms/step over baseline, in addition to the item-1 event-chain tax (arm A); item 1
    dominates, but both need fixing to fully close the gap.

Minimal reproduction

A Flutter Windows app with:

final window = na.WindowManager.instance.getCurrent()!; // or wrap the runner HWND
final id = window.addListener((e) { /* no-op */ });     // keep listener registered
// frameless:
window.titleBarStyle = na.TitleBarStyle.hidden;

Then drag a window edge horizontally at a moderate, steady pace and compare with the
addListener line removed (or removeListener before dragging). The difference is easy to
see with the frame vs. content boundary; it is most visible while the window contains a
static high-contrast layout (so any content lag is obvious).

If you want quantitative capture without a human drag: the "Quantified reproduction"
section above scripts the same message path (SetWindowPos left-edge oscillation from a
second process) and counts the resulting Dart-side events; we also suggest timing
WM_WINDOWPOSCHANGED handling in WindowGeometryProc (entry to return) and the Dart
delivery handshake with QueryPerformanceCounter, histogrammed over one 10 s load,
listener-registered vs. not.

Suggested fixes (any of, or better ideas)

  1. Coalesce/throttle Moved/Resized delivery — you already do exactly this for occlusion:
    arm a timer on burst events, and always emit at WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE
    session edges with latest geometry. Most apps need session edges + a throttled stream,
    not one event per WM_WINDOWPOSCHANGED.
  2. Skip the extra getter round-trips per event — the snapshot already holds the new
    WINDOWPOS; size/position could be derived from it instead of re-querying rect + DPI.
  3. Dispatcher fast path (if it is installed on app windows — open question 4): a
    message-class whitelist or anyHandlerCares bitmap so non-consumable messages reach the
    original procedure without the per-message mutex/vector/sort; consider SetWindowSubclass
    instead of the GWLP_WNDPROC swap.
  4. Document the per-window cost of geometry subscriptions so host apps can decide what they
    register.

Related

  • nativeapi-core #78 ("Verify: events, input and icon reports from …") — this is a Windows
    event-path report with a repro structure, in exactly the shape that issue asks for.
  • Earlier report from the same user: libnativeapi/nativeapi #18 (caption-drag region
    freezing during the modal SIZEMOVE loop; fixed by 0.5.3 "events are delivered on the
    thread that owns the windows") — this new report is about the per-step cost of the
    delivery path that fix produced.
  • Historical symptom sibling: libnativeapi/nativeapi #17 ("resize lags behind the
    pointer starting with 0.2.5") — the current SetWindowSubclass design was born from
    that fix (see the code comment quoted in root-cause item 2); the residual this issue
    reports is the other half of that trade.
  • Also hit while building probes (separate, already fixed in your unreleased tree — still
    absent from 0.5.10): AOT gen_snapshot "Class with illegal cid: native_window_event_t"
    when event struct becomes tree-shake-unreachable — dart-lang/sdk#50540 workaround,
    CHANGELOG Unreleased / your #26, thanks for that.
  • Status check (2026-11-10): 0.5.10 is the latest published; nativeapi-core main HEAD
    still contains the exact behavior above (byte-identical file), no related entry in either
    CHANGELOG's Unreleased.
Ngôn ngữ chính
TypeScript
Star
172
Fork
16
Merge trung bình
40 phút
Pull request đã merge (30 ngày)
2

Chuẩn bị môi trường

Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của libnativeapi/nativeapi

Tất cả issue của libnativeapi/nativeapi

Issue tương tự

Thêm issue về TypeScript

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.