Skip to content

Commit 6ff2c49

Browse files
vogellaclaude
andcommitted
Fix infinite Text redraw for SWT.SEARCH with custom background on macOS
On cocoa, Text.drawInteriorWithFrame_inView_searchfield queried the search field text via NSControl.stringValue to decide whether to draw the cancel icon. That call schedules a setNeedsDisplay: on the view from inside the paint pass. Widget.setNeedsDisplay then enqueues the view in display.needsDisplay, which is flushed after paint, marking the view dirty again and causing readAndDispatch() to spin forever. Read the text through NSCell.stringValue instead, which bypasses the control-level side effect. Also add the JUnit regression test from PR eclipse-platform#3260 reproducing the loop. Fixes eclipse-platform/eclipse.platform.ui#3920 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 81a811a commit 6ff2c49

3 files changed

Lines changed: 40 additions & 1 deletion

File tree

bundles/org.eclipse.swt/Eclipse SWT PI/cocoa/org/eclipse/swt/internal/cocoa/NSCell.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ public void setWraps(boolean wraps) {
174174
OS.objc_msgSend(this.id, OS.sel_setWraps_, wraps);
175175
}
176176

177+
public NSString stringValue() {
178+
long result = OS.objc_msgSend(this.id, OS.sel_stringValue);
179+
return result != 0 ? new NSString(result) : null;
180+
}
181+
177182
public NSString title() {
178183
long result = OS.objc_msgSend(this.id, OS.sel_title);
179184
return result != 0 ? new NSString(result) : null;

bundles/org.eclipse.swt/Eclipse SWT/cocoa/org/eclipse/swt/widgets/Text.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -748,7 +748,14 @@ void drawInteriorWithFrame_inView_searchfield (long id, long sel, NSRect cellFra
748748
cell.searchButtonCell().drawInteriorWithFrame(cell.searchButtonRectForBounds(cellFrame), view);
749749
}
750750

751-
if (cell.cancelButtonCell() != null && ((NSSearchField) view).stringValue().length() > 0) {
751+
/*
752+
* Read the text through NSCell.stringValue rather than NSControl.stringValue.
753+
* The NSControl variant triggers setNeedsDisplay: on the view from inside
754+
* the draw pass, which Widget.setNeedsDisplay then enqueues for another redraw
755+
* after paint, looping indefinitely (eclipse.platform.ui#3920).
756+
*/
757+
NSString cellValue = _cell.stringValue();
758+
if (cell.cancelButtonCell() != null && cellValue != null && cellValue.length() > 0) {
752759
cell.cancelButtonCell().drawInteriorWithFrame(cell.cancelButtonRectForBounds(cellFrame), view);
753760
}
754761
}

tests/org.eclipse.swt.tests/JUnit Tests/org/eclipse/swt/tests/junit/Test_org_eclipse_swt_widgets_Text.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
*******************************************************************************/
1414
package org.eclipse.swt.tests.junit;
1515

16+
import static java.lang.System.currentTimeMillis;
1617
import static org.eclipse.swt.tests.junit.SwtTestUtil.JENKINS_DETECT_ENV_VAR;
1718
import static org.eclipse.swt.tests.junit.SwtTestUtil.JENKINS_DETECT_REGEX;
1819
import static org.junit.jupiter.api.Assertions.assertEquals;
1920
import static org.junit.jupiter.api.Assertions.assertFalse;
2021
import static org.junit.jupiter.api.Assertions.assertThrows;
2122
import static org.junit.jupiter.api.Assertions.assertTrue;
23+
import static org.junit.jupiter.api.Assertions.fail;
2224

2325
import org.eclipse.swt.SWT;
2426
import org.eclipse.swt.events.ModifyListener;
@@ -1376,6 +1378,31 @@ public void test_showSelection() {
13761378
text.showSelection();
13771379
}
13781380

1381+
// Originally reported as https://github.com/eclipse-platform/eclipse.platform.ui/issues/3920
1382+
@Test
1383+
public void test_finiteRedraw() {
1384+
if ( text != null ) text.dispose();
1385+
// Style constants are causing
1386+
// org.eclipse.swt.widgets.Text.drawInteriorWithFrame_inView_searchfield(long, long, NSRect, long)
1387+
// to call
1388+
// org.eclipse.swt.internal.cocoa.NSControl.stringValue()
1389+
// which schedules redraw
1390+
text = new Text(shell, SWT.SEARCH | SWT.ICON_CANCEL);
1391+
// Background prevents early exit from drawInteriorWithFrame_inView_searchfield(long, long, NSRect, long)
1392+
text.setBackground(shell.getDisplay().getSystemColor(SWT.COLOR_RED));
1393+
setWidget(text);
1394+
Display display = shell.getDisplay();
1395+
text.forceFocus();
1396+
long stop = currentTimeMillis() + 1000;
1397+
// If redraws are constantly scheduled, readAndDispatch() will never return false.
1398+
// Side effects - high CPU usage, asyncExec() stops working in modal contexts
1399+
while (display.readAndDispatch()) {
1400+
if (currentTimeMillis() > stop) {
1401+
fail("UI should eventually stop refreshing");
1402+
}
1403+
}
1404+
}
1405+
13791406
/* custom */
13801407
Text text;
13811408
String delimiterString;

0 commit comments

Comments
 (0)