Skip to content

Commit b3a93e3

Browse files
committed
Find/replace overlay: unify async executions, avoid NPEs #2244
Several operation in the find/replace overlay need to be executed asynchronously as they may be called in OS callback which can lead to deadlocks in GTK. Currently, this processing is distributed and repeated across the class and did not check preconditions (in particular the dialog being opened and its shell being present) appropriate in every case. In some cases, when the overlay is already or temporarily closed but an asynchronous task is still to be executed, that task may fail with an NPE. This change moves the functionality for the asynchronous processing to a central place, ensuring that the precondition requiring the shell to exist to always be evaluated. Factoring out this functionality also makes explicit that the called operation is executed only if the overlay respectively its shell are currently open. It also fixes the mentioned NPEs. Fixes #2244
1 parent 31a9027 commit b3a93e3

File tree

1 file changed

+25
-19
lines changed
  • bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay

1 file changed

+25
-19
lines changed

bundles/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/findandreplace/overlay/FindReplaceOverlay.java

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
import static org.eclipse.ui.internal.findandreplace.overlay.FindReplaceShortcutUtil.registerActionShortcutsAtControl;
1717

1818
import java.util.List;
19-
import java.util.function.Supplier;
19+
import java.util.function.Consumer;
2020

2121
import org.osgi.framework.FrameworkUtil;
2222

@@ -156,7 +156,7 @@ public FindReplaceOverlay(Shell parent, IWorkbenchPart part, IFindReplaceTarget
156156
setShellStyle(SWT.MODELESS);
157157
setBlockOnOpen(false);
158158
targetPart = part;
159-
targetPartVisibilityHandler = new TargetPartVisibilityHandler(targetPart, this::getShell, this::close,
159+
targetPartVisibilityHandler = new TargetPartVisibilityHandler(targetPart, this::asyncExecIfOpen, this::close,
160160
this::updatePlacementAndVisibility);
161161
}
162162

@@ -195,21 +195,26 @@ private void performSelectAll() {
195195
private ControlListener shellMovementListener = new ControlListener() {
196196
@Override
197197
public void controlMoved(ControlEvent e) {
198-
asyncUpdatePlacementAndVisibility();
198+
asyncExecIfOpen(FindReplaceOverlay.this::updatePlacementAndVisibility);
199199
}
200200

201201
@Override
202202
public void controlResized(ControlEvent e) {
203-
asyncUpdatePlacementAndVisibility();
203+
asyncExecIfOpen(FindReplaceOverlay.this::updatePlacementAndVisibility);
204204
}
205205
};
206206

207-
private PaintListener widgetMovementListener = __ -> asyncUpdatePlacementAndVisibility();
207+
private PaintListener widgetMovementListener = __ -> asyncExecIfOpen(
208+
FindReplaceOverlay.this::updatePlacementAndVisibility);
208209

209-
private void asyncUpdatePlacementAndVisibility() {
210+
private void asyncExecIfOpen(Runnable operation) {
210211
Shell shell = getShell();
211212
if (shell != null) {
212-
shell.getDisplay().asyncExec(this::updatePlacementAndVisibility);
213+
shell.getDisplay().asyncExec(() -> {
214+
if (getShell() != null) {
215+
operation.run();
216+
}
217+
});
213218
}
214219
}
215220

@@ -228,17 +233,18 @@ public void shellDeactivated(ShellEvent e) {
228233
private static class TargetPartVisibilityHandler implements IPartListener2, IPageChangedListener {
229234
private final IWorkbenchPart targetPart;
230235
private final IWorkbenchPart topLevelPart;
231-
private final Supplier<Shell> shellProvider;
236+
private final Consumer<Runnable> asyncExecIfOpen;
232237
private final Runnable closeCallback;
233238
private final Runnable placementUpdateCallback;
234239

235240
private boolean isTopLevelVisible = true;
236241
private boolean isNestedLevelVisible = true;
237242

238-
TargetPartVisibilityHandler(IWorkbenchPart targetPart, Supplier<Shell> shellProvider, Runnable closeCallback,
243+
TargetPartVisibilityHandler(IWorkbenchPart targetPart, Consumer<Runnable> asyncExecIfOpen,
244+
Runnable closeCallback,
239245
Runnable placementUpdateCallback) {
240246
this.targetPart = targetPart;
241-
this.shellProvider = shellProvider;
247+
this.asyncExecIfOpen = asyncExecIfOpen;
242248
this.closeCallback = closeCallback;
243249
this.placementUpdateCallback = placementUpdateCallback;
244250
if (targetPart != null && targetPart.getSite() instanceof MultiPageEditorSite multiEditorSite) {
@@ -252,23 +258,23 @@ private static class TargetPartVisibilityHandler implements IPartListener2, IPag
252258
public void partBroughtToTop(IWorkbenchPartReference partRef) {
253259
if (partRef.getPart(false) == topLevelPart && !isTopLevelVisible) {
254260
this.isTopLevelVisible = true;
255-
shellProvider.get().getDisplay().asyncExec(this::adaptToPartActivationChange);
261+
asyncExecIfOpen.accept(this::adaptToPartActivationChange);
256262
}
257263
}
258264

259265
@Override
260266
public void partVisible(IWorkbenchPartReference partRef) {
261267
if (partRef.getPart(false) == topLevelPart && !isTopLevelVisible) {
262268
this.isTopLevelVisible = true;
263-
shellProvider.get().getDisplay().asyncExec(this::adaptToPartActivationChange);
269+
asyncExecIfOpen.accept(this::adaptToPartActivationChange);
264270
}
265271
}
266272

267273
@Override
268274
public void partHidden(IWorkbenchPartReference partRef) {
269275
if (partRef.getPart(false) == topLevelPart && isTopLevelVisible) {
270276
this.isTopLevelVisible = false;
271-
shellProvider.get().getDisplay().asyncExec(this::adaptToPartActivationChange);
277+
asyncExecIfOpen.accept(this::adaptToPartActivationChange);
272278
}
273279
}
274280

@@ -285,26 +291,26 @@ public void pageChanged(PageChangedEvent event) {
285291
boolean isPageVisible = event.getSelectedPage() == targetPart;
286292
if (isNestedLevelVisible != isPageVisible) {
287293
this.isNestedLevelVisible = isPageVisible;
288-
shellProvider.get().getDisplay().asyncExec(this::adaptToPartActivationChange);
294+
asyncExecIfOpen.accept(this::adaptToPartActivationChange);
289295
}
290296
}
291297
}
292298

293299
private void adaptToPartActivationChange() {
294-
if (shellProvider.get() == null || targetPart.getSite().getPart() == null) {
300+
if (targetPart.getSite().getPart() == null) {
295301
return;
296302
}
297303
placementUpdateCallback.run();
298304

299305
if (!isTargetVisible()) {
300306
targetPart.getSite().getShell().setActive();
301307
targetPart.setFocus();
302-
shellProvider.get().getDisplay().asyncExec(this::focusTargetWidget);
308+
asyncExecIfOpen.accept(this::focusTargetWidget);
303309
}
304310
}
305311

306312
private void focusTargetWidget() {
307-
if (shellProvider.get() == null || targetPart.getSite().getPart() == null) {
313+
if (targetPart.getSite().getPart() == null) {
308314
return;
309315
}
310316
if (targetPart instanceof StatusTextEditor textEditor) {
@@ -845,7 +851,7 @@ private void updatePlacementAndVisibility() {
845851
return;
846852
}
847853
if (isInvalidTargetShell()) {
848-
getShell().getDisplay().asyncExec(() -> {
854+
asyncExecIfOpen(() -> {
849855
if (isInvalidTargetShell()) {
850856
close();
851857
setParentShell(targetPart.getSite().getShell());
@@ -883,7 +889,7 @@ private boolean isInvalidTargetShell() {
883889
if (isInvalidTargetPart()) {
884890
return false;
885891
}
886-
return getShell() == null || !targetPart.getSite().getShell().equals(getShell().getParent());
892+
return !targetPart.getSite().getShell().equals(getShell().getParent());
887893
}
888894

889895
private Rectangle calculateAbsoluteControlBounds(Control control) {

0 commit comments

Comments
 (0)