Skip to content

Commit f74c9cf

Browse files
yotsudaclaude
andcommitted
Smoke: poll until DOM is ready before dispatching the click
On WebKitGTK, NavigationCompleted fires while document.readyState is still 'loading' (body not yet parsed: bodyKids=-1, dataLine=0), so the one-shot probe found no pointable element. Poll every 300ms until readyState != loading and [data-line] elements exist, then dispatch the click. Verified locally on Windows: exit 0, "SMOKE OK ... point:1". Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2f1f49b commit f74c9cf

1 file changed

Lines changed: 39 additions & 26 deletions

File tree

MarkdownPointer.Avalonia/MainWindow.axaml.cs

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ public partial class MainWindow : Window
1616
private readonly NativeWebView _webView;
1717
private readonly AvaloniaWebViewHost _host;
1818
private DispatcherTimer? _smokeTimer;
19+
private DispatcherTimer? _smokePoll;
1920
private bool _smokeProbed;
21+
private bool _smokeBusy;
2022

2123
public MainWindow()
2224
{
@@ -47,51 +49,62 @@ private void OnNavigationCompleted()
4749
// Headless smoke (--smoke): drive a page->host bridge round-trip on the real native
4850
// webview. If the host receives the message the JS posted, the JS<->WebKitGTK<->C#
4951
// bridge works; exit 0. Otherwise time out and exit 1.
50-
private async void StartSmokeProbe()
52+
// On WebKitGTK, NavigationCompleted fires while the document is still "loading" (body not
53+
// yet parsed), so poll until the rendered DOM has pointable [data-line] elements, then
54+
// dispatch a real click. The DOM is shared across JS worlds, so the click fires the page's
55+
// own main-world pointing handler -> BridgeShim -> native host (the genuine path).
56+
private void StartSmokeProbe()
5157
{
52-
_smokeTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(20) };
58+
_smokeTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(30) };
5359
_smokeTimer.Tick += (_, _) =>
5460
{
5561
Console.Error.WriteLine("SMOKE FAIL: no bridge message within timeout");
5662
Exit(1);
5763
};
5864
_smokeTimer.Start();
5965

60-
// Diagnostic: report what InvokeScript's JS context actually sees, then (if pointable
61-
// elements exist) dispatch a real click — the DOM is shared across JS worlds, so this
62-
// fires the page's own main-world pointing handler -> BridgeShim -> native host.
63-
const string probe = @"(function(){
64-
var diag = JSON.stringify({
65-
rs: document.readyState,
66-
url: String(location.href).slice(0,80),
67-
bodyKids: document.body ? document.body.childElementCount : -1,
68-
allEls: document.querySelectorAll('*').length,
69-
dataLine: document.querySelectorAll('[data-line]').length,
70-
htmlLen: document.documentElement ? document.documentElement.outerHTML.length : -1,
71-
chrome: typeof window.chrome,
72-
invoke: typeof window.invokeCSharpAction
73-
});
74-
var el = document.querySelector('[data-line]');
75-
if (el) ['mouseover','mousedown','click'].forEach(function(t){
76-
el.dispatchEvent(new MouseEvent(t, {bubbles:true, cancelable:true, view:window}));
77-
});
78-
return diag;
79-
})()";
66+
_smokePoll = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(300) };
67+
_smokePoll.Tick += OnSmokePoll;
68+
_smokePoll.Start();
69+
}
70+
71+
private const string SmokeProbeJs = @"(function(){
72+
if (document.readyState === 'loading') return 'waiting';
73+
if (document.querySelectorAll('[data-line]').length === 0) return 'waiting';
74+
var el = document.querySelector('[data-line]');
75+
['mouseover','mousedown','click'].forEach(function(t){
76+
el.dispatchEvent(new MouseEvent(t, {bubbles:true, cancelable:true, view:window}));
77+
});
78+
return 'dispatched';
79+
})()";
80+
81+
private async void OnSmokePoll(object? sender, EventArgs e)
82+
{
83+
if (_smokeBusy) return;
84+
_smokeBusy = true;
8085
try
8186
{
82-
var result = await _host.ExecuteScriptAsync(probe);
83-
Console.Error.WriteLine("SMOKE DIAG: " + result);
87+
var result = await _host.ExecuteScriptAsync(SmokeProbeJs);
88+
if (result != null && result.Contains("dispatched"))
89+
{
90+
_smokePoll?.Stop();
91+
Console.Error.WriteLine("SMOKE: dispatched click on a pointable element");
92+
}
8493
}
8594
catch (Exception ex)
8695
{
87-
Console.Error.WriteLine("SMOKE FAIL: InvokeScript threw: " + ex.Message);
88-
Exit(1);
96+
Console.Error.WriteLine("SMOKE probe error (retrying): " + ex.Message);
97+
}
98+
finally
99+
{
100+
_smokeBusy = false;
89101
}
90102
}
91103

92104
private void Exit(int code)
93105
{
94106
_smokeTimer?.Stop();
107+
_smokePoll?.Stop();
95108
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime life)
96109
life.Shutdown(code);
97110
else

0 commit comments

Comments
 (0)