Fix commands being executed multiple times due to stale isEnabled() check#4100
Open
HeikoKlare wants to merge 2 commits into
Open
Fix commands being executed multiple times due to stale isEnabled() check#4100HeikoKlare wants to merge 2 commits into
HeikoKlare wants to merge 2 commits into
Conversation
7c578a6 to
7b4895f
Compare
Contributor
|
This pull request changes some projects for the first time in this development cycle. An additional commit containing all the necessary changes was pushed to the top of this PR's branch. To obtain these changes (for example if you want to push more changes) either fetch from your fork or apply the git patch. Git patchFurther information are available in Common Build Issues - Missing version increments. |
Contributor
…pse-platform#4080 In commit 35f5f09, the commandHandled evaluation in KeyBindingDispatcher.executeCommand() was changed to include command.isEnabled() in addition to handler.isHandled(). This causes commands to be executed twice (e.g. Paste on a Combo widget): - command.isEnabled() reads the handler's enabled state without first calling setEnabled(evaluationContext), so it returns stale state that does not reflect the currently focused widget context. - Inside Command.executeWithChecks(), the framework calls setEnabled(event.getApplicationContext()) immediately before the same isEnabled() check, giving a fresh result. The two checks are not equivalent and can disagree for IHandler2 implementations. - When command.isEnabled() incorrectly returns false, commandHandled is false, executeCommand() returns false, and the key event is not consumed (event.doit remains true) even though the command is actually executed. The OS then processes the keystroke natively, causing a second execution of the same action. Fix 1: Revert determination of successful command handling to use only handler.isHandled(), as it was before commit 35f5f09. Fix 2: Change handleCommandExecution() to return a boolean (false if a CommandException was recorded after execution) and use that return value to update commandHandled in the synchronous non-Browser path. This restores the original behaviour where a handler that claims isHandled() but fails during execution (e.g. NotEnabledException after a fresh setEnabled() evaluation) does not eat the key event. The async Browser path is unchanged by design. Fixes eclipse-platform#4080
ee2ebe7 to
988bf3e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Since commit 35f5f09 ("Process KeyBinding Command Asyncly for Browser", fixes #3104), certain commands triggered via key bindings are executed twice. A prominent example is Paste (Ctrl+V) on a
Combowidget, where text from the clipboard is inserted twice.The duplicate execution follows two paths:
KeyBindingDispatcher→HandlerService→ the registered handler (e.g.WidgetMethodHandler)Root Cause
Change 1 —
command.isEnabled()added tocommandHandledevaluationCommit 35f5f09 changed the
commandHandledevaluation inexecuteCommand():commandHandledis whatexecuteCommand()returns, and that return value determines whetherprocessKeyEvent()setsevent.doit = falseto consume the key event.command.isEnabled()delegates tohandler.isEnabled(). ForIHandler2implementations, this reflects the result of the lastsetEnabled(evaluationContext)call — made during menu/toolbar refresh cycles, not at the point of the key event. The enabled state is therefore stale and does not reflect the currently active widget context.By contrast, inside
Command.executeWithChecks()(called fromhandlerService.executeHandler()), the framework callssetEnabled(event.getApplicationContext())immediately before the sameisEnabled()check, giving a fresh context-aware result. These two checks are not equivalent and do disagree for context-sensitive handlers likeWidgetMethodHandler.When
command.isEnabled()incorrectly returnsfalse:commandHandledisfalseexecuteCommand()returnsfalseevent.doitremainstrue)Change 2 —
commandHandled = falseonCommandExceptionwas removedThe original code set
commandHandled = falseif execution produced aCommandException(e.g. aNotEnabledExceptionthrown fromexecuteWithChecks()after a freshsetEnabled()evaluation). This ensured the key was not eaten when execution genuinely failed. The commit removed this line, leaving no mechanism to correctcommandHandledbased on the actual execution outcome for the synchronous path.Solution
Fix 1 (critical) — revert
commandHandledto use onlyhandler.isHandled()handler.isHandled()answers the right question here: "is there a handler registered and capable of processing this command?" It does not depend on stale evaluation context. This is sufficient to decide whether Eclipse should claim ownership of the key event.Why this is sound for the synchronous (non-Browser) path: execution happens immediately in the same call stack. If
isHandled()istruebut execution fails (see Fix 2),commandHandledis corrected before returning.Why this is sound for the async (Browser) path: the key event must be consumed or not consumed synchronously, before the async execution runs. Basing that decision on
handler.isHandled()is the right speculative choice — if we do not consume the key, the Browser's native handler will also process it, causing the exact duplicate-execution bug reported in #4080. The async path was introduced precisely to break a deadlock; eating the key speculatively is the correct trade-off.Fix 2 (secondary) — restore
commandHandled = falseonCommandExceptionfor the synchronous pathhandleCommandExecution()is changed to return aboolean(falseif aCommandExceptionwas recorded in thestaticContextafter execution,trueotherwise). The synchronous branch ofexecuteCommand()uses this return value to updatecommandHandledbefore returning:This restores the original correctness property: a handler that claims
isHandled()but fails during execution (becauseexecuteWithChecks()finds it disabled after a freshsetEnabled()call) does not eat the key event.The async Browser path remains
void— a return value is not available before the caller returns, and the speculative eat-the-key decision based onhandler.isHandled()is correct by the reasoning above.Fixes #4080