[Win32][Edge] Fix BrowserFunction race condition using AddScriptToExecuteOnDocumentCreated#3166
[Win32][Edge] Fix BrowserFunction race condition using AddScriptToExecuteOnDocumentCreated#3166HeikoKlare wants to merge 1 commit intoeclipse-platform:masterfrom
Conversation
e60583a to
a7f51b1
Compare
|
Test plan is done. The automated tests are run via CI anyway. I also successfully executed the reproduction snippet: #2449 (comment) @sratz can you please review this Edge enhancement? |
|
@sratz It would be great if this could make it into the upcoming release. Next week is M2 promotion and we should probably not merge something like this after M2. Do you see the chance that you can have a look until then? |
|
Aren't the functions now executed twice, as the handling in Also,
I think it would be nice if we did not need to override |
Ah, it's not the function itself being executed twice, but only the registration of it is done twice, so |
Good catch. I have adapted the change so that functions are only registered ones by using the stored
I agree that for the sake of symmetry, overwriting |
| for (BrowserFunction function : functions.values()) { | ||
| sb.append(function.functionString); | ||
| if (!functionScriptIds.containsKey(function.index)) { | ||
| sb.append(function.functionString); |
There was a problem hiding this comment.
should we attempt to register here again via AddScriptToExecuteOnDocumentCreated? So that for the next navigation iteration we go the new route?
There was a problem hiding this comment.
It sounds conceptually clean to try to eventually register every function via AddScriptToExecuteOnDocumentCreated. Actually, the current solution is quite unclean, as the way how a function is registered now depends on whether the function has initially been reigstered from a callback or not.
I wondered if we could just do everything via AddScriptToExecuteOnDocumentCreated without this "fallback" here by just calling that registration asynchronously if we are in a callback. I've adapted the PR accordingly. The only potential issue I see with that is an execution order in which the function is registered in a callback, and then the next browser navigation is started is before the asyncExec is processed. I am not sure if that's a relevant scenario we need to consider.
An alternative would be to just temporarily use AddScriptToExecuteOnDocumentCreated for the current navigation and to deregister the function right after the next navigation again, so that the default way of registering functions remains the embedding in the document done in handleNavigationStarting.
One issue with the solution of using AddScriptToExecuteOnDocumentCreated is that it seems to be quite slow. The execution time of test_TimerRegression_Issue2806 (which creates 10000 browser functions) increases from less than 1 second to more than 2 minutes on my machine.
…DocumentCreated Use AddScriptToExecuteOnDocumentCreated to persistently register every BrowserFunction so it is automatically injected before page scripts run on every navigation. When createFunction() is called inside a WebView2 callback, the persistent registration is deferred via asyncExec. This removes the need for the NavigationStarting fallback that manually re-injected non-persistent functions on each navigation. See eclipse-platform#20 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Fixes #20 (see also #2449 for a reproduction snippet).
In Edge/WebView2,
execute()is asynchronous. Whennew BrowserFunction()is called, the injection script is queued viaExecuteScript(), but if a page navigation completes before WebView2 processes that queued script, the function is unavailable in the new document. This race is most easily triggered when twoBrowserinstances are created in quick succession, as the second browser's initialization forces event-loop processing that can advance the first browser's navigation past the injection window.Fix: Override
createFunction()inEdgeto register every function script viaAddScriptToExecuteOnDocumentCreated. This WebView2 API guarantees the script runs on every future document creation, before any page scripts — eliminating the race condition. The script ID returned by the async registration is stored so it can be cleaned up viaRemoveScriptToExecuteOnDocumentCreatedwhen theBrowserFunctionis disposed.When
createFunction()is called from within a WebView2 callback (inCallback > 0), blocking on the registration would deadlock, so the persistent registration is deferred viaDisplay.asyncExec()to complete once the callback returns. This ensures every function is always persistently registered, removing the need for anyNavigationStartingfallback re-injection.Changes
ICoreWebView2.java: Add missingRemoveScriptToExecuteOnDocumentCreatedvtbl binding (index 28, betweenAddScriptToExecuteOnDocumentCreatedat 27 andExecuteScriptat 29)Edge.java: OverridecreateFunction()to persistently register every function viaAddScriptToExecuteOnDocumentCreated(deferred viaasyncExecwhen inside a callback); overridederegisterFunction()to callRemoveScriptToExecuteOnDocumentCreatedon disposal; remove theNavigationStartingfallback re-injection since all functions are now always persistently registeredTest_org_eclipse_swt_browser_Browser.java: Two regression tests (isEdge-only):test_BrowserFunction_availableBeforePageScripts_issue20: registers a function, navigates to a page whose inline<script>immediately calls it, asserts the function was invoked (tests theAddScriptToExecuteOnDocumentCreatedguarantee directly)test_BrowserFunction_availableOnLoad_concurrentInstances_issue20: creates two browsers concurrently without waiting for initialization, verifies both functions are callable when their pages complete loading (replicates the original bug report timing)Test plan
test_BrowserFunction_availableBeforePageScripts_issue20on Windows with Edgetest_BrowserFunction_availableOnLoad_concurrentInstances_issue20on Windows with Edgetest_BrowserFunction_*tests to verify no regressions